diff --git a/akka-cluster/src/main/scala/akka/cluster/Cluster.scala b/akka-cluster/src/main/scala/akka/cluster/Cluster.scala index c3e16de5e5..a86bc0148c 100644 --- a/akka-cluster/src/main/scala/akka/cluster/Cluster.scala +++ b/akka-cluster/src/main/scala/akka/cluster/Cluster.scala @@ -240,9 +240,9 @@ case class Gossip( /** * Increments the version for this 'Node'. */ - def +(node: VectorClock.Node): Gossip = copy(version = version + node) + def :+(node: VectorClock.Node): Gossip = copy(version = version :+ node) - def +(member: Member): Gossip = { + def :+(member: Member): Gossip = { if (members contains member) this else this copy (members = members + member) } @@ -474,7 +474,7 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector) private val state = { val member = Member(selfAddress, Joining) - val versionedGossip = Gossip(members = Gossip.emptyMembers + member) + vclockNode // add me as member and update my vector clock + val versionedGossip = Gossip(members = Gossip.emptyMembers + member) :+ vclockNode // add me as member and update my vector clock val seenVersionedGossip = versionedGossip seen selfAddress new AtomicReference[State](State(seenVersionedGossip)) } @@ -714,7 +714,7 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector) val newMembers = localMembers + Member(node, Joining) // add joining node as Joining val newGossip = localGossip copy (overview = newOverview, members = newMembers) - val versionedGossip = newGossip + vclockNode + val versionedGossip = newGossip :+ vclockNode val seenVersionedGossip = versionedGossip seen selfAddress val newState = localState copy (latestGossip = seenVersionedGossip) @@ -742,7 +742,7 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector) val newMembers = localMembers + Member(address, Leaving) // mark node as LEAVING val newGossip = localGossip copy (members = newMembers) - val versionedGossip = newGossip + vclockNode + val versionedGossip = newGossip :+ vclockNode val seenVersionedGossip = versionedGossip seen selfAddress val newState = localState copy (latestGossip = seenVersionedGossip) @@ -822,7 +822,7 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector) // update gossip overview val newOverview = localOverview copy (seen = newSeen, unreachable = newUnreachablePlusNewlyDownedMembers) val newGossip = localGossip copy (overview = newOverview, members = newMembers) // update gossip - val versionedGossip = newGossip + vclockNode + val versionedGossip = newGossip :+ vclockNode val newState = localState copy (latestGossip = versionedGossip seen selfAddress) if (!state.compareAndSet(localState, newState)) downing(address) // recur if we fail the update @@ -843,7 +843,7 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector) if (remoteGossip.version <> localGossip.version) { // concurrent val mergedGossip = remoteGossip merge localGossip - val versionedMergedGossip = mergedGossip + vclockNode + val versionedMergedGossip = mergedGossip :+ vclockNode // FIXME change to debug log level, when failure detector is stable log.info( @@ -1012,7 +1012,7 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector) val newGossip = localGossip copy (overview = newOverview, members = newMembers) // updating vclock and 'seen' table - val versionedGossip = newGossip + vclockNode + val versionedGossip = newGossip :+ vclockNode val seenVersionedGossip = versionedGossip seen selfAddress val newState = localState copy (latestGossip = seenVersionedGossip) @@ -1135,7 +1135,7 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector) // ---------------------- // 5. Updating the vclock version for the changes // ---------------------- - val versionedGossip = newGossip + vclockNode + val versionedGossip = newGossip :+ vclockNode // ---------------------- // 6. Updating the 'seen' table @@ -1162,23 +1162,39 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector) private def convergence(gossip: Gossip): Option[Gossip] = { val overview = gossip.overview val unreachable = overview.unreachable + val seen = overview.seen // First check that: - // 1. we don't have any members that are unreachable (unreachable.isEmpty == true), or + // 1. we don't have any members that are unreachable, or // 2. all unreachable members in the set have status DOWN // Else we can't continue to check for convergence // When that is done we check that all the entries in the 'seen' table have the same vector clock version - if (unreachable.isEmpty || !unreachable.exists { m ⇒ + // and that all members exists in seen table + val hasUnreachable = unreachable.nonEmpty && unreachable.exists { m ⇒ m.status != Down && m.status != Removed - }) { - val seen = gossip.overview.seen - val views = Set.empty[VectorClock] ++ seen.values + } + val allMembersInSeen = gossip.members.forall(m ⇒ seen.contains(m.address)) - if (views.size == 1) { + if (hasUnreachable) { + log.debug("Cluster Node [{}] - No cluster convergence, due to unreachable nodes [{}].", selfAddress, unreachable) + None + } else if (!allMembersInSeen) { + log.debug("Cluster Node [{}] - No cluster convergence, due to members not in seen table [{}].", selfAddress, + gossip.members.map(_.address) -- seen.keySet) + None + } else { + + val views = seen.values.toSet.size + + if (views == 1) { log.debug("Cluster Node [{}] - Cluster convergence reached: [{}]", selfAddress, gossip.members.mkString(", ")) Some(gossip) - } else None - } else None + } else { + log.debug("Cluster Node [{}] - No cluster convergence, since not all nodes have seen the same state yet. [{} of {}]", + selfAddress, views, seen.values.size) + None + } + } } private def isAvailable(state: State): Boolean = !isUnavailable(state) diff --git a/akka-cluster/src/main/scala/akka/cluster/VectorClock.scala b/akka-cluster/src/main/scala/akka/cluster/VectorClock.scala index 82c1b9881d..ed6724058f 100644 --- a/akka-cluster/src/main/scala/akka/cluster/VectorClock.scala +++ b/akka-cluster/src/main/scala/akka/cluster/VectorClock.scala @@ -19,7 +19,7 @@ class VectorClockException(message: String) extends AkkaException(message) */ trait Versioned[T] { def version: VectorClock - def +(node: VectorClock.Node): T + def :+(node: VectorClock.Node): T } /** @@ -142,7 +142,7 @@ case class VectorClock( /** * Increment the version for the node passed as argument. Returns a new VectorClock. */ - def +(node: Node): VectorClock = copy(versions = versions + (node -> Timestamp())) + def :+(node: Node): VectorClock = copy(versions = versions + (node -> Timestamp())) /** * Returns true if this and that are concurrent else false. diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/MembershipChangeListenerJoinSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/MembershipChangeListenerJoinSpec.scala index 2809ae820b..536fb3b58d 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/MembershipChangeListenerJoinSpec.scala +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/MembershipChangeListenerJoinSpec.scala @@ -18,7 +18,7 @@ object MembershipChangeListenerJoinMultiJvmSpec extends MultiNodeConfig { commonConfig( debugConfig(on = false) .withFallback(ConfigFactory.parseString("akka.cluster.leader-actions-interval = 5 s") // increase the leader action task interval to allow time checking for JOIN before leader moves it to UP - .withFallback(MultiNodeClusterSpec.clusterConfig))) + .withFallback(MultiNodeClusterSpec.clusterConfig))) } class MembershipChangeListenerJoinMultiJvmNode1 extends MembershipChangeListenerJoinSpec with FailureDetectorPuppetStrategy @@ -38,16 +38,16 @@ abstract class MembershipChangeListenerJoinSpec runOn(first) { val joinLatch = TestLatch() + val expectedAddresses = Set(firstAddress, secondAddress) cluster.registerListener(new MembershipChangeListener { def notify(members: SortedSet[Member]) { - if (members.size == 2 && members.exists(_.status == MemberStatus.Joining)) // second node is not part of node ring anymore + if (members.map(_.address) == expectedAddresses && members.exists(_.status == MemberStatus.Joining)) joinLatch.countDown() } }) testConductor.enter("registered-listener") joinLatch.await - cluster.convergence.isDefined must be(true) } runOn(second) { @@ -55,6 +55,8 @@ abstract class MembershipChangeListenerJoinSpec cluster.join(firstAddress) } + awaitUpConvergence(2) + testConductor.enter("after") } } diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/MembershipChangeListenerLeavingSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/MembershipChangeListenerLeavingSpec.scala index 57cec4f389..eda29ea0f0 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/MembershipChangeListenerLeavingSpec.scala +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/MembershipChangeListenerLeavingSpec.scala @@ -54,9 +54,11 @@ abstract class MembershipChangeListenerLeavingSpec runOn(third) { val latch = TestLatch() + val expectedAddresses = Set(firstAddress, secondAddress, thirdAddress) cluster.registerListener(new MembershipChangeListener { def notify(members: SortedSet[Member]) { - if (members.size == 3 && members.exists(m ⇒ m.address == secondAddress && m.status == MemberStatus.Leaving)) + if (members.map(_.address) == expectedAddresses && + members.exists(m ⇒ m.address == secondAddress && m.status == MemberStatus.Leaving)) latch.countDown() } }) diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/MembershipChangeListenerUpSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/MembershipChangeListenerUpSpec.scala index c89bbe1f0a..f48f9c8d9b 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/MembershipChangeListenerUpSpec.scala +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/MembershipChangeListenerUpSpec.scala @@ -29,6 +29,7 @@ abstract class MembershipChangeListenerUpSpec lazy val firstAddress = node(first).address lazy val secondAddress = node(second).address + lazy val thirdAddress = node(third).address "A set of connected cluster systems" must { @@ -38,9 +39,10 @@ abstract class MembershipChangeListenerUpSpec runOn(first, second) { val latch = TestLatch() + val expectedAddresses = Set(firstAddress, secondAddress) cluster.registerListener(new MembershipChangeListener { def notify(members: SortedSet[Member]) { - if (members.size == 2 && members.forall(_.status == MemberStatus.Up)) + if (members.map(_.address) == expectedAddresses && members.forall(_.status == MemberStatus.Up)) latch.countDown() } }) @@ -59,9 +61,10 @@ abstract class MembershipChangeListenerUpSpec "(when three nodes) after cluster convergence updates the membership table then all MembershipChangeListeners should be triggered" taggedAs LongRunningTest in { val latch = TestLatch() + val expectedAddresses = Set(firstAddress, secondAddress, thirdAddress) cluster.registerListener(new MembershipChangeListener { def notify(members: SortedSet[Member]) { - if (members.size == 3 && members.forall(_.status == MemberStatus.Up)) + if (members.map(_.address) == expectedAddresses && members.forall(_.status == MemberStatus.Up)) latch.countDown() } }) diff --git a/akka-cluster/src/test/scala/akka/cluster/ClusterSpec.scala b/akka-cluster/src/test/scala/akka/cluster/ClusterSpec.scala index 112da9d0c0..03f6460ea1 100644 --- a/akka-cluster/src/test/scala/akka/cluster/ClusterSpec.scala +++ b/akka-cluster/src/test/scala/akka/cluster/ClusterSpec.scala @@ -110,8 +110,7 @@ class ClusterSpec extends AkkaSpec(ClusterSpec.config) with BeforeAndAfter { cluster.joining(addresses(1)) cluster.latestGossip.members.map(_.address) must be(Set(selfAddress, addresses(1))) memberStatus(addresses(1)) must be(Some(MemberStatus.Joining)) - // FIXME why is it still convergence immediately after joining? - //cluster.convergence.isDefined must be(false) + cluster.convergence.isDefined must be(false) } "accept a few more joining nodes" in { diff --git a/akka-cluster/src/test/scala/akka/cluster/VectorClockSpec.scala b/akka-cluster/src/test/scala/akka/cluster/VectorClockSpec.scala index de1142b668..19ad9410c4 100644 --- a/akka-cluster/src/test/scala/akka/cluster/VectorClockSpec.scala +++ b/akka-cluster/src/test/scala/akka/cluster/VectorClockSpec.scala @@ -27,67 +27,67 @@ class VectorClockSpec extends AkkaSpec { "pass misc comparison test 1" in { val clock1_1 = VectorClock() - val clock2_1 = clock1_1 + Node("1") - val clock3_1 = clock2_1 + Node("2") - val clock4_1 = clock3_1 + Node("1") + val clock2_1 = clock1_1 :+ Node("1") + val clock3_1 = clock2_1 :+ Node("2") + val clock4_1 = clock3_1 :+ Node("1") val clock1_2 = VectorClock() - val clock2_2 = clock1_2 + Node("1") - val clock3_2 = clock2_2 + Node("2") - val clock4_2 = clock3_2 + Node("1") + val clock2_2 = clock1_2 :+ Node("1") + val clock3_2 = clock2_2 :+ Node("2") + val clock4_2 = clock3_2 :+ Node("1") clock4_1 <> clock4_2 must be(false) } "pass misc comparison test 2" in { val clock1_1 = VectorClock() - val clock2_1 = clock1_1 + Node("1") - val clock3_1 = clock2_1 + Node("2") - val clock4_1 = clock3_1 + Node("1") + val clock2_1 = clock1_1 :+ Node("1") + val clock3_1 = clock2_1 :+ Node("2") + val clock4_1 = clock3_1 :+ Node("1") val clock1_2 = VectorClock() - val clock2_2 = clock1_2 + Node("1") - val clock3_2 = clock2_2 + Node("2") - val clock4_2 = clock3_2 + Node("1") - val clock5_2 = clock4_2 + Node("3") + val clock2_2 = clock1_2 :+ Node("1") + val clock3_2 = clock2_2 :+ Node("2") + val clock4_2 = clock3_2 :+ Node("1") + val clock5_2 = clock4_2 :+ Node("3") clock4_1 < clock5_2 must be(true) } "pass misc comparison test 3" in { var clock1_1 = VectorClock() - val clock2_1 = clock1_1 + Node("1") + val clock2_1 = clock1_1 :+ Node("1") val clock1_2 = VectorClock() - val clock2_2 = clock1_2 + Node("2") + val clock2_2 = clock1_2 :+ Node("2") clock2_1 <> clock2_2 must be(true) } "pass misc comparison test 4" in { val clock1_3 = VectorClock() - val clock2_3 = clock1_3 + Node("1") - val clock3_3 = clock2_3 + Node("2") - val clock4_3 = clock3_3 + Node("1") + val clock2_3 = clock1_3 :+ Node("1") + val clock3_3 = clock2_3 :+ Node("2") + val clock4_3 = clock3_3 :+ Node("1") val clock1_4 = VectorClock() - val clock2_4 = clock1_4 + Node("1") - val clock3_4 = clock2_4 + Node("1") - val clock4_4 = clock3_4 + Node("3") + val clock2_4 = clock1_4 :+ Node("1") + val clock3_4 = clock2_4 :+ Node("1") + val clock4_4 = clock3_4 :+ Node("3") clock4_3 <> clock4_4 must be(true) } "pass misc comparison test 5" in { val clock1_1 = VectorClock() - val clock2_1 = clock1_1 + Node("2") - val clock3_1 = clock2_1 + Node("2") + val clock2_1 = clock1_1 :+ Node("2") + val clock3_1 = clock2_1 :+ Node("2") val clock1_2 = VectorClock() - val clock2_2 = clock1_2 + Node("1") - val clock3_2 = clock2_2 + Node("2") - val clock4_2 = clock3_2 + Node("2") - val clock5_2 = clock4_2 + Node("3") + val clock2_2 = clock1_2 :+ Node("1") + val clock3_2 = clock2_2 :+ Node("2") + val clock4_2 = clock3_2 :+ Node("2") + val clock5_2 = clock4_2 :+ Node("3") clock3_1 < clock5_2 must be(true) clock5_2 > clock3_1 must be(true) @@ -95,12 +95,12 @@ class VectorClockSpec extends AkkaSpec { "pass misc comparison test 6" in { val clock1_1 = VectorClock() - val clock2_1 = clock1_1 + Node("1") - val clock3_1 = clock2_1 + Node("2") + val clock2_1 = clock1_1 :+ Node("1") + val clock3_1 = clock2_1 :+ Node("2") val clock1_2 = VectorClock() - val clock2_2 = clock1_2 + Node("1") - val clock3_2 = clock2_2 + Node("1") + val clock2_2 = clock1_2 :+ Node("1") + val clock3_2 = clock2_2 :+ Node("1") clock3_1 <> clock3_2 must be(true) clock3_2 <> clock3_1 must be(true) @@ -108,14 +108,14 @@ class VectorClockSpec extends AkkaSpec { "pass misc comparison test 7" in { val clock1_1 = VectorClock() - val clock2_1 = clock1_1 + Node("1") - val clock3_1 = clock2_1 + Node("2") - val clock4_1 = clock3_1 + Node("2") - val clock5_1 = clock4_1 + Node("3") + val clock2_1 = clock1_1 :+ Node("1") + val clock3_1 = clock2_1 :+ Node("2") + val clock4_1 = clock3_1 :+ Node("2") + val clock5_1 = clock4_1 :+ Node("3") val clock1_2 = VectorClock() - val clock2_2 = clock1_2 + Node("2") - val clock3_2 = clock2_2 + Node("2") + val clock2_2 = clock1_2 :+ Node("2") + val clock3_2 = clock2_2 :+ Node("2") clock5_1 <> clock3_2 must be(true) clock3_2 <> clock5_1 must be(true) @@ -127,14 +127,14 @@ class VectorClockSpec extends AkkaSpec { val node3 = Node("3") val clock1_1 = VectorClock() - val clock2_1 = clock1_1 + node1 - val clock3_1 = clock2_1 + node2 - val clock4_1 = clock3_1 + node2 - val clock5_1 = clock4_1 + node3 + val clock2_1 = clock1_1 :+ node1 + val clock3_1 = clock2_1 :+ node2 + val clock4_1 = clock3_1 :+ node2 + val clock5_1 = clock4_1 :+ node3 val clock1_2 = VectorClock() - val clock2_2 = clock1_2 + node2 - val clock3_2 = clock2_2 + node2 + val clock2_2 = clock1_2 :+ node2 + val clock3_2 = clock2_2 :+ node2 val merged1 = clock3_2 merge clock5_1 merged1.versions.size must be(3) @@ -164,14 +164,14 @@ class VectorClockSpec extends AkkaSpec { val node4 = Node("4") val clock1_1 = VectorClock() - val clock2_1 = clock1_1 + node1 - val clock3_1 = clock2_1 + node2 - val clock4_1 = clock3_1 + node2 - val clock5_1 = clock4_1 + node3 + val clock2_1 = clock1_1 :+ node1 + val clock3_1 = clock2_1 :+ node2 + val clock4_1 = clock3_1 :+ node2 + val clock5_1 = clock4_1 :+ node3 val clock1_2 = VectorClock() - val clock2_2 = clock1_2 + node4 - val clock3_2 = clock2_2 + node4 + val clock2_2 = clock1_2 :+ node4 + val clock3_2 = clock2_2 :+ node4 val merged1 = clock3_2 merge clock5_1 merged1.versions.size must be(4) @@ -204,8 +204,8 @@ class VectorClockSpec extends AkkaSpec { val v1 = VectorClock() val v2 = VectorClock() - val vv1 = v1 + node1 - val vv2 = v2 + node2 + val vv1 = v1 :+ node1 + val vv2 = v2 :+ node2 (vv1 > v1) must equal(true) (vv2 > v2) must equal(true) @@ -225,12 +225,12 @@ class VectorClockSpec extends AkkaSpec { val a = VectorClock() val b = VectorClock() - val a1 = a + node1 - val b1 = b + node2 + val a1 = a :+ node1 + val b1 = b :+ node2 - var a2 = a1 + node1 + var a2 = a1 :+ node1 var c = a2.merge(b1) - var c1 = c + node3 + var c1 = c :+ node3 (c1 > a2) must equal(true) (c1 > b1) must equal(true) @@ -239,7 +239,7 @@ class VectorClockSpec extends AkkaSpec { "An instance of Versioned" must { class TestVersioned(val version: VectorClock = VectorClock()) extends Versioned[TestVersioned] { - def +(node: Node): TestVersioned = new TestVersioned(version + node) + def :+(node: Node): TestVersioned = new TestVersioned(version :+ node) } import Versioned.latestVersionOf @@ -251,67 +251,67 @@ class VectorClockSpec extends AkkaSpec { "happen before an identical versioned with a single additional event" in { val versioned1_1 = new TestVersioned() - val versioned2_1 = versioned1_1 + Node("1") - val versioned3_1 = versioned2_1 + Node("2") - val versioned4_1 = versioned3_1 + Node("1") + val versioned2_1 = versioned1_1 :+ Node("1") + val versioned3_1 = versioned2_1 :+ Node("2") + val versioned4_1 = versioned3_1 :+ Node("1") val versioned1_2 = new TestVersioned() - val versioned2_2 = versioned1_2 + Node("1") - val versioned3_2 = versioned2_2 + Node("2") - val versioned4_2 = versioned3_2 + Node("1") - val versioned5_2 = versioned4_2 + Node("3") + val versioned2_2 = versioned1_2 :+ Node("1") + val versioned3_2 = versioned2_2 :+ Node("2") + val versioned4_2 = versioned3_2 :+ Node("1") + val versioned5_2 = versioned4_2 :+ Node("3") latestVersionOf[TestVersioned](versioned4_1, versioned5_2) must be(versioned5_2) } "pass misc comparison test 1" in { var versioned1_1 = new TestVersioned() - val versioned2_1 = versioned1_1 + Node("1") + val versioned2_1 = versioned1_1 :+ Node("1") val versioned1_2 = new TestVersioned() - val versioned2_2 = versioned1_2 + Node("2") + val versioned2_2 = versioned1_2 :+ Node("2") latestVersionOf[TestVersioned](versioned2_1, versioned2_2) must be(versioned2_2) } "pass misc comparison test 2" in { val versioned1_3 = new TestVersioned() - val versioned2_3 = versioned1_3 + Node("1") - val versioned3_3 = versioned2_3 + Node("2") - val versioned4_3 = versioned3_3 + Node("1") + val versioned2_3 = versioned1_3 :+ Node("1") + val versioned3_3 = versioned2_3 :+ Node("2") + val versioned4_3 = versioned3_3 :+ Node("1") val versioned1_4 = new TestVersioned() - val versioned2_4 = versioned1_4 + Node("1") - val versioned3_4 = versioned2_4 + Node("1") - val versioned4_4 = versioned3_4 + Node("3") + val versioned2_4 = versioned1_4 :+ Node("1") + val versioned3_4 = versioned2_4 :+ Node("1") + val versioned4_4 = versioned3_4 :+ Node("3") latestVersionOf[TestVersioned](versioned4_3, versioned4_4) must be(versioned4_4) } "pass misc comparison test 3" in { val versioned1_1 = new TestVersioned() - val versioned2_1 = versioned1_1 + Node("2") - val versioned3_1 = versioned2_1 + Node("2") + val versioned2_1 = versioned1_1 :+ Node("2") + val versioned3_1 = versioned2_1 :+ Node("2") val versioned1_2 = new TestVersioned() - val versioned2_2 = versioned1_2 + Node("1") - val versioned3_2 = versioned2_2 + Node("2") - val versioned4_2 = versioned3_2 + Node("2") - val versioned5_2 = versioned4_2 + Node("3") + val versioned2_2 = versioned1_2 :+ Node("1") + val versioned3_2 = versioned2_2 :+ Node("2") + val versioned4_2 = versioned3_2 :+ Node("2") + val versioned5_2 = versioned4_2 :+ Node("3") latestVersionOf[TestVersioned](versioned3_1, versioned5_2) must be(versioned5_2) } "pass misc comparison test 4" in { val versioned1_1 = new TestVersioned() - val versioned2_1 = versioned1_1 + Node("1") - val versioned3_1 = versioned2_1 + Node("2") - val versioned4_1 = versioned3_1 + Node("2") - val versioned5_1 = versioned4_1 + Node("3") + val versioned2_1 = versioned1_1 :+ Node("1") + val versioned3_1 = versioned2_1 :+ Node("2") + val versioned4_1 = versioned3_1 :+ Node("2") + val versioned5_1 = versioned4_1 :+ Node("3") val versioned1_2 = new TestVersioned() - val versioned2_2 = versioned1_2 + Node("2") - val versioned3_2 = versioned2_2 + Node("2") + val versioned2_2 = versioned1_2 :+ Node("2") + val versioned3_2 = versioned2_2 :+ Node("2") latestVersionOf[TestVersioned](versioned5_1, versioned3_2) must be(versioned3_2) }