GOSSIP-64 Implement Max-Change-Sets
This commit is contained in:
@ -0,0 +1,12 @@
|
|||||||
|
package org.apache.gossip.crdt;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
// Interface extends CrdtSet interface with add and remove operation that are guaranteed to be immutable.
|
||||||
|
// If your implementation provide immutable add/remove operations you can extend AbstractCRDTStringSetTest to check it in the most ways.
|
||||||
|
|
||||||
|
public interface CrdtAddRemoveSet<T, SetType extends Set<T>, R extends CrdtAddRemoveSet<T, SetType, R>> extends CrdtSet<T, SetType, R> {
|
||||||
|
R add(T element);
|
||||||
|
|
||||||
|
R remove(T element);
|
||||||
|
}
|
@ -17,16 +17,16 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.gossip.crdt;
|
package org.apache.gossip.crdt;
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.core.Version;
|
import com.fasterxml.jackson.core.Version;
|
||||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
abstract class OrSetMixin<E> {
|
abstract class OrSetMixin<E> {
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
OrSetMixin(@JsonProperty("elements") Map<E, Set<UUID>> w, @JsonProperty("tombstones") Map<E, Set<UUID>> h) { }
|
OrSetMixin(@JsonProperty("elements") Map<E, Set<UUID>> w, @JsonProperty("tombstones") Map<E, Set<UUID>> h) { }
|
||||||
@ -37,8 +37,8 @@ abstract class OrSetMixin<E> {
|
|||||||
|
|
||||||
abstract class LWWSetMixin<ElementType> {
|
abstract class LWWSetMixin<ElementType> {
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
LWWSetMixin(@JsonProperty("data") Map<ElementType, LWWSet.Timestamps> struct) { }
|
LWWSetMixin(@JsonProperty("data") Map<ElementType, LwwSet.Timestamps> struct) { }
|
||||||
@JsonProperty("data") abstract Map<ElementType, LWWSet.Timestamps> getStruct();
|
@JsonProperty("data") abstract Map<ElementType, LwwSet.Timestamps> getStruct();
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class LWWSetTimestampsMixin {
|
abstract class LWWSetTimestampsMixin {
|
||||||
@ -48,6 +48,12 @@ abstract class LWWSetTimestampsMixin {
|
|||||||
@JsonProperty("remove") abstract long getLatestRemove();
|
@JsonProperty("remove") abstract long getLatestRemove();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract class MaxChangeSetMixin<E> {
|
||||||
|
@JsonCreator
|
||||||
|
MaxChangeSetMixin(@JsonProperty("data") Map<E, Integer> struct) { }
|
||||||
|
@JsonProperty("data") abstract Map<E, Integer> getStruct();
|
||||||
|
}
|
||||||
|
|
||||||
abstract class GrowOnlySetMixin<E>{
|
abstract class GrowOnlySetMixin<E>{
|
||||||
@JsonCreator
|
@JsonCreator
|
||||||
GrowOnlySetMixin(@JsonProperty("elements") Set<E> elements){ }
|
GrowOnlySetMixin(@JsonProperty("elements") Set<E> elements){ }
|
||||||
@ -84,8 +90,9 @@ public class CrdtModule extends SimpleModule {
|
|||||||
context.setMixInAnnotations(GrowOnlySet.class, GrowOnlySetMixin.class);
|
context.setMixInAnnotations(GrowOnlySet.class, GrowOnlySetMixin.class);
|
||||||
context.setMixInAnnotations(GrowOnlyCounter.class, GrowOnlyCounterMixin.class);
|
context.setMixInAnnotations(GrowOnlyCounter.class, GrowOnlyCounterMixin.class);
|
||||||
context.setMixInAnnotations(PNCounter.class, PNCounterMixin.class);
|
context.setMixInAnnotations(PNCounter.class, PNCounterMixin.class);
|
||||||
context.setMixInAnnotations(LWWSet.class, LWWSetMixin.class);
|
context.setMixInAnnotations(LwwSet.class, LWWSetMixin.class);
|
||||||
context.setMixInAnnotations(LWWSet.Timestamps.class, LWWSetTimestampsMixin.class);
|
context.setMixInAnnotations(LwwSet.Timestamps.class, LWWSetTimestampsMixin.class);
|
||||||
|
context.setMixInAnnotations(MaxChangeSet.class, MaxChangeSetMixin.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -20,12 +20,31 @@ package org.apache.gossip.crdt;
|
|||||||
import org.apache.gossip.manager.Clock;
|
import org.apache.gossip.manager.Clock;
|
||||||
import org.apache.gossip.manager.SystemClock;
|
import org.apache.gossip.manager.SystemClock;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class LWWSet<ElementType> implements CrdtSet<ElementType, Set<ElementType>, LWWSet<ElementType>> {
|
/*
|
||||||
|
Last write wins CrdtSet
|
||||||
|
Each operation has timestamp: when you add or remove SystemClock is used to get current time in nanoseconds.
|
||||||
|
When all add/remove operations are within the only node LWWSet is guaranteed to work like a Set.
|
||||||
|
If you have multiple nodes with ideally synchronized clocks:
|
||||||
|
You will observe operations on all machines later than on the initiator, but the last operations on cluster will win.
|
||||||
|
If you have some significant clock drift you will suffer from data loss.
|
||||||
|
|
||||||
|
Read more: https://github.com/aphyr/meangirls#lww-element-set
|
||||||
|
|
||||||
|
You can view examples of usage in tests:
|
||||||
|
LwwSetTest - unit tests
|
||||||
|
DataTest - integration test with 2 nodes, LWWSet was serialized/deserialized, sent between nodes, merged
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class LwwSet<ElementType> implements CrdtAddRemoveSet<ElementType, Set<ElementType>, LwwSet<ElementType>> {
|
||||||
static private Clock clock = new SystemClock();
|
static private Clock clock = new SystemClock();
|
||||||
|
|
||||||
private final Map<ElementType, Timestamps> struct;
|
private final Map<ElementType, Timestamps> struct;
|
||||||
@ -44,11 +63,11 @@ public class LWWSet<ElementType> implements CrdtSet<ElementType, Set<ElementType
|
|||||||
latestRemove = remove;
|
latestRemove = remove;
|
||||||
}
|
}
|
||||||
|
|
||||||
long getLatestAdd() {
|
long getLatestAdd(){
|
||||||
return latestAdd;
|
return latestAdd;
|
||||||
}
|
}
|
||||||
|
|
||||||
long getLatestRemove() {
|
long getLatestRemove(){
|
||||||
return latestRemove;
|
return latestRemove;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,23 +93,23 @@ public class LWWSet<ElementType> implements CrdtSet<ElementType, Set<ElementType
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public LWWSet(){
|
public LwwSet(){
|
||||||
struct = new HashMap<>();
|
struct = new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
public LWWSet(ElementType... elements){
|
public LwwSet(ElementType... elements){
|
||||||
this(new HashSet<>(Arrays.asList(elements)));
|
this(new HashSet<>(Arrays.asList(elements)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public LWWSet(Set<ElementType> set){
|
public LwwSet(Set<ElementType> set){
|
||||||
struct = new HashMap<>();
|
struct = new HashMap<>();
|
||||||
for (ElementType e : set){
|
for (ElementType e : set){
|
||||||
struct.put(e, new Timestamps().updateAdd());
|
struct.put(e, new Timestamps().updateAdd());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public LWWSet(LWWSet<ElementType> first, LWWSet<ElementType> second){
|
public LwwSet(LwwSet<ElementType> first, LwwSet<ElementType> second){
|
||||||
Function<ElementType, Timestamps> timestampsFor = p -> {
|
Function<ElementType, Timestamps> timestampsFor = p -> {
|
||||||
Timestamps firstTs = first.struct.get(p);
|
Timestamps firstTs = first.struct.get(p);
|
||||||
Timestamps secondTs = second.struct.get(p);
|
Timestamps secondTs = second.struct.get(p);
|
||||||
@ -103,33 +122,33 @@ public class LWWSet<ElementType> implements CrdtSet<ElementType, Set<ElementType
|
|||||||
.distinct().collect(Collectors.toMap(p -> p, timestampsFor));
|
.distinct().collect(Collectors.toMap(p -> p, timestampsFor));
|
||||||
}
|
}
|
||||||
|
|
||||||
public LWWSet<ElementType> add(ElementType e){
|
public LwwSet<ElementType> add(ElementType e){
|
||||||
return this.merge(new LWWSet<>(e));
|
return this.merge(new LwwSet<>(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
// for serialization
|
// for serialization
|
||||||
LWWSet(Map<ElementType, Timestamps> struct){
|
LwwSet(Map<ElementType, Timestamps> struct){
|
||||||
this.struct = struct;
|
this.struct = struct;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<ElementType, Timestamps> getStruct() {
|
Map<ElementType, Timestamps> getStruct(){
|
||||||
return struct;
|
return struct;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public LWWSet<ElementType> remove(ElementType e){
|
public LwwSet<ElementType> remove(ElementType e){
|
||||||
Timestamps eTimestamps = struct.get(e);
|
Timestamps eTimestamps = struct.get(e);
|
||||||
if (eTimestamps == null || !eTimestamps.isPresent()){
|
if (eTimestamps == null || !eTimestamps.isPresent()){
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
Map<ElementType, Timestamps> changeMap = new HashMap<>();
|
Map<ElementType, Timestamps> changeMap = new HashMap<>();
|
||||||
changeMap.put(e, eTimestamps.updateRemove());
|
changeMap.put(e, eTimestamps.updateRemove());
|
||||||
return this.merge(new LWWSet<>(changeMap));
|
return this.merge(new LwwSet<>(changeMap));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LWWSet<ElementType> merge(LWWSet<ElementType> other){
|
public LwwSet<ElementType> merge(LwwSet<ElementType> other){
|
||||||
return new LWWSet<>(this, other);
|
return new LwwSet<>(this, other);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -141,12 +160,12 @@ public class LWWSet<ElementType> implements CrdtSet<ElementType, Set<ElementType
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LWWSet<ElementType> optimize(){
|
public LwwSet<ElementType> optimize(){
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj){
|
public boolean equals(Object obj){
|
||||||
return this == obj || (obj != null && getClass() == obj.getClass() && value().equals(((LWWSet) obj).value()));
|
return this == obj || (obj != null && getClass() == obj.getClass() && value().equals(((LwwSet) obj).value()));
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.apache.gossip.crdt;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Max Change Set CrdtSet. Value which has changed the most wins.
|
||||||
|
You cannot delete an element which is not present, and cannot add an element which is already present.
|
||||||
|
MC-sets are compact and do the right thing when changes to elements are infrequent compared to the gossiping period.
|
||||||
|
|
||||||
|
Read more: https://github.com/aphyr/meangirls#max-change-sets
|
||||||
|
You can view examples of usage in tests:
|
||||||
|
MaxChangeSetTest - unit tests
|
||||||
|
DataTest - integration test with 2 nodes, MaxChangeSet was serialized/deserialized, sent between nodes, merged
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class MaxChangeSet<ElementType> implements CrdtAddRemoveSet<ElementType, Set<ElementType>, MaxChangeSet<ElementType>> {
|
||||||
|
private final Map<ElementType, Integer> struct;
|
||||||
|
|
||||||
|
public MaxChangeSet(){
|
||||||
|
struct = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public MaxChangeSet(ElementType... elements){
|
||||||
|
this(new HashSet<>(Arrays.asList(elements)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public MaxChangeSet(Set<ElementType> set){
|
||||||
|
struct = new HashMap<>();
|
||||||
|
for (ElementType e : set){
|
||||||
|
struct.put(e, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public MaxChangeSet(MaxChangeSet<ElementType> first, MaxChangeSet<ElementType> second){
|
||||||
|
Function<ElementType, Integer> valueFor = element ->
|
||||||
|
Math.max(first.struct.getOrDefault(element, 0), second.struct.getOrDefault(element, 0));
|
||||||
|
struct = Stream.concat(first.struct.keySet().stream(), second.struct.keySet().stream())
|
||||||
|
.distinct().collect(Collectors.toMap(p -> p, valueFor));
|
||||||
|
}
|
||||||
|
|
||||||
|
// for serialization
|
||||||
|
MaxChangeSet(Map<ElementType, Integer> struct){
|
||||||
|
this.struct = struct;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<ElementType, Integer> getStruct(){
|
||||||
|
return struct;
|
||||||
|
}
|
||||||
|
|
||||||
|
private MaxChangeSet<ElementType> increment(ElementType e){
|
||||||
|
Map<ElementType, Integer> changeMap = new HashMap<>();
|
||||||
|
changeMap.put(e, struct.getOrDefault(e, 0) + 1);
|
||||||
|
return this.merge(new MaxChangeSet<>(changeMap));
|
||||||
|
}
|
||||||
|
|
||||||
|
public MaxChangeSet<ElementType> add(ElementType e){
|
||||||
|
if (struct.getOrDefault(e, 0) % 2 == 1){
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return increment(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
public MaxChangeSet<ElementType> remove(ElementType e){
|
||||||
|
if (struct.getOrDefault(e, 0) % 2 == 0){
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
return increment(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MaxChangeSet<ElementType> merge(MaxChangeSet<ElementType> other){
|
||||||
|
return new MaxChangeSet<>(this, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<ElementType> value(){
|
||||||
|
return struct.entrySet().stream()
|
||||||
|
.filter(entry -> (entry.getValue() % 2 == 1))
|
||||||
|
.map(Map.Entry::getKey)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MaxChangeSet<ElementType> optimize(){
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj){
|
||||||
|
return this == obj || (obj != null && getClass() == obj.getClass() && value().equals(((MaxChangeSet) obj).value()));
|
||||||
|
}
|
||||||
|
}
|
@ -26,7 +26,7 @@ import org.apache.gossip.crdt.OrSet.Builder.Operation;
|
|||||||
/*
|
/*
|
||||||
* A immutable set
|
* A immutable set
|
||||||
*/
|
*/
|
||||||
public class OrSet<E> implements Crdt<Set<E>, OrSet<E>> {
|
public class OrSet<E> implements CrdtAddRemoveSet<E, Set<E>, OrSet<E>> {
|
||||||
|
|
||||||
private final Map<E, Set<UUID>> elements = new HashMap<>();
|
private final Map<E, Set<UUID>> elements = new HashMap<>();
|
||||||
private final Map<E, Set<UUID>> tombstones = new HashMap<>();
|
private final Map<E, Set<UUID>> tombstones = new HashMap<>();
|
||||||
@ -44,6 +44,10 @@ public class OrSet<E> implements Crdt<Set<E>, OrSet<E>> {
|
|||||||
|
|
||||||
@SafeVarargs
|
@SafeVarargs
|
||||||
public OrSet(E ... elements){
|
public OrSet(E ... elements){
|
||||||
|
this(new HashSet<>(Arrays.asList(elements)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public OrSet(Set<E> elements) {
|
||||||
for (E e: elements){
|
for (E e: elements){
|
||||||
internalAdd(e);
|
internalAdd(e);
|
||||||
}
|
}
|
||||||
@ -110,6 +114,14 @@ public class OrSet<E> implements Crdt<Set<E>, OrSet<E>> {
|
|||||||
val = computeValue();
|
val = computeValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public OrSet<E> add(E e) {
|
||||||
|
return this.merge(new OrSet<>(e));
|
||||||
|
}
|
||||||
|
|
||||||
|
public OrSet<E> remove(E e) {
|
||||||
|
return new OrSet<>(this, new Builder<E>().remove(e));
|
||||||
|
}
|
||||||
|
|
||||||
public OrSet.Builder<E> builder(){
|
public OrSet.Builder<E> builder(){
|
||||||
return new OrSet.Builder<>();
|
return new OrSet.Builder<>();
|
||||||
}
|
}
|
||||||
@ -233,15 +245,6 @@ public class OrSet<E> implements Crdt<Set<E>, OrSet<E>> {
|
|||||||
return value().toArray(a);
|
return value().toArray(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean add(E e) {
|
|
||||||
throw new IllegalArgumentException("Can not add");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean remove(Object o) {
|
|
||||||
throw new IllegalArgumentException();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean containsAll(Collection<?> c) {
|
public boolean containsAll(Collection<?> c) {
|
||||||
return this.value().containsAll(c);
|
return this.value().containsAll(c);
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
*
|
||||||
|
* Unle<F4>ss 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 org.apache.gossip.crdt;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.Ignore;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/*
|
||||||
|
Abstract test suit to test CrdtSets with Add and Remove operations.
|
||||||
|
It compares them with simple sets, validates add, remove, equals, value, etc. operations
|
||||||
|
To use it you should:
|
||||||
|
1. subclass this and implement constructors
|
||||||
|
2. implement CrdtAddRemoveSet in your CrdtSet
|
||||||
|
3. make your CrdtSet immutable
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Ignore
|
||||||
|
public abstract class AbstractCRDTStringSetTest<SetType extends CrdtAddRemoveSet<String, Set<String>, SetType>> {
|
||||||
|
abstract SetType construct(Set<String> set);
|
||||||
|
|
||||||
|
abstract SetType construct();
|
||||||
|
|
||||||
|
private Set<String> sampleSet;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup(){
|
||||||
|
sampleSet = new HashSet<>();
|
||||||
|
sampleSet.add("4");
|
||||||
|
sampleSet.add("5");
|
||||||
|
sampleSet.add("12");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void abstractSetConstructorTest(){
|
||||||
|
Assert.assertEquals(construct(sampleSet).value(), sampleSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void abstractStressWithSetTest(){
|
||||||
|
Set<String> hashSet = new HashSet<>();
|
||||||
|
SetType set = construct();
|
||||||
|
for (int it = 0; it < 40; it++){
|
||||||
|
SetType newSet;
|
||||||
|
if (it % 5 == 1){
|
||||||
|
//deleting existing
|
||||||
|
String forDelete = hashSet.stream().skip((long) (hashSet.size() * Math.random())).findFirst().get();
|
||||||
|
newSet = set.remove(forDelete);
|
||||||
|
Assert.assertEquals(set.value(), hashSet); // check old version is immutable
|
||||||
|
hashSet.remove(forDelete);
|
||||||
|
} else {
|
||||||
|
//adding
|
||||||
|
String forAdd = String.valueOf((int) (10000 * Math.random()));
|
||||||
|
newSet = set.add(forAdd);
|
||||||
|
Assert.assertEquals(set.value(), hashSet); // check old version is immutable
|
||||||
|
hashSet.add(forAdd);
|
||||||
|
}
|
||||||
|
set = newSet;
|
||||||
|
Assert.assertEquals(set.value(), hashSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void abstractEqualsTest(){
|
||||||
|
SetType set = construct(sampleSet);
|
||||||
|
Assert.assertFalse(set.equals(sampleSet));
|
||||||
|
SetType newSet = set.add("25");
|
||||||
|
sampleSet.add("25");
|
||||||
|
Assert.assertFalse(newSet.equals(set));
|
||||||
|
Assert.assertEquals(construct(sampleSet), newSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void abstractRemoveMissingTest(){
|
||||||
|
SetType set = construct(sampleSet);
|
||||||
|
set = set.add("25");
|
||||||
|
set = set.remove("25");
|
||||||
|
Assert.assertEquals(set.value(), sampleSet);
|
||||||
|
set = set.remove("25");
|
||||||
|
set = set.add("25");
|
||||||
|
sampleSet.add("25");
|
||||||
|
Assert.assertEquals(set.value(), sampleSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void abstractStressMergeTest(){
|
||||||
|
// in one-process context, add, remove and merge operations of lww are equal to operations of Set
|
||||||
|
// we've already checked it. Now just check merge
|
||||||
|
Set<String> hashSet1 = new HashSet<>(), hashSet2 = new HashSet<>();
|
||||||
|
SetType set1 = construct(), set2 = construct();
|
||||||
|
|
||||||
|
for (int it = 0; it < 100; it++){
|
||||||
|
String forAdd = String.valueOf((int) (10000 * Math.random()));
|
||||||
|
if (it % 2 == 0){
|
||||||
|
hashSet1.add(forAdd);
|
||||||
|
set1 = set1.add(forAdd);
|
||||||
|
} else {
|
||||||
|
hashSet2.add(forAdd);
|
||||||
|
set2 = set2.add(forAdd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert.assertEquals(set1.value(), hashSet1);
|
||||||
|
Assert.assertEquals(set2.value(), hashSet2);
|
||||||
|
Set<String> mergedSet = Stream.concat(hashSet1.stream(), hashSet2.stream()).collect(Collectors.toSet());
|
||||||
|
Assert.assertEquals(set1.merge(set2).value(), mergedSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void abstractOptimizeTest(){
|
||||||
|
Assert.assertEquals(construct(sampleSet).value(), sampleSet);
|
||||||
|
Assert.assertEquals(construct(sampleSet).optimize().value(), sampleSet);
|
||||||
|
}
|
||||||
|
}
|
@ -1,155 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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 org.apache.gossip.crdt;
|
|
||||||
|
|
||||||
import org.apache.gossip.manager.Clock;
|
|
||||||
import org.apache.gossip.manager.SystemClock;
|
|
||||||
import org.junit.Assert;
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
public class LWWSetTest {
|
|
||||||
static private Clock clock = new SystemClock();
|
|
||||||
private Set<Integer> sampleSet;
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup(){
|
|
||||||
sampleSet = new HashSet<>();
|
|
||||||
sampleSet.add(4);
|
|
||||||
sampleSet.add(5);
|
|
||||||
sampleSet.add(12);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void setConstructorTest(){
|
|
||||||
Assert.assertEquals(new LWWSet<>(sampleSet).value(), sampleSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void stressWithSetTest(){
|
|
||||||
Set<Integer> set = new HashSet<>();
|
|
||||||
LWWSet<Integer> lww = new LWWSet<>();
|
|
||||||
for (int it = 0; it < 100; it++){
|
|
||||||
LWWSet<Integer> newLww;
|
|
||||||
if (it % 5 == 1){
|
|
||||||
//deleting existing
|
|
||||||
Integer forDelete = set.stream().skip((long) (set.size() * Math.random())).findFirst().get();
|
|
||||||
newLww = lww.remove(forDelete);
|
|
||||||
Assert.assertEquals(lww.value(), set); // check old version is immutable
|
|
||||||
set.remove(forDelete);
|
|
||||||
} else {
|
|
||||||
//adding
|
|
||||||
Integer forAdd = (int) (10000 * Math.random());
|
|
||||||
newLww = lww.add(forAdd);
|
|
||||||
Assert.assertEquals(lww.value(), set); // check old version is immutable
|
|
||||||
set.add(forAdd);
|
|
||||||
}
|
|
||||||
lww = newLww;
|
|
||||||
Assert.assertEquals(lww.value(), set);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void equalsTest(){
|
|
||||||
LWWSet<Integer> lww = new LWWSet<>(sampleSet);
|
|
||||||
Assert.assertFalse(lww.equals(sampleSet));
|
|
||||||
LWWSet<Integer> newLww = lww.add(25);
|
|
||||||
sampleSet.add(25);
|
|
||||||
Assert.assertFalse(newLww.equals(lww));
|
|
||||||
Assert.assertEquals(new LWWSet<>(sampleSet), newLww);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void valueTest() {
|
|
||||||
Map<Character, LWWSet.Timestamps> map = new HashMap<>();
|
|
||||||
map.put('a', new LWWSet.Timestamps(1, 0));
|
|
||||||
map.put('b', new LWWSet.Timestamps(1, 2));
|
|
||||||
map.put('c', new LWWSet.Timestamps(3, 3));
|
|
||||||
Set<Character> toTest = new HashSet<>();
|
|
||||||
toTest.add('a'); // for 'a' addTime > removeTime
|
|
||||||
toTest.add('c'); // for 'c' times are equal, we prefer add to remove
|
|
||||||
Assert.assertEquals(new LWWSet<>(map).value(), toTest);
|
|
||||||
Assert.assertEquals(new LWWSet<>(map), new LWWSet<>('a', 'c'));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void removeMissingTest(){
|
|
||||||
LWWSet<Integer> lww = new LWWSet<>(sampleSet);
|
|
||||||
lww = lww.add(25);
|
|
||||||
lww = lww.remove(25);
|
|
||||||
Assert.assertEquals(lww.value(), sampleSet);
|
|
||||||
lww = lww.remove(25);
|
|
||||||
lww = lww.add(25);
|
|
||||||
sampleSet.add(25);
|
|
||||||
Assert.assertEquals(lww.value(), sampleSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void stressMergeTest(){
|
|
||||||
// in one-process context, add, remove and merge operations of lww are equal to operations of Set
|
|
||||||
// we've already checked it. Now just check merge
|
|
||||||
Set<Integer> set1 = new HashSet<>(), set2 = new HashSet<>();
|
|
||||||
LWWSet<Integer> lww1 = new LWWSet<>(), lww2 = new LWWSet<>();
|
|
||||||
|
|
||||||
for (int it = 0; it < 100; it++){
|
|
||||||
Integer forAdd = (int) (10000 * Math.random());
|
|
||||||
if (it % 2 == 0){
|
|
||||||
set1.add(forAdd);
|
|
||||||
lww1 = lww1.add(forAdd);
|
|
||||||
} else {
|
|
||||||
set2.add(forAdd);
|
|
||||||
lww2 = lww2.add(forAdd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Assert.assertEquals(lww1.value(), set1);
|
|
||||||
Assert.assertEquals(lww2.value(), set2);
|
|
||||||
Set<Integer> mergedSet = Stream.concat(set1.stream(), set2.stream()).collect(Collectors.toSet());
|
|
||||||
Assert.assertEquals(lww1.merge(lww2).value(), mergedSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void fakeTimeMergeTest(){
|
|
||||||
// try to create LWWSet with time from future (simulate other process with its own clock) and validate result
|
|
||||||
// check remove from the future
|
|
||||||
Map<Integer, LWWSet.Timestamps> map = new HashMap<>();
|
|
||||||
map.put(25, new LWWSet.Timestamps(clock.nanoTime(), clock.nanoTime() + 100000));
|
|
||||||
LWWSet<Integer> lww = new LWWSet<>(map);
|
|
||||||
Assert.assertEquals(lww, new LWWSet<Integer>());
|
|
||||||
//create new LWWSet with element 25, and merge with other LWW which has remove in future
|
|
||||||
Assert.assertEquals(new LWWSet<>(25).merge(lww), new LWWSet<Integer>());
|
|
||||||
|
|
||||||
// add in future
|
|
||||||
map.put(25, new LWWSet.Timestamps(clock.nanoTime() + 100000, 0));
|
|
||||||
lww = new LWWSet<>(map);
|
|
||||||
lww = lww.remove(25);
|
|
||||||
Assert.assertEquals(lww, new LWWSet<>(25)); // 25 is still here
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void optimizeTest(){
|
|
||||||
Assert.assertEquals(new LWWSet<>(sampleSet).value(), sampleSet);
|
|
||||||
Assert.assertEquals(new LWWSet<>(sampleSet).optimize().value(), sampleSet);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.apache.gossip.crdt;
|
||||||
|
|
||||||
|
import org.apache.gossip.manager.Clock;
|
||||||
|
import org.apache.gossip.manager.SystemClock;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class LwwSetTest extends AbstractCRDTStringSetTest<LwwSet<String>> {
|
||||||
|
static private Clock clock = new SystemClock();
|
||||||
|
|
||||||
|
LwwSet<String> construct(Set<String> set){
|
||||||
|
return new LwwSet<>(set);
|
||||||
|
}
|
||||||
|
|
||||||
|
LwwSet<String> construct(){
|
||||||
|
return new LwwSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void valueTest(){
|
||||||
|
Map<Character, LwwSet.Timestamps> map = new HashMap<>();
|
||||||
|
map.put('a', new LwwSet.Timestamps(1, 0));
|
||||||
|
map.put('b', new LwwSet.Timestamps(1, 2));
|
||||||
|
map.put('c', new LwwSet.Timestamps(3, 3));
|
||||||
|
Set<Character> toTest = new HashSet<>();
|
||||||
|
toTest.add('a'); // for 'a' addTime > removeTime
|
||||||
|
toTest.add('c'); // for 'c' times are equal, we prefer add to remove
|
||||||
|
Assert.assertEquals(new LwwSet<>(map).value(), toTest);
|
||||||
|
Assert.assertEquals(new LwwSet<>(map), new LwwSet<>('a', 'c'));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fakeTimeMergeTest(){
|
||||||
|
// try to create LWWSet with time from future (simulate other process with its own clock) and validate result
|
||||||
|
// check remove from the future
|
||||||
|
Map<Integer, LwwSet.Timestamps> map = new HashMap<>();
|
||||||
|
map.put(25, new LwwSet.Timestamps(clock.nanoTime(), Long.MAX_VALUE));
|
||||||
|
LwwSet<Integer> lww = new LwwSet<>(map);
|
||||||
|
Assert.assertEquals(lww, new LwwSet<Integer>());
|
||||||
|
//create new LWWSet with element 25, and merge with other LWW which has remove in future
|
||||||
|
Assert.assertEquals(new LwwSet<>(25).merge(lww), new LwwSet<Integer>());
|
||||||
|
|
||||||
|
// add in future
|
||||||
|
map.put(25, new LwwSet.Timestamps(Long.MAX_VALUE, 0));
|
||||||
|
lww = new LwwSet<>(map);
|
||||||
|
lww = lww.remove(25);
|
||||||
|
Assert.assertEquals(lww, new LwwSet<>(25)); // 25 is still here
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.apache.gossip.crdt;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class MaxChangeSetTest extends AbstractCRDTStringSetTest<MaxChangeSet<String>> {
|
||||||
|
MaxChangeSet<String> construct(Set<String> set){
|
||||||
|
return new MaxChangeSet<>(set);
|
||||||
|
}
|
||||||
|
|
||||||
|
MaxChangeSet<String> construct(){
|
||||||
|
return new MaxChangeSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void valueTest(){
|
||||||
|
Map<Character, Integer> struct = new HashMap<>();
|
||||||
|
struct.put('a', 0);
|
||||||
|
struct.put('b', 1);
|
||||||
|
struct.put('c', 2);
|
||||||
|
struct.put('d', 3);
|
||||||
|
Set<Character> result = new HashSet<>();
|
||||||
|
result.add('b');
|
||||||
|
result.add('d');
|
||||||
|
Assert.assertEquals(new MaxChangeSet<>(struct).value(), result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mergeTest(){
|
||||||
|
MaxChangeSet<Integer> set1 = new MaxChangeSet<Integer>().add(1); // Set with one operation on 1
|
||||||
|
MaxChangeSet<Integer> set2 = new MaxChangeSet<Integer>().add(1).remove(1); // two operations
|
||||||
|
Assert.assertEquals(set1.merge(set2), new MaxChangeSet<Integer>()); // empty set wins
|
||||||
|
|
||||||
|
set1 = set1.add(1).add(1).add(1);
|
||||||
|
// empty set still wins, repetitive operations do nothing, don't increase number of operations
|
||||||
|
Assert.assertEquals(set1.merge(set2), new MaxChangeSet<Integer>());
|
||||||
|
|
||||||
|
set1 = set1.remove(1).add(1); // 3 operations
|
||||||
|
Assert.assertEquals(set1.merge(set2), new MaxChangeSet<>(1)); // full set wins now
|
||||||
|
|
||||||
|
set2 = set2.remove(1).remove(1).remove(1);
|
||||||
|
// full set still wins, repetitive removes don't increase number of operations too
|
||||||
|
Assert.assertEquals(set1.merge(set2), new MaxChangeSet<>(1));
|
||||||
|
}
|
||||||
|
}
|
@ -17,17 +17,25 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.gossip.crdt;
|
package org.apache.gossip.crdt;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.SortedSet;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class OrSetTest {
|
import java.util.Arrays;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.SortedSet;
|
||||||
|
import java.util.TreeSet;
|
||||||
|
|
||||||
|
public class OrSetTest extends AbstractCRDTStringSetTest<OrSet<String>> {
|
||||||
|
OrSet<String> construct(){
|
||||||
|
return new OrSet<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
OrSet<String> construct(Set<String> set){
|
||||||
|
return new OrSet<>(set);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void atest() {
|
public void atest(){
|
||||||
OrSet<Integer> i = new OrSet<>(new OrSet.Builder<Integer>().add(4).add(5).add(6).remove(5));
|
OrSet<Integer> i = new OrSet<>(new OrSet.Builder<Integer>().add(4).add(5).add(6).remove(5));
|
||||||
Assert.assertArrayEquals(Arrays.asList(4, 6).toArray(), i.value().toArray());
|
Assert.assertArrayEquals(Arrays.asList(4, 6).toArray(), i.value().toArray());
|
||||||
}
|
}
|
||||||
@ -38,15 +46,15 @@ public class OrSetTest {
|
|||||||
Assert.assertArrayEquals(Arrays.asList(4, 6).toArray(), i.value().toArray());
|
Assert.assertArrayEquals(Arrays.asList(4, 6).toArray(), i.value().toArray());
|
||||||
OrSet<Integer> j = new OrSet<>(new OrSet.Builder<Integer>().add(9).add(4).add(5).remove(6));
|
OrSet<Integer> j = new OrSet<>(new OrSet.Builder<Integer>().add(9).add(4).add(5).remove(6));
|
||||||
OrSet<Integer> h = i.merge(j);
|
OrSet<Integer> h = i.merge(j);
|
||||||
Assert.assertEquals(new OrSet<Integer>(4,6,9,5), h);
|
Assert.assertEquals(new OrSet<>(4, 6, 9, 5), h);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void mergeTest2(){
|
public void mergeTest2(){
|
||||||
OrSet<Integer> i = new OrSet<>(new OrSet.Builder<Integer>().add(5).add(4).remove(4).add(6));
|
OrSet<Integer> i = new OrSet<>(new OrSet.Builder<Integer>().add(5).add(4).remove(4).add(6));
|
||||||
Assert.assertEquals(new OrSet<Integer>(5,6), i);
|
Assert.assertEquals(new OrSet<>(5, 6), i);
|
||||||
SortedSet<Integer> tree = new TreeSet<>();
|
SortedSet<Integer> tree = new TreeSet<>();
|
||||||
for (Integer in: i.value()){
|
for (Integer in : i.value()){
|
||||||
tree.add(in);
|
tree.add(in);
|
||||||
}
|
}
|
||||||
TreeSet<Integer> compare = new TreeSet<>();
|
TreeSet<Integer> compare = new TreeSet<>();
|
||||||
@ -56,21 +64,21 @@ public class OrSetTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void mergeTest4() {
|
public void mergeTest4(){
|
||||||
Assert.assertArrayEquals(new Integer[] {},
|
Assert.assertArrayEquals(new Integer[]{},
|
||||||
new OrSet<Integer>(new OrSet.Builder<Integer>().add(1).remove(1)).toArray());
|
new OrSet<>(new OrSet.Builder<Integer>().add(1).remove(1)).toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void mergeTest3(){
|
public void mergeTest3(){
|
||||||
OrSet<Integer> i = new OrSet<>(1);
|
OrSet<Integer> i = new OrSet<>(1);
|
||||||
OrSet<Integer> j = new OrSet<>(2);
|
OrSet<Integer> j = new OrSet<>(2);
|
||||||
OrSet<Integer> k = new OrSet<>(i.merge(j), new OrSet.Builder<Integer>().remove(1));
|
OrSet<Integer> k = new OrSet<>(i.merge(j), new OrSet.Builder<Integer>().remove(1));
|
||||||
Assert.assertArrayEquals(new Integer[] { 2 }, i.merge(j).merge(k).toArray());
|
Assert.assertArrayEquals(new Integer[]{2}, i.merge(j).merge(k).toArray());
|
||||||
Assert.assertArrayEquals(new Integer[] { 2 }, j.merge(i).merge(k).toArray());
|
Assert.assertArrayEquals(new Integer[]{2}, j.merge(i).merge(k).toArray());
|
||||||
Assert.assertArrayEquals(new Integer[] { 2 }, k.merge(i).merge(j).toArray());
|
Assert.assertArrayEquals(new Integer[]{2}, k.merge(i).merge(j).toArray());
|
||||||
Assert.assertArrayEquals(new Integer[] { 2 }, k.merge(j).merge(i).toArray());
|
Assert.assertArrayEquals(new Integer[]{2}, k.merge(j).merge(i).toArray());
|
||||||
Assert.assertEquals(j , i.merge(j.merge(k)));
|
Assert.assertEquals(j, i.merge(j.merge(k)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -81,7 +89,7 @@ public class OrSetTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void mergeTestSame() {
|
public void mergeTestSame(){
|
||||||
OrSet<Integer> i = new OrSet<>(19);
|
OrSet<Integer> i = new OrSet<>(19);
|
||||||
OrSet<Integer> j = new OrSet<>(19);
|
OrSet<Integer> j = new OrSet<>(19);
|
||||||
OrSet<Integer> k = i.merge(j);
|
OrSet<Integer> k = i.merge(j);
|
||||||
|
@ -17,69 +17,81 @@
|
|||||||
*/
|
*/
|
||||||
package org.apache.gossip;
|
package org.apache.gossip;
|
||||||
|
|
||||||
import java.net.URI;
|
import io.teknek.tunit.TUnit;
|
||||||
import java.net.URISyntaxException;
|
import org.apache.gossip.crdt.CrdtAddRemoveSet;
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import org.apache.gossip.crdt.GrowOnlyCounter;
|
import org.apache.gossip.crdt.GrowOnlyCounter;
|
||||||
import org.apache.gossip.crdt.GrowOnlySet;
|
import org.apache.gossip.crdt.GrowOnlySet;
|
||||||
import org.apache.gossip.crdt.LWWSet;
|
import org.apache.gossip.crdt.LwwSet;
|
||||||
|
import org.apache.gossip.crdt.MaxChangeSet;
|
||||||
import org.apache.gossip.crdt.OrSet;
|
import org.apache.gossip.crdt.OrSet;
|
||||||
import org.apache.gossip.crdt.PNCounter;
|
import org.apache.gossip.crdt.PNCounter;
|
||||||
import org.apache.gossip.manager.GossipManager;
|
import org.apache.gossip.manager.GossipManager;
|
||||||
import org.apache.gossip.manager.GossipManagerBuilder;
|
import org.apache.gossip.manager.GossipManagerBuilder;
|
||||||
import org.apache.gossip.model.PerNodeDataMessage;
|
import org.apache.gossip.model.PerNodeDataMessage;
|
||||||
import org.apache.gossip.model.SharedDataMessage;
|
import org.apache.gossip.model.SharedDataMessage;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import io.teknek.tunit.TUnit;
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
public class DataTest extends AbstractIntegrationBase {
|
public class DataTest {
|
||||||
|
private final String gCounterKey = "crdtgc";
|
||||||
|
private final String pnCounterKey = "crdtpn";
|
||||||
|
|
||||||
private String orSetKey = "cror";
|
private static final List<GossipManager> clients = new ArrayList<>();
|
||||||
private String lwwSetKey = "crlww";
|
|
||||||
private String gCounterKey = "crdtgc";
|
@BeforeClass
|
||||||
private String pnCounterKey = "crdtpn";
|
public static void initializeMembers() throws InterruptedException, UnknownHostException, URISyntaxException{
|
||||||
|
final int clusterMembers = 2;
|
||||||
|
|
||||||
@Test
|
|
||||||
public void dataTest() throws InterruptedException, UnknownHostException, URISyntaxException {
|
|
||||||
GossipSettings settings = new GossipSettings();
|
GossipSettings settings = new GossipSettings();
|
||||||
settings.setPersistRingState(false);
|
settings.setPersistRingState(false);
|
||||||
settings.setPersistDataState(false);
|
settings.setPersistDataState(false);
|
||||||
String cluster = UUID.randomUUID().toString();
|
String cluster = UUID.randomUUID().toString();
|
||||||
int seedNodes = 1;
|
|
||||||
List<Member> startupMembers = new ArrayList<>();
|
List<Member> startupMembers = new ArrayList<>();
|
||||||
for (int i = 1; i < seedNodes + 1; ++i) {
|
for (int i = 0; i < clusterMembers; ++i){
|
||||||
URI uri = new URI("udp://" + "127.0.0.1" + ":" + (50000 + i));
|
int id = i + 1;
|
||||||
startupMembers.add(new RemoteMember(cluster, uri, i + ""));
|
URI uri = new URI("udp://" + "127.0.0.1" + ":" + (50000 + id));
|
||||||
|
startupMembers.add(new RemoteMember(cluster, uri, id + ""));
|
||||||
}
|
}
|
||||||
final List<GossipManager> clients = new ArrayList<>();
|
|
||||||
final int clusterMembers = 2;
|
for (Member member : startupMembers){
|
||||||
for (int i = 1; i < clusterMembers + 1; ++i) {
|
GossipManager gossipService = GossipManagerBuilder.newBuilder().cluster(cluster).uri(member.getUri())
|
||||||
URI uri = new URI("udp://" + "127.0.0.1" + ":" + (50000 + i));
|
.id(member.getId()).gossipMembers(startupMembers).gossipSettings(settings).build();
|
||||||
GossipManager gossipService = GossipManagerBuilder
|
|
||||||
.newBuilder()
|
|
||||||
.cluster(cluster).uri(uri)
|
|
||||||
.id(i + "")
|
|
||||||
.gossipMembers(startupMembers)
|
|
||||||
.gossipSettings(settings).build();
|
|
||||||
clients.add(gossipService);
|
clients.add(gossipService);
|
||||||
gossipService.init();
|
gossipService.init();
|
||||||
register(gossipService);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void shutdownMembers(){
|
||||||
|
for (final GossipManager client : clients){
|
||||||
|
client.shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void simpleDataTest(){
|
||||||
TUnit.assertThat(() -> {
|
TUnit.assertThat(() -> {
|
||||||
int total = 0;
|
int total = 0;
|
||||||
for (int i = 0; i < clusterMembers; ++i) {
|
for (GossipManager client : clients){
|
||||||
total += clients.get(i).getLiveMembers().size();
|
total += client.getLiveMembers().size();
|
||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
}).afterWaitingAtMost(20, TimeUnit.SECONDS).isEqualTo(2);
|
}).afterWaitingAtMost(10, TimeUnit.SECONDS).isEqualTo(2);
|
||||||
|
|
||||||
clients.get(0).gossipPerNodeData(generatePerNodeMsg("a", "b"));
|
clients.get(0).gossipPerNodeData(generatePerNodeMsg("a", "b"));
|
||||||
clients.get(0).gossipSharedData(generateSharedMsg("a", "c"));
|
clients.get(0).gossipSharedData(generateSharedMsg("a", "c"));
|
||||||
|
|
||||||
@ -89,7 +101,7 @@ public class DataTest extends AbstractIntegrationBase {
|
|||||||
return "";
|
return "";
|
||||||
else
|
else
|
||||||
return x.getPayload();
|
return x.getPayload();
|
||||||
}).afterWaitingAtMost(20, TimeUnit.SECONDS).isEqualTo("b");
|
}).afterWaitingAtMost(10, TimeUnit.SECONDS).isEqualTo("b");
|
||||||
|
|
||||||
TUnit.assertThat(() -> {
|
TUnit.assertThat(() -> {
|
||||||
SharedDataMessage x = clients.get(1).findSharedGossipData("a");
|
SharedDataMessage x = clients.get(1).findSharedGossipData("a");
|
||||||
@ -97,175 +109,118 @@ public class DataTest extends AbstractIntegrationBase {
|
|||||||
return "";
|
return "";
|
||||||
else
|
else
|
||||||
return x.getPayload();
|
return x.getPayload();
|
||||||
}).afterWaitingAtMost(20, TimeUnit.SECONDS).isEqualTo("c");
|
}).afterWaitingAtMost(10, TimeUnit.SECONDS).isEqualTo("c");
|
||||||
|
|
||||||
givenDifferentDatumsInSet(clients);
|
|
||||||
assertThatListIsMerged(clients);
|
|
||||||
|
|
||||||
testOrSet(clients);
|
|
||||||
testLWWSet(clients);
|
|
||||||
|
|
||||||
testGrowOnlyCounter(clients);
|
|
||||||
testPNCounter(clients);
|
|
||||||
|
|
||||||
for (int i = 0; i < clusterMembers; ++i) {
|
|
||||||
clients.get(i).shutdown();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testOrSet(final List<GossipManager> clients) {
|
Set<String> setFromList(String... elements){
|
||||||
// populate
|
return new HashSet<>(Arrays.asList(elements));
|
||||||
clients.get(0).merge(generateSharedMsg(orSetKey, new OrSet<>("1", "2")));
|
}
|
||||||
clients.get(1).merge(generateSharedMsg(orSetKey, new OrSet<>("3", "4")));
|
|
||||||
|
|
||||||
// assert merge
|
void crdtSetTest(String key, Function<Set<String>, CrdtAddRemoveSet<String, Set<String>, ?>> construct){
|
||||||
assertMerged(clients.get(0), orSetKey, new OrSet<>("1", "2", "3", "4").value());
|
//populate
|
||||||
assertMerged(clients.get(1), orSetKey, new OrSet<>("1", "2", "3", "4").value());
|
clients.get(0).merge(generateSharedMsg(key, construct.apply(setFromList("1", "2"))));
|
||||||
|
clients.get(1).merge(generateSharedMsg(key, construct.apply(setFromList("3", "4"))));
|
||||||
|
|
||||||
// drop element
|
assertMergedCrdt(key, construct.apply(setFromList("1", "2", "3", "4")).value());
|
||||||
|
|
||||||
|
//drop element
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
OrSet<String> o = (OrSet<String>) clients.get(0).findCrdt(orSetKey);
|
CrdtAddRemoveSet<String, ?, ?> set = (CrdtAddRemoveSet<String, ?, ?>) clients.get(0).findCrdt(key);
|
||||||
OrSet<String> o2 = new OrSet<>(o, new OrSet.Builder<String>().remove("3"));
|
clients.get(0).merge(generateSharedMsg(key, set.remove("3")));
|
||||||
clients.get(0).merge(generateSharedMsg(orSetKey, o2));
|
|
||||||
|
|
||||||
// assert deletion
|
//assert deletion
|
||||||
assertMerged(clients.get(0), orSetKey, new OrSet<>("1", "2", "4").value());
|
assertMergedCrdt(key, construct.apply(setFromList("1", "2", "4")).value());
|
||||||
assertMerged(clients.get(1), orSetKey, new OrSet<>("1", "2", "4").value());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testLWWSet(final List<GossipManager> clients) {
|
@Test
|
||||||
// populate
|
public void OrSetTest(){
|
||||||
clients.get(0).merge(generateSharedMsg(lwwSetKey, new LWWSet<>("1", "2")));
|
crdtSetTest("cror", OrSet::new);
|
||||||
clients.get(1).merge(generateSharedMsg(lwwSetKey, new LWWSet<>("3", "4")));
|
|
||||||
|
|
||||||
// assert merge
|
|
||||||
assertMerged(clients.get(0), lwwSetKey, new LWWSet<>("1", "2", "3", "4").value());
|
|
||||||
assertMerged(clients.get(1), lwwSetKey, new LWWSet<>("1", "2", "3", "4").value());
|
|
||||||
|
|
||||||
// drop element
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
LWWSet<String> lww = (LWWSet<String>) clients.get(0).findCrdt(lwwSetKey);
|
|
||||||
clients.get(0).merge(generateSharedMsg(lwwSetKey, lww.remove("3")));
|
|
||||||
|
|
||||||
// assert deletion
|
|
||||||
assertMerged(clients.get(0), lwwSetKey, new OrSet<>("1", "2", "4").value());
|
|
||||||
assertMerged(clients.get(1), lwwSetKey, new OrSet<>("1", "2", "4").value());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testGrowOnlyCounter(List<GossipManager> clients) {
|
@Test
|
||||||
givenDifferentIncrement(clients);
|
public void LWWSetTest(){
|
||||||
assertThatCountIsUpdated(clients, 3);
|
crdtSetTest("crlww", LwwSet::new);
|
||||||
givenIncreaseOther(clients);
|
|
||||||
assertThatCountIsUpdated(clients, 7);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testPNCounter(List<GossipManager> clients) {
|
@Test
|
||||||
givenPNCounter(clients);
|
public void MaxChangeSetTest(){
|
||||||
assertThatPNCounterSettlesAt(clients, 0);
|
crdtSetTest("crmcs", MaxChangeSet::new);
|
||||||
int[] delta1 = { 2, 3 };
|
|
||||||
givenPNCounterUpdate(clients, delta1);
|
|
||||||
assertThatPNCounterSettlesAt(clients, 5);
|
|
||||||
int[] delta2 = { -3, 5 };
|
|
||||||
givenPNCounterUpdate(clients, delta2);
|
|
||||||
assertThatPNCounterSettlesAt(clients, 7);
|
|
||||||
int[] delta3 = { 1, 1 };
|
|
||||||
givenPNCounterUpdate(clients, delta3);
|
|
||||||
assertThatPNCounterSettlesAt(clients, 9);
|
|
||||||
int[] delta4 = { 1, -7 };
|
|
||||||
givenPNCounterUpdate(clients, delta4);
|
|
||||||
assertThatPNCounterSettlesAt(clients, 3);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void givenDifferentIncrement(final List<GossipManager> clients) {
|
@Test
|
||||||
|
public void GrowOnlyCounterTest(){
|
||||||
|
Consumer<Long> assertCountUpdated = count -> {
|
||||||
|
for (GossipManager client : clients){
|
||||||
|
TUnit.assertThat(() -> client.findCrdt(gCounterKey))
|
||||||
|
.afterWaitingAtMost(10, TimeUnit.SECONDS)
|
||||||
|
.isEqualTo(new GrowOnlyCounter(new GrowOnlyCounter.Builder(client).increment(count)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//generate different increment
|
||||||
Object payload = new GrowOnlyCounter(new GrowOnlyCounter.Builder(clients.get(0)).increment(1L));
|
Object payload = new GrowOnlyCounter(new GrowOnlyCounter.Builder(clients.get(0)).increment(1L));
|
||||||
clients.get(0).merge(generateSharedMsg(gCounterKey, payload));
|
clients.get(0).merge(generateSharedMsg(gCounterKey, payload));
|
||||||
payload = new GrowOnlyCounter(new GrowOnlyCounter.Builder(clients.get(1)).increment(2L));
|
payload = new GrowOnlyCounter(new GrowOnlyCounter.Builder(clients.get(1)).increment(2L));
|
||||||
clients.get(1).merge(generateSharedMsg(gCounterKey, payload));
|
clients.get(1).merge(generateSharedMsg(gCounterKey, payload));
|
||||||
}
|
|
||||||
|
|
||||||
private void givenIncreaseOther(final List<GossipManager> clients) {
|
assertCountUpdated.accept((long) 3);
|
||||||
|
|
||||||
|
//update one
|
||||||
GrowOnlyCounter gc = (GrowOnlyCounter) clients.get(1).findCrdt(gCounterKey);
|
GrowOnlyCounter gc = (GrowOnlyCounter) clients.get(1).findCrdt(gCounterKey);
|
||||||
GrowOnlyCounter gc2 = new GrowOnlyCounter(gc,
|
GrowOnlyCounter gc2 = new GrowOnlyCounter(gc,
|
||||||
new GrowOnlyCounter.Builder(clients.get(1)).increment(4L));
|
new GrowOnlyCounter.Builder(clients.get(1)).increment(4L));
|
||||||
|
|
||||||
clients.get(1).merge(generateSharedMsg(gCounterKey, gc2));
|
clients.get(1).merge(generateSharedMsg(gCounterKey, gc2));
|
||||||
|
|
||||||
|
assertCountUpdated.accept((long) 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertMerged(final GossipManager client, String key, final Set<String> expected) {
|
@Test
|
||||||
TUnit.assertThat(() -> client.findCrdt(key).value()).afterWaitingAtMost(10, TimeUnit.SECONDS)
|
public void PNCounterTest(){
|
||||||
.isEqualTo(expected);
|
Consumer<List<Integer>> counterUpdate = list -> {
|
||||||
}
|
int clientIndex = 0;
|
||||||
|
for (int delta : list){
|
||||||
|
PNCounter c = (PNCounter) clients.get(clientIndex).findCrdt(pnCounterKey);
|
||||||
|
c = new PNCounter(c, new PNCounter.Builder(clients.get(clientIndex)).increment(((long) delta)));
|
||||||
|
clients.get(clientIndex).merge(generateSharedMsg(pnCounterKey, c));
|
||||||
|
clientIndex = (clientIndex + 1) % clients.size();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private void givenDifferentDatumsInSet(final List<GossipManager> clients) {
|
// given PNCounter
|
||||||
clients.get(0).merge(CrdtMessage("1"));
|
clients.get(0).merge(generateSharedMsg(pnCounterKey, new PNCounter(new PNCounter.Builder(clients.get(0)))));
|
||||||
clients.get(1).merge(CrdtMessage("2"));
|
clients.get(1).merge(generateSharedMsg(pnCounterKey, new PNCounter(new PNCounter.Builder(clients.get(1)))));
|
||||||
}
|
|
||||||
|
|
||||||
private void assertThatCountIsUpdated(final List<GossipManager> clients, long finalCount) {
|
assertMergedCrdt(pnCounterKey, (long) 0);
|
||||||
TUnit.assertThat(() -> clients.get(0).findCrdt(gCounterKey))
|
|
||||||
.afterWaitingAtMost(10, TimeUnit.SECONDS).isEqualTo(new GrowOnlyCounter(
|
|
||||||
new GrowOnlyCounter.Builder(clients.get(0)).increment(finalCount)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertThatListIsMerged(final List<GossipManager> clients) {
|
List<List<Integer>> updateLists = new ArrayList<>();
|
||||||
TUnit.assertThat(() -> clients.get(0).findCrdt("cr")).afterWaitingAtMost(10, TimeUnit.SECONDS)
|
updateLists.add(Arrays.asList(2, 3));
|
||||||
.isEqualTo(new GrowOnlySet<>(Arrays.asList("1", "2")));
|
updateLists.add(Arrays.asList(-3, 5));
|
||||||
}
|
updateLists.add(Arrays.asList(1, 1));
|
||||||
|
updateLists.add(Arrays.asList(1, -7));
|
||||||
|
|
||||||
private void givenPNCounter(List<GossipManager> clients) {
|
Long[] expectedResults = {5L, 7L, 9L, 3L};
|
||||||
{
|
|
||||||
SharedDataMessage d = new SharedDataMessage();
|
for (int i = 0; i < updateLists.size(); i++){
|
||||||
d.setKey(pnCounterKey);
|
counterUpdate.accept(updateLists.get(i));
|
||||||
d.setPayload(new PNCounter(new PNCounter.Builder(clients.get(0))));
|
assertMergedCrdt(pnCounterKey, expectedResults[i]);
|
||||||
d.setExpireAt(Long.MAX_VALUE);
|
|
||||||
d.setTimestamp(System.currentTimeMillis());
|
|
||||||
clients.get(0).merge(d);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
SharedDataMessage d = new SharedDataMessage();
|
|
||||||
d.setKey(pnCounterKey);
|
|
||||||
d.setPayload(new PNCounter(new PNCounter.Builder(clients.get(1))));
|
|
||||||
d.setExpireAt(Long.MAX_VALUE);
|
|
||||||
d.setTimestamp(System.currentTimeMillis());
|
|
||||||
clients.get(1).merge(d);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void givenPNCounterUpdate(List<GossipManager> clients, int[] deltaArray) {
|
@Test
|
||||||
int clientIndex = 0;
|
public void GrowOnlySetTest(){
|
||||||
for (int delta: deltaArray) {
|
clients.get(0).merge(generateSharedMsg("cr", new GrowOnlySet<>(Arrays.asList("1"))));
|
||||||
PNCounter c = (PNCounter) clients.get(clientIndex).findCrdt(pnCounterKey);
|
clients.get(1).merge(generateSharedMsg("cr", new GrowOnlySet<>(Arrays.asList("2"))));
|
||||||
c = new PNCounter(c, new PNCounter.Builder(clients.get(clientIndex)).increment(((long)delta)));
|
|
||||||
SharedDataMessage d = new SharedDataMessage();
|
assertMergedCrdt("cr", new GrowOnlySet<>(Arrays.asList("1", "2")).value());
|
||||||
d.setKey(pnCounterKey);
|
}
|
||||||
d.setPayload(c);
|
|
||||||
d.setExpireAt(Long.MAX_VALUE);
|
private void assertMergedCrdt(String key, Object expected){
|
||||||
d.setTimestamp(System.currentTimeMillis());
|
for (GossipManager client : clients){
|
||||||
clients.get(clientIndex).merge(d);
|
TUnit.assertThat(() -> client.findCrdt(key).value())
|
||||||
clientIndex = (clientIndex + 1) % clients.size();
|
.afterWaitingAtMost(10, TimeUnit.SECONDS).isEqualTo(expected);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertThatPNCounterSettlesAt(List<GossipManager> clients, long expectedValue) {
|
private PerNodeDataMessage generatePerNodeMsg(String key, Object payload){
|
||||||
for (GossipManager client: clients) {
|
|
||||||
TUnit.assertThat(() -> {
|
|
||||||
long value = 0;
|
|
||||||
Object o = client.findCrdt(pnCounterKey);
|
|
||||||
if (o != null) {
|
|
||||||
PNCounter c = (PNCounter)o;
|
|
||||||
value = c.value();
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}).afterWaitingAtMost(10, TimeUnit.SECONDS)
|
|
||||||
.isEqualTo(expectedValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private SharedDataMessage CrdtMessage(String item) {
|
|
||||||
return generateSharedMsg("cr", new GrowOnlySet<>(Arrays.asList(item)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private PerNodeDataMessage generatePerNodeMsg(String key, Object payload) {
|
|
||||||
PerNodeDataMessage g = new PerNodeDataMessage();
|
PerNodeDataMessage g = new PerNodeDataMessage();
|
||||||
g.setExpireAt(Long.MAX_VALUE);
|
g.setExpireAt(Long.MAX_VALUE);
|
||||||
g.setKey(key);
|
g.setKey(key);
|
||||||
@ -274,7 +229,7 @@ public class DataTest extends AbstractIntegrationBase {
|
|||||||
return g;
|
return g;
|
||||||
}
|
}
|
||||||
|
|
||||||
private SharedDataMessage generateSharedMsg(String key, Object payload) {
|
private SharedDataMessage generateSharedMsg(String key, Object payload){
|
||||||
SharedDataMessage d = new SharedDataMessage();
|
SharedDataMessage d = new SharedDataMessage();
|
||||||
d.setKey(key);
|
d.setKey(key);
|
||||||
d.setPayload(payload);
|
d.setPayload(payload);
|
||||||
|
@ -22,7 +22,8 @@ import com.codahale.metrics.MetricRegistry;
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import org.apache.gossip.GossipSettings;
|
import org.apache.gossip.GossipSettings;
|
||||||
import org.apache.gossip.Member;
|
import org.apache.gossip.Member;
|
||||||
import org.apache.gossip.crdt.LWWSet;
|
import org.apache.gossip.crdt.LwwSet;
|
||||||
|
import org.apache.gossip.crdt.MaxChangeSet;
|
||||||
import org.apache.gossip.crdt.OrSet;
|
import org.apache.gossip.crdt.OrSet;
|
||||||
import org.apache.gossip.manager.GossipManager;
|
import org.apache.gossip.manager.GossipManager;
|
||||||
import org.apache.gossip.manager.GossipManagerBuilder;
|
import org.apache.gossip.manager.GossipManagerBuilder;
|
||||||
@ -82,22 +83,34 @@ public class JacksonTest {
|
|||||||
Assert.assertEquals(back, i);
|
Assert.assertEquals(back, i);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
void jacksonCrdtSeDeTest(Object value, Class<?> cl){
|
||||||
public void jacksonCrdtLWWSetTest() {
|
|
||||||
ObjectMapper objectMapper = JacksonProtocolManager.buildObjectMapper(simpleSettings(new GossipSettings()));
|
ObjectMapper objectMapper = JacksonProtocolManager.buildObjectMapper(simpleSettings(new GossipSettings()));
|
||||||
|
|
||||||
LWWSet<String> lww = new LWWSet<>("a", "b", "c");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
String lwwS = objectMapper.writeValueAsString(lww);
|
String valueS = objectMapper.writeValueAsString(value);
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
LWWSet<String> parsedLww = objectMapper.readValue(lwwS, LWWSet.class);
|
Object parsedValue = objectMapper.readValue(valueS, cl);
|
||||||
Assert.assertEquals(lww, parsedLww);
|
Assert.assertEquals(value, parsedValue);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Assert.fail("LWWSet se/de error");
|
Assert.fail("Jackson se/de error");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void jacksonOrSetTest(){
|
||||||
|
jacksonCrdtSeDeTest(new OrSet<>("1", "2", "3"), OrSet.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void jacksonLWWSetTest(){
|
||||||
|
jacksonCrdtSeDeTest(new LwwSet<>("1", "2", "3"), LwwSet.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void jacksonMaxChangeSetTest(){
|
||||||
|
jacksonCrdtSeDeTest(new MaxChangeSet<>("1", "2", "3"), MaxChangeSet.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testMessageEqualityAssumptions() {
|
public void testMessageEqualityAssumptions() {
|
||||||
long timeA = System.nanoTime();
|
long timeA = System.nanoTime();
|
||||||
|
Reference in New Issue
Block a user