This is an automated email from the ASF dual-hosted git repository.
dcapwell pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra-accord.git
The following commit(s) were added to refs/heads/trunk by this push:
new bc81f81c CEP-15: (Accord) Migrate Accord away from JDK random to a new
interface RandomSource
bc81f81c is described below
commit bc81f81c75f93c73989a30bbc51b5c241a893c1a
Author: David Capwell <[email protected]>
AuthorDate: Thu Jan 26 12:06:53 2023 -0800
CEP-15: (Accord) Migrate Accord away from JDK random to a new interface
RandomSource
patch by David Capwell; reviewed by Blake Eggleston for CASSANDRA-18213
---
accord-core/build.gradle | 3 -
accord-core/src/main/java/accord/local/Node.java | 10 +-
.../src/main/java/accord/primitives/RangeDeps.java | 9 +-
.../src/main/java/accord/utils/DefaultRandom.java | 44 ++--
.../src/main/java/accord/utils/RandomSource.java | 269 +++++++++++++++++++++
.../main/java/accord/utils/RelationMultiMap.java | 4 +-
.../java/accord/utils/WrappedRandomSource.java | 97 ++++++++
.../src/test/java/accord/burn/BurnTest.java | 25 +-
.../accord/burn/BurnTestConfigurationService.java | 13 +-
.../accord/burn/random/FrequentLargeRange.java | 76 ++++++
.../src/test/java/accord/burn/random/IntRange.java | 41 ++--
.../test/java/accord/burn/random/RandomInt.java | 36 +--
.../test/java/accord/burn/random/RandomLong.java | 34 +--
.../java/accord/burn/random/RandomRangeTest.java | 83 +++++++
.../java/accord/burn/random/RandomWalkRange.java | 61 +++++
.../burn/random/SegmentedRandomRangeTest.java | 182 ++++++++++++++
.../tracking/FastPathTrackerReconciler.java | 7 +-
.../tracking/InvalidationTrackerReconciler.java | 6 +-
.../tracking/QuorumTrackerReconciler.java | 6 +-
.../coordinate/tracking/ReadTrackerReconciler.java | 6 +-
.../tracking/RecoveryTrackerReconciler.java | 6 +-
.../coordinate/tracking/TrackerReconciler.java | 18 +-
.../coordinate/tracking/TrackerReconcilerTest.java | 6 +-
.../src/test/java/accord/impl/basic/Cluster.java | 10 +-
.../src/test/java/accord/impl/basic/NodeSink.java | 6 +-
.../java/accord/impl/basic/RandomDelayQueue.java | 13 +-
.../java/accord/impl/basic/UniformRandomQueue.java | 13 +-
.../test/java/accord/impl/mock/MockCluster.java | 9 +-
.../java/accord/local/ImmutableCommandTest.java | 5 +-
.../test/java/accord/messages/PreAcceptTest.java | 4 +-
.../test/java/accord/primitives/KeyDepsTest.java | 41 ++--
.../java/accord/topology/TopologyRandomizer.java | 37 ++-
.../src/test/java/accord/utils/AccordGens.java | 154 ++++++++++++
accord-core/src/test/java/accord/utils/Gen.java | 108 +++------
accord-core/src/test/java/accord/utils/Gens.java | 145 ++++++++++-
.../src/test/java/accord/utils/Property.java | 58 ++++-
accord-maelstrom/build.gradle | 2 +-
.../src/main/java/accord/maelstrom/Cluster.java | 24 +-
.../src/main/java/accord/maelstrom/Datum.java | 16 ++
.../src/main/java/accord/maelstrom/Json.java | 82 ++++---
.../main/java/accord/maelstrom/MaelstromKey.java | 16 ++
.../src/main/java/accord/maelstrom/Main.java | 4 +-
.../src/test/java/accord/maelstrom/JsonTest.java | 90 +++++++
.../src/test/java/accord/maelstrom/Runner.java | 244 +++++--------------
.../java/accord/maelstrom/SimpleRandomTest.java | 58 ++---
.../src/main/groovy/accord.java-conventions.gradle | 2 +
46 files changed, 1618 insertions(+), 565 deletions(-)
diff --git a/accord-core/build.gradle b/accord-core/build.gradle
index e92e62b5..3c1472bc 100644
--- a/accord-core/build.gradle
+++ b/accord-core/build.gradle
@@ -18,9 +18,6 @@
plugins {
id 'accord.java-conventions'
- // export test classes for subprojects
- id 'java-library'
- id 'java-test-fixtures'
id 'maven-publish'
}
diff --git a/accord-core/src/main/java/accord/local/Node.java
b/accord-core/src/main/java/accord/local/Node.java
index 344e10b9..fbf59874 100644
--- a/accord-core/src/main/java/accord/local/Node.java
+++ b/accord-core/src/main/java/accord/local/Node.java
@@ -18,7 +18,10 @@
package accord.local;
-import java.util.*;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
@@ -35,6 +38,7 @@ import accord.utils.MapReduceConsume;
import accord.utils.async.AsyncChain;
import accord.utils.async.AsyncResult;
import accord.utils.async.AsyncResults;
+import accord.utils.RandomSource;
import com.google.common.annotations.VisibleForTesting;
import accord.api.*;
@@ -119,7 +123,7 @@ public class Node implements ConfigurationService.Listener,
NodeTimeService
private final LongSupplier nowSupplier;
private final AtomicReference<Timestamp> now;
private final Agent agent;
- private final Random random;
+ private final RandomSource random;
// TODO (expected, consider): this really needs to be thought through some
more, as it needs to be per-instance in some cases, and per-node in others
private final Scheduler scheduler;
@@ -128,7 +132,7 @@ public class Node implements ConfigurationService.Listener,
NodeTimeService
private final Map<TxnId, AsyncResult<? extends Outcome>> coordinating =
new ConcurrentHashMap<>();
public Node(Id id, MessageSink messageSink, ConfigurationService
configService, LongSupplier nowSupplier,
- Supplier<DataStore> dataSupplier, ShardDistributor
shardDistributor, Agent agent, Random random, Scheduler scheduler,
TopologySorter.Supplier topologySorter,
+ Supplier<DataStore> dataSupplier, ShardDistributor
shardDistributor, Agent agent, RandomSource random, Scheduler scheduler,
TopologySorter.Supplier topologySorter,
Function<Node, ProgressLog.Factory> progressLogFactory,
CommandStores.Factory factory)
{
this.id = id;
diff --git a/accord-core/src/main/java/accord/primitives/RangeDeps.java
b/accord-core/src/main/java/accord/primitives/RangeDeps.java
index 3bef67e0..22d49080 100644
--- a/accord-core/src/main/java/accord/primitives/RangeDeps.java
+++ b/accord-core/src/main/java/accord/primitives/RangeDeps.java
@@ -612,14 +612,7 @@ public class RangeDeps implements
Iterable<Map.Entry<Range, TxnId>>
public static SymmetricComparator<? super Range> rangeComparator()
{
- return RangeDeps::compare;
- }
-
- public static int compare(Range left, Range right)
- {
- int c = left.start().compareTo(right.start());
- if (c == 0) c = left.end().compareTo(right.end());
- return c;
+ return Range::compare;
}
private static final RangeDepsAdapter ADAPTER = new RangeDepsAdapter();
diff --git a/accord-maelstrom/build.gradle
b/accord-core/src/main/java/accord/utils/DefaultRandom.java
similarity index 53%
copy from accord-maelstrom/build.gradle
copy to accord-core/src/main/java/accord/utils/DefaultRandom.java
index cc08d3f4..8efff223 100644
--- a/accord-maelstrom/build.gradle
+++ b/accord-core/src/main/java/accord/utils/DefaultRandom.java
@@ -16,35 +16,29 @@
* limitations under the License.
*/
-plugins {
- id 'accord.java-conventions'
-}
+package accord.utils;
-dependencies {
- implementation project(':accord-core')
- implementation group: 'com.google.code.findbugs', name: 'jsr305', version:
'3.0.2'
- implementation 'com.google.code.gson:gson:2.8.7'
+import java.util.Random;
- testImplementation(testFixtures(project(':accord-core')))
-}
+public class DefaultRandom extends Random implements RandomSource
+{
+ public DefaultRandom()
+ {
+ }
-jar {
- manifest {
- attributes(
- 'Main-Class': 'accord.maelstrom.Main',
- )
+ public DefaultRandom(long seed)
+ {
+ super(seed);
+ }
+
+ @Override
+ public DefaultRandom fork() {
+ return new DefaultRandom(nextLong());
}
-}
-task fatJar(type: Jar) {
- manifest.from jar.manifest
- archiveClassifier = 'all'
- from {
- configurations.runtimeClasspath.collect { it.isDirectory() ? it :
zipTree(it) }
- } {
- exclude "META-INF/*.SF"
- exclude "META-INF/*.DSA"
- exclude "META-INF/*.RSA"
+ @Override
+ public Random asJdkRandom()
+ {
+ return this;
}
- with jar
}
diff --git a/accord-core/src/main/java/accord/utils/RandomSource.java
b/accord-core/src/main/java/accord/utils/RandomSource.java
new file mode 100644
index 00000000..56b6ef5e
--- /dev/null
+++ b/accord-core/src/main/java/accord/utils/RandomSource.java
@@ -0,0 +1,269 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package accord.utils;
+
+import java.util.Random;
+import java.util.stream.DoubleStream;
+import java.util.stream.IntStream;
+import java.util.stream.LongStream;
+
+public interface RandomSource
+{
+ static RandomSource wrap(Random random)
+ {
+ return new WrappedRandomSource(random);
+ }
+
+ void nextBytes(byte[] bytes);
+
+ boolean nextBoolean();
+
+ int nextInt();
+
+ default int nextInt(int maxExclusive)
+ {
+ return nextInt(0, maxExclusive);
+ }
+
+ default int nextInt(int minInclusive, int maxExclusive)
+ {
+ // this is diff behavior than ThreadLocalRandom, which returns nextInt
+ if (minInclusive >= maxExclusive)
+ throw new IllegalArgumentException(String.format("Min (%s) should
be less than max (%d).", minInclusive, maxExclusive));
+
+ int result = nextInt();
+ int delta = maxExclusive - minInclusive;
+ int mask = delta - 1;
+ if ((delta & mask) == 0) // power of two
+ result = (result & mask) + minInclusive;
+ else if (delta > 0)
+ {
+ // reject over-represented candidates
+ for (int u = result >>> 1; // ensure nonnegative
+ u + mask - (result = u % delta) < 0; // rejection check
+ u = nextInt() >>> 1) // retry
+ ;
+ result += minInclusive;
+ }
+ else
+ {
+ // range not representable as int
+ while (result < minInclusive || result >= maxExclusive)
+ result = nextInt();
+ }
+ return result;
+ }
+
+ default IntStream ints()
+ {
+ return IntStream.generate(this::nextInt);
+ }
+
+ default IntStream ints(int maxExclusive)
+ {
+ return IntStream.generate(() -> nextInt(maxExclusive));
+ }
+
+ default IntStream ints(int minInclusive, int maxExclusive)
+ {
+ return IntStream.generate(() -> nextInt(minInclusive, maxExclusive));
+ }
+
+ long nextLong();
+
+ default long nextLong(long maxExclusive)
+ {
+ return nextLong(0, maxExclusive);
+ }
+
+ default long nextLong(long minInclusive, long maxExclusive)
+ {
+ // this is diff behavior than ThreadLocalRandom, which returns nextLong
+ if (minInclusive >= maxExclusive)
+ throw new IllegalArgumentException(String.format("Min (%s) should
be less than max (%d).", minInclusive, maxExclusive));
+
+ long result = nextLong();
+ long delta = maxExclusive - minInclusive;
+ long mask = delta - 1;
+ if ((delta & mask) == 0L) // power of two
+ result = (result & mask) + minInclusive;
+ else if (delta > 0L)
+ {
+ // reject over-represented candidates
+ for (long u = result >>> 1; // ensure nonnegative
+ u + mask - (result = u % delta) < 0L; // rejection check
+ u = nextLong() >>> 1) // retry
+ ;
+ result += minInclusive;
+ }
+ else
+ {
+ // range not representable as long
+ while (result < minInclusive || result >= maxExclusive)
+ result = nextLong();
+ }
+ return result;
+ }
+
+ default LongStream longs()
+ {
+ return LongStream.generate(this::nextLong);
+ }
+
+ default LongStream longs(long maxExclusive)
+ {
+ return LongStream.generate(() -> nextLong(maxExclusive));
+ }
+
+ default LongStream longs(long minInclusive, long maxExclusive)
+ {
+ return LongStream.generate(() -> nextLong(minInclusive, maxExclusive));
+ }
+
+ float nextFloat();
+
+ double nextDouble();
+
+ default double nextDouble(double maxExclusive)
+ {
+ return nextDouble(0, maxExclusive);
+ }
+
+ default double nextDouble(double minInclusive, double maxExclusive)
+ {
+ if (minInclusive >= maxExclusive)
+ throw new IllegalArgumentException(String.format("Min (%s) should
be less than max (%d).", minInclusive, maxExclusive));
+
+ double result = nextDouble();
+ result = result * (maxExclusive - minInclusive) + minInclusive;
+ if (result >= maxExclusive) // correct for rounding
+ result =
Double.longBitsToDouble(Double.doubleToLongBits(maxExclusive) - 1);
+ return result;
+ }
+
+ default DoubleStream doubles()
+ {
+ return DoubleStream.generate(this::nextDouble);
+ }
+
+ default DoubleStream doubles(double maxExclusive)
+ {
+ return DoubleStream.generate(() -> nextDouble(maxExclusive));
+ }
+
+ default DoubleStream doubles(double minInclusive, double maxExclusive)
+ {
+ return DoubleStream.generate(() -> nextDouble(minInclusive,
maxExclusive));
+ }
+
+ double nextGaussian();
+
+ void setSeed(long seed);
+
+ RandomSource fork();
+
+ /**
+ * Returns true with a probability of {@code chance}. This logic is
logically the same as
+ * <pre>{@code nextFloat() < chance}</pre>
+ *
+ * @param chance cumulative probability in range [0..1]
+ */
+ default boolean decide(float chance)
+ {
+ return nextFloat() < chance;
+ }
+
+ /**
+ * Returns true with a probability of {@code chance}. This logic is
logically the same as
+ * <pre>{@code nextDouble() < chance}</pre>
+ *
+ * @param chance cumulative probability in range [0..1]
+ */
+ default boolean decide(double chance)
+ {
+ return nextDouble() < chance;
+ }
+
+ default long reset()
+ {
+ long seed = nextLong();
+ setSeed(seed);
+ return seed;
+ }
+
+ default Random asJdkRandom()
+ {
+ return new Random()
+ {
+ @Override
+ public void setSeed(long seed)
+ {
+ RandomSource.this.setSeed(seed);
+ }
+
+ @Override
+ public void nextBytes(byte[] bytes)
+ {
+ RandomSource.this.nextBytes(bytes);
+ }
+
+ @Override
+ public int nextInt()
+ {
+ return RandomSource.this.nextInt();
+ }
+
+ @Override
+ public int nextInt(int bound)
+ {
+ return RandomSource.this.nextInt(bound);
+ }
+
+ @Override
+ public long nextLong()
+ {
+ return RandomSource.this.nextLong();
+ }
+
+ @Override
+ public boolean nextBoolean()
+ {
+ return RandomSource.this.nextBoolean();
+ }
+
+ @Override
+ public float nextFloat()
+ {
+ return RandomSource.this.nextFloat();
+ }
+
+ @Override
+ public double nextDouble()
+ {
+ return RandomSource.this.nextDouble();
+ }
+
+ @Override
+ public double nextGaussian()
+ {
+ return RandomSource.this.nextGaussian();
+ }
+ };
+ }
+}
diff --git a/accord-core/src/main/java/accord/utils/RelationMultiMap.java
b/accord-core/src/main/java/accord/utils/RelationMultiMap.java
index cd3c8fb1..0e6ab273 100644
--- a/accord-core/src/main/java/accord/utils/RelationMultiMap.java
+++ b/accord-core/src/main/java/accord/utils/RelationMultiMap.java
@@ -221,7 +221,7 @@ public class RelationMultiMap
{
sortedKeyIndexes = new int[keyCount];
sortedKeys = Arrays.copyOf(keys, keyCount);
- Arrays.sort(sortedKeys);
+ Arrays.sort(sortedKeys, adapter.keyComparator());
for (int i = 1 ; i < keyCount ; ++i)
{
@@ -230,7 +230,7 @@ public class RelationMultiMap
+ Arrays.toString(Arrays.copyOf(keys,
keyCount)) + ")");
}
for (int i = 0 ; i < keyCount ; ++i)
- sortedKeyIndexes[Arrays.binarySearch(sortedKeys, keys[i])]
= i;
+ sortedKeyIndexes[Arrays.binarySearch(sortedKeys, keys[i],
adapter.keyComparator())] = i;
cachedKeys.forceDiscard(keys, keyCount);
}
diff --git a/accord-core/src/main/java/accord/utils/WrappedRandomSource.java
b/accord-core/src/main/java/accord/utils/WrappedRandomSource.java
new file mode 100644
index 00000000..3d02c101
--- /dev/null
+++ b/accord-core/src/main/java/accord/utils/WrappedRandomSource.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package accord.utils;
+
+import java.util.Random;
+
+class WrappedRandomSource implements RandomSource
+{
+ private final Random random;
+
+ WrappedRandomSource(Random random)
+ {
+ this.random = random;
+ }
+
+ @Override
+ public Random asJdkRandom()
+ {
+ return random;
+ }
+
+ @Override
+ public void nextBytes(byte[] bytes)
+ {
+ random.nextBytes(bytes);
+ }
+
+ @Override
+ public boolean nextBoolean()
+ {
+ return random.nextBoolean();
+ }
+
+ @Override
+ public int nextInt()
+ {
+ return random.nextInt();
+ }
+
+ @Override
+ public int nextInt(int maxExclusive)
+ {
+ return random.nextInt(maxExclusive);
+ }
+
+ @Override
+ public long nextLong()
+ {
+ return random.nextLong();
+ }
+
+ @Override
+ public float nextFloat()
+ {
+ return random.nextFloat();
+ }
+
+ @Override
+ public double nextDouble()
+ {
+ return random.nextDouble();
+ }
+
+ @Override
+ public double nextGaussian()
+ {
+ return random.nextGaussian();
+ }
+
+ @Override
+ public void setSeed(long seed)
+ {
+ random.setSeed(seed);
+ }
+
+ @Override
+ public RandomSource fork()
+ {
+ return new WrappedRandomSource(new Random(nextLong()));
+ }
+}
diff --git a/accord-core/src/test/java/accord/burn/BurnTest.java
b/accord-core/src/test/java/accord/burn/BurnTest.java
index f144032c..995a0285 100644
--- a/accord-core/src/test/java/accord/burn/BurnTest.java
+++ b/accord-core/src/test/java/accord/burn/BurnTest.java
@@ -23,7 +23,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
-import java.util.Random;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ExecutionException;
@@ -38,6 +37,8 @@ import java.util.function.Consumer;
import java.util.function.LongSupplier;
import java.util.function.Predicate;
+import accord.utils.DefaultRandom;
+import accord.utils.RandomSource;
import accord.impl.IntHashKey;
import accord.impl.basic.Cluster;
import accord.impl.basic.PropagatingPendingQueue;
@@ -66,7 +67,7 @@ public class BurnTest
{
private static final Logger logger =
LoggerFactory.getLogger(BurnTest.class);
- static List<Packet> generate(Random random, List<Id> clients, List<Id>
nodes, int keyCount, int operations)
+ static List<Packet> generate(RandomSource random, List<Id> clients,
List<Id> nodes, int keyCount, int operations)
{
List<Key> keys = new ArrayList<>();
for (int i = 0 ; i < keyCount ; ++i)
@@ -128,17 +129,17 @@ public class BurnTest
return packets;
}
- private static Key randomKey(Random random, List<Key> keys, Set<Key> notIn)
+ private static Key randomKey(RandomSource random, List<Key> keys, Set<Key>
notIn)
{
return keys.get(randomKeyIndex(random, keys, notIn));
}
- private static int randomKeyIndex(Random random, List<Key> keys, Set<Key>
notIn)
+ private static int randomKeyIndex(RandomSource random, List<Key> keys,
Set<Key> notIn)
{
return randomKeyIndex(random, keys, notIn::contains);
}
- private static int randomKeyIndex(Random random, List<Key> keys,
Predicate<Key> notIn)
+ private static int randomKeyIndex(RandomSource random, List<Key> keys,
Predicate<Key> notIn)
{
int i;
while (notIn.test(keys.get(i = random.nextInt(keys.size()))));
@@ -147,7 +148,7 @@ public class BurnTest
static void burn(TopologyFactory topologyFactory, List<Id> clients,
List<Id> nodes, int keyCount, int operations, int concurrency) throws
IOException
{
- Random random = new Random();
+ RandomSource random = new DefaultRandom();
long seed = random.nextLong();
System.out.println(seed);
random.setSeed(seed);
@@ -156,7 +157,7 @@ public class BurnTest
static void burn(long seed, TopologyFactory topologyFactory, List<Id>
clients, List<Id> nodes, int keyCount, int operations, int concurrency) throws
IOException
{
- Random random = new Random();
+ RandomSource random = new DefaultRandom();
System.out.println(seed);
random.setSeed(seed);
burn(random, topologyFactory, clients, nodes, keyCount, operations,
concurrency);
@@ -166,7 +167,7 @@ public class BurnTest
{
ReconcilingLogger logReconciler = new ReconcilingLogger(logger);
- Random random1 = new Random(), random2 = new Random();
+ RandomSource random1 = new DefaultRandom(), random2 = new
DefaultRandom();
random1.setSeed(seed);
random2.setSeed(seed);
ExecutorService exec = Executors.newFixedThreadPool(2);
@@ -188,7 +189,7 @@ public class BurnTest
assert logReconciler.reconcile();
}
- static void burn(Random random, TopologyFactory topologyFactory, List<Id>
clients, List<Id> nodes, int keyCount, int operations, int concurrency)
+ static void burn(RandomSource random, TopologyFactory topologyFactory,
List<Id> clients, List<Id> nodes, int keyCount, int operations, int concurrency)
{
List<Throwable> failures = Collections.synchronizedList(new
ArrayList<>());
PendingQueue queue = new PropagatingPendingQueue(failures, new
Factory(random).get());
@@ -268,7 +269,7 @@ public class BurnTest
{
Cluster.run(toArray(nodes, Id[]::new), () -> queue,
responseSink, failures::add,
- () -> new Random(random.nextLong()),
+ () -> random.fork(),
() -> new AtomicLong()::incrementAndGet,
topologyFactory, () -> null);
}
@@ -313,7 +314,7 @@ public class BurnTest
count = 1;
break;
case "--loop-seed":
- seedGenerator = new Random(Long.parseLong(args[i +
1]))::nextLong;
+ seedGenerator = new DefaultRandom(Long.parseLong(args[i +
1]))::nextLong;
}
}
while (count-- > 0)
@@ -332,7 +333,7 @@ public class BurnTest
{
logger.info("Seed: {}", seed);
Cluster.trace.trace("Seed: {}", seed);
- Random random = new Random(seed);
+ RandomSource random = new DefaultRandom(seed);
try
{
List<Id> clients = generateIds(true, 1 + random.nextInt(4));
diff --git
a/accord-core/src/test/java/accord/burn/BurnTestConfigurationService.java
b/accord-core/src/test/java/accord/burn/BurnTestConfigurationService.java
index d2dd593b..2f86e935 100644
--- a/accord-core/src/test/java/accord/burn/BurnTestConfigurationService.java
+++ b/accord-core/src/test/java/accord/burn/BurnTestConfigurationService.java
@@ -18,9 +18,9 @@
package accord.burn;
-import accord.api.ConfigurationService;
import accord.api.MessageSink;
import accord.api.TestableConfigurationService;
+import accord.utils.RandomSource;
import accord.local.Node;
import accord.messages.*;
import accord.topology.Topology;
@@ -30,7 +30,10 @@ import accord.utils.async.AsyncResults;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -41,11 +44,11 @@ public class BurnTestConfigurationService implements
TestableConfigurationServic
private final Node.Id node;
private final MessageSink messageSink;
private final Function<Node.Id, Node> lookup;
- private final Supplier<Random> randomSupplier;
+ private final Supplier<RandomSource> randomSupplier;
private final Map<Long, FetchTopology> pendingEpochs = new HashMap<>();
private final EpochHistory epochs = new EpochHistory();
- private final List<ConfigurationService.Listener> listeners = new
ArrayList<>();
+ private final List<Listener> listeners = new ArrayList<>();
private final TopologyUpdates topologyUpdates;
private static class EpochState
@@ -125,7 +128,7 @@ public class BurnTestConfigurationService implements
TestableConfigurationServic
}
}
- public BurnTestConfigurationService(Node.Id node, MessageSink messageSink,
Supplier<Random> randomSupplier, Topology topology, Function<Node.Id, Node>
lookup, TopologyUpdates topologyUpdates)
+ public BurnTestConfigurationService(Node.Id node, MessageSink messageSink,
Supplier<RandomSource> randomSupplier, Topology topology, Function<Node.Id,
Node> lookup, TopologyUpdates topologyUpdates)
{
this.node = node;
this.messageSink = messageSink;
diff --git
a/accord-core/src/test/java/accord/burn/random/FrequentLargeRange.java
b/accord-core/src/test/java/accord/burn/random/FrequentLargeRange.java
new file mode 100644
index 00000000..42417980
--- /dev/null
+++ b/accord-core/src/test/java/accord/burn/random/FrequentLargeRange.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package accord.burn.random;
+
+import accord.utils.Invariants;
+import accord.utils.RandomSource;
+
+public class FrequentLargeRange implements RandomLong
+{
+ private final RandomLong small, large;
+ private final double ratio;
+ private final int steps;
+ private final double lower, upper;
+ private int run = -1;
+ private long smallCount = 0, largeCount = 0;
+
+ public FrequentLargeRange(RandomLong small, RandomLong large, double ratio)
+ {
+ Invariants.checkArgument(ratio > 0 && ratio <= 1);
+ this.small = small;
+ this.large = large;
+ this.ratio = ratio;
+ this.steps = (int) (1 / ratio);
+ this.lower = ratio * .8;
+ this.upper = ratio * 1.2;
+ }
+
+ @Override
+ public long getLong(RandomSource randomSource)
+ {
+ if (run != -1)
+ {
+ run--;
+ largeCount++;
+ return large.getLong(randomSource);
+ }
+ double currentRatio = largeCount / (double) (smallCount + largeCount);
+ if (currentRatio < lower)
+ {
+ // not enough large
+ largeCount++;
+ return large.getLong(randomSource);
+ }
+ if (currentRatio > upper)
+ {
+ // not enough small
+ smallCount++;
+ return small.getLong(randomSource);
+ }
+ if (randomSource.nextDouble() < ratio)
+ {
+ run = randomSource.nextInt(steps);
+ run--;
+ largeCount++;
+ return large.getLong(randomSource);
+ }
+ smallCount++;
+ return small.getLong(randomSource);
+ }
+}
diff --git a/accord-maelstrom/build.gradle
b/accord-core/src/test/java/accord/burn/random/IntRange.java
similarity index 53%
copy from accord-maelstrom/build.gradle
copy to accord-core/src/test/java/accord/burn/random/IntRange.java
index cc08d3f4..5ebc2cd2 100644
--- a/accord-maelstrom/build.gradle
+++ b/accord-core/src/test/java/accord/burn/random/IntRange.java
@@ -16,35 +16,26 @@
* limitations under the License.
*/
-plugins {
- id 'accord.java-conventions'
-}
+package accord.burn.random;
-dependencies {
- implementation project(':accord-core')
- implementation group: 'com.google.code.findbugs', name: 'jsr305', version:
'3.0.2'
- implementation 'com.google.code.gson:gson:2.8.7'
+import accord.utils.RandomSource;
- testImplementation(testFixtures(project(':accord-core')))
-}
+public class IntRange implements RandomInt
+{
+ public final int min, max;
+ private final int maxDelta;
-jar {
- manifest {
- attributes(
- 'Main-Class': 'accord.maelstrom.Main',
- )
+ public IntRange(int min, int max)
+ {
+ if (min >= max) throw new IllegalArgumentException(String.format("Min
(%s) should be less than max (%d).", min, max));
+ this.min = min;
+ this.max = max;
+ this.maxDelta = max - min + 1;
}
-}
-task fatJar(type: Jar) {
- manifest.from jar.manifest
- archiveClassifier = 'all'
- from {
- configurations.runtimeClasspath.collect { it.isDirectory() ? it :
zipTree(it) }
- } {
- exclude "META-INF/*.SF"
- exclude "META-INF/*.DSA"
- exclude "META-INF/*.RSA"
+ @Override
+ public int getInt(RandomSource randomSource)
+ {
+ return min + randomSource.nextInt(maxDelta);
}
- with jar
}
diff --git a/accord-maelstrom/build.gradle
b/accord-core/src/test/java/accord/burn/random/RandomInt.java
similarity index 52%
copy from accord-maelstrom/build.gradle
copy to accord-core/src/test/java/accord/burn/random/RandomInt.java
index cc08d3f4..a8fbbd12 100644
--- a/accord-maelstrom/build.gradle
+++ b/accord-core/src/test/java/accord/burn/random/RandomInt.java
@@ -16,35 +16,17 @@
* limitations under the License.
*/
-plugins {
- id 'accord.java-conventions'
-}
-
-dependencies {
- implementation project(':accord-core')
- implementation group: 'com.google.code.findbugs', name: 'jsr305', version:
'3.0.2'
- implementation 'com.google.code.gson:gson:2.8.7'
+package accord.burn.random;
- testImplementation(testFixtures(project(':accord-core')))
-}
+import accord.utils.RandomSource;
-jar {
- manifest {
- attributes(
- 'Main-Class': 'accord.maelstrom.Main',
- )
- }
-}
+public interface RandomInt extends RandomLong
+{
+ int getInt(RandomSource randomSource);
-task fatJar(type: Jar) {
- manifest.from jar.manifest
- archiveClassifier = 'all'
- from {
- configurations.runtimeClasspath.collect { it.isDirectory() ? it :
zipTree(it) }
- } {
- exclude "META-INF/*.SF"
- exclude "META-INF/*.DSA"
- exclude "META-INF/*.RSA"
+ @Override
+ default long getLong(RandomSource randomSource)
+ {
+ return getInt(randomSource);
}
- with jar
}
diff --git a/accord-maelstrom/build.gradle
b/accord-core/src/test/java/accord/burn/random/RandomLong.java
similarity index 52%
copy from accord-maelstrom/build.gradle
copy to accord-core/src/test/java/accord/burn/random/RandomLong.java
index cc08d3f4..1bc8c482 100644
--- a/accord-maelstrom/build.gradle
+++ b/accord-core/src/test/java/accord/burn/random/RandomLong.java
@@ -16,35 +16,11 @@
* limitations under the License.
*/
-plugins {
- id 'accord.java-conventions'
-}
-
-dependencies {
- implementation project(':accord-core')
- implementation group: 'com.google.code.findbugs', name: 'jsr305', version:
'3.0.2'
- implementation 'com.google.code.gson:gson:2.8.7'
+package accord.burn.random;
- testImplementation(testFixtures(project(':accord-core')))
-}
-
-jar {
- manifest {
- attributes(
- 'Main-Class': 'accord.maelstrom.Main',
- )
- }
-}
+import accord.utils.RandomSource;
-task fatJar(type: Jar) {
- manifest.from jar.manifest
- archiveClassifier = 'all'
- from {
- configurations.runtimeClasspath.collect { it.isDirectory() ? it :
zipTree(it) }
- } {
- exclude "META-INF/*.SF"
- exclude "META-INF/*.DSA"
- exclude "META-INF/*.RSA"
- }
- with jar
+public interface RandomLong
+{
+ long getLong(RandomSource randomSource);
}
diff --git a/accord-core/src/test/java/accord/burn/random/RandomRangeTest.java
b/accord-core/src/test/java/accord/burn/random/RandomRangeTest.java
new file mode 100644
index 00000000..68c134a8
--- /dev/null
+++ b/accord-core/src/test/java/accord/burn/random/RandomRangeTest.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package accord.burn.random;
+
+import accord.utils.Gen;
+import accord.utils.Gens;
+import accord.utils.RandomSource;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+
+import static accord.utils.Property.qt;
+
+class RandomRangeTest
+{
+ @Test
+ void intRange()
+ {
+ test((ignore, min, max) -> new IntRange(min, max));
+ }
+
+ @Test
+ void randomWalkRange()
+ {
+ test(RandomWalkRange::new);
+ }
+
+ void test(Factory factory)
+ {
+ int samples = 1000;
+ qt().forAll(Gens.random(), ranges()).check((rs, range) -> {
+ RandomLong randRange = factory.create(rs, range.min, range.max);
+ for (int i = 0; i < samples; i++)
+ Assertions.assertThat(randRange.getLong(rs)).isBetween((long)
range.min, (long) range.max);
+ });
+ }
+
+ private static Gen<Range> ranges()
+ {
+ Range range = new Range();
+ Gen<Integer> numGen = Gens.ints().between(0, 1000);
+ int[] buffer = new int[2];
+ return rs -> {
+ buffer[0] = numGen.next(rs);
+ do
+ {
+ buffer[1] = numGen.next(rs);
+ }
+ while (buffer[0] == buffer[1]);
+ Arrays.sort(buffer);
+ range.min = buffer[0];
+ range.max = buffer[1];
+ return range;
+ };
+ }
+
+ private static class Range
+ {
+ int min, max;
+ }
+
+ private interface Factory
+ {
+ RandomLong create(RandomSource random, int min, int max);
+ }
+}
\ No newline at end of file
diff --git a/accord-core/src/test/java/accord/burn/random/RandomWalkRange.java
b/accord-core/src/test/java/accord/burn/random/RandomWalkRange.java
new file mode 100644
index 00000000..c618cf7e
--- /dev/null
+++ b/accord-core/src/test/java/accord/burn/random/RandomWalkRange.java
@@ -0,0 +1,61 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package accord.burn.random;
+
+import accord.utils.RandomSource;
+
+public class RandomWalkRange implements RandomLong
+{
+ public final int min, max;
+ private final int maxStepSize;
+ long cur;
+
+ public RandomWalkRange(RandomSource random, int min, int max)
+ {
+ this.min = min;
+ this.max = max;
+ this.maxStepSize = maxStepSize(random, min, max);
+ this.cur = random.nextLong(min, max + 1);
+ }
+
+ @Override
+ public long getLong(RandomSource randomSource)
+ {
+ long step = randomSource.nextLong(-maxStepSize, maxStepSize + 1);
+ long cur = this.cur;
+ this.cur = step > 0 ? Math.min(max, cur + step)
+ : Math.max(min, cur + step);
+ return cur;
+ }
+
+ private static int maxStepSize(RandomSource random, int min, int max)
+ {
+ switch (random.nextInt(3))
+ {
+ case 0:
+ return Math.max(1, (max/32) - (min/32));
+ case 1:
+ return Math.max(1, (max/256) - (min/256));
+ case 2:
+ return Math.max(1, (max/2048) - (min/2048));
+ default:
+ return Math.max(1, (max/16384) - (min/16384));
+ }
+ }
+}
diff --git
a/accord-core/src/test/java/accord/burn/random/SegmentedRandomRangeTest.java
b/accord-core/src/test/java/accord/burn/random/SegmentedRandomRangeTest.java
new file mode 100644
index 00000000..caddc241
--- /dev/null
+++ b/accord-core/src/test/java/accord/burn/random/SegmentedRandomRangeTest.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package accord.burn.random;
+
+import accord.utils.Gen;
+import accord.utils.Gens;
+import accord.utils.RandomSource;
+import org.agrona.collections.Long2LongHashMap;
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.stream.LongStream;
+
+import static accord.utils.Property.qt;
+
+class SegmentedRandomRangeTest
+{
+ enum Type
+ {
+ IntRange,
+ RandomWalkRange
+ }
+
+ private static final class TestCase
+ {
+ private final int minSmall, maxSmall, minLarge, maxLarge, largeRatio;
+ private final Type type;
+
+ private TestCase(int minSmall, int maxSmall, int minLarge, int
maxLarge, int largeRatio, Type type)
+ {
+ this.minSmall = minSmall;
+ this.maxSmall = maxSmall;
+ this.minLarge = minLarge;
+ this.maxLarge = maxLarge;
+ this.largeRatio = largeRatio;
+ this.type = type;
+ }
+
+ RandomLong min(RandomSource random)
+ {
+ return create(random, minSmall, maxSmall);
+ }
+
+ RandomLong max(RandomSource random)
+ {
+ return create(random, minLarge, maxLarge);
+ }
+
+ private RandomLong create(RandomSource random, int min, int max)
+ {
+ switch (type)
+ {
+ case IntRange: return new IntRange(min, max);
+ case RandomWalkRange: return new RandomWalkRange(random, min,
max);
+ default: throw new UnsupportedOperationException("type " +
type);
+ }
+ }
+
+ double ratio()
+ {
+ return 1 / (double) largeRatio;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "TestCase{" +
+ "minSmall=" + minSmall +
+ ", maxSmall=" + maxSmall +
+ ", minLarge=" + minLarge +
+ ", maxLarge=" + maxLarge +
+ ", largeRatio=" + largeRatio +
+ ", type=" + type +
+ '}';
+ }
+ }
+
+ @Test
+ void disjoint()
+ {
+ Gen<Integer> range = Gens.ints().between(100, 100000000);
+ Gen<Integer> ratio = Gens.ints().between(6, 20);
+ Gen<Type> typeGen = Gens.enums().all(Type.class);
+ int[] ints = new int[3];
+ Gen<TestCase> test = rs -> {
+ ints[0] = range.next(rs);
+ ints[1] = range.next(rs);
+ ints[2] = range.next(rs);
+ Arrays.sort(ints);
+ int largeRatio = ratio.next(rs);
+ return new TestCase(0, ints[0], ints[1], ints[2], largeRatio,
typeGen.next(rs));
+ };
+ qt().forAll(Gens.random(), test).check(SegmentedRandomRangeTest::test);
+ }
+
+ private static void test(RandomSource rs, TestCase tc)
+ {
+ double ratio = tc.ratio();
+ FrequentLargeRange period = new FrequentLargeRange(tc.min(rs),
tc.max(rs), ratio);
+ int numSamples = 1000;
+ int maxResamples = 1000;
+
+ double target = ratio * 100.0D;
+ double upperBounds = target * 1.2;
+ double lowerBounds = target * .8;
+
+ int largeSeq = 0;
+ int largeCount = 0;
+ Long2LongHashMap largeSeqCounts = new Long2LongHashMap(0);
+ int resamples = 0;
+ for (int i = 0; i < numSamples; i++)
+ {
+ long size = period.getLong(rs);
+ if (size > tc.maxSmall)
+ {
+ largeCount++;
+ largeSeq++;
+ }
+ else
+ {
+ largeSeqCounts.compute(largeSeq, (ignore, accm) -> accm + 1);
+ largeSeq = 0;
+ }
+ if (i == numSamples - 1)
+ {
+ // keep going if ratio is off...
+ double actual = (double) largeCount / numSamples * 100.0;
+ if (actual < lowerBounds || actual > upperBounds)
+ {
+ if (resamples == maxResamples)
+ throw new AssertionError(String.format("Unable to
match target rate in %d re-samples; actual=%f, target=%f, bounds=(%f, %f)",
resamples, actual, target, lowerBounds, upperBounds));
+ numSamples += 100;
+ resamples++;
+ }
+ }
+ }
+
+ checkSequences(largeSeqCounts);
+ assertRatio(numSamples, upperBounds, lowerBounds, largeCount);
+ }
+
+ private static void checkSequences(Long2LongHashMap largeSeqCounts)
+ {
+ long[] keys = new long[largeSeqCounts.size()];
+ Long2LongHashMap.KeyIterator it = largeSeqCounts.keySet().iterator();
+ int idx = 0;
+ while (it.hasNext())
+ keys[idx++] = it.nextValue();
+ if (LongStream.of(keys).anyMatch(seq -> seq > 5))
+ return;
+ StringBuilder sb = new StringBuilder("No large sequences detected;
saw\n");
+ Arrays.sort(keys);
+ for (long key : keys)
+
sb.append('\t').append(key).append('\t').append(largeSeqCounts.get(key)).append('\n');
+ throw new AssertionError(sb.toString());
+ }
+
+ private static void assertRatio(int numSamples, double upperBounds, double
lowerBounds, long largeCount)
+ {
+ double largePercent = largeCount / (double) numSamples * 100.0;
+ Assertions.assertThat(largePercent)
+ .describedAs("Expected ratio was not respected")
+ .isBetween(lowerBounds, upperBounds);
+ }
+}
\ No newline at end of file
diff --git
a/accord-core/src/test/java/accord/coordinate/tracking/FastPathTrackerReconciler.java
b/accord-core/src/test/java/accord/coordinate/tracking/FastPathTrackerReconciler.java
index 59c811c6..b6947225 100644
---
a/accord-core/src/test/java/accord/coordinate/tracking/FastPathTrackerReconciler.java
+++
b/accord-core/src/test/java/accord/coordinate/tracking/FastPathTrackerReconciler.java
@@ -18,25 +18,24 @@
package accord.coordinate.tracking;
+import accord.utils.RandomSource;
import accord.coordinate.tracking.FastPathTracker.FastPathShardTracker;
-import accord.coordinate.tracking.QuorumTracker.QuorumShardTracker;
import accord.local.Node;
import accord.topology.Topologies;
import org.junit.jupiter.api.Assertions;
import java.util.ArrayList;
-import java.util.Random;
public class FastPathTrackerReconciler extends
TrackerReconciler<FastPathShardTracker, FastPathTracker,
FastPathTrackerReconciler.Rsp>
{
enum Rsp { FAST, SLOW, FAIL }
- FastPathTrackerReconciler(Random random, Topologies topologies)
+ FastPathTrackerReconciler(RandomSource random, Topologies topologies)
{
this(random, new FastPathTracker(topologies));
}
- private FastPathTrackerReconciler(Random random, FastPathTracker tracker)
+ private FastPathTrackerReconciler(RandomSource random, FastPathTracker
tracker)
{
super(random, Rsp.class, tracker, new ArrayList<>(tracker.nodes()));
}
diff --git
a/accord-core/src/test/java/accord/coordinate/tracking/InvalidationTrackerReconciler.java
b/accord-core/src/test/java/accord/coordinate/tracking/InvalidationTrackerReconciler.java
index 35f0a91e..34202a5f 100644
---
a/accord-core/src/test/java/accord/coordinate/tracking/InvalidationTrackerReconciler.java
+++
b/accord-core/src/test/java/accord/coordinate/tracking/InvalidationTrackerReconciler.java
@@ -18,24 +18,24 @@
package accord.coordinate.tracking;
+import accord.utils.RandomSource;
import accord.coordinate.tracking.InvalidationTracker.InvalidationShardTracker;
import accord.local.Node;
import accord.topology.Topologies;
import org.junit.jupiter.api.Assertions;
import java.util.ArrayList;
-import java.util.Random;
public class InvalidationTrackerReconciler extends
TrackerReconciler<InvalidationShardTracker, InvalidationTracker,
InvalidationTrackerReconciler.Rsp>
{
enum Rsp { PROMISED_FAST, NOT_PROMISED_FAST, PROMISED_SLOW,
NOT_PROMISED_SLOW, FAIL }
- InvalidationTrackerReconciler(Random random, Topologies topologies)
+ InvalidationTrackerReconciler(RandomSource random, Topologies topologies)
{
this(random, new InvalidationTracker(topologies));
}
- private InvalidationTrackerReconciler(Random random, InvalidationTracker
tracker)
+ private InvalidationTrackerReconciler(RandomSource random,
InvalidationTracker tracker)
{
super(random, Rsp.class, tracker, new ArrayList<>(tracker.nodes()));
}
diff --git
a/accord-core/src/test/java/accord/coordinate/tracking/QuorumTrackerReconciler.java
b/accord-core/src/test/java/accord/coordinate/tracking/QuorumTrackerReconciler.java
index cc3046df..0d705501 100644
---
a/accord-core/src/test/java/accord/coordinate/tracking/QuorumTrackerReconciler.java
+++
b/accord-core/src/test/java/accord/coordinate/tracking/QuorumTrackerReconciler.java
@@ -18,24 +18,24 @@
package accord.coordinate.tracking;
+import accord.utils.RandomSource;
import accord.coordinate.tracking.QuorumTracker.QuorumShardTracker;
import accord.local.Node;
import accord.topology.Topologies;
import org.junit.jupiter.api.Assertions;
import java.util.ArrayList;
-import java.util.Random;
public class QuorumTrackerReconciler extends
TrackerReconciler<QuorumShardTracker, QuorumTracker,
QuorumTrackerReconciler.Rsp>
{
enum Rsp { QUORUM, FAIL }
- QuorumTrackerReconciler(Random random, Topologies topologies)
+ QuorumTrackerReconciler(RandomSource random, Topologies topologies)
{
this(random, new QuorumTracker(topologies));
}
- private QuorumTrackerReconciler(Random random, QuorumTracker tracker)
+ private QuorumTrackerReconciler(RandomSource random, QuorumTracker tracker)
{
super(random, Rsp.class, tracker, new ArrayList<>(tracker.nodes()));
}
diff --git
a/accord-core/src/test/java/accord/coordinate/tracking/ReadTrackerReconciler.java
b/accord-core/src/test/java/accord/coordinate/tracking/ReadTrackerReconciler.java
index 0c71d70e..c912c95b 100644
---
a/accord-core/src/test/java/accord/coordinate/tracking/ReadTrackerReconciler.java
+++
b/accord-core/src/test/java/accord/coordinate/tracking/ReadTrackerReconciler.java
@@ -18,6 +18,7 @@
package accord.coordinate.tracking;
+import accord.utils.RandomSource;
import accord.coordinate.tracking.ReadTracker.ReadShardTracker;
import accord.local.Node;
import accord.topology.Topologies;
@@ -25,7 +26,6 @@ import org.junit.jupiter.api.Assertions;
import java.util.ArrayList;
import java.util.List;
-import java.util.Random;
public class ReadTrackerReconciler extends TrackerReconciler<ReadShardTracker,
ReadTracker, ReadTrackerReconciler.Rsp>
{
@@ -46,12 +46,12 @@ public class ReadTrackerReconciler extends
TrackerReconciler<ReadShardTracker, R
}
}
- ReadTrackerReconciler(Random random, Topologies topologies)
+ ReadTrackerReconciler(RandomSource random, Topologies topologies)
{
this(random, new InFlightCapturingReadTracker(topologies));
}
- private ReadTrackerReconciler(Random random, InFlightCapturingReadTracker
tracker)
+ private ReadTrackerReconciler(RandomSource random,
InFlightCapturingReadTracker tracker)
{
super(random, Rsp.class, tracker, tracker.inflight);
}
diff --git
a/accord-core/src/test/java/accord/coordinate/tracking/RecoveryTrackerReconciler.java
b/accord-core/src/test/java/accord/coordinate/tracking/RecoveryTrackerReconciler.java
index 4519fa63..5e2a2a7c 100644
---
a/accord-core/src/test/java/accord/coordinate/tracking/RecoveryTrackerReconciler.java
+++
b/accord-core/src/test/java/accord/coordinate/tracking/RecoveryTrackerReconciler.java
@@ -18,25 +18,25 @@
package accord.coordinate.tracking;
+import accord.utils.RandomSource;
import accord.coordinate.tracking.RecoveryTracker.RecoveryShardTracker;
import accord.local.Node;
import accord.topology.Topologies;
import org.junit.jupiter.api.Assertions;
import java.util.ArrayList;
-import java.util.Random;
// TODO (required, testing): check fast path accounting
public class RecoveryTrackerReconciler extends
TrackerReconciler<RecoveryShardTracker, RecoveryTracker,
RecoveryTrackerReconciler.Rsp>
{
enum Rsp { FAST, SLOW, FAIL }
- RecoveryTrackerReconciler(Random random, Topologies topologies)
+ RecoveryTrackerReconciler(RandomSource random, Topologies topologies)
{
this(random, new RecoveryTracker(topologies));
}
- private RecoveryTrackerReconciler(Random random, RecoveryTracker tracker)
+ private RecoveryTrackerReconciler(RandomSource random, RecoveryTracker
tracker)
{
super(random, Rsp.class, tracker, new ArrayList<>(tracker.nodes()));
}
diff --git
a/accord-core/src/test/java/accord/coordinate/tracking/TrackerReconciler.java
b/accord-core/src/test/java/accord/coordinate/tracking/TrackerReconciler.java
index a265633c..3736dd8f 100644
---
a/accord-core/src/test/java/accord/coordinate/tracking/TrackerReconciler.java
+++
b/accord-core/src/test/java/accord/coordinate/tracking/TrackerReconciler.java
@@ -19,6 +19,8 @@
package accord.coordinate.tracking;
import accord.burn.TopologyUpdates;
+import accord.utils.DefaultRandom;
+import accord.utils.RandomSource;
import accord.impl.IntHashKey;
import accord.impl.SizeOfIntersectionSorter;
import accord.impl.TopologyFactory;
@@ -28,20 +30,24 @@ import accord.topology.Topology;
import accord.topology.TopologyRandomizer;
import org.junit.jupiter.api.Assertions;
-import java.util.*;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.EnumMap;
+import java.util.List;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public abstract class TrackerReconciler<ST extends ShardTracker, T extends
AbstractTracker<ST, ?>, E extends Enum<E>>
{
- final Random random;
+ final RandomSource random;
final E[] events;
final EnumMap<E, Integer>[] counts;
final T tracker;
final List<Id> inflight;
- protected TrackerReconciler(Random random, Class<E> events, T tracker,
List<Id> inflight)
+ protected TrackerReconciler(RandomSource random, Class<E> events, T
tracker, List<Id> inflight)
{
this.random = random;
this.events = events.getEnumConstants();
@@ -86,17 +92,17 @@ public abstract class TrackerReconciler<ST extends
ShardTracker, T extends Abstr
abstract void validate(RequestStatus status);
protected static <ST extends ShardTracker, T extends AbstractTracker<ST,
?>, E extends Enum<E>>
- List<TrackerReconciler<ST, T, E>> generate(long seed, BiFunction<Random,
Topologies, ? extends TrackerReconciler<ST, T, E>> constructor)
+ List<TrackerReconciler<ST, T, E>> generate(long seed,
BiFunction<RandomSource, Topologies, ? extends TrackerReconciler<ST, T, E>>
constructor)
{
System.out.println("seed: " + seed);
- Random random = new Random(seed);
+ RandomSource random = new DefaultRandom(seed);
return topologies(random).map(topologies -> constructor.apply(random,
topologies))
.collect(Collectors.toList());
}
// TODO (required, testing): generalise and parameterise topology
generation a bit more
// also, select a subset of the generated
topologies to correctly simulate topology consumption logic
- private static Stream<Topologies> topologies(Random random)
+ private static Stream<Topologies> topologies(RandomSource random)
{
TopologyFactory factory = new TopologyFactory(2 + random.nextInt(3),
IntHashKey.ranges(4 + random.nextInt(12)));
List<Id> nodes = cluster(factory.rf * (1 +
random.nextInt(factory.shardRanges.length - 1)));
diff --git
a/accord-core/src/test/java/accord/coordinate/tracking/TrackerReconcilerTest.java
b/accord-core/src/test/java/accord/coordinate/tracking/TrackerReconcilerTest.java
index 22b7425f..d10a48ba 100644
---
a/accord-core/src/test/java/accord/coordinate/tracking/TrackerReconcilerTest.java
+++
b/accord-core/src/test/java/accord/coordinate/tracking/TrackerReconcilerTest.java
@@ -18,10 +18,10 @@
package accord.coordinate.tracking;
+import accord.utils.RandomSource;
import accord.topology.Topologies;
import org.junit.jupiter.api.Test;
-import java.util.Random;
import java.util.function.BiFunction;
public class TrackerReconcilerTest
@@ -57,7 +57,7 @@ public class TrackerReconcilerTest
}
static <ST extends ShardTracker, T extends AbstractTracker<ST, ?>, E
extends Enum<E>>
- void test(int count, BiFunction<Random, Topologies, ? extends
TrackerReconciler<ST, T, E>> constructor)
+ void test(int count, BiFunction<RandomSource, Topologies, ? extends
TrackerReconciler<ST, T, E>> constructor)
{
long seed = System.currentTimeMillis();
while (--count >= 0)
@@ -65,7 +65,7 @@ public class TrackerReconcilerTest
}
static <ST extends ShardTracker, T extends AbstractTracker<ST, ?>, E
extends Enum<E>>
- void test(long seed, BiFunction<Random, Topologies, ? extends
TrackerReconciler<ST, T, E>> constructor)
+ void test(long seed, BiFunction<RandomSource, Topologies, ? extends
TrackerReconciler<ST, T, E>> constructor)
{
for (TrackerReconciler<?, ?, ?> test :
TrackerReconciler.generate(seed, constructor))
test.test();
diff --git a/accord-core/src/test/java/accord/impl/basic/Cluster.java
b/accord-core/src/test/java/accord/impl/basic/Cluster.java
index 6ec6bf14..28983033 100644
--- a/accord-core/src/test/java/accord/impl/basic/Cluster.java
+++ b/accord-core/src/test/java/accord/impl/basic/Cluster.java
@@ -27,7 +27,6 @@ import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
-import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -50,6 +49,7 @@ import accord.messages.Reply;
import accord.messages.Request;
import accord.topology.TopologyRandomizer;
import accord.topology.Topology;
+import accord.utils.RandomSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -76,7 +76,7 @@ public class Cluster implements Scheduler
this.partitionSet = new HashSet<>();
}
- NodeSink create(Id self, Random random)
+ NodeSink create(Id self, RandomSource random)
{
NodeSink sink = new NodeSink(self, lookup, this, random);
sinks.put(self, sink);
@@ -206,7 +206,7 @@ public class Cluster implements Scheduler
run.run();
}
- public static void run(Id[] nodes, Supplier<PendingQueue> queueSupplier,
Consumer<Packet> responseSink, Consumer<Throwable> onFailure, Supplier<Random>
randomSupplier, Supplier<LongSupplier> nowSupplier, TopologyFactory
topologyFactory, Supplier<Packet> in)
+ public static void run(Id[] nodes, Supplier<PendingQueue> queueSupplier,
Consumer<Packet> responseSink, Consumer<Throwable> onFailure,
Supplier<RandomSource> randomSupplier, Supplier<LongSupplier> nowSupplier,
TopologyFactory topologyFactory, Supplier<Packet> in)
{
TopologyUpdates topologyUpdates = new TopologyUpdates();
Topology topology = topologyFactory.toTopology(nodes);
@@ -227,9 +227,9 @@ public class Cluster implements Scheduler
}
List<Id> nodesList = new ArrayList<>(Arrays.asList(nodes));
- Random shuffleRandom = randomSupplier.get();
+ RandomSource shuffleRandom = randomSupplier.get();
Scheduled chaos = sinks.recurring(() -> {
- Collections.shuffle(nodesList, shuffleRandom);
+ Collections.shuffle(nodesList, shuffleRandom.asJdkRandom());
int partitionSize =
shuffleRandom.nextInt((topologyFactory.rf+1)/2);
sinks.partitionSet = new LinkedHashSet<>(nodesList.subList(0,
partitionSize));
}, 5L, SECONDS);
diff --git a/accord-core/src/test/java/accord/impl/basic/NodeSink.java
b/accord-core/src/test/java/accord/impl/basic/NodeSink.java
index 006fa13d..bad4ed09 100644
--- a/accord-core/src/test/java/accord/impl/basic/NodeSink.java
+++ b/accord-core/src/test/java/accord/impl/basic/NodeSink.java
@@ -20,10 +20,10 @@ package accord.impl.basic;
import java.util.LinkedHashMap;
import java.util.Map;
-import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
+import accord.utils.RandomSource;
import accord.coordinate.Timeout;
import accord.local.Node;
import accord.local.Node.Id;
@@ -40,12 +40,12 @@ public class NodeSink implements MessageSink
final Id self;
final Function<Id, Node> lookup;
final Cluster parent;
- final Random random;
+ final RandomSource random;
int nextMessageId = 0;
Map<Long, Callback> callbacks = new LinkedHashMap<>();
- public NodeSink(Id self, Function<Id, Node> lookup, Cluster parent, Random
random)
+ public NodeSink(Id self, Function<Id, Node> lookup, Cluster parent,
RandomSource random)
{
this.self = self;
this.lookup = lookup;
diff --git a/accord-core/src/test/java/accord/impl/basic/RandomDelayQueue.java
b/accord-core/src/test/java/accord/impl/basic/RandomDelayQueue.java
index d7131e2c..7c95f418 100644
--- a/accord-core/src/test/java/accord/impl/basic/RandomDelayQueue.java
+++ b/accord-core/src/test/java/accord/impl/basic/RandomDelayQueue.java
@@ -18,8 +18,9 @@
package accord.impl.basic;
+import accord.utils.RandomSource;
+
import java.util.PriorityQueue;
-import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
@@ -27,9 +28,9 @@ public class RandomDelayQueue<T> implements PendingQueue
{
public static class Factory implements Supplier<PendingQueue>
{
- final Random seeds;
+ final RandomSource seeds;
- public Factory(Random seeds)
+ public Factory(RandomSource seeds)
{
this.seeds = seeds;
}
@@ -37,7 +38,7 @@ public class RandomDelayQueue<T> implements PendingQueue
@Override
public PendingQueue get()
{
- return new RandomDelayQueue<>(new Random(seeds.nextLong()));
+ return new RandomDelayQueue<>(seeds.fork());
}
}
@@ -70,11 +71,11 @@ public class RandomDelayQueue<T> implements PendingQueue
}
final PriorityQueue<Item> queue = new PriorityQueue<>();
- final Random random;
+ final RandomSource random;
long now;
int seq;
- RandomDelayQueue(Random random)
+ RandomDelayQueue(RandomSource random)
{
this.random = random;
}
diff --git
a/accord-core/src/test/java/accord/impl/basic/UniformRandomQueue.java
b/accord-core/src/test/java/accord/impl/basic/UniformRandomQueue.java
index 309e1938..45d55c30 100644
--- a/accord-core/src/test/java/accord/impl/basic/UniformRandomQueue.java
+++ b/accord-core/src/test/java/accord/impl/basic/UniformRandomQueue.java
@@ -18,8 +18,9 @@
package accord.impl.basic;
+import accord.utils.RandomSource;
+
import java.util.PriorityQueue;
-import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
@@ -27,9 +28,9 @@ public class UniformRandomQueue<T> implements PendingQueue
{
public static class Factory implements Supplier<PendingQueue>
{
- final Random seeds;
+ final RandomSource seeds;
- public Factory(Random seeds)
+ public Factory(RandomSource seeds)
{
this.seeds = seeds;
}
@@ -37,7 +38,7 @@ public class UniformRandomQueue<T> implements PendingQueue
@Override
public PendingQueue get()
{
- return new UniformRandomQueue<>(new Random(seeds.nextLong()));
+ return new UniformRandomQueue<>(seeds.fork());
}
}
@@ -60,9 +61,9 @@ public class UniformRandomQueue<T> implements PendingQueue
}
final PriorityQueue<Item> queue = new PriorityQueue<>();
- final Random random;
+ final RandomSource random;
- public UniformRandomQueue(Random random)
+ public UniformRandomQueue(RandomSource random)
{
this.random = random;
}
diff --git a/accord-core/src/test/java/accord/impl/mock/MockCluster.java
b/accord-core/src/test/java/accord/impl/mock/MockCluster.java
index 90613ff4..2a888642 100644
--- a/accord-core/src/test/java/accord/impl/mock/MockCluster.java
+++ b/accord-core/src/test/java/accord/impl/mock/MockCluster.java
@@ -19,7 +19,6 @@
package accord.impl.mock;
import accord.NetworkFilter;
-import accord.api.Key;
import accord.api.MessageSink;
import accord.coordinate.Timeout;
import accord.impl.*;
@@ -27,7 +26,9 @@ import accord.local.Node;
import accord.local.Node.Id;
import accord.local.ShardDistributor;
import accord.primitives.Ranges;
+import accord.utils.DefaultRandom;
import accord.utils.EpochFunction;
+import accord.utils.RandomSource;
import accord.utils.ThreadPoolScheduler;
import accord.primitives.TxnId;
import accord.messages.Callback;
@@ -53,7 +54,7 @@ public class MockCluster implements Network, AutoCloseable,
Iterable<Node>
{
private static final Logger logger =
LoggerFactory.getLogger(MockCluster.class);
- private final Random random;
+ private final RandomSource random;
private final Config config;
private final LongSupplier nowSupplier;
private final Map<Id, Node> nodes = new ConcurrentHashMap<>();
@@ -68,7 +69,7 @@ public class MockCluster implements Network, AutoCloseable,
Iterable<Node>
private MockCluster(Builder builder)
{
this.config = new Config(builder);
- this.random = new Random(config.seed);
+ this.random = new DefaultRandom(config.seed);
this.nowSupplier = builder.nowSupplier;
this.messageSinkFactory = builder.messageSinkFactory;
this.onFetchTopology = builder.onFetchTopology;
@@ -110,7 +111,7 @@ public class MockCluster implements Network, AutoCloseable,
Iterable<Node>
() -> store,
new ShardDistributor.EvenSplit(8, ignore -> new
IntKey.Splitter()),
new TestAgent(),
- new Random(random.nextLong()),
+ random.fork(),
new ThreadPoolScheduler(),
SizeOfIntersectionSorter.SUPPLIER,
SimpleProgressLog::new,
diff --git a/accord-core/src/test/java/accord/local/ImmutableCommandTest.java
b/accord-core/src/test/java/accord/local/ImmutableCommandTest.java
index e8f965de..be0df4d3 100644
--- a/accord-core/src/test/java/accord/local/ImmutableCommandTest.java
+++ b/accord-core/src/test/java/accord/local/ImmutableCommandTest.java
@@ -30,12 +30,13 @@ import accord.local.Status.Known;
import accord.primitives.*;
import accord.topology.Topology;
import com.google.common.collect.Lists;
+import accord.utils.DefaultRandom;
+
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import javax.annotation.Nullable;
import java.util.List;
-import java.util.Random;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicReference;
@@ -95,7 +96,7 @@ public class ImmutableCommandTest
private static Node createNode(Id id, CommandStoreSupport storeSupport)
{
return new Node(id, null, new MockConfigurationService(null, (epoch,
service) -> { }, storeSupport.local.get()),
- new MockCluster.Clock(100), () -> storeSupport.data,
new ShardDistributor.EvenSplit(8, ignore -> new IntKey.Splitter()), new
TestAgent(), new Random(), null,
+ new MockCluster.Clock(100), () -> storeSupport.data,
new ShardDistributor.EvenSplit(8, ignore -> new IntKey.Splitter()), new
TestAgent(), new DefaultRandom(), null,
SizeOfIntersectionSorter.SUPPLIER, ignore -> ignore2
-> new NoOpProgressLog(), InMemoryCommandStores.Synchronized::new);
}
diff --git a/accord-core/src/test/java/accord/messages/PreAcceptTest.java
b/accord-core/src/test/java/accord/messages/PreAcceptTest.java
index 23b7b822..ae339a1e 100644
--- a/accord-core/src/test/java/accord/messages/PreAcceptTest.java
+++ b/accord-core/src/test/java/accord/messages/PreAcceptTest.java
@@ -31,6 +31,7 @@ import accord.api.Scheduler;
import accord.impl.mock.MockCluster.Clock;
import accord.primitives.*;
import accord.topology.Topology;
+import accord.utils.DefaultRandom;
import accord.utils.EpochFunction;
import accord.utils.ThreadPoolScheduler;
import accord.local.*;
@@ -39,7 +40,6 @@ import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.List;
-import java.util.Random;
import java.util.function.BiFunction;
import java.util.stream.Stream;
@@ -75,7 +75,7 @@ public class PreAcceptTest
() -> store,
new ShardDistributor.EvenSplit(8, ignore -> new
IntKey.Splitter()),
new TestAgent(),
- new Random(),
+ new DefaultRandom(),
scheduler,
SizeOfIntersectionSorter.SUPPLIER,
SimpleProgressLog::new,
diff --git a/accord-core/src/test/java/accord/primitives/KeyDepsTest.java
b/accord-core/src/test/java/accord/primitives/KeyDepsTest.java
index 53e566fd..953626da 100644
--- a/accord-core/src/test/java/accord/primitives/KeyDepsTest.java
+++ b/accord-core/src/test/java/accord/primitives/KeyDepsTest.java
@@ -18,7 +18,14 @@
package accord.primitives;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -31,8 +38,10 @@ import java.util.stream.IntStream;
import accord.impl.IntHashKey.Hash;
import accord.primitives.KeyDeps.Builder;
+import accord.utils.DefaultRandom;
import accord.utils.Gen;
import accord.utils.Gens;
+import accord.utils.RandomSource;
import accord.utils.RelationMultiMap.Entry;
import com.google.common.collect.Sets;
import org.junit.jupiter.api.Assertions;
@@ -73,7 +82,7 @@ public class KeyDepsTest
private static void testMerge(long seed, int uniqueTxnIdsRange, int
epochRange, int hlcRange, int nodeRange,
int uniqueKeysRange, int emptyKeysRange, int
keyRange, int totalCountRange, int mergeCountRange)
{
- Random random = random(seed);
+ RandomSource random = random(seed);
Supplier<Deps> supplier = supplier(random, uniqueTxnIdsRange,
epochRange, hlcRange, 0, nodeRange,
uniqueKeysRange, emptyKeysRange,
keyRange, totalCountRange);
int count = 1 + random.nextInt(mergeCountRange);
@@ -92,7 +101,7 @@ public class KeyDepsTest
private static void testWith(long seed, int uniqueTxnIdsRange, int
epochRange, int hlcRange, int nodeRange,
int uniqueKeysRange, int emptyKeysRange, int
keyRange, int totalCountRange, int mergeCountRange)
{
- Random random = random(seed);
+ RandomSource random = random(seed);
Supplier<Deps> supplier = supplier(random, uniqueTxnIdsRange,
epochRange, hlcRange, 0, nodeRange,
uniqueKeysRange, emptyKeysRange,
keyRange, totalCountRange);
Deps cur = supplier.get();
@@ -283,7 +292,7 @@ public class KeyDepsTest
{
builder.nextKey(key);
List<TxnId> ids = new ArrayList<>(deps.canonical.get(key));
- Collections.shuffle(ids, random);
+ Collections.shuffle(ids, random.asJdkRandom());
ids.forEach(builder::add);
}
@@ -303,24 +312,24 @@ public class KeyDepsTest
this.test = test;
}
- static Deps generate(Gen.Random random)
+ static Deps generate(RandomSource random)
{
int epochRange = 3;
int hlcRange = 500;
double uniqueTxnIdsPercentage = 0.66D;
- int uniqueTxnIds = random.nextPositive((int) ((hlcRange *
epochRange) * uniqueTxnIdsPercentage));
+ int uniqueTxnIds = random.nextInt(1, (int) ((hlcRange *
epochRange) * uniqueTxnIdsPercentage));
- int nodeRange = random.nextPositive(4);
+ int nodeRange = random.nextInt(1, 4);
int uniqueKeys = random.nextInt(2, 200);
int emptyKeys = random.nextInt(0, 10);
int keyRange = random.nextInt(uniqueKeys + emptyKeys, 400);
- int totalCount = random.nextPositive(1000);
+ int totalCount = random.nextInt(1, 1000);
Deps deps = generate(random, uniqueTxnIds, epochRange, hlcRange,
0, nodeRange, uniqueKeys, emptyKeys, keyRange, totalCount);
deps.testSimpleEquality();
return deps;
}
- static Deps generate(Random random, int uniqueTxnIds, int epochRange,
int hlcRange, int flagsRange, int nodeRange,
+ static Deps generate(RandomSource random, int uniqueTxnIds, int
epochRange, int hlcRange, int flagsRange, int nodeRange,
int uniqueKeys, int emptyKeys, int keyRange, int
totalCount)
{
// populateKeys is a subset of keys
@@ -446,7 +455,7 @@ public class KeyDepsTest
}
}
- private static Ranges randomKeyRanges(Random random, int countRange, int
valueRange)
+ private static Ranges randomKeyRanges(RandomSource random, int countRange,
int valueRange)
{
int count = countRange == 1 ? 1 : 1 + random.nextInt(countRange - 1);
Hash[] hashes;
@@ -468,14 +477,14 @@ public class KeyDepsTest
private static void testOneRandom(long seed, int uniqueTxnIds, int
epochRange, int hlcRange, int nodeRange,
int uniqueKeys, int emptyKeys, int
keyRange, int totalCountRange)
{
- Random random = random(seed);
+ RandomSource random = random(seed);
int totalCount = 1 + random.nextInt(totalCountRange - 1);
testOneDeps(random,
KeyDepsTest.Deps.generate(random, uniqueTxnIds,
epochRange, hlcRange, 0, nodeRange, uniqueKeys, emptyKeys, keyRange,
totalCount),
keyRange);
}
- private static Supplier<Deps> supplier(Random random, int
uniqueTxnIdsRange, int epochRange, int hlcRange, int flagRange, int nodeRange,
+ private static Supplier<Deps> supplier(RandomSource random, int
uniqueTxnIdsRange, int epochRange, int hlcRange, int flagRange, int nodeRange,
int uniqueKeysRange, int
emptyKeysRange, int keyRange, int totalCountRange)
{
return () -> {
@@ -492,7 +501,7 @@ public class KeyDepsTest
};
}
- private static void testOneDeps(Random random, Deps deps, int keyRange)
+ private static void testOneDeps(RandomSource random, Deps deps, int
keyRange)
{
deps.testSimpleEquality();
{
@@ -559,12 +568,10 @@ public class KeyDepsTest
return ThreadLocalRandom.current().nextLong();
}
- private static Random random(long seed)
+ private static RandomSource random(long seed)
{
logger.info("Seed {}", seed);
- Random random = new Random();
- random.setSeed(seed);
- return random;
+ return new DefaultRandom(seed);
}
public static void main(String[] args)
diff --git a/accord-core/src/test/java/accord/topology/TopologyRandomizer.java
b/accord-core/src/test/java/accord/topology/TopologyRandomizer.java
index 56e86560..73d6143e 100644
--- a/accord-core/src/test/java/accord/topology/TopologyRandomizer.java
+++ b/accord-core/src/test/java/accord/topology/TopologyRandomizer.java
@@ -19,6 +19,7 @@
package accord.topology;
import accord.burn.TopologyUpdates;
+import accord.utils.RandomSource;
import accord.impl.IntHashKey;
import accord.impl.IntHashKey.Hash;
import accord.local.Node;
@@ -27,24 +28,34 @@ import accord.primitives.Ranges;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import java.util.*;
-import java.util.function.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
// TODO (required, testing): add change replication factor
public class TopologyRandomizer
{
private static final Logger logger =
LoggerFactory.getLogger(TopologyRandomizer.class);
- private final Random random;
+ private final RandomSource random;
private final BiConsumer<Node.Id, Topology> notifier;
private final List<Topology> epochs = new ArrayList<>();
private final Map<Node.Id, Ranges> previouslyReplicated = new HashMap<>();
private final TopologyUpdates topologyUpdates;
- public TopologyRandomizer(Supplier<Random> randomSupplier, Topology
initialTopology, TopologyUpdates topologyUpdates, Function<Node.Id, Node>
lookup)
+ public TopologyRandomizer(Supplier<RandomSource> randomSupplier, Topology
initialTopology, TopologyUpdates topologyUpdates, Function<Node.Id, Node>
lookup)
{
this(randomSupplier, initialTopology, topologyUpdates, (id, topology)
-> topologyUpdates.notify(lookup.apply(id), topology.nodes(), topology));
}
- public TopologyRandomizer(Supplier<Random> randomSupplier, Topology
initialTopology, TopologyUpdates topologyUpdates, BiConsumer<Node.Id, Topology>
notifier)
+ public TopologyRandomizer(Supplier<RandomSource> randomSupplier, Topology
initialTopology, TopologyUpdates topologyUpdates, BiConsumer<Node.Id, Topology>
notifier)
{
this.random = randomSupplier.get();
this.topologyUpdates = topologyUpdates;
@@ -61,26 +72,26 @@ public class TopologyRandomizer
MEMBERSHIP(TopologyRandomizer::updateMembership),
FASTPATH(TopologyRandomizer::updateFastPath);
- private final BiFunction<Shard[], Random, Shard[]> function;
+ private final BiFunction<Shard[], RandomSource, Shard[]> function;
- UpdateType(BiFunction<Shard[], Random, Shard[]> function)
+ UpdateType(BiFunction<Shard[], RandomSource, Shard[]> function)
{
this.function = function;
}
- public Shard[] apply(Shard[] shards, Random random)
+ public Shard[] apply(Shard[] shards, RandomSource random)
{
return function.apply(shards, random);
}
- static UpdateType kind(Random random)
+ static UpdateType kind(RandomSource random)
{
int idx = random.nextInt(values().length);
return values()[idx];
}
}
- private static Shard[] updateBoundary(Shard[] shards, Random random)
+ private static Shard[] updateBoundary(Shard[] shards, RandomSource random)
{
int idx = random.nextInt(shards.length - 1);
Shard left = shards[idx];
@@ -104,7 +115,7 @@ public class TopologyRandomizer
return shards;
}
- private static Shard[] updateMembership(Shard[] shards, Random random)
+ private static Shard[] updateMembership(Shard[] shards, RandomSource
random)
{
if (shards.length <= 1)
return shards;
@@ -156,7 +167,7 @@ public class TopologyRandomizer
return shards;
}
- private static Set<Node.Id> newFastPath(List<Node.Id> nodes, Random random)
+ private static Set<Node.Id> newFastPath(List<Node.Id> nodes, RandomSource
random)
{
List<Node.Id> available = new ArrayList<>(nodes);
int rf = available.size();
@@ -174,7 +185,7 @@ public class TopologyRandomizer
return fastPath;
}
- private static Shard[] updateFastPath(Shard[] shards, Random random)
+ private static Shard[] updateFastPath(Shard[] shards, RandomSource random)
{
int idx = random.nextInt(shards.length);
Shard shard = shards[idx];
diff --git a/accord-core/src/test/java/accord/utils/AccordGens.java
b/accord-core/src/test/java/accord/utils/AccordGens.java
new file mode 100644
index 00000000..175a5e39
--- /dev/null
+++ b/accord-core/src/test/java/accord/utils/AccordGens.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package accord.utils;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.BiFunction;
+
+import accord.api.Key;
+import accord.api.RoutingKey;
+import accord.impl.IntHashKey;
+import accord.impl.IntKey;
+import accord.local.Node;
+import accord.primitives.Deps;
+import accord.primitives.KeyDeps;
+import accord.primitives.Range;
+import accord.primitives.RangeDeps;
+import accord.primitives.Routable;
+import accord.primitives.Timestamp;
+import accord.primitives.Txn;
+import accord.primitives.TxnId;
+
+public class AccordGens
+{
+ public static Gen.LongGen epochs()
+ {
+ return Gens.longs().between(0, Timestamp.MAX_EPOCH);
+ }
+
+ public static Gen<TxnId> txnIds()
+ {
+ return txnIds(epochs()::nextLong, RandomSource::nextLong,
RandomSource::nextInt);
+ }
+
+ public static Gen<TxnId> txnIds(Gen.LongGen epochs, Gen.LongGen hlcs,
Gen.IntGen nodes)
+ {
+ Gen<Txn.Kind> kinds = Gens.enums().all(Txn.Kind.class);
+ Gen<Routable.Domain> domains = Gens.enums().all(Routable.Domain.class);
+ return rs -> new TxnId(epochs.nextLong(rs), hlcs.nextLong(rs),
kinds.next(rs), domains.next(rs), new Node.Id(nodes.nextInt(rs)));
+ }
+
+ public static Gen<Key> intKeys()
+ {
+ return rs -> new IntKey.Raw(rs.nextInt());
+ }
+
+ public static Gen<Key> intHashKeys()
+ {
+ return rs -> IntHashKey.key(rs.nextInt());
+ }
+
+ public static Gen<KeyDeps> keyDeps(Gen<Key> keyGen)
+ {
+ return keyDeps(keyGen, txnIds());
+ }
+
+ public static Gen<KeyDeps> keyDeps(Gen<Key> keyGen, Gen<TxnId> idGen)
+ {
+ double emptyProb = .2D;
+ return rs -> {
+ if (rs.decide(emptyProb)) return KeyDeps.NONE;
+ Set<Key> seenKeys = new HashSet<>();
+ Set<TxnId> seenTxn = new HashSet<>();
+ Gen<Key> uniqKeyGen = keyGen.filter(seenKeys::add);
+ Gen<TxnId> uniqIdGen = idGen.filter(seenTxn::add);
+ try (KeyDeps.Builder builder = KeyDeps.builder())
+ {
+ for (int i = 0, numKeys = rs.nextInt(1, 10); i < numKeys; i++)
+ {
+ builder.nextKey(uniqKeyGen.next(rs));
+ seenTxn.clear();
+ for (int j = 0, numTxn = rs.nextInt(1, 10); j < numTxn;
j++)
+ builder.add(uniqIdGen.next(rs));
+ }
+ return builder.build();
+ }
+ };
+ }
+
+ public interface RangeFactory
+ {
+ Range create(RandomSource rs, RoutingKey a, RoutingKey b);
+ }
+
+ public static Gen<Range> ranges(Gen<RoutingKey> keyGen, BiFunction<? super
RoutingKey, ? super RoutingKey, ? extends Range> factory)
+ {
+ return ranges(keyGen, (ignore, a, b) -> factory.apply(a, b));
+ }
+
+ public static Gen<Range> ranges(Gen<RoutingKey> keyGen)
+ {
+ return ranges(keyGen, (rs, a, b) -> {
+ boolean left = rs.nextBoolean();
+ return Range.range(a, b, left, !left);
+ });
+ }
+
+ public static Gen<Range> ranges(Gen<RoutingKey> keyGen, RangeFactory
factory)
+ {
+ RoutingKey[] keys = new RoutingKey[2];
+ return rs -> {
+ keys[0] = keyGen.next(rs);
+ // range doesn't allow a=b
+ do keys[1] = keyGen.next(rs);
+ while (Objects.equals(keys[0], keys[1]));
+ Arrays.sort(keys);
+ return factory.create(rs, keys[0], keys[1]);
+ };
+ }
+
+ public static Gen<RangeDeps> rangeDeps(Gen<Range> rangeGen)
+ {
+ return rangeDeps(rangeGen, txnIds());
+ }
+
+ public static Gen<RangeDeps> rangeDeps(Gen<Range> rangeGen, Gen<TxnId>
idGen)
+ {
+ double emptyProb = .2D;
+ return rs -> {
+ if (rs.decide(emptyProb)) return RangeDeps.NONE;
+ RangeDeps.Builder builder = RangeDeps.builder();
+ for (int i = 0, numKeys = rs.nextInt(1, 10); i < numKeys; i++)
+ {
+ builder.nextKey(rangeGen.next(rs));
+ for (int j = 0, numTxn = rs.nextInt(1, 10); j < numTxn; j++)
+ builder.add(idGen.next(rs));
+ }
+ return builder.build();
+ };
+ }
+
+ public static Gen<Deps> deps(Gen<KeyDeps> keyDepsGen, Gen<RangeDeps>
rangeDepsGen)
+ {
+ return rs -> new Deps(keyDepsGen.next(rs), rangeDepsGen.next(rs));
+ }
+}
diff --git a/accord-core/src/test/java/accord/utils/Gen.java
b/accord-core/src/test/java/accord/utils/Gen.java
index f4827f9e..f1b00e24 100644
--- a/accord-core/src/test/java/accord/utils/Gen.java
+++ b/accord-core/src/test/java/accord/utils/Gen.java
@@ -20,6 +20,8 @@ package accord.utils;
import java.util.function.BiFunction;
import java.util.function.Function;
+import java.util.function.IntPredicate;
+import java.util.function.LongPredicate;
import java.util.function.Predicate;
public interface Gen<A> {
@@ -32,14 +34,14 @@ public interface Gen<A> {
return fn;
}
- A next(Random random);
+ A next(RandomSource random);
default <B> Gen<B> map(Function<A, B> fn)
{
return r -> fn.apply(this.next(r));
}
- default <B> Gen<B> map(BiFunction<Random, A, B> fn)
+ default <B> Gen<B> map(BiFunction<RandomSource, A, B> fn)
{
return r -> fn.apply(r, this.next(r));
}
@@ -57,93 +59,63 @@ public interface Gen<A> {
};
}
- class Random extends java.util.Random
+ interface IntGen extends Gen<Integer>
{
- public Random(long seed) {
- super(seed);
- }
-
- public int nextInt(int origin, int bound)
- {
- if (origin >= bound)
- throw new IllegalArgumentException("bound (" + bound + ") must
be greater than origin (" + origin + ")");
- int r = nextInt();
- if (origin < bound) {
- int n = bound - origin, m = n - 1;
- if ((n & m) == 0)
- r = (r & m) + origin;
- else if (n > 0) {
- for (int u = r >>> 1;
- u + m - (r = u % n) < 0;
- u = nextInt() >>> 1)
- ;
- r += origin;
- }
- else {
- while (r < origin || r >= bound)
- r = nextInt();
- }
- }
- return r;
- }
-
- public int allPositive()
- {
- return nextInt(1, Integer.MAX_VALUE);
- }
+ int nextInt(RandomSource random);
- public int nextPositive(int upper)
+ @Override
+ default Integer next(RandomSource random)
{
- return nextInt(1, upper);
+ return nextInt(random);
}
- public long nextLong(long bound)
+ default Gen.IntGen filterInt(IntPredicate fn)
{
- return nextLong(0, bound);
+ return rs -> {
+ int value;
+ do
+ {
+ value = nextInt(rs);
+ }
+ while (!fn.test(value));
+ return value;
+ };
}
- public long nextLong(long origin, long bound)
+ @Override
+ default Gen.IntGen filter(Predicate<Integer> fn)
{
- if (origin >= bound)
- throw new IllegalArgumentException("bound must be greater than
origin");
- long r = nextLong();
- long n = bound - origin, m = n - 1;
- if ((n & m) == 0L) // power of two
- r = (r & m) + origin;
- else if (n > 0L) { // reject over-represented candidates
- for (long u = r >>> 1; // ensure nonnegative
- u + m - (r = u % n) < 0L; // rejection check
- u = nextLong() >>> 1) // retry
- ;
- r += origin;
- }
- else { // range not representable as long
- while (r < origin || r >= bound)
- r = nextLong();
- }
- return r;
+ return filterInt(i -> fn.test(i));
}
}
- interface IntGen extends Gen<Integer>
+ interface LongGen extends Gen<Long>
{
- int nextInt(Random random);
+ long nextLong(RandomSource random);
@Override
- default Integer next(Random random)
+ default Long next(RandomSource random)
{
- return nextInt(random);
+ return nextLong(random);
}
- }
- interface LongGen extends Gen<Long>
- {
- long nextLong(Random random);
+ default Gen.LongGen filterLong(LongPredicate fn)
+ {
+ return rs -> {
+ long value;
+ do
+ {
+ value = nextLong(rs);
+ }
+ while (!fn.test(value));
+ return value;
+ };
+ }
@Override
- default Long next(Random random)
+ default Gen.LongGen filter(Predicate<Long> fn)
{
- return nextLong(random);
+ return filterLong(i -> fn.test(i));
}
}
}
diff --git a/accord-core/src/test/java/accord/utils/Gens.java
b/accord-core/src/test/java/accord/utils/Gens.java
index 0aa7d0f4..7cb12f74 100644
--- a/accord-core/src/test/java/accord/utils/Gens.java
+++ b/accord-core/src/test/java/accord/utils/Gens.java
@@ -25,8 +25,10 @@ import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.function.Function;
import java.util.function.Supplier;
+
public class Gens {
private Gens() {
}
@@ -52,10 +54,45 @@ public class Gens {
return rs -> ts.get(offset.nextInt(rs));
}
- public static Gen<Gen.Random> random() {
+ public static Gen<char[]> charArray(Gen.IntGen sizes, char[] domain)
+ {
+ return charArray(sizes, domain, (a, b) -> true);
+ }
+
+ public interface IntCharBiPredicate
+ {
+ boolean test(int a, char b);
+ }
+
+ public static Gen<char[]> charArray(Gen.IntGen sizes, char[] domain,
IntCharBiPredicate fn)
+ {
+ Gen.IntGen indexGen = ints().between(0, domain.length - 1);
+ return rs -> {
+ int size = sizes.nextInt(rs);
+ char[] is = new char[size];
+ for (int i = 0; i != size; i++)
+ {
+ char c;
+ do
+ {
+ c = domain[indexGen.nextInt(rs)];
+ }
+ while (!fn.test(i, c));
+ is[i] = c;
+ }
+ return is;
+ };
+ }
+
+ public static Gen<RandomSource> random() {
return r -> r;
}
+ public static BooleanDSL bools()
+ {
+ return new BooleanDSL();
+ }
+
public static IntDSL ints()
{
return new IntDSL();
@@ -86,6 +123,19 @@ public class Gens {
return new EnumDSL();
}
+ public static StringDSL strings()
+ {
+ return new StringDSL();
+ }
+
+ public static class BooleanDSL
+ {
+ public Gen<Boolean> all()
+ {
+ return RandomSource::nextBoolean;
+ }
+ }
+
public static class IntDSL
{
public Gen.IntGen of(int value)
@@ -95,12 +145,12 @@ public class Gens {
public Gen.IntGen all()
{
- return Gen.Random::nextInt;
+ return RandomSource::nextInt;
}
public Gen.IntGen between(int min, int max)
{
- Invariants.checkArgument(max >= min);
+ Invariants.checkArgument(max >= min, "max (%d) < min (%d)", max,
min);
if (min == max)
return of(min);
// since bounds is exclusive, if max == max_value unable to do +1
to include... so will return a gen
@@ -118,7 +168,7 @@ public class Gens {
}
public Gen.LongGen all() {
- return Gen.Random::nextLong;
+ return RandomSource::nextLong;
}
public Gen.LongGen between(long min, long max) {
@@ -140,6 +190,87 @@ public class Gens {
return pick(klass.getEnumConstants());
}
}
+
+ public static class StringDSL
+ {
+ public Gen<String> of(Gen.IntGen sizes, char[] domain)
+ {
+ // note, map is overloaded so String::new is ambugious to javac,
so need a lambda here
+ return charArray(sizes, domain).map(c -> new String(c));
+ }
+
+ public SizeBuilder<String> of(char[] domain)
+ {
+ return new SizeBuilder<>(sizes -> of(sizes, domain));
+ }
+
+ public Gen<String> of(Gen.IntGen sizes, char[] domain,
IntCharBiPredicate fn)
+ {
+ // note, map is overloaded so String::new is ambugious to javac,
so need a lambda here
+ return charArray(sizes, domain, fn).map(c -> new String(c));
+ }
+
+ public SizeBuilder<String> of(char[] domain, IntCharBiPredicate fn)
+ {
+ return new SizeBuilder<>(sizes -> of(sizes, domain, fn));
+ }
+
+ public Gen<String> all(Gen.IntGen sizes)
+ {
+ return betweenCodePoints(sizes, Character.MIN_CODE_POINT,
Character.MAX_CODE_POINT);
+ }
+
+ public SizeBuilder<String> all()
+ {
+ return new SizeBuilder<>(this::all);
+ }
+
+ public Gen<String> ascii(Gen.IntGen sizes)
+ {
+ return betweenCodePoints(sizes, 0, 127);
+ }
+
+ public SizeBuilder<String> ascii()
+ {
+ return new SizeBuilder<>(this::ascii);
+ }
+
+ public Gen<String> betweenCodePoints(Gen.IntGen sizes, int min, int
max)
+ {
+ Gen.IntGen codePointGen = ints().between(min,
max).filter(Character::isDefined);
+ return rs -> {
+ int[] array = new int[sizes.nextInt(rs)];
+ for (int i = 0; i < array.length; i++)
+ array[i] = codePointGen.nextInt(rs);
+ return new String(array, 0, array.length);
+ };
+ }
+
+ public SizeBuilder<String> betweenCodePoints(int min, int max)
+ {
+ return new SizeBuilder<>(sizes -> betweenCodePoints(sizes, min,
max));
+ }
+ }
+
+ public static class SizeBuilder<T>
+ {
+ private final Function<Gen.IntGen, Gen<T>> fn;
+
+ public SizeBuilder(Function<Gen.IntGen, Gen<T>> fn)
+ {
+ this.fn = fn;
+ }
+
+ public Gen<T> ofLength(int fixed)
+ {
+ return ofLengthBetween(fixed, fixed);
+ }
+
+ public Gen<T> ofLengthBetween(int min, int max)
+ {
+ return fn.apply(ints().between(min, max));
+ }
+ }
public static class ListDSL<T> implements BaseSequenceDSL<ListDSL<T>,
List<T>> {
private final Gen<T> fn;
@@ -285,7 +416,7 @@ public class Gens {
}
@Override
- public T next(Random random)
+ public T next(RandomSource random)
{
T value;
while (!seen.add((value = fn.next(random)))) {}
@@ -308,7 +439,7 @@ public class Gens {
this.base = new GenReset<>(fn);
}
@Override
- public int nextInt(Random random) {
+ public int nextInt(RandomSource random) {
return base.next(random);
}
@@ -327,7 +458,7 @@ public class Gens {
this.base = new GenReset<>(fn);
}
@Override
- public long nextLong(Random random) {
+ public long nextLong(RandomSource random) {
return base.next(random);
}
diff --git a/accord-core/src/test/java/accord/utils/Property.java
b/accord-core/src/test/java/accord/utils/Property.java
index 9bbbc6d6..3e9cb5d6 100644
--- a/accord-core/src/test/java/accord/utils/Property.java
+++ b/accord-core/src/test/java/accord/utils/Property.java
@@ -18,13 +18,9 @@
package accord.utils;
-import accord.utils.Gen.Random;
-
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
public class Property
{
@@ -76,6 +72,11 @@ public class Property
{
return new DoubleBuilder<>(a, b, this);
}
+
+ public <A, B, C> TrippleBuilder<A, B, C> forAll(Gen<A> a, Gen<B> b,
Gen<C> c)
+ {
+ return new TrippleBuilder<>(a, b, c, this);
+ }
}
private static Object normalizeValue(Object value)
@@ -149,7 +150,7 @@ public class Property
public void check(FailingConsumer<T> fn)
{
- Random random = new Random(seed);
+ RandomSource random = new DefaultRandom(seed);
for (int i = 0; i < examples; i++)
{
T value = null;
@@ -189,7 +190,7 @@ public class Property
public void check(FailingBiConsumer<A, B> fn)
{
- Random random = new Random(seed);
+ RandomSource random = new DefaultRandom(seed);
for (int i = 0; i < examples; i++)
{
A a = null;
@@ -212,6 +213,51 @@ public class Property
}
}
+ public interface FailingTriConsumer<A, B, C>
+ {
+ void accept(A a, B b, C c) throws Exception;
+ }
+
+ public static class TrippleBuilder<A, B, C> extends
Common<TrippleBuilder<A, B, C>>
+ {
+ private final Gen<A> as;
+ private final Gen<B> bs;
+ private final Gen<C> cs;
+
+ public TrippleBuilder(Gen<A> as, Gen<B> bs, Gen<C> cs, Common<?> other)
+ {
+ super(other);
+ this.as = as;
+ this.bs = bs;
+ this.cs = cs;
+ }
+
+ public void check(FailingTriConsumer<A, B, C> fn)
+ {
+ RandomSource random = new DefaultRandom(seed);
+ for (int i = 0; i < examples; i++)
+ {
+ A a = null;
+ B b = null;
+ C c = null;
+ try
+ {
+ checkInterrupted();
+ fn.accept(a = as.next(random), b = bs.next(random), c =
cs.next(random));
+ }
+ catch (Throwable t)
+ {
+ throw new PropertyError(propertyError(this, t, a, b, c),
t);
+ }
+ if (pure)
+ {
+ seed = random.nextLong();
+ random.setSeed(seed);
+ }
+ }
+ }
+ }
+
private static void checkInterrupted() throws InterruptedException {
if (Thread.currentThread().isInterrupted())
throw new InterruptedException();
diff --git a/accord-maelstrom/build.gradle b/accord-maelstrom/build.gradle
index cc08d3f4..2499217a 100644
--- a/accord-maelstrom/build.gradle
+++ b/accord-maelstrom/build.gradle
@@ -25,7 +25,7 @@ dependencies {
implementation group: 'com.google.code.findbugs', name: 'jsr305', version:
'3.0.2'
implementation 'com.google.code.gson:gson:2.8.7'
- testImplementation(testFixtures(project(':accord-core')))
+ testImplementation project(':accord-core').sourceSets.test.output
}
jar {
diff --git a/accord-maelstrom/src/main/java/accord/maelstrom/Cluster.java
b/accord-maelstrom/src/main/java/accord/maelstrom/Cluster.java
index 95130f5c..74de7eb1 100644
--- a/accord-maelstrom/src/main/java/accord/maelstrom/Cluster.java
+++ b/accord-maelstrom/src/main/java/accord/maelstrom/Cluster.java
@@ -24,7 +24,15 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -33,7 +41,6 @@ import java.util.function.Supplier;
import accord.coordinate.Timeout;
import accord.impl.SizeOfIntersectionSorter;
-import accord.local.CommandStores;
import accord.impl.SimpleProgressLog;
import accord.impl.InMemoryCommandStores;
import accord.local.Node;
@@ -46,6 +53,7 @@ import accord.messages.ReplyContext;
import accord.messages.Request;
import accord.api.Scheduler;
import accord.topology.Topology;
+import accord.utils.RandomSource;
// TODO (low priority, testing): merge with accord.impl.basic.Cluster
public class Cluster implements Scheduler
@@ -68,12 +76,12 @@ public class Cluster implements Scheduler
final Id self;
final Function<Id, Node> lookup;
final Cluster parent;
- final Random random;
+ final RandomSource random;
int nextMessageId = 0;
Map<Long, Callback> callbacks = new LinkedHashMap<>();
- public InstanceSink(Id self, Function<Id, Node> lookup, Cluster
parent, Random random)
+ public InstanceSink(Id self, Function<Id, Node> lookup, Cluster
parent, RandomSource random)
{
this.self = self;
this.lookup = lookup;
@@ -125,7 +133,7 @@ public class Cluster implements Scheduler
this.partitionSet = new HashSet<>();
}
- InstanceSink create(Id self, Random random)
+ InstanceSink create(Id self, RandomSource random)
{
InstanceSink sink = new InstanceSink(self, lookup, this, random);
sinks.put(self, sink);
@@ -272,7 +280,7 @@ public class Cluster implements Scheduler
run.run();
}
- public static void run(Id[] nodes, QueueSupplier queueSupplier,
Consumer<Packet> responseSink, Supplier<Random> randomSupplier,
Supplier<LongSupplier> nowSupplier, TopologyFactory topologyFactory,
InputStream stdin, OutputStream stderr) throws IOException
+ public static void run(Id[] nodes, QueueSupplier queueSupplier,
Consumer<Packet> responseSink, Supplier<RandomSource> randomSupplier,
Supplier<LongSupplier> nowSupplier, TopologyFactory topologyFactory,
InputStream stdin, OutputStream stderr) throws IOException
{
try (BufferedReader in = new BufferedReader(new
InputStreamReader(stdin)))
{
@@ -289,7 +297,7 @@ public class Cluster implements Scheduler
}
}
- public static void run(Id[] nodes, QueueSupplier queueSupplier,
Consumer<Packet> responseSink, Supplier<Random> randomSupplier,
Supplier<LongSupplier> nowSupplier, TopologyFactory topologyFactory,
Supplier<Packet> in, OutputStream stderr)
+ public static void run(Id[] nodes, QueueSupplier queueSupplier,
Consumer<Packet> responseSink, Supplier<RandomSource> randomSupplier,
Supplier<LongSupplier> nowSupplier, TopologyFactory topologyFactory,
Supplier<Packet> in, OutputStream stderr)
{
Topology topology = topologyFactory.toTopology(nodes);
Map<Id, Node> lookup = new HashMap<>();
@@ -309,7 +317,7 @@ public class Cluster implements Scheduler
List<Id> nodesList = new ArrayList<>(Arrays.asList(nodes));
sinks.recurring(() ->
{
- Collections.shuffle(nodesList,
randomSupplier.get());
+ Collections.shuffle(nodesList,
randomSupplier.get().asJdkRandom());
int partitionSize =
randomSupplier.get().nextInt((topologyFactory.rf+1)/2);
sinks.partitionSet = new
HashSet<>(nodesList.subList(0, partitionSize));
}, 5L, TimeUnit.SECONDS);
diff --git a/accord-maelstrom/src/main/java/accord/maelstrom/Datum.java
b/accord-maelstrom/src/main/java/accord/maelstrom/Datum.java
index 696f7d61..90f93e08 100644
--- a/accord-maelstrom/src/main/java/accord/maelstrom/Datum.java
+++ b/accord-maelstrom/src/main/java/accord/maelstrom/Datum.java
@@ -19,6 +19,7 @@
package accord.maelstrom;
import java.io.IOException;
+import java.util.Objects;
import java.util.function.BiFunction;
import java.util.zip.CRC32;
@@ -44,6 +45,21 @@ public class Datum implements Comparable<Datum>
return Integer.compare(this.hash, that.hash);
}
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ Hash hash1 = (Hash) o;
+ return hash == hash1.hash;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(hash);
+ }
+
public String toString()
{
return "#" + hash;
diff --git a/accord-maelstrom/src/main/java/accord/maelstrom/Json.java
b/accord-maelstrom/src/main/java/accord/maelstrom/Json.java
index fcb56dfc..63b757c3 100644
--- a/accord-maelstrom/src/main/java/accord/maelstrom/Json.java
+++ b/accord-maelstrom/src/main/java/accord/maelstrom/Json.java
@@ -79,8 +79,10 @@ public class Json
{
switch (id.charAt(0))
{
- case 'c': return new Id(-Integer.parseInt(id.substring(1)));
- case 'n':return new Id( Integer.parseInt(id.substring(1)));
+ //TODO(Review) - toString idn't remove the - so doing - parseInt
makes the value positive, which changes
+ // the value
+ case 'c': return new Id( Integer.parseInt(id.substring(1)));
+ case 'n': return new Id( Integer.parseInt(id.substring(1)));
default: throw new IllegalStateException();
}
}
@@ -314,6 +316,8 @@ public class Json
@Override
public void write(JsonWriter out, Deps value) throws IOException
{
+ out.beginObject();
+ out.name("keyDeps");
out.beginArray();
for (Map.Entry<Key, TxnId> e : value.keyDeps)
{
@@ -323,6 +327,7 @@ public class Json
out.endArray();
}
out.endArray();
+ out.name("rangeDeps");
out.beginArray();
for (Map.Entry<Range, TxnId> e : value.rangeDeps)
{
@@ -333,42 +338,61 @@ public class Json
out.endArray();
}
out.endArray();
+ out.endObject();
}
@Override
public Deps read(JsonReader in) throws IOException
{
- KeyDeps keyDeps;
- try (KeyDeps.Builder builder = KeyDeps.builder())
- {
- in.beginArray();
- while (in.hasNext())
- {
- in.beginArray();
- Key key = MaelstromKey.readKey(in);
- TxnId txnId = GSON.fromJson(in, TxnId.class);
- builder.add(key, txnId);
- in.endArray();
- }
- in.endArray();
- keyDeps = builder.build();
- }
- RangeDeps rangeDeps;
- try (RangeDeps.Builder builder = RangeDeps.builder())
+ KeyDeps keyDeps = KeyDeps.NONE;
+ RangeDeps rangeDeps = RangeDeps.NONE;
+ in.beginObject();
+ while (in.hasNext())
{
- in.beginArray();
- while (in.hasNext())
+ String name;
+ switch (name = in.nextName())
{
- in.beginArray();
- RoutingKey start = MaelstromKey.readRouting(in);
- RoutingKey end = MaelstromKey.readRouting(in);
- TxnId txnId = GSON.fromJson(in, TxnId.class);
- builder.add(new MaelstromKey.Range(start, end), txnId);
- in.endArray();
+ case "keyDeps":
+ {
+ try (KeyDeps.Builder builder = KeyDeps.builder())
+ {
+ in.beginArray();
+ while (in.hasNext())
+ {
+ in.beginArray();
+ Key key = MaelstromKey.readKey(in);
+ TxnId txnId = GSON.fromJson(in, TxnId.class);
+ builder.add(key, txnId);
+ in.endArray();
+ }
+ in.endArray();
+ keyDeps = builder.build();
+ }
+ }
+ break;
+ case "rangeDeps":
+ {
+ try (RangeDeps.Builder builder = RangeDeps.builder())
+ {
+ in.beginArray();
+ while (in.hasNext())
+ {
+ in.beginArray();
+ RoutingKey start =
MaelstromKey.readRouting(in);
+ RoutingKey end = MaelstromKey.readRouting(in);
+ TxnId txnId = GSON.fromJson(in, TxnId.class);
+ builder.add(new MaelstromKey.Range(start,
end), txnId);
+ in.endArray();
+ }
+ in.endArray();
+ rangeDeps = builder.build();
+ }
+ }
+ break;
+ default: throw new AssertionError("Unknown name: " + name);
}
- in.endArray();
- rangeDeps = builder.build();
}
+ in.endObject();
return new Deps(keyDeps, rangeDeps);
}
};
diff --git a/accord-maelstrom/src/main/java/accord/maelstrom/MaelstromKey.java
b/accord-maelstrom/src/main/java/accord/maelstrom/MaelstromKey.java
index b42af64c..7dbca6c7 100644
--- a/accord-maelstrom/src/main/java/accord/maelstrom/MaelstromKey.java
+++ b/accord-maelstrom/src/main/java/accord/maelstrom/MaelstromKey.java
@@ -19,6 +19,7 @@
package accord.maelstrom;
import java.io.IOException;
+import java.util.Objects;
import accord.api.RoutingKey;
@@ -223,6 +224,21 @@ public class MaelstromKey implements RoutableKey
return new Routing(datum.value.hashCode());
}
+ @Override
+ public boolean equals(Object o)
+ {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ MaelstromKey that = (MaelstromKey) o;
+ return Objects.equals(datum, that.datum);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return Objects.hash(datum);
+ }
+
@Override
public String toString()
{
diff --git a/accord-maelstrom/src/main/java/accord/maelstrom/Main.java
b/accord-maelstrom/src/main/java/accord/maelstrom/Main.java
index 1df71373..955645c8 100644
--- a/accord-maelstrom/src/main/java/accord/maelstrom/Main.java
+++ b/accord-maelstrom/src/main/java/accord/maelstrom/Main.java
@@ -24,7 +24,6 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.Map;
-import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@@ -41,6 +40,7 @@ import accord.api.Scheduler;
import accord.local.ShardDistributor;
import accord.messages.ReplyContext;
import accord.topology.Topology;
+import accord.utils.DefaultRandom;
import accord.utils.ThreadPoolScheduler;
import accord.maelstrom.Packet.Type;
import accord.api.MessageSink;
@@ -162,7 +162,7 @@ public class Main
sink = new StdoutSink(System::currentTimeMillis, scheduler, start,
init.self, out, err);
on = new Node(init.self, sink, new SimpleConfigService(topology),
System::currentTimeMillis,
MaelstromStore::new, new
ShardDistributor.EvenSplit(8, ignore -> new MaelstromKey.Splitter()),
- MaelstromAgent.INSTANCE, new Random(), scheduler,
SizeOfIntersectionSorter.SUPPLIER,
+ MaelstromAgent.INSTANCE, new DefaultRandom(),
scheduler, SizeOfIntersectionSorter.SUPPLIER,
SimpleProgressLog::new,
InMemoryCommandStores.SingleThread::new);
err.println("Initialized node " + init.self);
err.flush();
diff --git a/accord-maelstrom/src/test/java/accord/maelstrom/JsonTest.java
b/accord-maelstrom/src/test/java/accord/maelstrom/JsonTest.java
new file mode 100644
index 00000000..af73e245
--- /dev/null
+++ b/accord-maelstrom/src/test/java/accord/maelstrom/JsonTest.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package accord.maelstrom;
+
+import org.junit.jupiter.api.Test;
+
+import accord.api.Key;
+import accord.primitives.Deps;
+import accord.primitives.Range;
+import accord.primitives.RangeDeps;
+import accord.utils.AccordGens;
+import accord.utils.Gen;
+import accord.utils.Gens;
+import org.assertj.core.api.Assertions;
+
+import static accord.utils.Property.qt;
+
+class JsonTest
+{
+ @Test
+ void serdeDeps()
+ {
+ qt().forAll(depsGen()).check(JsonTest::serde);
+ }
+
+ private static <T> void serde(T expected)
+ {
+ String json = Json.GSON.toJson(expected);
+ T parsed;
+ try
+ {
+ parsed = Json.GSON.<T>fromJson(json, expected.getClass());
+ }
+ catch (Throwable t)
+ {
+ throw new AssertionError("Unable to parse json: " + json, t);
+ }
+ Assertions.assertThat(parsed)
+ .isEqualTo(expected);
+ }
+
+ static Gen<Key> keyGen()
+ {
+ Gen<Datum.Kind> kindGen = Gens.enums().all(Datum.Kind.class);
+ Gen<String> strings = Gens.strings().all().ofLengthBetween(0, 10);
+ return rs -> {
+ Datum.Kind next = kindGen.next(rs);
+ switch (next)
+ {
+ case STRING: return new MaelstromKey.Key(Datum.Kind.STRING,
strings.next(rs));
+ case LONG: return new MaelstromKey.Key(Datum.Kind.LONG,
rs.nextLong());
+ case HASH: return new MaelstromKey.Key(Datum.Kind.HASH, new
Datum.Hash(rs.nextInt()));
+ case DOUBLE: return new MaelstromKey.Key(Datum.Kind.DOUBLE,
rs.nextDouble());
+ default: throw new AssertionError("Unknown kind: " + next);
+ }
+ };
+ }
+
+ static Gen<Range> rangeGen()
+ {
+ return AccordGens.ranges(keyGen().map(Key::toUnseekable),
MaelstromKey.Range::new);
+ }
+
+ static Gen<RangeDeps> rangeDepsGen()
+ {
+ return AccordGens.rangeDeps(rangeGen());
+ }
+
+ static Gen<Deps> depsGen()
+ {
+ return AccordGens.deps(AccordGens.keyDeps(keyGen()), rangeDepsGen());
+ }
+
+}
\ No newline at end of file
diff --git a/accord-maelstrom/src/test/java/accord/maelstrom/Runner.java
b/accord-maelstrom/src/test/java/accord/maelstrom/Runner.java
index f67b5bf9..70b120b7 100644
--- a/accord-maelstrom/src/test/java/accord/maelstrom/Runner.java
+++ b/accord-maelstrom/src/test/java/accord/maelstrom/Runner.java
@@ -18,32 +18,36 @@
package accord.maelstrom;
-import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;
-import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
-import java.util.function.Function;
import java.util.function.Supplier;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import accord.local.Node.Id;
import accord.maelstrom.Cluster.Queue;
import accord.maelstrom.Cluster.QueueSupplier;
+import accord.utils.DefaultRandom;
+import accord.utils.RandomSource;
import static accord.utils.Utils.toArray;
public class Runner
{
+ private static final Logger logger = LoggerFactory.getLogger(Runner.class);
+
static class StandardQueue<T> implements Queue<T>
{
static class Factory implements QueueSupplier
{
- final Random seeds;
+ final RandomSource seeds;
- Factory(Random seeds)
+ Factory(RandomSource seeds)
{
this.seeds = seeds;
}
@@ -51,7 +55,7 @@ public class Runner
@Override
public <T> Queue<T> get()
{
- return new StandardQueue<>(new Random(seeds.nextLong()));
+ return new StandardQueue<>(seeds.fork());
}
}
@@ -78,29 +82,29 @@ public class Runner
}
final PriorityQueue<Item<T>> queue = new PriorityQueue<>();
- final Random random;
+ final RandomSource random;
long now;
int seq;
- StandardQueue(Random random)
+ StandardQueue(RandomSource random)
{
this.random = random;
}
@Override
- public void add(T item)
+ public synchronized void add(T item)
{
add(item, random.nextInt(500), TimeUnit.MILLISECONDS);
}
@Override
- public void add(T item, long delay, TimeUnit units)
+ public synchronized void add(T item, long delay, TimeUnit units)
{
queue.add(new Item<>(now + units.toMillis(delay), seq++, item));
}
@Override
- public T poll()
+ public synchronized T poll()
{
Item<T> item = queue.poll();
if (item == null)
@@ -110,210 +114,78 @@ public class Runner
}
@Override
- public int size()
+ public synchronized int size()
{
return queue.size();
}
}
- static class RandomQueue<T> implements Queue<T>
+ static void run(int nodeCount, QueueSupplier queueSupplier,
Supplier<RandomSource> randomSupplier, TopologyFactory factory, String ...
commands) throws IOException
{
- static class Factory implements QueueSupplier
+ run(nodeCount, queueSupplier, randomSupplier, factory, new
Supplier<Packet>()
{
- final Random seeds;
-
- Factory(Random seeds)
- {
- this.seeds = seeds;
- }
-
+ int i = 0;
@Override
- public <T> Queue<T> get()
+ public Packet get()
{
- return new RandomQueue<>(new Random(seeds.nextLong()));
+ return i == commands.length ? null :
Packet.parse(commands[i++]);
}
- }
-
- static class Entry<T> implements Comparable<Entry<T>>
- {
- final double priority;
- final T value;
+ });
+ }
- Entry(double priority, T value)
- {
- this.priority = priority;
- this.value = value;
- }
+ static void run(int nodeCount, QueueSupplier queueSupplier,
Supplier<RandomSource> randomSupplier, TopologyFactory factory,
Supplier<Packet> commands) throws IOException
+ {
+ List<Id> nodes = new ArrayList<>();
+ for (int i = 1 ; i <= nodeCount ; ++i)
+ nodes.add(new Id(i));
- @Override
- public int compareTo(Entry<T> that)
- {
- return Double.compare(this.priority, that.priority);
- }
- }
+ Cluster.run(toArray(nodes, Id[]::new), queueSupplier, ignore -> {},
randomSupplier, () -> new AtomicLong()::incrementAndGet, factory, commands,
System.err);
+ }
- final PriorityQueue<Entry<T>> queue = new PriorityQueue<>();
- final Random random;
+ public static Builder test()
+ {
+ return new
Builder(Thread.currentThread().getStackTrace()[2].getMethodName());
+ }
- public RandomQueue(Random random)
- {
- this.random = random;
- }
+ public static class Builder
+ {
+ public static final String ACCORD_TEST_SEED = "accord.test.%s.seed";
- @Override
- public int size()
- {
- return queue.size();
- }
+ private final String key;
+ private long seed;
+ private int nodeCount = 3;
+ private TopologyFactory factory = new TopologyFactory(4, 3);
- @Override
- public void add(T item)
+ private Builder(String name)
{
- queue.add(new Entry<>(random.nextDouble(), item));
+ key = String.format(ACCORD_TEST_SEED, name);
+ String userSeed = System.getProperty(key, null);
+ seed = userSeed != null ? Long.parseLong(userSeed) :
System.nanoTime();
}
- @Override
- public void add(T item, long delay, TimeUnit units)
+ Builder seed(long seed)
{
- queue.add(new Entry<>(random.nextDouble(), item));
+ this.seed = seed;
+ return this;
}
- @Override
- public T poll()
+ Builder nodeCount(int nodeCount)
{
- return unwrap(queue.poll());
+ this.nodeCount = nodeCount;
+ return this;
}
- private static <T> T unwrap(Entry<T> e)
+ Builder factory(TopologyFactory factory)
{
- return e == null ? null : e.value;
+ this.factory = factory;
+ return this;
}
- }
-
- static <T> Supplier<T> parseOutput(boolean delay, String output,
Function<String, T> parse)
- {
- return parseOutput(delay, output.split("\n"), parse);
- }
-
- static <T> Supplier<T> parseOutput(boolean delay, BufferedReader output,
Function<String, T> parse)
- {
- return parseOutput(delay, output.lines().toArray(String[]::new),
parse);
- }
- static <T> Supplier<T> parseOutput(boolean delay, String[] output,
Function<String, T> parse)
- {
- long[] nanos = new long[output.length];
- String[] commands = new String[output.length];
-
- for (int i = 0 ; i < output.length ; ++i)
+ void run(String ... commands) throws IOException
{
- String command = output[i];
- long at =
TimeUnit.MILLISECONDS.toNanos(Long.parseLong(command.substring(0,
command.indexOf(' '))));
- command = command.substring(command.indexOf(' ') + 1);
- if (i > 0 && at <= nanos[i-1]) at = nanos[i-1] + 1;
- nanos[i] = at;
- commands[i] = command;
+ logger.info("Seed {}; rerun with -D{}={}", seed, key, seed);
+ RandomSource randomSource = new DefaultRandom(seed);
+ Runner.run(nodeCount, new StandardQueue.Factory(randomSource),
randomSource::fork, factory, commands);
}
-
- long start = System.nanoTime();
- return new Supplier<T>()
- {
- int i = 0;
- @Override
- public T get()
- {
- if (i == commands.length)
- return null;
-
- while (delay)
- {
- long wait = start + nanos[i] - System.nanoTime();
- if (wait <= 0) break;
- try
- {
- TimeUnit.NANOSECONDS.sleep(wait);
- }
- catch (InterruptedException e)
- {
- throw new IllegalStateException(e);
- }
- }
-
- return parse.apply(commands[i++]);
- }
- };
- }
-
- static void parseNode(TopologyFactory factory, boolean delay, String
output) throws IOException
- {
- Main.listen(factory, parseOutput(delay, output, Function.identity()),
System.out, System.err);
- }
-
- // TODO (low priority, maelstrom): we need to align response ids with the
input; for now replies are broken
- static void replay(int nodeCount, TopologyFactory factory, boolean delay,
Supplier<Packet> input) throws IOException
- {
- run(nodeCount, new QueueSupplier()
- {
- @Override
- public <T> Queue<T> get()
- {
- return new Queue<T>()
- {
- @Override
- public void add(T t)
- {
- }
-
- @Override
- public void add(T item, long delay, TimeUnit units)
- {
- }
-
- @Override
- public T poll()
- {
- return (T)input.get();
- }
-
- @Override
- public int size()
- {
- return 0;
- }
- };
- }
- }, Random::new, factory, () -> null);
- }
-
- static void run(TopologyFactory factory, String ... commands) throws
IOException
- {
- run(3, factory, commands);
- }
-
- static void run(int nodeCount, TopologyFactory factory, String ...
commands) throws IOException
- {
- run(nodeCount, new StandardQueue.Factory(new Random()), Random::new,
factory, commands);
- }
-
- static void run(int nodeCount, QueueSupplier queueSupplier,
Supplier<Random> randomSupplier, TopologyFactory factory, String ... commands)
throws IOException
- {
- run(nodeCount, queueSupplier, randomSupplier, factory, new
Supplier<Packet>()
- {
- int i = 0;
- @Override
- public Packet get()
- {
- return i == commands.length ? null :
Packet.parse(commands[i++]);
- }
- });
- }
-
- static void run(int nodeCount, QueueSupplier queueSupplier,
Supplier<Random> randomSupplier, TopologyFactory factory, Supplier<Packet>
commands) throws IOException
- {
- List<Id> nodes = new ArrayList<>();
- for (int i = 1 ; i <= nodeCount ; ++i)
- nodes.add(new Id(i));
-
- Cluster.run(toArray(nodes, Id[]::new), queueSupplier, ignore -> {},
randomSupplier, () -> new AtomicLong()::incrementAndGet, factory, commands,
System.err);
}
}
diff --git
a/accord-maelstrom/src/test/java/accord/maelstrom/SimpleRandomTest.java
b/accord-maelstrom/src/test/java/accord/maelstrom/SimpleRandomTest.java
index 94dd163f..e7772f75 100644
--- a/accord-maelstrom/src/test/java/accord/maelstrom/SimpleRandomTest.java
+++ b/accord-maelstrom/src/test/java/accord/maelstrom/SimpleRandomTest.java
@@ -19,65 +19,51 @@
package accord.maelstrom;
import java.io.IOException;
-import java.util.Random;
import org.junit.jupiter.api.Test;
-import accord.maelstrom.Runner.StandardQueue.Factory;
-
-import static accord.maelstrom.Runner.run;
+import static accord.maelstrom.Runner.test;
public class SimpleRandomTest
{
@Test
public void testLaunch() throws IOException
{
- run(new TopologyFactory(4, 3));
+ test().run();
}
@Test
public void testEmptyRead() throws IOException
{
- run(new TopologyFactory(4, 3),
-
"{\"src\":\"c1\",\"dest\":\"n1\",\"body\":{\"type\":\"txn\",\"msg_id\":1,\"txn\":"
- + "[[\"r\", 1, null]]}}"
- );
+
test().run("{\"src\":\"c1\",\"dest\":\"n1\",\"body\":{\"type\":\"txn\",\"msg_id\":1,\"txn\":"
+ + "[[\"r\", 1, null]]}}");
}
@Test
public void testReadAndWrite() throws IOException
{
- run(new TopologyFactory(4, 3),
-
"{\"src\":\"c1\",\"dest\":\"n1\",\"body\":{\"type\":\"txn\",\"msg_id\":1,\"txn\":"
- + "[[\"r\", 1, null],[\"append\", 1, 1]]}}",
-
"{\"src\":\"c1\",\"dest\":\"n1\",\"body\":{\"type\":\"txn\",\"msg_id\":1,\"txn\":"
- + "[[\"r\", 1, null],[\"append\", 1, 2]]}}",
-
"{\"src\":\"c1\",\"dest\":\"n1\",\"body\":{\"type\":\"txn\",\"msg_id\":1,\"txn\":"
- + "[[\"r\", 1, null]]}}"
- );
+
test().run("{\"src\":\"c1\",\"dest\":\"n1\",\"body\":{\"type\":\"txn\",\"msg_id\":1,\"txn\":"
+ + "[[\"r\", 1, null],[\"append\", 1, 1]]}}",
+
"{\"src\":\"c1\",\"dest\":\"n1\",\"body\":{\"type\":\"txn\",\"msg_id\":1,\"txn\":"
+ + "[[\"r\", 1, null],[\"append\", 1, 2]]}}",
+
"{\"src\":\"c1\",\"dest\":\"n1\",\"body\":{\"type\":\"txn\",\"msg_id\":1,\"txn\":"
+ + "[[\"r\", 1, null]]}}");
}
@Test
public void testReadAndWriteRandomMultiKey() throws IOException
{
- Random random = new Random();
- long seed = random.nextLong();
- System.out.println(seed);
- random.setSeed(seed);
-
- run(5, new Factory(random), () -> new Random(random.nextLong()), new
TopologyFactory(4, 3),
-
"{\"src\":\"c1\",\"dest\":\"n1\",\"body\":{\"type\":\"txn\",\"msg_id\":1,\"txn\":"
- + "[[\"r\", 1, null],[\"r\", 2, null],[\"r\", 3,
null],[\"append\", 1, 1],[\"append\", 2, 3]]}}",
-
"{\"src\":\"c1\",\"dest\":\"n2\",\"body\":{\"type\":\"txn\",\"msg_id\":2,\"txn\":"
- + "[[\"r\", 1, null],[\"r\", 2, null],[\"r\", 3,
null],[\"append\", 1, 2],[\"append\", 3, 3]]}}",
-
"{\"src\":\"c1\",\"dest\":\"n3\",\"body\":{\"type\":\"txn\",\"msg_id\":3,\"txn\":"
- + "[[\"r\", 1, null],[\"r\", 2, null],[\"r\", 3,
null],[\"append\", 1, 3],[\"append\", 2, 2]]}}",
-
"{\"src\":\"c1\",\"dest\":\"n1\",\"body\":{\"type\":\"txn\",\"msg_id\":4,\"txn\":"
- + "[[\"r\", 1, null],[\"r\", 2, null],[\"r\", 3,
null],[\"append\", 1, 4],[\"append\", 3, 2]]}}",
-
"{\"src\":\"c1\",\"dest\":\"n2\",\"body\":{\"type\":\"txn\",\"msg_id\":5,\"txn\":"
- + "[[\"r\", 1, null],[\"r\", 2, null],[\"r\", 3,
null],[\"append\", 1, 5],[\"append\", 2, 1]]}}",
-
"{\"src\":\"c1\",\"dest\":\"n3\",\"body\":{\"type\":\"txn\",\"msg_id\":6,\"txn\":"
- + "[[\"r\", 1, null],[\"r\", 2, null],[\"r\", 3,
null],[\"append\", 1, 6],[\"append\", 3, 1]]}}"
- );
+
test().nodeCount(5).run("{\"src\":\"c1\",\"dest\":\"n1\",\"body\":{\"type\":\"txn\",\"msg_id\":1,\"txn\":"
+ + "[[\"r\", 1, null],[\"r\", 2, null],[\"r\",
3, null],[\"append\", 1, 1],[\"append\", 2, 3]]}}",
+
"{\"src\":\"c1\",\"dest\":\"n2\",\"body\":{\"type\":\"txn\",\"msg_id\":2,\"txn\":"
+ + "[[\"r\", 1, null],[\"r\", 2, null],[\"r\",
3, null],[\"append\", 1, 2],[\"append\", 3, 3]]}}",
+
"{\"src\":\"c1\",\"dest\":\"n3\",\"body\":{\"type\":\"txn\",\"msg_id\":3,\"txn\":"
+ + "[[\"r\", 1, null],[\"r\", 2, null],[\"r\",
3, null],[\"append\", 1, 3],[\"append\", 2, 2]]}}",
+
"{\"src\":\"c1\",\"dest\":\"n1\",\"body\":{\"type\":\"txn\",\"msg_id\":4,\"txn\":"
+ + "[[\"r\", 1, null],[\"r\", 2, null],[\"r\",
3, null],[\"append\", 1, 4],[\"append\", 3, 2]]}}",
+
"{\"src\":\"c1\",\"dest\":\"n2\",\"body\":{\"type\":\"txn\",\"msg_id\":5,\"txn\":"
+ + "[[\"r\", 1, null],[\"r\", 2, null],[\"r\",
3, null],[\"append\", 1, 5],[\"append\", 2, 1]]}}",
+
"{\"src\":\"c1\",\"dest\":\"n3\",\"body\":{\"type\":\"txn\",\"msg_id\":6,\"txn\":"
+ + "[[\"r\", 1, null],[\"r\", 2, null],[\"r\",
3, null],[\"append\", 1, 6],[\"append\", 3, 1]]}}");
}
}
diff --git a/buildSrc/src/main/groovy/accord.java-conventions.gradle
b/buildSrc/src/main/groovy/accord.java-conventions.gradle
index 3ca1d577..2465a8bc 100644
--- a/buildSrc/src/main/groovy/accord.java-conventions.gradle
+++ b/buildSrc/src/main/groovy/accord.java-conventions.gradle
@@ -44,6 +44,8 @@ dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0'
testImplementation 'ch.qos.logback:logback-classic:1.2.3'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0'
+
+ testImplementation group: 'org.assertj', name: 'assertj-core', version:
'3.24.2'
}
task copyMainDependencies(type: Copy) {
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]