2025-04-12 14:44:37 +02:00
|
|
|
import * as t from 'lib0/testing'
|
2025-04-19 15:21:14 +02:00
|
|
|
import * as idmap from '../src/utils/IdMap.js'
|
2025-04-30 22:12:09 +02:00
|
|
|
import * as prng from 'lib0/prng'
|
|
|
|
|
import * as math from 'lib0/math'
|
|
|
|
|
import { compareIdmaps as compareIdMaps, createIdMap, ID, createRandomIdSet, createRandomIdMap, createAttributionItem } from './testHelper.js'
|
2025-04-19 15:15:34 +02:00
|
|
|
import * as YY from '../src/internals.js'
|
2025-04-12 14:44:37 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @template T
|
|
|
|
|
* @param {Array<[number, number, number, Array<T>]>} ops
|
|
|
|
|
*/
|
|
|
|
|
const simpleConstructAttrs = ops => {
|
2025-04-18 20:26:05 +02:00
|
|
|
const attrs = createIdMap()
|
2025-04-12 14:44:37 +02:00
|
|
|
ops.forEach(op => {
|
2025-04-29 22:42:56 +02:00
|
|
|
attrs.add(op[0], op[1], op[2], op[3].map(v => createAttributionItem('', v)))
|
2025-04-12 14:44:37 +02:00
|
|
|
})
|
|
|
|
|
return attrs
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {t.TestCase} _tc
|
|
|
|
|
*/
|
|
|
|
|
export const testAmMerge = _tc => {
|
|
|
|
|
const attrs = [42]
|
|
|
|
|
t.group('filter out empty items (1))', () => {
|
2025-04-30 22:12:09 +02:00
|
|
|
compareIdMaps(
|
2025-04-12 14:44:37 +02:00
|
|
|
simpleConstructAttrs([[0, 1, 0, attrs]]),
|
|
|
|
|
simpleConstructAttrs([])
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
t.group('filter out empty items (2))', () => {
|
2025-04-30 22:12:09 +02:00
|
|
|
compareIdMaps(
|
2025-04-12 14:44:37 +02:00
|
|
|
simpleConstructAttrs([[0, 1, 0, attrs], [0, 2, 0, attrs]]),
|
|
|
|
|
simpleConstructAttrs([])
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
t.group('filter out empty items (3 - end))', () => {
|
2025-04-30 22:12:09 +02:00
|
|
|
compareIdMaps(
|
2025-04-12 14:44:37 +02:00
|
|
|
simpleConstructAttrs([[0, 1, 1, attrs], [0, 2, 0, attrs]]),
|
|
|
|
|
simpleConstructAttrs([[0, 1, 1, attrs]])
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
t.group('filter out empty items (4 - middle))', () => {
|
2025-04-30 22:12:09 +02:00
|
|
|
compareIdMaps(
|
2025-04-12 14:44:37 +02:00
|
|
|
simpleConstructAttrs([[0, 1, 1, attrs], [0, 2, 0, attrs], [0, 3, 1, attrs]]),
|
|
|
|
|
simpleConstructAttrs([[0, 1, 1, attrs], [0, 3, 1, attrs]])
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
t.group('filter out empty items (5 - beginning))', () => {
|
2025-04-30 22:12:09 +02:00
|
|
|
compareIdMaps(
|
2025-04-12 14:44:37 +02:00
|
|
|
simpleConstructAttrs([[0, 1, 0, attrs], [0, 2, 1, attrs], [0, 3, 1, attrs]]),
|
|
|
|
|
simpleConstructAttrs([[0, 2, 1, attrs], [0, 3, 1, attrs]])
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
t.group('merge of overlapping id ranges', () => {
|
2025-04-30 22:12:09 +02:00
|
|
|
compareIdMaps(
|
2025-04-12 14:44:37 +02:00
|
|
|
simpleConstructAttrs([[0, 1, 2, attrs], [0, 0, 2, attrs]]),
|
|
|
|
|
simpleConstructAttrs([[0, 0, 3, attrs]])
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
t.group('construct without hole', () => {
|
2025-04-30 22:12:09 +02:00
|
|
|
compareIdMaps(
|
2025-04-12 14:44:37 +02:00
|
|
|
simpleConstructAttrs([[0, 1, 2, attrs], [0, 3, 1, attrs]]),
|
|
|
|
|
simpleConstructAttrs([[0, 1, 3, attrs]])
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
t.group('no merge of overlapping id ranges with different attributes', () => {
|
2025-04-30 22:12:09 +02:00
|
|
|
compareIdMaps(
|
2025-04-12 14:44:37 +02:00
|
|
|
simpleConstructAttrs([[0, 1, 2, [1]], [0, 0, 2, [2]]]),
|
2025-04-12 16:12:00 +02:00
|
|
|
simpleConstructAttrs([[0, 0, 1, [2]], [0, 1, 1, [1, 2]], [0, 2, 1, [1]]])
|
2025-04-12 14:44:37 +02:00
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {t.TestCase} tc
|
|
|
|
|
*/
|
2025-04-18 20:26:05 +02:00
|
|
|
export const testRepeatMergingMultipleIdMaps = tc => {
|
2025-04-12 14:44:37 +02:00
|
|
|
const clients = 4
|
2025-04-12 16:12:00 +02:00
|
|
|
const clockRange = 5
|
2025-04-12 14:44:37 +02:00
|
|
|
/**
|
2025-04-19 15:21:14 +02:00
|
|
|
* @type {Array<idmap.IdMap<number>>}
|
2025-04-12 14:44:37 +02:00
|
|
|
*/
|
|
|
|
|
const sets = []
|
|
|
|
|
for (let i = 0; i < 3; i++) {
|
2025-04-18 20:26:05 +02:00
|
|
|
sets.push(createRandomIdMap(tc.prng, clients, clockRange, [1, 2, 3]))
|
2025-04-12 14:44:37 +02:00
|
|
|
}
|
2025-04-19 15:21:14 +02:00
|
|
|
const merged = idmap.mergeIdMaps(sets)
|
|
|
|
|
const mergedReverse = idmap.mergeIdMaps(sets.reverse())
|
2025-04-30 22:12:09 +02:00
|
|
|
compareIdMaps(merged, mergedReverse)
|
2025-04-19 15:21:14 +02:00
|
|
|
const composed = idmap.createIdMap()
|
2025-04-12 14:44:37 +02:00
|
|
|
for (let iclient = 0; iclient < clients; iclient++) {
|
|
|
|
|
for (let iclock = 0; iclock < clockRange + 42; iclock++) {
|
2025-04-30 22:12:09 +02:00
|
|
|
const mergedHas = merged.hasId(new ID(iclient, iclock))
|
|
|
|
|
const oneHas = sets.some(ids => ids.hasId(new ID(iclient, iclock)))
|
2025-04-12 14:44:37 +02:00
|
|
|
t.assert(mergedHas === oneHas)
|
2025-05-09 20:34:18 +02:00
|
|
|
const mergedAttrs = merged.sliceId(new ID(iclient, iclock), 1)
|
2025-04-21 01:13:41 +02:00
|
|
|
mergedAttrs.forEach(a => {
|
|
|
|
|
if (a.attrs != null) {
|
|
|
|
|
composed.add(iclient, a.clock, a.len, a.attrs)
|
|
|
|
|
}
|
|
|
|
|
})
|
2025-04-12 14:44:37 +02:00
|
|
|
}
|
|
|
|
|
}
|
2025-04-30 22:12:09 +02:00
|
|
|
compareIdMaps(merged, composed)
|
2025-04-12 14:44:37 +02:00
|
|
|
}
|
|
|
|
|
|
2025-04-12 17:20:21 +02:00
|
|
|
/**
|
|
|
|
|
* @param {t.TestCase} tc
|
|
|
|
|
*/
|
|
|
|
|
export const testRepeatRandomDiffing = tc => {
|
|
|
|
|
const clients = 4
|
|
|
|
|
const clockRange = 100
|
|
|
|
|
const attrs = [1, 2, 3]
|
2025-04-19 15:33:09 +02:00
|
|
|
const idset1 = createRandomIdMap(tc.prng, clients, clockRange, attrs)
|
|
|
|
|
const idset2 = createRandomIdMap(tc.prng, clients, clockRange, attrs)
|
|
|
|
|
const merged = idmap.mergeIdMaps([idset1, idset2])
|
|
|
|
|
const e1 = idmap.diffIdMap(idset1, idset2)
|
|
|
|
|
const e2 = idmap.diffIdMap(merged, idset2)
|
2025-04-30 22:12:09 +02:00
|
|
|
compareIdMaps(e1, e2)
|
2025-04-19 15:15:34 +02:00
|
|
|
const copy = YY.decodeIdMap(YY.encodeIdMap(e1))
|
2025-04-30 22:12:09 +02:00
|
|
|
compareIdMaps(e1, copy)
|
2025-04-12 17:20:21 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {t.TestCase} tc
|
|
|
|
|
*/
|
|
|
|
|
export const testRepeatRandomDiffing2 = tc => {
|
|
|
|
|
const clients = 4
|
|
|
|
|
const clockRange = 100
|
|
|
|
|
const attrs = [1, 2, 3]
|
2025-04-19 15:21:14 +02:00
|
|
|
const idmap1 = createRandomIdMap(tc.prng, clients, clockRange, attrs)
|
|
|
|
|
const idmap2 = createRandomIdMap(tc.prng, clients, clockRange, attrs)
|
2025-04-12 17:20:21 +02:00
|
|
|
const idsExclude = createRandomIdSet(tc.prng, clients, clockRange)
|
2025-04-19 15:21:14 +02:00
|
|
|
const merged = idmap.mergeIdMaps([idmap1, idmap2])
|
|
|
|
|
const mergedExcluded = idmap.diffIdMap(merged, idsExclude)
|
|
|
|
|
const e1 = idmap.diffIdMap(idmap1, idsExclude)
|
|
|
|
|
const e2 = idmap.diffIdMap(idmap2, idsExclude)
|
|
|
|
|
const excludedMerged = idmap.mergeIdMaps([e1, e2])
|
2025-04-30 22:12:09 +02:00
|
|
|
compareIdMaps(mergedExcluded, excludedMerged)
|
2025-04-19 15:15:34 +02:00
|
|
|
const copy = YY.decodeIdMap(YY.encodeIdMap(mergedExcluded))
|
2025-04-30 22:12:09 +02:00
|
|
|
compareIdMaps(mergedExcluded, copy)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {t.TestCase} tc
|
|
|
|
|
*/
|
|
|
|
|
export const testRepeatRandomDeletes = tc => {
|
|
|
|
|
const clients = 1
|
|
|
|
|
const clockRange = 100
|
|
|
|
|
const idset = createRandomIdMap(tc.prng, clients, clockRange, [])
|
|
|
|
|
const client = Array.from(idset.clients.keys())[0]
|
|
|
|
|
const clock = prng.int31(tc.prng, 0, clockRange)
|
|
|
|
|
const len = prng.int31(tc.prng, 0, math.round((clockRange - clock) * 1.2)) // allow exceeding range to cover more edge cases
|
|
|
|
|
const idsetOfDeletes = idmap.createIdMap()
|
|
|
|
|
idsetOfDeletes.add(client, clock, len, [])
|
|
|
|
|
const diffed = idmap.diffIdMap(idset, idsetOfDeletes)
|
|
|
|
|
idset.delete(client, clock, len)
|
|
|
|
|
for (let i = 0; i < len; i++) {
|
|
|
|
|
t.assert(!idset.has(client, clock + i))
|
|
|
|
|
}
|
|
|
|
|
compareIdMaps(idset, diffed)
|
2025-04-12 17:20:21 +02:00
|
|
|
}
|
2025-05-09 20:34:18 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {t.TestCase} tc
|
|
|
|
|
*/
|
|
|
|
|
export const testrepeatRandomIntersects = tc => {
|
|
|
|
|
const clients = 4
|
|
|
|
|
const clockRange = 100
|
|
|
|
|
const ids1 = createRandomIdMap(tc.prng, clients, clockRange, [1])
|
|
|
|
|
const ids2 = createRandomIdMap(tc.prng, clients, clockRange, ['two'])
|
|
|
|
|
const intersected = idmap.intersectMaps(ids1, ids2)
|
|
|
|
|
for (let client = 0; client < clients; client++) {
|
|
|
|
|
for (let clock = 0; clock < clockRange; clock++) {
|
|
|
|
|
t.assert((ids1.has(client, clock) && ids2.has(client, clock)) === intersected.has(client, clock))
|
|
|
|
|
/**
|
|
|
|
|
* @type {Array<any>?}
|
|
|
|
|
*/
|
|
|
|
|
const slice1 = ids1.slice(client, clock, 1)[0].attrs
|
|
|
|
|
/**
|
|
|
|
|
* @type {Array<any>?}
|
|
|
|
|
*/
|
|
|
|
|
const slice2 = ids2.slice(client, clock, 1)[0].attrs
|
|
|
|
|
/**
|
|
|
|
|
* @type {Array<any>?}
|
|
|
|
|
*/
|
|
|
|
|
const expectedAttrs = (slice1 != null && slice2 != null) ? slice1.concat(slice2) : null
|
|
|
|
|
const attrs = intersected.slice(client, clock, 1)[0].attrs
|
|
|
|
|
t.assert(attrs?.length === expectedAttrs?.length)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const diffed1 = idmap.diffIdMap(ids1, ids2)
|
|
|
|
|
const altDiffed1 = idmap.diffIdMap(ids1, intersected)
|
|
|
|
|
compareIdMaps(diffed1, altDiffed1)
|
|
|
|
|
}
|