| /* |
| * Copyright 2019 The Android Open Source Project |
| * |
| * Licensed 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. |
| */ |
| |
| @file:OptIn(InternalComposeApi::class) |
| package androidx.compose.runtime |
| |
| import kotlin.random.Random |
| import kotlin.test.Test |
| import kotlin.test.assertEquals |
| import kotlin.test.assertFalse |
| import kotlin.test.assertSame |
| import kotlin.test.assertTrue |
| |
| @OptIn(InternalComposeApi::class) |
| class SlotTableTests { |
| @Test |
| fun testCanCreate() { |
| SlotTable() |
| } |
| |
| @Test |
| fun testIsEmpty() { |
| val slots = SlotTable() |
| assertTrue(slots.isEmpty) |
| slots.write { writer -> |
| writer.beginInsert() |
| writer.startGroup(treeRoot) |
| writer.endGroup() |
| writer.endInsert() |
| } |
| assertFalse(slots.isEmpty) |
| } |
| |
| @Test |
| fun testCanInsert() { |
| val slots = SlotTable() |
| slots.write { writer -> |
| writer.beginInsert() |
| writer.startGroup(37) |
| writer.update(1) |
| writer.endGroup() |
| writer.endInsert() |
| } |
| slots.verifyWellFormed() |
| } |
| |
| @Test |
| fun testValidateSlots() { |
| val slots = testSlotsNumbered() |
| slots.verifyWellFormed() |
| slots.read { reader -> |
| reader.startGroup() |
| repeat(100) { |
| assertEquals(it, reader.groupKey) |
| reader.skipGroup() |
| } |
| reader.endGroup() |
| } |
| } |
| |
| @Test |
| fun testInsertAtTheStart() { |
| val slots = testSlotsNumbered() |
| slots.verifyWellFormed() |
| slots.write { writer -> |
| writer.startGroup() |
| writer.beginInsert() |
| writer.startGroup(-100) |
| writer.endGroup() |
| writer.endInsert() |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| slots.verifyWellFormed() |
| slots.read { reader -> |
| reader.startGroup() |
| assertEquals(-100, reader.groupKey) |
| reader.skipGroup() |
| repeat(100) { |
| assertEquals(it, reader.groupKey) |
| reader.skipGroup() |
| } |
| reader.endGroup() |
| } |
| } |
| |
| @Test |
| fun testInsertAtTheEnd() { |
| val slots = testSlotsNumbered() |
| slots.write { writer -> |
| writer.startGroup() |
| writer.skipToGroupEnd() |
| writer.beginInsert() |
| writer.startGroup(-100) |
| writer.endGroup() |
| writer.endInsert() |
| writer.endGroup() |
| } |
| slots.verifyWellFormed() |
| slots.read { reader -> |
| reader.startGroup() |
| repeat(100) { |
| assertEquals(it, reader.groupKey) |
| reader.skipGroup() |
| } |
| assertEquals(-100, reader.groupKey) |
| reader.skipGroup() |
| reader.endGroup() |
| } |
| } |
| |
| @Test |
| fun testInsertInTheMiddle() { |
| val slots = testSlotsNumbered() |
| val seekAmount = slots.read { reader -> |
| reader.startGroup() |
| val start = reader.currentGroup |
| repeat(50) { |
| reader.skipGroup() |
| } |
| reader.currentGroup - start |
| } |
| slots.write { writer -> |
| writer.startGroup() |
| writer.advanceBy(seekAmount) |
| writer.beginInsert() |
| writer.startGroup(-100) |
| writer.endGroup() |
| writer.endInsert() |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| slots.verifyWellFormed() |
| slots.read { reader -> |
| reader.startGroup() |
| repeat(50) { reader.skipGroup() } |
| assertEquals(-100, reader.groupKey) |
| reader.skipToGroupEnd() |
| reader.endGroup() |
| } |
| } |
| |
| @Test |
| fun testRemoveAtTheStart() { |
| val slots = testSlotsNumbered() |
| slots.write { writer -> |
| writer.startGroup() |
| repeat(50) { writer.removeGroup() } |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| slots.verifyWellFormed() |
| slots.read { reader -> |
| reader.startGroup() |
| for (i in 50 until 100) { |
| assertEquals(i, reader.groupKey) |
| reader.skipGroup() |
| } |
| reader.endGroup() |
| } |
| } |
| |
| @Test |
| fun testRemoveAtTheEnd() { |
| val slots = testSlotsNumbered() |
| slots.write { writer -> |
| writer.startGroup() |
| repeat(50) { writer.skipGroup() } |
| repeat(50) { writer.removeGroup() } |
| writer.endGroup() |
| } |
| slots.verifyWellFormed() |
| slots.read { reader -> |
| reader.startGroup() |
| repeat(50) { |
| assertEquals(it, reader.groupKey) |
| reader.skipGroup() |
| } |
| reader.endGroup() |
| } |
| } |
| |
| @Test |
| fun testRemoveInTheMiddle() { |
| val slots = testSlotsNumbered() |
| slots.write { writer -> |
| writer.startGroup() |
| repeat(25) { writer.skipGroup() } |
| repeat(50) { writer.removeGroup() } |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| slots.verifyWellFormed() |
| slots.read { reader -> |
| reader.startGroup() |
| for (i in 0 until 25) { |
| assertEquals(i, reader.groupKey) |
| reader.skipGroup() |
| } |
| for (i in 75 until 100) { |
| assertEquals(i, reader.groupKey) |
| reader.skipGroup() |
| } |
| reader.endGroup() |
| } |
| } |
| |
| @Test |
| fun testRemoveTwoSlices() { |
| val slots = testSlotsNumbered() |
| slots.write { writer -> |
| writer.startGroup() |
| repeat(40) { writer.skipGroup() } |
| repeat(10) { writer.removeGroup() } |
| repeat(20) { writer.skipGroup() } |
| repeat(10) { writer.removeGroup() } |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| slots.read { reader -> |
| reader.startGroup() |
| for (i in 0 until 40) { |
| assertEquals(i, reader.groupKey) |
| reader.skipGroup() |
| } |
| for (i in 50 until 70) { |
| assertEquals(i, reader.groupKey) |
| reader.skipGroup() |
| } |
| for (i in 80 until 100) { |
| assertEquals(i, reader.groupKey) |
| reader.skipGroup() |
| } |
| reader.endGroup() |
| } |
| } |
| |
| // Anchor tests |
| |
| @Test |
| fun testAllocateAnchors() { |
| val slots = testSlotsNumbered() |
| val anchors = slots.read { reader -> |
| val anchors = mutableListOf<Anchor>() |
| reader.startGroup() |
| repeat(7) { |
| repeat(10) { reader.skipGroup() } |
| anchors.add(reader.anchor()) |
| } |
| reader.skipToGroupEnd() |
| reader.endGroup() |
| anchors |
| } |
| slots.read { reader -> |
| anchors.forEachIndexed { index, anchor -> |
| val key = reader.groupKey(anchor.toIndexFor(slots)) |
| assertEquals((index + 1) * 10, key) |
| } |
| } |
| } |
| |
| @Test |
| fun testAnchorsTrackInserts() { |
| val slots = testSlotsNumbered() |
| val anchors = slots.read { reader -> |
| val anchors = mutableListOf<Anchor>() |
| reader.startGroup() |
| repeat(7) { |
| repeat(10) { reader.skipGroup() } |
| anchors.add(reader.anchor()) |
| } |
| reader.skipToGroupEnd() |
| reader.endGroup() |
| anchors |
| } |
| slots.write { writer -> |
| writer.startGroup() |
| repeat(41) { writer.skipGroup() } |
| writer.beginInsert() |
| repeat(30) { |
| writer.startGroup(-100 - it) |
| writer.endGroup() |
| } |
| writer.endInsert() |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| slots.read { reader -> |
| for (index in 1..7) { |
| val anchor = anchors[index - 1] |
| assertEquals(index * 10, reader.groupKey(anchor)) |
| } |
| } |
| } |
| |
| @Test |
| fun testEmptySlotTableAnchorAtNegativeOneStaysNegativeOne() { |
| val slots = SlotTable() |
| val anchor = slots.read { reader -> reader.anchor(-1) } |
| assertEquals(-1, anchor.toIndexFor(slots)) |
| slots.write { writer -> |
| writer.beginInsert() |
| writer.startGroup(treeRoot) |
| writer.startGroup(-100) |
| writer.endGroup() |
| writer.endGroup() |
| writer.endInsert() |
| } |
| assertEquals(-1, anchor.toIndexFor(slots)) |
| } |
| |
| @Test |
| fun testAnchorTracksExactRemovesUpwards() { |
| val slots = testSlotsNumbered() |
| val anchors = slots.read { reader -> (1..7).map { reader.anchor(it * 10 + 1) } } |
| slots.write { writer -> |
| writer.startGroup() |
| writer.skipGroup() |
| for (index in 1..7) { |
| writer.advanceBy(9) |
| val removedAnchors = writer.removeGroup() |
| assertTrue(removedAnchors) |
| assertFalse(anchors[index - 1].valid) |
| } |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| slots.verifyWellFormed() |
| } |
| |
| @Test |
| fun testAnchorTrackRemoves() { |
| val slots = testSlotsNumbered() |
| val anchors = slots.read { reader -> (1..7).map { reader.anchor(it * 10 + 1) } } |
| slots.write { writer -> |
| writer.startGroup() |
| writer.advanceBy(40) |
| repeat(20) { writer.removeGroup() } |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| slots.verifyWellFormed() |
| slots.read { reader -> |
| for (index in 1..7) { |
| val anchor = anchors[index - 1] |
| val expected = (index * 10).let { |
| if (it in 40 until 60) 0 else it |
| } |
| assertEquals(expected, reader.groupKey(anchor)) |
| } |
| } |
| } |
| |
| @Test |
| fun testAnchorMoves() { |
| val slots = SlotTable() |
| |
| fun buildSlots(range: List<Int>): Map<Anchor, Any?> { |
| val anchors = mutableMapOf<Anchor, Any?>() |
| slots.write { writer -> |
| fun item(value: Int, block: () -> Unit) { |
| writer.startGroup(value) |
| block() |
| writer.endGroup() |
| } |
| |
| fun element(key: Int) { |
| writer.startGroup(key) |
| writer.endGroup() |
| } |
| |
| writer.beginInsert() |
| writer.startGroup(treeRoot) |
| for (i in range) { |
| item(i) { |
| val key = i * 100 |
| anchors[writer.anchor()] = key |
| element(key) |
| } |
| } |
| writer.endGroup() |
| writer.endInsert() |
| } |
| slots.verifyWellFormed() |
| return anchors |
| } |
| |
| fun validate(anchors: Map<Anchor, Any?>) { |
| slots.verifyWellFormed() |
| slots.read { reader -> |
| for (anchor in anchors) { |
| assertEquals(anchor.value, reader.groupKey(anchor.key)) |
| } |
| } |
| } |
| |
| fun moveItems() { |
| slots.write { writer -> |
| writer.startGroup() |
| writer.skipGroup() |
| writer.moveGroup(4) |
| writer.skipGroup() |
| writer.skipGroup() |
| writer.moveGroup(1) |
| writer.skipGroup() |
| writer.moveGroup(1) |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| } |
| |
| val expected = listOf(1, 2, 3, 4, 5, 6, 7) |
| val anchors = buildSlots(expected) |
| validate(anchors) |
| moveItems() |
| validate(anchors) |
| } |
| |
| @Test |
| fun testRemovingDuplicateAnchorsMidRange() { |
| val slots = testSlotsNumbered() |
| |
| val anchors = slots.read { reader -> (0 until 10).map { reader.anchor(30) } } |
| slots.write { writer -> |
| writer.startGroup() |
| repeat(20) { writer.skipGroup() } |
| repeat(20) { writer.removeGroup() } |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| for (anchor in anchors) { |
| assertFalse(anchor.valid) |
| } |
| } |
| |
| @Test |
| fun testRemovingDuplicateAnchorsStartRange() { |
| val slots = testSlotsNumbered() |
| val anchors = slots.read { reader -> (0 until 10).map { reader.anchor(30) } } |
| slots.write { writer -> |
| writer.startGroup() |
| repeat(29) { writer.skipGroup() } |
| repeat(30) { writer.removeGroup() } |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| for (anchor in anchors) { |
| assertFalse(anchor.valid) |
| } |
| } |
| |
| @Test |
| fun testRemovingDuplicateAnchorsEndRange() { |
| val slots = testSlotsNumbered() |
| val anchors = slots.read { reader -> (0 until 10).map { reader.anchor(30) } } |
| slots.write { writer -> |
| writer.startGroup() |
| repeat(19) { writer.skipGroup() } |
| repeat(11) { writer.removeGroup() } |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| for (anchor in anchors) { |
| assertFalse(anchor.valid) |
| } |
| } |
| |
| @Test |
| fun testDuplicateAnchorIdentity() { |
| val slots = testSlotsNumbered() |
| val anchors = slots.read { reader -> (0 until 10).map { reader.anchor(it * 5) } } |
| slots.read { reader -> |
| anchors.forEachIndexed { index, anchor -> |
| assertSame(anchor, reader.anchor(index * 5)) |
| } |
| } |
| } |
| |
| // Group with slots test |
| |
| @Test |
| fun testEmptySlotTable() { |
| val slotTable = SlotTable() |
| slotTable.verifyWellFormed() |
| |
| slotTable.read { reader -> |
| assertEquals(0, reader.groupKey) |
| } |
| } |
| |
| @Test |
| fun testTestItems() { |
| val slots = testItems() |
| slots.verifyWellFormed() |
| validateItems(slots) |
| } |
| |
| @Test |
| fun testExtractKeys() { |
| val slots = testItems() |
| slots.verifyWellFormed() |
| val expectedLocations = mutableListOf<Int>() |
| val expectedNodes = mutableListOf<Int>() |
| slots.read { reader -> |
| reader.startGroup() |
| while (!reader.isGroupEnd) { |
| expectedLocations.add(reader.currentGroup) |
| expectedNodes.add(if (reader.isNode) 1 else reader.nodeCount) |
| reader.skipGroup() |
| } |
| reader.endGroup() |
| } |
| slots.read { reader -> |
| reader.startGroup() |
| val keys = reader.extractKeys() |
| assertEquals(10, keys.size) |
| keys.forEachIndexed { i, keyAndLocation -> |
| assertEquals(i + 1, keyAndLocation.key) |
| assertEquals(i + 1, keyAndLocation.objectKey) |
| assertEquals(expectedLocations[i], keyAndLocation.location) |
| assertEquals(expectedNodes[i], keyAndLocation.nodes) |
| assertEquals(i, keyAndLocation.index) |
| } |
| } |
| } |
| |
| @Test |
| fun testInsertAnItem() { |
| val slots = testItems() |
| slots.write { writer -> |
| writer.startGroup(treeRoot) |
| writer.skipGroup() |
| writer.beginInsert() |
| writer.startGroup(1000) |
| writer.update(10) |
| writer.update(20) |
| writer.endGroup() |
| writer.endInsert() |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| slots.verifyWellFormed() |
| slots.read { reader -> |
| reader.startGroup() |
| reader.skipGroup() |
| assertEquals(1000, reader.groupKey) |
| reader.startGroup() |
| assertEquals(10, reader.next()) |
| assertEquals(20, reader.next()) |
| reader.endGroup() |
| reader.skipToGroupEnd() |
| reader.endGroup() |
| } |
| } |
| |
| @Test |
| fun removeAnItem() { |
| val slots = testItems() |
| slots.write { writer -> |
| writer.startGroup() |
| writer.skipGroup() |
| writer.removeGroup() |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| slots.verifyWellFormed() |
| } |
| |
| @Test |
| fun testMoveAnItem() { |
| val slots = testItems() |
| slots.write { writer -> |
| writer.startGroup(treeRoot) |
| writer.skipGroup() |
| writer.moveGroup(4) |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| slots.verifyWellFormed() |
| slots.read { reader -> |
| reader.startGroup() |
| reader.expectGroup(1) |
| reader.expectGroup(6) |
| reader.expectGroup(2) |
| reader.expectGroup(3) |
| reader.expectGroup(4) |
| reader.expectGroup(5) |
| reader.expectGroup(7) |
| reader.expectGroup(8) |
| reader.expectGroup(9) |
| reader.expectGroup(10) |
| reader.endGroup() |
| } |
| } |
| |
| @Test |
| fun testCountNodes() { |
| val slots = testItems() |
| slots.read { reader -> |
| reader.startGroup() |
| for (i in 1..10) { |
| val count = reader.expectGroup(i) |
| assertEquals(i + 1, count) |
| } |
| reader.endGroup() |
| } |
| } |
| |
| @Test |
| fun testCountNestedNodes() { |
| val slots = SlotTable() |
| slots.write { writer -> |
| writer.beginInsert() |
| writer.startGroup(treeRoot) |
| writer.startGroup(0) |
| repeat(10) { |
| writer.startGroup(0) |
| repeat(3) { |
| writer.startNode(1, 1) |
| writer.endGroup() |
| } |
| assertEquals(3, writer.endGroup()) |
| } |
| assertEquals(30, writer.endGroup()) |
| writer.endGroup() |
| writer.endInsert() |
| } |
| slots.verifyWellFormed() |
| |
| slots.read { reader -> |
| reader.startGroup() |
| assertEquals(30, reader.expectGroup(0)) |
| reader.endGroup() |
| } |
| } |
| |
| @Test |
| fun testUpdateNestedNodeCountOnInsert() { |
| val slots = SlotTable() |
| slots.write { writer -> |
| writer.beginInsert() |
| writer.startGroup(treeRoot) |
| writer.startGroup(0) |
| repeat(10) { |
| writer.startGroup(0) |
| repeat(3) { |
| writer.startGroup(0) |
| writer.startNode(1, 1) |
| writer.endGroup() |
| assertEquals(1, writer.endGroup()) |
| } |
| assertEquals(3, writer.endGroup()) |
| } |
| assertEquals(30, writer.endGroup()) |
| writer.endGroup() |
| writer.endInsert() |
| } |
| slots.verifyWellFormed() |
| |
| slots.write { writer -> |
| writer.startGroup() |
| writer.startGroup() |
| |
| repeat(3) { |
| assertEquals(3, writer.skipGroup()) |
| } |
| |
| writer.startGroup() |
| writer.beginInsert() |
| repeat(2) { |
| writer.startGroup(-100) |
| writer.startNode(1, 1) |
| writer.endGroup() |
| assertEquals(1, writer.endGroup()) |
| } |
| writer.endInsert() |
| repeat(3) { writer.skipGroup() } |
| assertEquals(5, writer.endGroup()) |
| |
| repeat(6) { |
| assertEquals(3, writer.skipGroup()) |
| } |
| |
| assertEquals(32, writer.endGroup()) |
| writer.endGroup() |
| } |
| slots.verifyWellFormed() |
| } |
| |
| @Test |
| fun testUpdateNestedNodeCountOnRemove() { |
| val slots = SlotTable() |
| slots.write { writer -> |
| writer.beginInsert() |
| writer.startGroup(treeRoot) |
| writer.startGroup(0) |
| repeat(10) { |
| writer.startGroup(0) |
| repeat(3) { |
| writer.startGroup(0) |
| writer.startNode(1, 1) |
| writer.endGroup() |
| assertEquals(1, writer.endGroup()) |
| } |
| assertEquals(3, writer.endGroup()) |
| } |
| assertEquals(30, writer.endGroup()) |
| writer.endGroup() |
| writer.endInsert() |
| } |
| slots.verifyWellFormed() |
| |
| slots.write { writer -> |
| writer.startGroup(treeRoot) |
| writer.startGroup(0) |
| |
| repeat(3) { |
| assertEquals(3, writer.skipGroup()) |
| } |
| |
| writer.startGroup(0) |
| |
| repeat(2) { writer.removeGroup() } |
| repeat(1) { writer.skipGroup() } |
| assertEquals(1, writer.endGroup()) |
| |
| repeat(6) { |
| assertEquals(3, writer.skipGroup()) |
| } |
| |
| assertEquals(28, writer.endGroup()) |
| |
| writer.endGroup() |
| } |
| slots.verifyWellFormed() |
| } |
| |
| @Test |
| fun testNodesResetNodeCount() { |
| val slots = SlotTable() |
| slots.write { writer -> |
| writer.beginInsert() |
| writer.startGroup(treeRoot) |
| writer.startGroup(0) |
| writer.startNode(1, 1) |
| repeat(10) { |
| writer.startNode(1, 1) |
| writer.startGroup(0) |
| repeat(3) { |
| writer.startNode(1, 1) |
| writer.endGroup() |
| } |
| assertEquals(3, writer.endGroup()) |
| writer.endGroup() |
| } |
| writer.endGroup() |
| assertEquals(1, writer.endGroup()) |
| writer.endGroup() |
| writer.endInsert() |
| } |
| slots.verifyWellFormed() |
| } |
| |
| @Test |
| fun testSkipANode() { |
| val slots = SlotTable() |
| slots.write { writer -> |
| writer.beginInsert() |
| writer.startGroup(treeRoot) |
| writer.startGroup(0) |
| writer.startNode(1, 1) |
| repeat(10) { |
| writer.startNode(1, 1) |
| writer.startGroup(0) |
| repeat(3) { |
| writer.startNode(1, 1) |
| writer.endGroup() |
| } |
| assertEquals(3, writer.endGroup()) |
| writer.endGroup() |
| } |
| writer.endGroup() |
| assertEquals(1, writer.endGroup()) |
| writer.endGroup() |
| writer.endInsert() |
| } |
| slots.verifyWellFormed() |
| |
| slots.read { reader -> |
| reader.startGroup() |
| reader.startGroup() |
| assertEquals(1, reader.skipGroup()) |
| reader.endGroup() |
| reader.endGroup() |
| } |
| } |
| |
| @Test |
| fun testStartEmpty() { |
| val slots = SlotTable() |
| slots.read { reader -> |
| reader.beginEmpty() |
| reader.startGroup() |
| assertEquals(true, reader.inEmpty) |
| assertEquals(Composer.Empty, reader.next()) |
| reader.endGroup() |
| reader.endEmpty() |
| } |
| } |
| |
| @Test |
| fun testReportGroupSize() { |
| val slots = SlotTable() |
| slots.write { writer -> |
| writer.beginInsert() |
| writer.startGroup(treeRoot) |
| writer.startGroup(0) |
| repeat(10) { |
| writer.startNode(1, 1) |
| writer.endGroup() |
| } |
| writer.endGroup() |
| writer.startGroup(705) |
| writer.update(42) |
| writer.endGroup() |
| writer.endGroup() |
| writer.endInsert() |
| } |
| slots.verifyWellFormed() |
| |
| slots.read { reader -> |
| reader.startGroup() |
| val size = reader.groupSize |
| val savedCurrent = reader.currentGroup |
| reader.skipGroup() |
| assertEquals(size, reader.currentGroup - savedCurrent) |
| reader.startGroup() |
| assertEquals(42, reader.next()) |
| assertTrue(reader.isGroupEnd) |
| reader.endGroup() |
| assertTrue(reader.isGroupEnd) |
| reader.endGroup() |
| } |
| } |
| |
| @Test |
| fun testMoveGroup() { |
| val slots = SlotTable() |
| |
| val anchors = mutableListOf<Anchor>() |
| |
| fun buildSlots() { |
| slots.write { writer -> |
| fun item(key: Int, block: () -> Unit) { |
| writer.startGroup(key, key) |
| block() |
| writer.endGroup() |
| } |
| |
| fun element(key: Int, block: () -> Unit) { |
| writer.startNode(key, key) |
| block() |
| writer.endGroup() |
| } |
| |
| fun value(value: Any) { |
| writer.update(value) |
| } |
| |
| fun innerItem(i: Int) { |
| item(i) { |
| value(i) |
| value(25) |
| item(26) { |
| item(28) { |
| value(30) |
| item(31) { |
| item(33) { |
| item(35) { |
| value(36) |
| item(37) { |
| value(39) |
| element(40) { |
| value(42) |
| value(43) |
| element(44) { |
| value(46) |
| value(47) |
| } |
| element(48) { |
| value(50) |
| value(51) |
| value(52) |
| value(53) |
| } |
| element(54) { |
| value(56) |
| value(57) |
| value(58) |
| value(59) |
| } |
| anchors.add(writer.anchor()) |
| element(60) { |
| value(62) |
| value(63) |
| value(64) |
| value(65) |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // Build a slot table to that duplicates the structure of the slot table produced |
| // in the code generation test testMovement() |
| writer.beginInsert() |
| item(0) { |
| item(2) { |
| item(4) { |
| item(6) { |
| value(8) |
| item(9) { |
| item(11) { |
| item(12) { |
| value(14) |
| item(15) { |
| value(17) |
| element(18) { |
| value(20) |
| value(21) |
| for (i in 1..5) { |
| innerItem(i) |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| writer.endInsert() |
| } |
| } |
| |
| fun validateSlots(range: List<Int>) { |
| slots.verifyWellFormed() |
| slots.read { reader -> |
| fun value(value: Any?) { |
| assertEquals(value, reader.next()) |
| } |
| |
| fun item(key: Int, block: () -> Unit) { |
| assertEquals(reader.groupKey, key) |
| assertEquals(reader.groupObjectKey, key) |
| reader.startGroup() |
| block() |
| reader.endGroup() |
| } |
| |
| fun element(key: Int, block: () -> Unit) { |
| assertEquals(reader.groupObjectKey, key) |
| reader.startNode() |
| block() |
| reader.endGroup() |
| } |
| |
| fun innerBlock(i: Int) { |
| item(i) { |
| value(i) |
| value(25) |
| item(26) { |
| item(28) { |
| value(30) |
| item(31) { |
| item(33) { |
| item(35) { |
| value(36) |
| item(37) { |
| value(39) |
| element(40) { |
| value(42) |
| value(43) |
| element(44) { |
| value(46) |
| value(47) |
| } |
| element(48) { |
| value(50) |
| value(51) |
| value(52) |
| value(53) |
| } |
| element(54) { |
| value(56) |
| value(57) |
| value(58) |
| value(59) |
| } |
| element(60) { |
| value(62) |
| value(63) |
| value(64) |
| value(65) |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| item(0) { |
| item(2) { |
| item(4) { |
| item(6) { |
| value(8) |
| item(9) { |
| item(11) { |
| item(12) { |
| value(14) |
| item(15) { |
| value(17) |
| element(18) { |
| value(20) |
| value(21) |
| for (i in range) { |
| innerBlock(i) |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| fun moveItem5Up() { |
| slots.write { writer -> |
| fun item(key: Int, block: () -> Unit) { |
| writer.startGroup(key, key) |
| block() |
| writer.endGroup() |
| } |
| |
| fun element(key: Int, block: () -> Unit) { |
| writer.startNode(key, key) |
| block() |
| writer.endGroup() |
| } |
| |
| fun value(value: Any) { |
| writer.update(value) |
| } |
| item(0) { |
| item(2) { |
| item(4) { |
| item(6) { |
| value(8) |
| item(9) { |
| item(11) { |
| item(12) { |
| value(14) |
| item(15) { |
| value(17) |
| element(18) { |
| value(20) |
| value(21) |
| |
| // Skip three items |
| writer.skipGroup() |
| writer.skipGroup() |
| writer.skipGroup() |
| |
| // Move one item up |
| writer.moveGroup(1) |
| |
| // Skip them |
| writer.skipGroup() |
| writer.skipGroup() |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| buildSlots() |
| validateSlots((1..5).toList()) |
| moveItem5Up() |
| validateSlots(listOf(1, 2, 3, 5, 4)) |
| |
| // Validate tha the anchors still refer to a slot with value 62 |
| slots.read { reader -> |
| for (anchor in anchors) { |
| assertEquals(60, reader.groupObjectKey(anchor.toIndexFor(slots))) |
| } |
| } |
| } |
| |
| @Test |
| fun testValidateSlotTableIndexes() { |
| val (slots, _) = narrowTrees() |
| slots.verifyWellFormed() |
| } |
| |
| @Test |
| fun testRemoveRandomGroup() { |
| val (slots, anchors) = narrowTrees() |
| slots.verifyWellFormed() |
| val random = Random(1000) |
| val slotsToRemove = anchors.shuffled(random) |
| slotsToRemove.forEach { anchor -> |
| if (anchor.valid) { |
| slots.write { writer -> |
| writer.startGroup(treeRoot) |
| |
| // Skip to the group location |
| writer.seek(anchor) |
| |
| // Ensure parent is started. |
| writer.ensureStarted(writer.parent(anchor)) |
| |
| // Remove the group. |
| writer.removeGroup() |
| |
| // Close the parent |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| |
| // Close the root |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| slots.verifyWellFormed() |
| } |
| } |
| } |
| |
| @Test |
| fun testMovingEntireTable() { |
| val (sourceTable, _) = narrowTrees() |
| val destinationTable = SlotTable() |
| val groupsSize = sourceTable.groupsSize |
| val slotsSize = sourceTable.slotsSize |
| destinationTable.write { writer -> |
| writer.beginInsert() |
| writer.moveFrom(sourceTable, 0) |
| writer.endInsert() |
| } |
| sourceTable.verifyWellFormed() |
| destinationTable.verifyWellFormed() |
| assertEquals(0, sourceTable.groupsSize) |
| assertEquals(0, sourceTable.slotsSize) |
| assertEquals(groupsSize, destinationTable.groupsSize) |
| assertEquals(slotsSize, destinationTable.slotsSize) |
| } |
| |
| @Test |
| fun testMovingOneGroup() { |
| val sourceTable = SlotTable() |
| val anchors = mutableListOf<Anchor>() |
| sourceTable.write { writer -> |
| writer.beginInsert() |
| anchors.add(writer.anchor()) |
| writer.startGroup(10) |
| writer.update(100) |
| writer.update(200) |
| writer.endGroup() |
| writer.endInsert() |
| } |
| sourceTable.verifyWellFormed() |
| |
| val destinationTable = SlotTable() |
| destinationTable.write { writer -> |
| writer.beginInsert() |
| writer.startGroup(treeRoot) |
| writer.startGroup(1000) |
| writer.endGroup() |
| writer.endGroup() |
| writer.endInsert() |
| } |
| destinationTable.verifyWellFormed() |
| |
| destinationTable.write { writer -> |
| writer.startGroup() |
| writer.startGroup() |
| writer.beginInsert() |
| writer.moveFrom( |
| sourceTable, |
| anchors.first().toIndexFor(sourceTable) |
| ) |
| writer.endInsert() |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| destinationTable.verifyWellFormed() |
| destinationTable.read { reader -> |
| assertEquals(10, reader.groupKey(anchors.first())) |
| } |
| sourceTable.verifyWellFormed() |
| } |
| |
| @Test |
| fun testMovingANodeGroup() { |
| val sourceTable = SlotTable() |
| val anchors = mutableListOf<Anchor>() |
| sourceTable.write { writer -> |
| writer.beginInsert() |
| anchors.add(writer.anchor()) |
| writer.startNode(10, 10) |
| writer.update(100) |
| writer.update(200) |
| writer.endGroup() |
| writer.endInsert() |
| } |
| sourceTable.verifyWellFormed() |
| |
| val destinationTable = SlotTable() |
| destinationTable.write { writer -> |
| writer.beginInsert() |
| writer.startGroup(treeRoot) |
| writer.startGroup(1000) |
| writer.endGroup() |
| writer.endGroup() |
| writer.endInsert() |
| } |
| destinationTable.verifyWellFormed() |
| |
| destinationTable.write { writer -> |
| writer.startGroup() |
| writer.startGroup() |
| writer.beginInsert() |
| writer.moveFrom( |
| sourceTable, |
| anchors.first().toIndexFor(sourceTable) |
| ) |
| writer.endInsert() |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| destinationTable.verifyWellFormed() |
| destinationTable.read { reader -> |
| val anchor = anchors.first() |
| assertEquals(125, reader.groupKey(anchor)) |
| assertEquals(10, reader.groupObjectKey(anchor.toIndexFor(destinationTable))) |
| } |
| sourceTable.verifyWellFormed() |
| } |
| |
| @Test |
| fun testMovingMultipleRootGroups() { |
| val sourceTable = SlotTable() |
| val anchors = mutableListOf<Anchor>() |
| val moveCount = 5 |
| sourceTable.write { writer -> |
| writer.beginInsert() |
| repeat(moveCount) { |
| anchors.add(writer.anchor()) |
| writer.startGroup(10) |
| writer.update(100) |
| writer.update(200) |
| writer.endGroup() |
| } |
| writer.endInsert() |
| } |
| sourceTable.verifyWellFormed() |
| |
| val destinationTable = SlotTable() |
| destinationTable.write { writer -> |
| writer.beginInsert() |
| writer.startGroup(treeRoot) |
| writer.startGroup(1000) |
| writer.endGroup() |
| writer.endGroup() |
| writer.endInsert() |
| } |
| destinationTable.verifyWellFormed() |
| |
| destinationTable.write { writer -> |
| writer.startGroup() |
| writer.startGroup() |
| writer.beginInsert() |
| writer.moveFrom( |
| sourceTable, |
| anchors.first().toIndexFor(sourceTable) |
| ) |
| writer.endInsert() |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| destinationTable.verifyWellFormed() |
| destinationTable.read { reader -> |
| assertEquals(10, reader.groupKey(anchors.first())) |
| } |
| sourceTable.verifyWellFormed() |
| } |
| |
| @Test |
| fun testMovingGroups() { |
| val random = Random(1116) |
| val (sourceTable, sourceAnchors) = narrowTrees() |
| val destinationTable = SlotTable() |
| |
| destinationTable.write { writer -> |
| writer.beginInsert() |
| writer.startGroup(treeRoot) |
| |
| writer.startGroup(1122) |
| writer.endGroup() |
| |
| writer.endGroup() |
| writer.endInsert() |
| } |
| |
| val slotsToMove = sourceAnchors.shuffled(random) |
| val slotKeys = sourceTable.read { reader -> |
| slotsToMove.map { anchor -> |
| val location = anchor.toIndexFor(sourceTable) |
| reader.groupKey(location) |
| } |
| } |
| |
| val movedAnchors = mutableSetOf<Anchor>() |
| slotsToMove.forEach { anchor -> |
| if (anchor !in movedAnchors) { |
| destinationTable.write { writer -> |
| writer.startGroup(treeRoot) |
| writer.startGroup(1122) |
| |
| writer.skipToGroupEnd() |
| writer.beginInsert() |
| movedAnchors += writer.moveFrom( |
| sourceTable, |
| anchor.toIndexFor(sourceTable) |
| ) |
| sourceTable.verifyWellFormed() |
| writer.verifyDataAnchors() |
| writer.endInsert() |
| |
| writer.endGroup() |
| writer.endGroup() |
| } |
| |
| // Both the source and destinations should be well-formed. |
| destinationTable.verifyWellFormed() |
| sourceTable.verifyWellFormed() |
| } |
| } |
| |
| // Verify the anchors still point to the correct groups |
| val movedKeys = destinationTable.read { reader -> |
| slotsToMove.map { anchor -> |
| val location = anchor.toIndexFor(destinationTable) |
| reader.groupKey(location) |
| } |
| } |
| assertEquals(slotKeys.size, movedKeys.size, "slot keys changed") |
| for (index in slotKeys.indices) { |
| val sourceKey = slotKeys[index] |
| val movedKey = movedKeys[index] |
| assertEquals(sourceKey, movedKey, "Key at $index changed") |
| } |
| } |
| |
| @Test |
| fun testToIndexFor() { |
| val (slots, anchors) = narrowTrees() |
| val indexes = anchors.map { it.toIndexFor(slots) } |
| slots.write { writer -> |
| indexes.forEachIndexed { i, index -> |
| assertEquals(index, anchors[i].toIndexFor(writer)) |
| } |
| } |
| } |
| |
| @Test |
| fun testReaderGroupSize() { |
| val slots = testItems() |
| slots.read { reader -> |
| fun testGroup(index: Int, expectedParent: Int): Int { |
| assertEquals(expectedParent, reader.parentOf(index)) |
| val size = reader.groupSize(index) |
| var child = index + 1 |
| val end = index + size |
| while (child < end) { |
| child += testGroup(child, index) |
| } |
| return size |
| } |
| testGroup(0, -1) |
| } |
| } |
| |
| @Test |
| fun testWriterGroupSize() { |
| val slots = testItems() |
| slots.write { writer -> |
| fun testGroup(index: Int): Int { |
| val size = writer.groupSize(index) |
| var child = index + 1 |
| val end = index + size |
| while (child < end) { |
| child += testGroup(child) |
| } |
| return size |
| } |
| testGroup(0) |
| } |
| } |
| |
| @Test |
| fun testReaderParentNodes() { |
| val slots = testItems() |
| slots.read { reader -> |
| fun testGroup(): Pair<Int, Int> { |
| val isNode = reader.isNode |
| reader.startGroup() |
| var childNodes = 0 |
| var expectedNodes = 0 |
| while (!reader.isGroupEnd) { |
| val (groupNodes, parentNodes) = testGroup() |
| childNodes += groupNodes |
| if (expectedNodes > 0) { |
| assertEquals(expectedNodes, parentNodes) |
| } else { |
| expectedNodes = parentNodes |
| } |
| } |
| assertEquals(expectedNodes, childNodes) |
| reader.endGroup() |
| return (if (isNode) 1 else childNodes) to reader.parentNodes |
| } |
| |
| testGroup() |
| } |
| } |
| |
| @Test |
| fun testReaderParent() { |
| val slots = testItems() |
| slots.read { reader -> |
| fun testGroup(expectedParent: Int) { |
| assertEquals(expectedParent, reader.parent) |
| val current = reader.currentGroup |
| reader.group { |
| val end = current + reader.groupSize(current) |
| while (reader.currentGroup < end) { |
| testGroup(current) |
| } |
| } |
| } |
| testGroup(-1) |
| } |
| } |
| |
| @Test |
| fun testWriterParent() { |
| val slots = testItems() |
| slots.write { writer -> |
| fun testGroup(expectedParent: Int) { |
| assertEquals(expectedParent, writer.parent) |
| val current = writer.currentGroup |
| writer.group { |
| val end = current + writer.groupSize(current) |
| while (writer.currentGroup < end) { |
| testGroup(current) |
| } |
| } |
| } |
| testGroup(-1) |
| } |
| } |
| |
| @Test |
| fun testReaderParentIndex() { |
| val slots = testItems() |
| slots.read { reader -> |
| fun testGroup(index: Int, expectedParent: Int): Int { |
| assertEquals(expectedParent, reader.parent(index)) |
| val size = reader.groupSize(index) |
| val end = index + size |
| var child = index + 1 |
| while (child < end) { |
| child += testGroup(child, index) |
| } |
| return size |
| } |
| testGroup(0, -1) |
| } |
| } |
| |
| @Test |
| fun testWriterParentIndex() { |
| val slots = testItems() |
| slots.write { writer -> |
| fun testGroup(index: Int, expectedParent: Int): Int { |
| assertEquals(expectedParent, writer.parent(index)) |
| val size = writer.groupSize(index) |
| val end = index + size |
| var child = index + 1 |
| while (child < end) { |
| child += testGroup(child, index) |
| } |
| return size |
| } |
| testGroup(0, -1) |
| } |
| } |
| |
| @Test |
| fun testReaderIsNode() { |
| val slots = testItems() |
| slots.read { reader -> |
| var count = 0 |
| fun countNodes() { |
| if (reader.isNode) { |
| count++ |
| reader.skipGroup() |
| } else { |
| reader.startGroup() |
| while (!reader.isGroupEnd) |
| countNodes() |
| reader.endGroup() |
| } |
| } |
| countNodes() |
| |
| assertEquals(reader.nodeCount(0), count) |
| } |
| } |
| |
| @Test |
| fun testWriterIsNode() { |
| val slots = testItems() |
| val expectedCount = slots.read { it.nodeCount(0) } |
| slots.write { writer -> |
| var count = 0 |
| fun countNodes() { |
| if (writer.isNode) { |
| count++ |
| writer.skipGroup() |
| } else { |
| writer.startGroup() |
| while (!writer.isGroupEnd) |
| countNodes() |
| writer.endGroup() |
| } |
| } |
| countNodes() |
| |
| assertEquals(expectedCount, count) |
| } |
| } |
| |
| @Test |
| fun testReaderIsNodeIndex() { |
| val slots = testItems() |
| slots.read { reader -> |
| var count = 0 |
| fun countNodes(index: Int): Int { |
| val size = reader.groupSize(index) |
| if (reader.isNode(index)) count++ |
| else { |
| val end = index + size |
| var child = index + 1 |
| while (child < end) { |
| child += countNodes(child) |
| } |
| } |
| return size |
| } |
| countNodes(0) |
| |
| assertEquals(reader.nodeCount(0), count) |
| } |
| } |
| |
| @Test |
| fun testReaderNodeIndex() { |
| val slots = testItems() |
| slots.read { reader -> |
| fun testGroup(index: Int): Int { |
| if (reader.isNode(index)) { |
| assertEquals( |
| expected = "node for key ${reader.groupObjectKey(index)}", |
| actual = reader.node(index) |
| ) |
| } |
| val size = reader.groupSize(index) |
| val end = index + size |
| var child = index + 1 |
| while (child < end) { |
| child += testGroup(child) |
| } |
| return size |
| } |
| testGroup(0) |
| } |
| } |
| |
| @Test |
| fun testWriterNodeIndex() { |
| val slots = testItems() |
| slots.write { writer -> |
| fun testGroup(index: Int): Int { |
| val node = writer.node(index) |
| if (node != null) { |
| assertEquals( |
| expected = "node for key ${writer.groupObjectKey(index)}", |
| actual = node |
| ) |
| } |
| val size = writer.groupSize(index) |
| val end = index + size |
| var child = index + 1 |
| while (child < end) { |
| child += testGroup(child) |
| } |
| return size |
| } |
| testGroup(0) |
| } |
| } |
| |
| @Test |
| fun testReaderHasObjectKeyIndex() { |
| val slots = testItems() |
| slots.read { reader -> |
| fun testGroup(index: Int): Int { |
| if (!reader.isNode(index) && reader.hasObjectKey(index)) { |
| assertEquals(reader.groupKey(index), reader.groupObjectKey(index)) |
| } |
| val size = reader.groupSize(index) |
| val end = index + size |
| var child = index + 1 |
| while (child < end) { |
| child += testGroup(child) |
| } |
| return size |
| } |
| testGroup(0) |
| } |
| } |
| |
| @Test |
| fun testGroupEnd() { |
| val slots = testItems() |
| slots.read { reader -> |
| fun testGroup() { |
| reader.startGroup() |
| val expectedEnd = reader.groupEnd |
| while (!reader.isGroupEnd) { |
| testGroup() |
| } |
| assertEquals(expectedEnd, reader.currentGroup) |
| reader.endGroup() |
| } |
| testGroup() |
| } |
| } |
| |
| @Test |
| fun testGroupEndByIndex() { |
| val slots = testItems() |
| slots.read { reader -> |
| fun testGroup(index: Int): Int { |
| val expectedGroupEnd = reader.groupEnd(index) |
| val size = reader.groupSize(index) |
| var child = index + 1 |
| val end = index + size |
| while (child < end) { |
| child += testGroup(child) |
| } |
| assertEquals(child, expectedGroupEnd) |
| return size |
| } |
| |
| testGroup(0) |
| } |
| } |
| |
| @Test |
| fun testCurrentEnd() { |
| val slots = testItems() |
| slots.read { reader -> |
| fun testGroup() { |
| reader.startGroup() |
| val expectedEnd = reader.groupEnd |
| while (!reader.isGroupEnd) { |
| assertEquals(expectedEnd, reader.currentEnd) |
| testGroup() |
| } |
| reader.endGroup() |
| } |
| testGroup() |
| } |
| } |
| |
| @Test |
| fun testReaderGroupAux() { |
| val slots = SlotTable() |
| val object1 = object {} |
| val object2 = object {} |
| slots.write { writer -> |
| writer.beginInsert() |
| writer.startGroup(treeRoot) |
| writer.startData(1, object1) |
| writer.endGroup() |
| writer.startData(2, 2, object2) |
| writer.endGroup() |
| writer.endGroup() |
| writer.endInsert() |
| } |
| slots.read { reader -> |
| reader.startGroup() |
| assertEquals(object1, reader.groupAux) |
| reader.skipGroup() |
| assertEquals(object2, reader.groupAux) |
| reader.skipGroup() |
| reader.endGroup() |
| } |
| } |
| |
| @Test |
| fun testReaderGroupAuxByIndex() { |
| val slots = SlotTable() |
| val object1 = object {} |
| val object2 = object {} |
| var object1Index = 0 |
| var object2Index = 0 |
| slots.write { writer -> |
| writer.beginInsert() |
| writer.startGroup(treeRoot) |
| object1Index = writer.currentGroup |
| writer.startData(1, object1) |
| writer.endGroup() |
| object2Index = writer.currentGroup |
| writer.startData(2, 2, object2) |
| writer.endGroup() |
| writer.endGroup() |
| writer.endInsert() |
| } |
| slots.read { reader -> |
| assertEquals(object1, reader.groupAux(object1Index)) |
| assertEquals(object2, reader.groupAux(object2Index)) |
| } |
| } |
| |
| @Test |
| fun testWriterGroupAuxByIndex() { |
| val slots = SlotTable() |
| val object1 = object {} |
| val object2 = object {} |
| var object1Index = 0 |
| var object2Index = 0 |
| var emptyIndex = 0 |
| slots.write { writer -> |
| writer.beginInsert() |
| writer.startGroup(treeRoot) |
| object1Index = writer.currentGroup |
| writer.startData(1, object1) |
| writer.endGroup() |
| object2Index = writer.currentGroup |
| writer.startData(2, 2, object2) |
| writer.endGroup() |
| emptyIndex = writer.currentGroup |
| writer.startGroup(3) |
| writer.endGroup() |
| writer.endGroup() |
| writer.endInsert() |
| } |
| slots.write { writer -> |
| assertEquals(object1, writer.groupAux(object1Index)) |
| assertEquals(object2, writer.groupAux(object2Index)) |
| assertEquals(Composer.Empty, writer.groupAux(emptyIndex)) |
| } |
| } |
| |
| @Test |
| fun testWriterGroupKeyByIndex() { |
| val slots = testItems() |
| val keyIndexMap = mutableMapOf<Int, Int>() |
| slots.read { reader -> |
| fun collectGroup() { |
| keyIndexMap[reader.currentGroup] = reader.groupKey |
| reader.startGroup() |
| while (!reader.isGroupEnd) { |
| collectGroup() |
| } |
| reader.endGroup() |
| } |
| |
| collectGroup() |
| } |
| slots.write { writer -> |
| for ((index, expectedKey) in keyIndexMap) { |
| assertEquals(expectedKey, writer.groupKey(index)) |
| } |
| } |
| } |
| |
| @Test |
| fun testWriterGroupObjectKeyByIndex() { |
| val slots = testItems() |
| val keyIndexMap = mutableMapOf<Int, Any?>() |
| slots.read { reader -> |
| fun collectGroup() { |
| keyIndexMap[reader.currentGroup] = reader.groupObjectKey |
| reader.startGroup() |
| while (!reader.isGroupEnd) { |
| collectGroup() |
| } |
| reader.endGroup() |
| } |
| |
| collectGroup() |
| } |
| slots.write { writer -> |
| for ((index, expectedKey) in keyIndexMap) { |
| assertEquals(expectedKey, writer.groupObjectKey(index)) |
| } |
| } |
| } |
| |
| @Test |
| fun testReposition() { |
| val slots = testItems() |
| val parentsOf = mutableMapOf<Int, Int>() |
| slots.read { reader -> |
| fun collectGroup() { |
| parentsOf[reader.currentGroup] = reader.parent |
| reader.startGroup() |
| while (!reader.isGroupEnd) { |
| collectGroup() |
| } |
| reader.endGroup() |
| } |
| collectGroup() |
| } |
| |
| slots.read { reader -> |
| for ((index, parent) in parentsOf) { |
| reader.reposition(index) |
| assertEquals(parent, reader.parent) |
| } |
| } |
| } |
| |
| @Test |
| fun testUpdatingNodeWithStartNode() { |
| val slots = SlotTable() |
| val anchors = mutableListOf<Anchor>() |
| slots.write { writer -> |
| writer.insert { |
| writer.group(treeRoot) { |
| writer.group(10) { |
| // start a node group with its node. |
| anchors.add(writer.anchor()) |
| writer.startNode(30) |
| writer.endGroup() |
| // start another node the same way |
| anchors.add(writer.anchor()) |
| writer.startNode(40) |
| writer.endGroup() |
| } |
| } |
| } |
| } |
| |
| slots.write { writer -> |
| val index = anchors[0].toIndexFor(writer) |
| writer.advanceBy(index - writer.currentGroup) |
| writer.ensureStarted(writer.parent(index)) |
| writer.startNode(30, node = 300) |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| writer.advanceBy(anchors[1].toIndexFor(writer) - writer.currentGroup) |
| writer.startNode(40, node = 400) |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| |
| slots.read { reader -> |
| reader.group { |
| reader.group(10) { |
| assertEquals(30, reader.groupObjectKey) |
| assertEquals(300, reader.groupNode) |
| reader.skipGroup() |
| assertEquals(40, reader.groupObjectKey) |
| assertEquals(400, reader.groupNode) |
| reader.skipGroup() |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun testSeekToInsertingAtTheEndOfTheTable() { |
| val slots = SlotTable() |
| var parentAnchor = slots.read { it.anchor() } |
| var insertAnchor = slots.read { it.anchor() } |
| slots.write { writer -> |
| writer.insert { |
| writer.group(treeRoot) { |
| writer.group(100) { |
| parentAnchor = writer.anchor() |
| writer.group(10) { |
| writer.nodeGroup(5, 500) |
| writer.nodeGroup(6, 600) |
| insertAnchor = writer.anchor() |
| } |
| } |
| } |
| } |
| } |
| |
| slots.verifyWellFormed() |
| |
| slots.write { writer -> |
| writer.startGroup() |
| writer.seek(parentAnchor) |
| writer.startGroup() |
| writer.seek(insertAnchor) |
| writer.beginInsert() |
| writer.nodeGroup(7, 700) |
| writer.endInsert() |
| writer.endGroup() |
| writer.endGroup() |
| } |
| |
| slots.verifyWellFormed() |
| |
| slots.read { reader -> |
| reader.group(treeRoot) { |
| reader.group(100) { |
| reader.group(10) { |
| reader.expectNode(5, 500) { } |
| reader.expectNode(6, 600) { } |
| reader.expectNode(7, 700) { } |
| } |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun testSeekToInsertingAtTheStartOfAGroup() { |
| val slots = SlotTable() |
| var parentAnchor = slots.read { it.anchor() } |
| var insertAnchor = slots.read { it.anchor() } |
| slots.write { writer -> |
| writer.insert { |
| writer.group(treeRoot) { |
| writer.group(100) { |
| parentAnchor = writer.anchor() |
| writer.group(10) { |
| insertAnchor = writer.anchor() |
| writer.nodeGroup(5, 500) |
| writer.nodeGroup(6, 600) |
| } |
| } |
| } |
| } |
| } |
| |
| slots.verifyWellFormed() |
| |
| slots.write { writer -> |
| writer.startGroup() |
| writer.seek(insertAnchor) |
| writer.ensureStarted(parentAnchor) |
| writer.beginInsert() |
| writer.nodeGroup(7, 700) |
| writer.endInsert() |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| writer.endGroup() |
| } |
| |
| slots.verifyWellFormed() |
| |
| slots.read { reader -> |
| reader.group(treeRoot) { |
| reader.group(100) { |
| reader.group(10) { |
| reader.expectNode(7, 700) { } |
| reader.expectNode(5, 500) { } |
| reader.expectNode(6, 600) { } |
| } |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun testSeekToInsertingAtInTheMiddleOfAGroup() { |
| val slots = SlotTable() |
| var parentAnchor = slots.read { it.anchor() } |
| var insertAnchor = slots.read { it.anchor() } |
| slots.write { writer -> |
| writer.insert { |
| writer.group(treeRoot) { |
| writer.group(100) { |
| parentAnchor = writer.anchor() |
| writer.group(10) { |
| writer.nodeGroup(5, 500) |
| insertAnchor = writer.anchor() |
| writer.nodeGroup(6, 600) |
| } |
| } |
| } |
| } |
| } |
| |
| slots.verifyWellFormed() |
| |
| slots.write { writer -> |
| writer.startGroup() |
| writer.seek(insertAnchor) |
| writer.ensureStarted(parentAnchor) |
| writer.beginInsert() |
| writer.nodeGroup(7, 700) |
| writer.endInsert() |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| writer.endGroup() |
| } |
| |
| slots.verifyWellFormed() |
| |
| slots.read { reader -> |
| reader.group(treeRoot) { |
| reader.group(100) { |
| reader.group(10) { |
| reader.expectNode(5, 500) { } |
| reader.expectNode(7, 700) { } |
| reader.expectNode(6, 600) { } |
| } |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun testUpdatingNodeWithUpdateParentNode() { |
| val slots = SlotTable() |
| val anchors = mutableListOf<Anchor>() |
| slots.write { writer -> |
| writer.insert { |
| writer.group(treeRoot) { |
| writer.group(10) { |
| // start a node group with its node. |
| anchors.add(writer.anchor()) |
| writer.startNode(30) |
| writer.endGroup() |
| // start another node the same way |
| anchors.add(writer.anchor()) |
| writer.startNode(40) |
| writer.endGroup() |
| } |
| } |
| } |
| } |
| |
| slots.write { writer -> |
| writer.group { |
| writer.advanceBy(anchors[0].toIndexFor(writer) - writer.currentGroup) |
| writer.startGroup() |
| writer.updateParentNode(300) |
| writer.endGroup() |
| writer.advanceBy(anchors[1].toIndexFor(writer) - writer.currentGroup) |
| writer.startGroup() |
| writer.updateParentNode(400) |
| writer.endGroup() |
| writer.skipToGroupEnd() |
| } |
| } |
| |
| slots.read { reader -> |
| reader.group { |
| reader.group(10) { |
| assertEquals(30, reader.groupObjectKey) |
| assertEquals(300, reader.groupNode) |
| reader.skipGroup() |
| assertEquals(40, reader.groupObjectKey) |
| assertEquals(400, reader.groupNode) |
| reader.skipGroup() |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun testUpdatingNodeWithUpdateNode() { |
| val slots = SlotTable() |
| val anchors = mutableListOf<Anchor>() |
| slots.write { writer -> |
| writer.insert { |
| writer.group(treeRoot) { |
| writer.group(10) { |
| // start a node group with its node. |
| anchors.add(writer.anchor()) |
| writer.startNode(30) |
| writer.endGroup() |
| // start another node the same way |
| anchors.add(writer.anchor()) |
| writer.startNode(40) |
| writer.endGroup() |
| } |
| } |
| } |
| } |
| |
| slots.write { writer -> |
| writer.group { |
| writer.advanceBy(anchors[0].toIndexFor(writer) - writer.currentGroup) |
| writer.updateNode(300) |
| writer.advanceBy(anchors[1].toIndexFor(writer) - writer.currentGroup) |
| writer.updateNode(400) |
| writer.skipToGroupEnd() |
| } |
| } |
| |
| slots.read { reader -> |
| reader.group { |
| reader.group(10) { |
| assertEquals(30, reader.groupObjectKey) |
| assertEquals(300, reader.groupNode) |
| reader.skipGroup() |
| assertEquals(40, reader.groupObjectKey) |
| assertEquals(400, reader.groupNode) |
| reader.skipGroup() |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun testUpdatingAuxWithUpdateAux() { |
| val slots = SlotTable() |
| val anchors = mutableListOf<Anchor>() |
| slots.write { writer -> |
| writer.insert { |
| writer.group(treeRoot) { |
| writer.group(10) { |
| anchors.add(writer.anchor()) |
| writer.startData(30, null) |
| writer.endGroup() |
| anchors.add(writer.anchor()) |
| writer.startData(40, null) |
| writer.endGroup() |
| } |
| } |
| } |
| } |
| |
| slots.write { writer -> |
| writer.group { |
| writer.advanceBy(anchors[0].toIndexFor(writer) - writer.currentGroup) |
| writer.updateAux(300) |
| writer.advanceBy(anchors[1].toIndexFor(writer) - writer.currentGroup) |
| writer.updateAux(400) |
| writer.skipToGroupEnd() |
| } |
| } |
| |
| slots.read { reader -> |
| reader.group { |
| reader.group(10) { |
| assertEquals(30, reader.groupKey) |
| assertEquals(300, reader.groupAux) |
| reader.skipGroup() |
| assertEquals(40, reader.groupKey) |
| assertEquals(400, reader.groupAux) |
| reader.skipGroup() |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun testWriterSetByIndex() { |
| val slots = SlotTable() |
| val outerGroups = 10 |
| val outerGroupKeyBase = 100 |
| val innerGroups = 10 |
| val innerGroupKeyBase = 1000 |
| val dataCount = 5 |
| |
| data class SlotInfo( |
| val anchor: Anchor, |
| val index: Int, |
| val value: Int |
| ) |
| |
| slots.write { writer -> |
| writer.insert { |
| writer.group(treeRoot) { |
| repeat(outerGroups) { outerKey -> |
| writer.group(outerKey + outerGroupKeyBase) { |
| repeat(innerGroups) { innerKey -> |
| writer.group(innerKey + innerGroupKeyBase) { |
| repeat(dataCount) { |
| writer.update(it) |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| fun validate(dataOffset: Int = 0): List<SlotInfo> { |
| val slotInfo = mutableListOf<SlotInfo>() |
| slots.read { reader -> |
| reader.group(treeRoot) { |
| repeat(outerGroups) { outerKey -> |
| reader.group(outerKey + outerGroupKeyBase) { |
| repeat(innerGroups) { innerKey -> |
| val anchor = reader.anchor() |
| reader.group(innerKey + innerGroupKeyBase) { |
| repeat(dataCount) { |
| val index = reader.groupSlotIndex |
| val value = reader.next() as Int |
| slotInfo.add(SlotInfo(anchor, index, value)) |
| assertEquals(it + dataOffset, value) |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| return slotInfo |
| } |
| |
| val slotInfo = validate() |
| |
| val dataOffset = 10 |
| slots.write { writer -> |
| for ((anchor, index, value) in slotInfo) { |
| writer.seek(anchor) |
| val previous = writer.set(index, value + dataOffset) |
| assertEquals(value, previous) |
| } |
| } |
| |
| validate(dataOffset) |
| } |
| |
| @Test |
| fun testReaderSlot() { |
| val groups = 10 |
| val items = 10 |
| val slots = SlotTable() |
| slots.write { writer -> |
| writer.insert { |
| writer.group(treeRoot) { |
| repeat(groups) { key -> |
| writer.group(key) { |
| repeat(items) { item -> |
| writer.update(item) |
| } |
| } |
| } |
| } |
| } |
| } |
| slots.read { reader -> |
| reader.group { |
| repeat(groups) { |
| reader.group { |
| repeat(items) { item -> |
| assertEquals(reader.slot, item) |
| assertEquals(item, reader.next()) |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun testWriterSkip() { |
| val groups = 10 |
| val items = 10 |
| val slots = SlotTable() |
| slots.write { writer -> |
| writer.insert { |
| writer.group(treeRoot) { |
| repeat(groups) { key -> |
| writer.group(key) { |
| repeat(items) { item -> |
| writer.update(item) |
| } |
| } |
| } |
| } |
| } |
| } |
| slots.write { writer -> |
| writer.group { |
| repeat(groups) { |
| writer.group { |
| repeat(items) { item -> |
| if (item % 2 == 0) { |
| writer.update(item * 100) |
| } else { |
| writer.skip() |
| } |
| } |
| } |
| } |
| } |
| } |
| slots.read { reader -> |
| reader.group { |
| repeat(groups) { |
| reader.group { |
| repeat(items) { item -> |
| assertEquals(reader.next(), if (item % 2 == 0) item * 100 else item) |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun testWriterGroupSlots() { |
| val slots = testItems() |
| val allSlots = slots.write { writer -> writer.groupSlots() }.toList() |
| val sumSlots = slots.write { writer -> |
| val list = mutableListOf<Any?>() |
| writer.startGroup() |
| while (!writer.isGroupEnd) { |
| list.addAll(writer.groupSlots().toList()) |
| writer.skipGroup() |
| } |
| writer.endGroup() |
| list |
| } |
| assertEquals(allSlots.size, sumSlots.size) |
| allSlots.forEachIndexed { index, item -> |
| assertEquals(item, sumSlots[index]) |
| } |
| } |
| |
| @Test |
| fun testWriterClosed() { |
| val slots = testItems() |
| val writer = slots.openWriter() |
| assertFalse(writer.closed) |
| writer.close() |
| assertTrue(writer.closed) |
| } |
| |
| @Test |
| fun testOwnsAnchor() { |
| val (slots1, anchors1) = narrowTrees() |
| val (slots2, anchors2) = narrowTrees() |
| for (anchor in anchors1) { |
| assertTrue(slots1.ownsAnchor(anchor)) |
| assertFalse(slots2.ownsAnchor(anchor)) |
| } |
| for (anchor in anchors2) { |
| assertFalse(slots1.ownsAnchor(anchor)) |
| assertTrue(slots2.ownsAnchor(anchor)) |
| } |
| } |
| |
| @Test |
| fun testMultipleRoots() { |
| val slots = SlotTable() |
| val anchors = mutableListOf<Anchor>() |
| repeat(10) { |
| slots.write { writer -> |
| anchors.add(writer.anchor()) |
| writer.beginInsert() |
| writer.startGroup(it + 100) |
| repeat(it) { writer.update(it) } |
| repeat(it) { |
| writer.startGroup(it + 1000) |
| writer.endGroup() |
| } |
| writer.endGroup() |
| writer.endInsert() |
| } |
| } |
| slots.verifyWellFormed() |
| } |
| |
| @Test |
| fun testCanRestoreParent() { |
| val anchors = mutableMapOf<Int, List<Anchor>>() |
| val slots = SlotTable() |
| slots.write { writer -> |
| writer.beginInsert() |
| writer.startGroup(treeRoot) |
| repeat(10) { outerKey -> |
| val nestedAnchors = mutableListOf<Anchor>() |
| anchors[outerKey] = nestedAnchors |
| writer.startGroup(outerKey) |
| repeat(10) { innerKey -> |
| writer.startGroup(innerKey + 1000) |
| repeat(10) { anchoredKey -> |
| if (anchoredKey % 3 == 0) |
| nestedAnchors.add(writer.anchor()) |
| writer.startGroup(anchoredKey + 2000) |
| writer.update("anchored value") |
| writer.endGroup() |
| } |
| writer.endGroup() |
| } |
| writer.endGroup() |
| } |
| writer.endGroup() |
| writer.endInsert() |
| } |
| slots.read { reader -> |
| reader.startGroup() // root |
| repeat(10) { outerKey -> |
| assertEquals(outerKey, reader.groupKey) |
| reader.startGroup() |
| val nestedAnchors = anchors[outerKey] ?: error("Missing anchor list for $outerKey") |
| val parent = reader.parent |
| for (anchor in nestedAnchors) { |
| reader.reposition(anchor.toIndexFor(slots)) |
| assertTrue(reader.groupKey >= 2000) |
| reader.startGroup() |
| assertEquals("anchored value", reader.next()) |
| reader.endGroup() |
| } |
| reader.restoreParent(parent) |
| reader.skipToGroupEnd() |
| reader.endGroup() |
| } |
| } |
| } |
| |
| @Test |
| fun testCanRemoveRootGroup() { |
| val slots = SlotTable() |
| slots.write { writer -> |
| writer.beginInsert() |
| writer.startGroup(100, 100) |
| writer.endGroup() |
| writer.startGroup(200, 200) |
| writer.update(300) |
| writer.update(400) |
| writer.endGroup() |
| writer.endInsert() |
| } |
| slots.read { reader -> |
| reader.expectGroup(100, 100) |
| reader.expectGroup(200, 200) { |
| assertEquals(300, reader.next()) |
| assertEquals(400, reader.next()) |
| } |
| } |
| slots.write { writer -> |
| writer.removeGroup() |
| } |
| slots.read { reader -> |
| reader.expectGroup(200, 200) { |
| assertEquals(300, reader.next()) |
| assertEquals(400, reader.next()) |
| } |
| } |
| slots.write { writer -> |
| writer.removeGroup() |
| } |
| assertTrue(slots.isEmpty) |
| } |
| |
| @Test |
| fun testReplacesWithZeroSizeGroup() { |
| val outerGroupCount = 10 |
| val outerKeyBase = 0 |
| val innerGroupCount = 10 |
| val innerKeyBase = 100 |
| val bottomGroupCount = 5 |
| val bottomKeyBase = 1000 |
| val replaceMod = 2 |
| val slots = SlotTable().also { |
| it.write { writer -> |
| writer.beginInsert() |
| writer.startGroup(treeRoot) |
| repeat(outerGroupCount) { outerKey -> |
| writer.startGroup(outerKeyBase + outerKey) |
| repeat(innerGroupCount) { innerKey -> |
| writer.startGroup(innerKeyBase + innerKey) |
| repeat(bottomGroupCount) { bottomKey -> |
| writer.startGroup(bottomKeyBase + bottomKey, bottomKey) |
| writer.update("Some data") |
| writer.endGroup() |
| } |
| writer.endGroup() |
| } |
| writer.endGroup() |
| } |
| writer.endGroup() |
| writer.endInsert() |
| } |
| } |
| slots.verifyWellFormed() |
| val sourceTable = SlotTable().also { |
| it.write { writer -> |
| writer.beginInsert() |
| repeat(outerGroupCount * innerGroupCount) { |
| writer.startGroup(0) |
| writer.endGroup() |
| } |
| writer.endInsert() |
| } |
| } |
| sourceTable.verifyWellFormed() |
| slots.write { writer -> |
| writer.startGroup() |
| repeat(outerGroupCount) { |
| writer.startGroup() |
| repeat(innerGroupCount) { innerGroupKey -> |
| if (innerGroupKey % replaceMod == 0) { |
| writer.beginInsert() |
| writer.moveFrom(sourceTable, 0) |
| writer.endInsert() |
| writer.removeGroup() |
| } else { |
| writer.skipGroup() |
| } |
| writer.verifyDataAnchors() |
| } |
| writer.endGroup() |
| } |
| writer.endGroup() |
| } |
| slots.verifyWellFormed() |
| } |
| |
| @Test |
| fun testInsertOfZeroGroups() { |
| val sourceAnchors = mutableListOf<Anchor>() |
| val sourceTable = SlotTable().also { |
| it.write { writer -> |
| writer.beginInsert() |
| sourceAnchors.add(writer.anchor()) |
| writer.startGroup(0) |
| writer.update("0: Some value") |
| writer.endGroup() |
| sourceAnchors.add(writer.anchor()) |
| writer.startGroup(0) |
| repeat(5) { |
| writer.startGroup(1) |
| writer.endGroup() |
| } |
| writer.endGroup() |
| sourceAnchors.add(writer.anchor()) |
| writer.startGroup(0) |
| writer.endGroup() |
| writer.endInsert() |
| } |
| } |
| |
| var container = Anchor(0) |
| val destinationAnchors = mutableListOf<Anchor>() |
| val slots = SlotTable().also { |
| it.write { writer -> |
| writer.insert { |
| writer.group(treeRoot) { |
| writer.group(10) { |
| writer.update("10: Some data") |
| } |
| container = writer.anchor() |
| writer.group(100) { |
| destinationAnchors.add(writer.anchor()) |
| writer.group(500) { } |
| destinationAnchors.add(writer.anchor()) |
| writer.group(1000) { |
| writer.update("1000: Some data") |
| } |
| destinationAnchors.add(writer.anchor()) |
| writer.group(2000) { |
| writer.update("2000: Some data") |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| slots.write { writer -> |
| var started = false |
| repeat(sourceAnchors.size) { |
| val sourceAnchor = sourceAnchors[it] |
| val destinationAnchor = destinationAnchors[it] |
| writer.advanceBy(destinationAnchor.toIndexFor(writer) - writer.currentGroup) |
| if (!started) { |
| writer.ensureStarted(0) |
| writer.ensureStarted(container) |
| started = true |
| } |
| writer.beginInsert() |
| writer.moveFrom( |
| sourceTable, |
| sourceAnchor.toIndexFor(sourceTable) |
| ) |
| writer.endInsert() |
| } |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| |
| slots.read { reader -> |
| reader.expectGroup(treeRoot) { |
| reader.expectGroup(10) { |
| reader.expectData("10: Some data") |
| } |
| reader.expectGroup(100) { |
| reader.expectGroup(0) |
| reader.expectGroup(500) |
| reader.expectGroup(0) |
| reader.expectGroup(1000) { |
| reader.expectData("1000: Some data") |
| } |
| reader.expectGroup(0) |
| reader.expectGroup(2000) { |
| reader.expectData("2000: Some data") |
| } |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun testMoveOfZeroGroup() { |
| val slots = SlotTable().also { |
| it.write { writer -> |
| writer.insert { |
| writer.group(treeRoot) { |
| writer.group(10) { |
| writer.group(100) { |
| writer.update("100: 1") |
| writer.update("100: 2") |
| } |
| writer.group(200) { |
| writer.update("200: 1") |
| writer.update("200: 2") |
| } |
| writer.group(300) { |
| writer.update("300: 1") |
| writer.update("300: 2") |
| } |
| // Empty group |
| writer.group(0) { } |
| writer.group(400) { |
| writer.update("400: 1") |
| writer.update("400: 2") |
| } |
| } |
| } |
| } |
| } |
| } |
| slots.verifyWellFormed() |
| slots.read { reader -> |
| reader.expectGroup(treeRoot) { |
| reader.expectGroup(10) { |
| reader.expectGroup(100) { |
| reader.expectData("100: 1") |
| reader.expectData("100: 2") |
| } |
| reader.expectGroup(200) { |
| reader.expectData("200: 1") |
| reader.expectData("200: 2") |
| } |
| reader.expectGroup(300) { |
| reader.expectData("300: 1") |
| reader.expectData("300: 2") |
| } |
| reader.expectGroup(0) |
| reader.expectGroup(400) { |
| reader.expectData("400: 1") |
| reader.expectData("400: 2") |
| } |
| } |
| } |
| } |
| slots.write { writer -> |
| writer.startGroup() |
| writer.startGroup() |
| writer.skipGroup() |
| writer.insert { |
| writer.group(150) { |
| writer.update("150: 1") |
| writer.update("150: 2") |
| } |
| } |
| writer.skipGroup() |
| writer.moveGroup(1) |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| writer.endGroup() |
| } |
| slots.verifyWellFormed() |
| slots.read { reader -> |
| reader.expectGroup(treeRoot) { |
| reader.expectGroup(10) { |
| reader.expectGroup(100) { |
| reader.expectData("100: 1") |
| reader.expectData("100: 2") |
| } |
| reader.expectGroup(150) { |
| reader.expectData("150: 1") |
| reader.expectData("150: 2") |
| } |
| reader.expectGroup(200) { |
| reader.expectData("200: 1") |
| reader.expectData("200: 2") |
| } |
| reader.expectGroup(0) |
| reader.expectGroup(300) { |
| reader.expectData("300: 1") |
| reader.expectData("300: 2") |
| } |
| reader.expectGroup(400) { |
| reader.expectData("400: 1") |
| reader.expectData("400: 2") |
| } |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun testReaderGet() { |
| val slots = SlotTable().also { |
| it.write { writer -> |
| writer.insert { |
| writer.group(treeRoot) { |
| writer.group(10) { |
| writer.update("10: 0") |
| writer.update("10: 1") |
| writer.update("10: 2") |
| } |
| writer.group(20) { |
| writer.update("20: 0") |
| writer.update("20: 1") |
| writer.update("20: 2") |
| } |
| } |
| } |
| } |
| } |
| slots.verifyWellFormed() |
| slots.read { reader -> |
| reader.startGroup() |
| reader.startGroup() |
| assertEquals("10: 0", reader.get(0)) |
| assertEquals("10: 1", reader.get(1)) |
| assertEquals("10: 2", reader.get(2)) |
| reader.endGroup() |
| reader.startGroup() |
| assertEquals("20: 0", reader.get(0)) |
| assertEquals("20: 1", reader.get(1)) |
| assertEquals("20: 2", reader.get(2)) |
| reader.endGroup() |
| reader.endGroup() |
| } |
| } |
| |
| @Test |
| fun testReaderGroupGet() { |
| val slots = SlotTable().also { |
| it.write { writer -> |
| writer.insert { |
| writer.group(treeRoot) { |
| writer.group(10) { |
| writer.update("10: 0") |
| writer.update("10: 1") |
| writer.update("10: 2") |
| } |
| writer.group(20) { |
| writer.update("20: 0") |
| writer.update("20: 1") |
| writer.update("20: 2") |
| } |
| } |
| } |
| } |
| } |
| slots.verifyWellFormed() |
| slots.read { reader -> |
| reader.startGroup() |
| assertEquals("10: 0", reader.groupGet(0)) |
| assertEquals("10: 1", reader.groupGet(1)) |
| assertEquals("10: 2", reader.groupGet(2)) |
| reader.skipGroup() |
| assertEquals("20: 0", reader.groupGet(0)) |
| assertEquals("20: 1", reader.groupGet(1)) |
| assertEquals("20: 2", reader.groupGet(2)) |
| reader.skipGroup() |
| reader.endGroup() |
| } |
| } |
| |
| @Test |
| fun testReaderSize() { |
| val slots = testItems() |
| slots.read { reader -> |
| assertEquals(slots.groupsSize, reader.size) |
| } |
| } |
| |
| @Test |
| fun testWriterSize() { |
| val slots = testItems() |
| slots.write { writer -> |
| assertEquals(slots.groupsSize, writer.size) |
| writer.startGroup() |
| writer.insert { |
| writer.group(1000) { |
| writer.update("1000: 1") |
| } |
| } |
| assertEquals(slots.groupsSize + 1, writer.size) |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| } |
| |
| @Test |
| fun testGroupSlotCount() { |
| val slots = SlotTable().also { |
| it.write { writer -> |
| writer.insert { |
| writer.group(treeRoot) { |
| writer.group(10) { |
| repeat(5) { item -> |
| writer.update("Data $item") |
| } |
| } |
| } |
| } |
| } |
| } |
| slots.read { reader -> |
| reader.expectGroup(treeRoot) { |
| assertEquals(reader.groupSlotCount, 5) |
| reader.skipGroup() |
| } |
| } |
| } |
| |
| @Test |
| fun testRemoveDataBoundaryCondition() { |
| // Remove when the slot table contains amount that would make the slotGapSize 0 |
| // Test insert exactly 64 data slots. |
| val slots = SlotTable().also { |
| it.write { writer -> |
| writer.insert { |
| writer.group(treeRoot) { |
| repeat(4) { |
| writer.group(it * 10 + 100) { |
| repeat(8) { value -> |
| writer.update(value) |
| } |
| } |
| } |
| writer.group(1000) { |
| repeat(16) { value -> |
| writer.update(value) |
| } |
| } |
| repeat(2) { |
| writer.group(it * 10 + 200) { |
| repeat(8) { value -> |
| writer.update(value) |
| } |
| } |
| } |
| repeat(10) { |
| writer.group(300 + it) { } |
| } |
| } |
| } |
| } |
| } |
| slots.verifyWellFormed() |
| |
| slots.write { writer -> |
| writer.group(treeRoot) { |
| repeat(4) { writer.skipGroup() } |
| writer.removeGroup() |
| writer.skipGroup() |
| writer.set(4, 100) |
| writer.skipToGroupEnd() |
| } |
| } |
| slots.verifyWellFormed() |
| } |
| |
| @Test |
| fun testInsertDataBoundaryCondition() { |
| // Test insert exactly 64 data slots. |
| val slots = SlotTable().also { |
| it.write { writer -> |
| writer.insert { |
| writer.group(treeRoot) { |
| writer.group(10) { |
| writer.group(100) { |
| repeat(10) { item -> writer.update(item) } |
| } |
| writer.group(200) { |
| repeat(10) { item -> writer.update(item) } |
| } |
| } |
| } |
| } |
| } |
| } |
| slots.verifyWellFormed() |
| |
| val sourceTable = SlotTable().also { |
| it.write { writer -> |
| writer.insert { |
| writer.group(150) { |
| repeat(64) { item -> writer.update("Inserted item $item") } |
| } |
| } |
| } |
| } |
| sourceTable.verifyWellFormed() |
| |
| slots.write { writer -> |
| writer.group { |
| writer.group { |
| writer.skipGroup() |
| writer.insert { |
| writer.moveFrom(sourceTable, 0) |
| } |
| writer.skipToGroupEnd() |
| } |
| } |
| } |
| slots.verifyWellFormed() |
| |
| slots.read { reader -> |
| reader.expectGroup(treeRoot) { |
| reader.expectGroup(10) { |
| reader.expectGroup(100) { |
| repeat(10) { item -> reader.expectData(item) } |
| } |
| reader.expectGroup(150) { |
| repeat(64) { item -> reader.expectData("Inserted item $item") } |
| } |
| reader.expectGroup(200) { |
| repeat(10) { item -> reader.expectData(item) } |
| } |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun testGroupsBoundaryCondition() { |
| // Test inserting exactly 32 groups with 2 data items each |
| val slots = SlotTable().also { |
| it.write { writer -> |
| writer.insert { |
| writer.group(treeRoot) { |
| writer.group(10) { |
| writer.group(100) { |
| repeat(10) { item -> writer.update(item) } |
| } |
| writer.group(200) { |
| repeat(10) { item -> writer.update(item) } |
| } |
| } |
| } |
| } |
| } |
| } |
| slots.verifyWellFormed() |
| |
| val sourceTable = SlotTable().also { |
| it.write { writer -> |
| writer.insert { |
| writer.group(150) { |
| repeat(2) { item -> writer.update("Inserted item $item") } |
| repeat(31) { key -> |
| writer.group(150 + key) { |
| repeat(2) { item -> writer.update("Inserted item $item") } |
| } |
| } |
| } |
| } |
| } |
| } |
| sourceTable.verifyWellFormed() |
| |
| slots.write { writer -> |
| writer.group { |
| writer.group { |
| writer.skipGroup() |
| writer.insert { |
| writer.moveFrom(sourceTable, 0) |
| } |
| writer.skipToGroupEnd() |
| } |
| } |
| } |
| slots.verifyWellFormed() |
| |
| slots.read { reader -> |
| reader.expectGroup(treeRoot) { |
| reader.expectGroup(10) { |
| reader.expectGroup(100) { |
| repeat(10) { item -> reader.expectData(item) } |
| } |
| reader.expectGroup(150) { |
| repeat(2) { item -> reader.expectData("Inserted item $item") } |
| repeat(31) { key -> |
| reader.expectGroup(150 + key) { |
| repeat(2) { item -> reader.expectData("Inserted item $item") } |
| } |
| } |
| } |
| reader.expectGroup(200) { |
| repeat(10) { item -> reader.expectData(item) } |
| } |
| } |
| } |
| } |
| } |
| |
| @Test // regression b/173822943 |
| fun testGroupInsertBoundaryCondition() { |
| // Test inserting when there is an empty gap. |
| SlotTable().also { |
| it.write { writer -> |
| writer.insert { |
| writer.group(treeRoot) { |
| repeat(7) { outer -> |
| writer.group(100 + outer) { |
| repeat(8) { inner -> |
| writer.group(200 + inner) { } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| it.verifyWellFormed() |
| |
| it.write { writer -> |
| writer.group { |
| repeat(3) { writer.skipGroup() } |
| writer.insert { |
| writer.group(300) { } |
| } |
| writer.verifyParentAnchors() |
| writer.skipToGroupEnd() |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun canRepositionReaderPastEndOfTable() { |
| val slots = SlotTable().also { |
| it.write { writer -> |
| // Create exactly 256 groups |
| repeat(256) { |
| writer.insert { |
| writer.startGroup(0) |
| writer.endGroup() |
| } |
| } |
| } |
| } |
| |
| slots.read { reader -> |
| reader.reposition(reader.size) |
| // Expect the above not to crash. |
| } |
| } |
| |
| @Test |
| fun canRemoveFromFullTable() { |
| // Create a table that is exactly 64 entries |
| val slots = SlotTable().also { |
| it.write { writer -> |
| writer.insert { |
| repeat(7) { outer -> |
| writer.group(10 + outer) { |
| repeat(8) { inner -> |
| writer.group(inner) { } |
| } |
| } |
| } |
| writer.group(30) { } |
| } |
| } |
| } |
| slots.verifyWellFormed() |
| |
| // Remove the first group |
| slots.write { writer -> |
| writer.removeGroup() |
| } |
| slots.verifyWellFormed() |
| } |
| |
| @Test |
| fun canInsertAuxData() { |
| val slots = SlotTable().also { |
| it.write { writer -> |
| writer.insert { |
| // Insert a normal aux data. |
| writer.startData(10, 10, "10") |
| writer.endGroup() |
| |
| // Insert using insertAux |
| writer.startGroup(20) |
| writer.insertAux("20") |
| writer.endGroup() |
| |
| // Insert using insertAux after a slot value was added. |
| writer.startGroup(30) |
| writer.update(300) |
| writer.insertAux("30") |
| writer.endGroup() |
| |
| // Insert using insertAux after a group with an object key |
| writer.startGroup(40, 40) |
| writer.insertAux("40") |
| writer.endGroup() |
| |
| // Insert aux into an object key with a value slot and then add another value. |
| writer.startGroup(50, 50) |
| writer.update(500) |
| writer.insertAux("50") |
| writer.update(501) |
| writer.endGroup() |
| |
| // Insert aux after two slot values and then add another value. |
| writer.startGroup(60) |
| writer.update(600) |
| writer.update(601) |
| writer.insertAux("60") |
| writer.update(602) |
| writer.endGroup() |
| |
| // Write a trail group to ensure that the slot table is valid after the |
| // insertAux |
| writer.startGroup(1000) |
| writer.update(10000) |
| writer.update(10001) |
| writer.endGroup() |
| } |
| } |
| } |
| slots.verifyWellFormed() |
| slots.read { reader -> |
| assertEquals(10, reader.groupKey) |
| assertEquals(10, reader.groupObjectKey) |
| assertEquals("10", reader.groupAux) |
| reader.skipGroup() |
| assertEquals(20, reader.groupKey) |
| assertEquals("20", reader.groupAux) |
| reader.skipGroup() |
| assertEquals(30, reader.groupKey) |
| assertEquals("30", reader.groupAux) |
| reader.startGroup() |
| assertEquals(300, reader.next()) |
| reader.endGroup() |
| assertEquals(40, reader.groupKey) |
| assertEquals(40, reader.groupObjectKey) |
| assertEquals("40", reader.groupAux) |
| reader.skipGroup() |
| assertEquals(50, reader.groupKey) |
| assertEquals(50, reader.groupObjectKey) |
| assertEquals("50", reader.groupAux) |
| reader.startGroup() |
| assertEquals(500, reader.next()) |
| assertEquals(501, reader.next()) |
| reader.endGroup() |
| assertEquals(60, reader.groupKey) |
| assertEquals("60", reader.groupAux) |
| reader.startGroup() |
| assertEquals(600, reader.next()) |
| assertEquals(601, reader.next()) |
| assertEquals(602, reader.next()) |
| reader.endGroup() |
| assertEquals(1000, reader.groupKey) |
| reader.startGroup() |
| assertEquals(10000, reader.next()) |
| assertEquals(10001, reader.next()) |
| reader.endGroup() |
| } |
| } |
| |
| @Test |
| fun incorrectUsageReportsInternalException() = expectError("internal") { |
| val table = SlotTable() |
| table.write { |
| table.write { } |
| } |
| } |
| |
| @Test |
| fun prioritySet_Ordering() { |
| val set = PrioritySet() |
| |
| repeat(100) { |
| Random.nextInt().let { |
| if (it < Int.MAX_VALUE) |
| set.add(it) |
| set.validateHeap() |
| } |
| } |
| var lastValue = Int.MAX_VALUE |
| while (set.isNotEmpty()) { |
| val m = set.takeMax() |
| assertTrue(lastValue > m) |
| lastValue = m |
| } |
| } |
| |
| @Test |
| fun prioritySet_Completeness() { |
| val set = PrioritySet() |
| val values = Array(100) { it }.also { it.shuffle() } |
| values.forEach { |
| set.add(it) |
| set.validateHeap() |
| } |
| |
| repeat(100) { |
| val expected = 99 - it |
| assertFalse(set.isEmpty()) |
| assertEquals(expected, set.takeMax()) |
| set.validateHeap() |
| } |
| assertTrue(set.isEmpty()) |
| } |
| |
| @Test |
| fun prioritySet_Deduplicate() { |
| val set = PrioritySet() |
| val values = Array(100) { it / 4 }.also { it.shuffle() } |
| values.forEach { |
| set.add(it) |
| set.validateHeap() |
| } |
| |
| repeat(25) { |
| val expected = 24 - it |
| assertFalse(set.isEmpty()) |
| assertEquals(expected, set.takeMax()) |
| set.validateHeap() |
| } |
| |
| assertTrue(set.isEmpty()) |
| } |
| |
| @Test |
| fun canCheckAnEmptyTableForAMark() { |
| val table = SlotTable() |
| assertFalse(table.containsMark()) |
| } |
| |
| @Test |
| fun canMarkAGroup() { |
| val table = SlotTable() |
| table.write { writer -> |
| writer.insert { |
| writer.group(0) { |
| writer.group(1) { |
| writer.group(2) { |
| writer.markGroup() |
| } |
| writer.group(3) { |
| writer.group(4) { } |
| } |
| } |
| writer.group(5) { |
| writer.markGroup() |
| writer.group(6) { |
| writer.markGroup() |
| } |
| } |
| } |
| } |
| } |
| table.verifyWellFormed() |
| table.read { reader -> |
| fun assertMark() = assertTrue(reader.hasMark(reader.parent)) |
| fun assertNoMark() = assertFalse(reader.hasMark(reader.parent)) |
| fun assertContainsMark() = assertTrue(reader.containsMark(reader.parent)) |
| fun assertDoesNotContainMarks() = assertFalse(reader.containsMark(reader.parent)) |
| |
| reader.group(0) { |
| assertNoMark() |
| assertContainsMark() |
| reader.group(1) { |
| assertNoMark() |
| assertContainsMark() |
| reader.group(2) { |
| assertMark() |
| assertDoesNotContainMarks() |
| } |
| reader.group(3) { |
| assertNoMark() |
| assertDoesNotContainMarks() |
| reader.group(4) { |
| assertNoMark() |
| assertDoesNotContainMarks() |
| } |
| } |
| } |
| reader.group(5) { |
| assertMark() |
| assertContainsMark() |
| reader.group(6) { |
| assertMark() |
| assertDoesNotContainMarks() |
| } |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun canRemoveAMarkedGroups() { |
| val slots = SlotTable() |
| slots.write { writer -> |
| writer.insert { |
| writer.group(0) { |
| repeat(10) { key -> |
| writer.group(key) { |
| if (key % 2 == 0) writer.markGroup() |
| } |
| } |
| } |
| } |
| } |
| slots.verifyWellFormed() |
| slots.read { reader -> |
| assertTrue(reader.containsMark(0)) |
| } |
| |
| slots.write { writer -> |
| writer.group(0) { |
| repeat(10) { key -> |
| if (key % 2 == 0) |
| writer.removeGroup() |
| else |
| writer.skipGroup() |
| } |
| } |
| } |
| slots.verifyWellFormed() |
| |
| slots.read { reader -> |
| assertFalse(reader.containsMark(0)) |
| } |
| } |
| |
| @Test |
| fun canInsertAMarkedGroup() { |
| val slots = SlotTable() |
| slots.write { writer -> |
| writer.insert { |
| writer.group(0) { |
| writer.group(1) { } |
| } |
| } |
| } |
| slots.verifyWellFormed() |
| |
| slots.write { writer -> |
| writer.group(0) { |
| writer.group(1) { |
| writer.insert { |
| writer.group(2) { |
| writer.markGroup() |
| } |
| } |
| } |
| } |
| } |
| slots.verifyWellFormed() |
| |
| slots.read { reader -> |
| fun assertMark() = assertTrue(reader.hasMark(reader.parent)) |
| fun assertNoMark() = assertFalse(reader.hasMark(reader.parent)) |
| fun assertContainsMark() = assertTrue(reader.containsMark(reader.parent)) |
| fun assertDoesNotContainMarks() = assertFalse(reader.containsMark(reader.parent)) |
| |
| reader.group(0) { |
| assertNoMark() |
| assertContainsMark() |
| reader.group(1) { |
| assertNoMark() |
| assertContainsMark() |
| reader.group(2) { |
| assertMark() |
| assertDoesNotContainMarks() |
| } |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun canInsertAMarkedTableGroup() { |
| val slots = SlotTable() |
| slots.write { writer -> |
| writer.insert { |
| writer.group(0) { } |
| } |
| } |
| slots.verifyWellFormed() |
| |
| val insertTable = SlotTable() |
| insertTable.write { writer -> |
| writer.insert { |
| writer.group(1) { |
| writer.group(2) { |
| writer.markGroup() |
| } |
| } |
| } |
| } |
| insertTable.verifyWellFormed() |
| |
| slots.write { writer -> |
| writer.group(0) { |
| writer.insert { |
| writer.moveFrom(insertTable, 0) |
| } |
| } |
| } |
| slots.verifyWellFormed() |
| slots.read { reader -> |
| assertTrue(reader.containsMark(0)) |
| } |
| } |
| |
| @Test |
| fun canMoveTo() { |
| val slots = SlotTable() |
| var anchor = Anchor(-1) |
| |
| // Create a slot table |
| slots.write { writer -> |
| writer.insert { |
| writer.group(100) { |
| writer.group(200) { |
| repeat(5) { |
| writer.group(1000 + it) { |
| writer.group(2000 + it) { |
| if (it == 3) { |
| anchor = writer.anchor(writer.parent) |
| writer.markGroup(writer.parent) |
| } |
| repeat(it) { node -> |
| writer.nodeGroup(2000 + node, node) |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| assertTrue(slots.ownsAnchor(anchor)) |
| |
| // Move the anchored group into another table |
| val movedNodes = SlotTable() |
| movedNodes.write { movedNodesWriter -> |
| movedNodesWriter.insert { |
| slots.write { writer -> |
| writer.group { |
| writer.group { |
| repeat(5) { |
| if (it == 3) { |
| writer.moveTo(anchor, 0, movedNodesWriter) |
| } |
| writer.skipGroup() |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| // Validate the slot table |
| assertFalse(slots.ownsAnchor(anchor)) |
| assertTrue(movedNodes.ownsAnchor(anchor)) |
| slots.verifyWellFormed() |
| movedNodes.verifyWellFormed() |
| |
| slots.read { reader -> |
| reader.expectGroup(100) { |
| reader.expectGroup(200) { |
| repeat(5) { |
| reader.expectGroup(1000 + it) { |
| if (it != 3) { |
| reader.expectGroup(2000 + it) { |
| repeat(it) { node -> |
| reader.expectNode(2000 + node, node) |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| movedNodes.read { reader -> |
| reader.expectGroup(2003) { |
| repeat(3) { node -> |
| reader.expectNode(2000 + node, node) |
| } |
| } |
| } |
| |
| // Insert the nodes back |
| slots.write { writer -> |
| writer.group { |
| writer.group { |
| repeat(5) { |
| if (it == 3) { |
| writer.group { |
| writer.insert { |
| writer.moveFrom(movedNodes, 0) |
| } |
| } |
| } else writer.skipGroup() |
| } |
| } |
| } |
| } |
| |
| // Validate the move back |
| assertTrue(slots.ownsAnchor(anchor)) |
| assertFalse(movedNodes.ownsAnchor(anchor)) |
| slots.verifyWellFormed() |
| movedNodes.verifyWellFormed() |
| |
| assertEquals(0, movedNodes.groupsSize) |
| |
| slots.read { reader -> |
| reader.expectGroup(100) { |
| reader.expectGroup(200) { |
| repeat(5) { |
| reader.expectGroup(1000 + it) { |
| reader.expectGroup(2000 + it) { |
| repeat(it) { node -> |
| reader.expectNode(2000 + node, node) |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun canDeleteAGroupAfterMovingPartOfItsContent() { |
| val slots = SlotTable() |
| var deleteAnchor = Anchor(-1) |
| var moveAnchor = Anchor(-1) |
| |
| // Create a slot table |
| slots.write { writer -> |
| writer.insert { |
| writer.group(100) { |
| writer.group(200) { |
| writer.group(300) { |
| writer.group(400) { |
| writer.group(500) { |
| deleteAnchor = writer.anchor(writer.parent) |
| writer.nodeGroup(501, 501) { |
| writer.group(600) { |
| writer.group(700) { |
| moveAnchor = writer.anchor(writer.parent) |
| writer.markGroup(writer.parent) |
| writer.group(800) { |
| writer.nodeGroup(801, 801) |
| } |
| writer.group(900) { |
| writer.nodeGroup(901, 901) |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| val movedNodes = SlotTable() |
| movedNodes.write { movedNodesWriter -> |
| movedNodesWriter.insert { |
| slots.write { writer -> |
| val deleteLocation = writer.anchorIndex(deleteAnchor) |
| |
| writer.advanceBy(deleteLocation) |
| writer.ensureStarted(0) |
| writer.ensureStarted(writer.parent(deleteLocation)) |
| writer.moveTo(moveAnchor, 0, movedNodesWriter) |
| assertEquals(deleteLocation, writer.currentGroup) |
| writer.removeGroup() |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| } |
| } |
| |
| movedNodes.verifyWellFormed() |
| slots.verifyWellFormed() |
| |
| // Validate slots |
| slots.read { reader -> |
| reader.expectGroup(100) { |
| reader.expectGroup(200) { |
| reader.expectGroup(300) { |
| reader.expectGroup(400) |
| } |
| } |
| } |
| } |
| |
| // Validate moved nodes |
| movedNodes.read { reader -> |
| reader.expectGroup(700) { |
| reader.expectGroup(800) { |
| reader.expectNode(801, 801) |
| } |
| reader.expectGroup(900) { |
| reader.expectNode(901, 901) |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun canMoveAndDeleteAfterAnInsert() { |
| val slots = SlotTable() |
| var insertAnchor = Anchor(-1) |
| var deleteAnchor = Anchor(-1) |
| var moveAnchor = Anchor(-1) |
| |
| // Create a slot table |
| slots.write { writer -> |
| writer.insert { |
| writer.group(100) { |
| writer.group(200) { |
| writer.group(300) { |
| writer.group(400) { |
| writer.group(450) { |
| insertAnchor = writer.anchor(writer.parent) |
| } |
| writer.group(500) { |
| deleteAnchor = writer.anchor(writer.parent) |
| writer.nodeGroup(501, 501) { |
| writer.group(600) { |
| writer.group(700) { |
| moveAnchor = writer.anchor(writer.parent) |
| writer.markGroup(writer.parent) |
| writer.group(800) { |
| writer.nodeGroup(801, 801) |
| } |
| writer.group(900) { |
| writer.nodeGroup(901, 901) |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| val movedNodes = SlotTable() |
| movedNodes.write { movedNodesWriter -> |
| movedNodesWriter.insert { |
| slots.write { writer -> |
| writer.seek(insertAnchor) |
| writer.ensureStarted(0) |
| writer.group() { |
| writer.insert { |
| writer.group(455) { |
| writer.nodeGroup(456, 456) |
| } |
| } |
| } |
| |
| // Move and delete |
| val deleteLocation = writer.anchorIndex(deleteAnchor) |
| writer.seek(deleteAnchor) |
| assertEquals(deleteLocation, writer.currentGroup) |
| writer.ensureStarted(0) |
| writer.ensureStarted(writer.parent(deleteLocation)) |
| writer.moveTo(moveAnchor, 0, movedNodesWriter) |
| assertEquals(deleteLocation, writer.currentGroup) |
| writer.removeGroup() |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| } |
| } |
| |
| movedNodes.verifyWellFormed() |
| slots.verifyWellFormed() |
| } |
| |
| @Test |
| fun canMoveAGroupFromATableIntoAnotherGroup() { |
| val slots = SlotTable() |
| var insertAnchor = Anchor(-1) |
| |
| // Create a slot table |
| slots.write { writer -> |
| writer.insert { |
| writer.group(100) { |
| writer.group(200) { |
| writer.group(300) { |
| writer.group(400) { |
| writer.group(410) { |
| writer.update(1) |
| writer.update(2) |
| } |
| writer.group(450) { |
| insertAnchor = writer.anchor(writer.parent) |
| } |
| writer.group(460) { |
| writer.update(3) |
| writer.update(4) |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| slots.verifyWellFormed() |
| |
| val insertTable = SlotTable() |
| insertTable.write { writer -> |
| writer.insert { |
| writer.group(1000) { |
| writer.update(100) |
| writer.update(200) |
| writer.nodeGroup(125, 1000) |
| writer.nodeGroup(125, 2000) |
| } |
| } |
| } |
| insertTable.verifyWellFormed() |
| |
| slots.write { writer -> |
| writer.seek(insertAnchor) |
| writer.ensureStarted(0) |
| writer.ensureStarted(writer.parent(writer.currentGroup)) |
| writer.moveIntoGroupFrom(0, insertTable, 0) |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| } |
| slots.verifyWellFormed() |
| |
| slots.read { reader -> |
| reader.expectGroup(100) { |
| reader.expectGroup(200) { |
| reader.expectGroup(300) { |
| reader.expectGroup(400) { |
| reader.expectGroup(410) { |
| reader.expectData(1) |
| reader.expectData(2) |
| } |
| reader.expectGroup(450) { |
| reader.expectGroup(1000) { |
| reader.expectData(100) |
| reader.expectData(200) |
| reader.expectNode(125, 1000) |
| reader.expectNode(125, 2000) |
| } |
| } |
| reader.expectGroup(460) { |
| reader.expectData(3) |
| reader.expectData(4) |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| @Test |
| fun canMoveAGroupFromATableIntoAnotherGroupAndModifyThatGroup() { |
| val slots = SlotTable() |
| var insertAnchor = Anchor(-1) |
| |
| // Create a slot table |
| slots.write { writer -> |
| writer.insert { |
| writer.group(100) { |
| writer.group(200) { |
| writer.group(300) { |
| writer.group(400) { |
| writer.group(410) { |
| writer.update(1) |
| writer.update(2) |
| } |
| writer.group(450) { |
| insertAnchor = writer.anchor(writer.parent) |
| } |
| writer.group(460) { |
| writer.update(3) |
| writer.update(4) |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| slots.verifyWellFormed() |
| |
| val insertTable = SlotTable() |
| insertTable.write { writer -> |
| writer.insert { |
| writer.group(1000) { |
| writer.update(100) |
| writer.update(200) |
| writer.nodeGroup(125, 1000) |
| writer.nodeGroup(125, 2000) |
| } |
| } |
| } |
| insertTable.verifyWellFormed() |
| |
| val (previous1, previous2) = slots.write { writer -> |
| writer.seek(insertAnchor) |
| writer.ensureStarted(0) |
| writer.ensureStarted(writer.parent(writer.currentGroup)) |
| writer.moveIntoGroupFrom(0, insertTable, 0) |
| writer.startGroup() |
| writer.startGroup() |
| val previous1 = writer.update(300) |
| val previous2 = writer.update(400) |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| writer.endGroup() |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| writer.skipToGroupEnd() |
| writer.endGroup() |
| previous1 to previous2 |
| } |
| slots.verifyWellFormed() |
| |
| assertEquals(100, previous1) |
| assertEquals(200, previous2) |
| |
| slots.read { reader -> |
| reader.expectGroup(100) { |
| reader.expectGroup(200) { |
| reader.expectGroup(300) { |
| reader.expectGroup(400) { |
| reader.expectGroup(410) { |
| reader.expectData(1) |
| reader.expectData(2) |
| } |
| reader.expectGroup(450) { |
| reader.expectGroup(1000) { |
| reader.expectData(300) |
| reader.expectData(400) |
| reader.expectNode(125, 1000) |
| reader.expectNode(125, 2000) |
| } |
| } |
| reader.expectGroup(460) { |
| reader.expectData(3) |
| reader.expectData(4) |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| @OptIn(InternalComposeApi::class) |
| internal inline fun SlotWriter.group(block: () -> Unit) { |
| startGroup() |
| block() |
| endGroup() |
| } |
| |
| @OptIn(InternalComposeApi::class) |
| internal inline fun SlotWriter.group(key: Int, block: () -> Unit) { |
| startGroup(key) |
| block() |
| endGroup() |
| } |
| |
| @OptIn(InternalComposeApi::class) |
| internal inline fun SlotWriter.nodeGroup(key: Int, node: Any, block: () -> Unit = { }) { |
| startNode(key, node) |
| block() |
| endGroup() |
| } |
| @OptIn(InternalComposeApi::class) |
| internal inline fun SlotWriter.insert(block: () -> Unit) { |
| beginInsert() |
| block() |
| endInsert() |
| } |
| |
| @OptIn(InternalComposeApi::class) |
| internal inline fun SlotReader.group(key: Int, block: () -> Unit) { |
| assertEquals(key, groupKey) |
| startGroup() |
| block() |
| endGroup() |
| } |
| |
| @OptIn(InternalComposeApi::class) |
| internal inline fun SlotReader.group(block: () -> Unit) { |
| startGroup() |
| block() |
| endGroup() |
| } |
| |
| @OptIn(InternalComposeApi::class) |
| private inline fun SlotReader.expectNode(key: Int, node: Any, block: () -> Unit = { }) { |
| assertEquals(key, groupObjectKey) |
| assertEquals(node, groupNode) |
| startNode() |
| block() |
| endGroup() |
| } |
| |
| private const val treeRoot = -1 |
| private const val elementKey = 100 |
| |
| @OptIn(InternalComposeApi::class) |
| private fun testSlotsNumbered(): SlotTable { |
| val slotTable = SlotTable() |
| slotTable.write { writer -> |
| writer.beginInsert() |
| writer.startGroup(treeRoot) |
| repeat(100) { |
| writer.startGroup(it) |
| writer.endGroup() |
| } |
| writer.endGroup() |
| writer.endInsert() |
| } |
| return slotTable |
| } |
| |
| // Creates 0 until 10 items each with 10 elements numbered 0...n with 0..n slots |
| @OptIn(InternalComposeApi::class) |
| private fun testItems(): SlotTable { |
| val slots = SlotTable() |
| slots.write { writer -> |
| writer.beginInsert() |
| writer.startGroup(treeRoot) |
| |
| fun item(key: Int, block: () -> Unit) { |
| writer.startGroup(key, key) |
| block() |
| writer.endGroup() |
| } |
| |
| fun element(key: Int, block: () -> Unit) { |
| writer.startNode(key, "node for key $key") |
| block() |
| writer.endGroup() |
| } |
| |
| for (key in 1..10) { |
| item(key) { |
| for (item in 0..key) { |
| element(key * elementKey + item) { |
| for (element in 0..key) |
| writer.update(-element) |
| } |
| } |
| } |
| } |
| |
| writer.endGroup() |
| writer.endInsert() |
| } |
| |
| return slots |
| } |
| |
| @OptIn(InternalComposeApi::class) |
| private fun validateItems(slots: SlotTable) { |
| slots.read { reader -> |
| check(reader.groupKey == treeRoot) { "Invalid root key" } |
| reader.startGroup() |
| |
| fun item(key: Int, block: () -> Unit) { |
| check(reader.groupKey == key) { |
| "Unexpected key at ${reader.currentGroup}, expected $key, " + |
| "received ${reader.groupKey}" |
| } |
| check(reader.groupObjectKey == key) { |
| "Unexpected data key at ${reader.currentGroup}, expected $key, received ${ |
| reader.groupObjectKey |
| }" |
| } |
| reader.startGroup() |
| block() |
| reader.endGroup() |
| } |
| |
| fun element(key: Int, block: () -> Unit) { |
| check(reader.isNode) { "Expected a node group" } |
| check(reader.groupObjectKey == key) { "Invalid node key at ${reader.currentGroup}" } |
| check(reader.groupNode == "node for key $key") { |
| "Unexpected node value at ${reader.currentGroup}" |
| } |
| reader.startNode() |
| block() |
| reader.endGroup() |
| } |
| |
| for (key in 1..10) { |
| item(key) { |
| for (item in 0..key) { |
| element(key * elementKey + item) { |
| for (element in 0..key) { |
| val received = reader.next() |
| check(-element == received) { |
| "Unexpected slot value $element received $received" |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| reader.endGroup() |
| } |
| } |
| |
| @OptIn(InternalComposeApi::class) |
| private fun narrowTrees(): Pair<SlotTable, List<Anchor>> { |
| val slots = SlotTable() |
| val anchors = mutableListOf<Anchor>() |
| slots.write { writer -> |
| writer.beginInsert() |
| writer.startGroup(treeRoot) |
| |
| fun item(key: Int, block: () -> Unit) { |
| writer.startGroup(key) |
| block() |
| writer.endGroup() |
| } |
| |
| fun element(key: Int, block: () -> Unit) { |
| writer.startNode(key, key) |
| block() |
| writer.endGroup() |
| } |
| |
| fun tree(key: Int, width: Int, depth: Int) { |
| anchors.add(writer.anchor()) |
| item(key) { |
| when { |
| width > 0 -> for (childKey in 1..width) { |
| tree(childKey, width - 1, depth + 1) |
| } |
| depth > 0 -> { |
| tree(1001, width, depth - 1) |
| } |
| else -> { |
| repeat(depth + 2) { |
| element(-1) { } |
| } |
| } |
| } |
| } |
| } |
| |
| element(1000) { |
| tree(0, 5, 5) |
| } |
| writer.endGroup() |
| writer.endInsert() |
| } |
| |
| return slots to anchors |
| } |
| |
| @OptIn(InternalComposeApi::class) |
| private fun SlotReader.expectGroup(key: Int): Int { |
| assertEquals(key, groupKey) |
| return skipGroup() |
| } |
| |
| @OptIn(InternalComposeApi::class) |
| private fun SlotReader.expectGroup( |
| key: Int, |
| block: () -> Unit |
| ) { |
| assertEquals(key, groupKey) |
| startGroup() |
| block() |
| endGroup() |
| } |
| |
| @OptIn(InternalComposeApi::class) |
| private fun SlotReader.expectData(value: Any) { |
| assertEquals(value, next()) |
| } |
| |
| @OptIn(InternalComposeApi::class) |
| private fun SlotReader.expectGroup( |
| key: Int, |
| objectKey: Any?, |
| block: () -> Unit = { skipToGroupEnd() } |
| ) { |
| assertEquals(key, groupKey) |
| assertEquals(objectKey, groupObjectKey) |
| startGroup() |
| block() |
| endGroup() |
| } |
| |
| private fun <T> Iterator<T>.toList(): List<T> { |
| val list = mutableListOf<T>() |
| while (hasNext()) { |
| list.add(next()) |
| } |
| return list |
| } |
| |
| internal fun expectError(message: String, block: () -> Unit) { |
| var exceptionThrown = false |
| try { |
| block() |
| } catch (e: Throwable) { |
| exceptionThrown = true |
| assertTrue( |
| e.message?.contains(message) == true, |
| "Expected \"${e.message}\" to contain \"$message\"" |
| ) |
| } |
| assertTrue( |
| exceptionThrown, |
| "Expected test to throw an exception containing \"$message\"" |
| ) |
| } |