2025-04-08 20:50:20 +02:00
|
|
|
import * as t from 'lib0/testing'
|
|
|
|
|
import * as d from '../src/utils/IdSet.js'
|
2025-04-30 22:12:09 +02:00
|
|
|
import * as math from 'lib0/math'
|
|
|
|
|
import * as prng from 'lib0/prng'
|
2025-04-12 17:20:21 +02:00
|
|
|
import { compareIdSets, createRandomIdSet, ID } from './testHelper.js'
|
2025-04-08 20:50:20 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {Array<[number, number, number]>} ops
|
|
|
|
|
*/
|
|
|
|
|
const simpleConstructIdSet = ops => {
|
2025-04-19 15:33:09 +02:00
|
|
|
const idset = d.createIdSet()
|
2025-04-08 20:50:20 +02:00
|
|
|
ops.forEach(op => {
|
2025-04-19 15:33:09 +02:00
|
|
|
d.addToIdSet(idset, op[0], op[1], op[2])
|
2025-04-08 20:50:20 +02:00
|
|
|
})
|
2025-04-19 15:33:09 +02:00
|
|
|
return idset
|
2025-04-08 20:50:20 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {t.TestCase} _tc
|
|
|
|
|
*/
|
|
|
|
|
export const testIdsetMerge = _tc => {
|
|
|
|
|
t.group('filter out empty items (1))', () => {
|
|
|
|
|
compareIdSets(
|
|
|
|
|
simpleConstructIdSet([[0, 1, 0]]),
|
|
|
|
|
simpleConstructIdSet([])
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
t.group('filter out empty items (2))', () => {
|
|
|
|
|
compareIdSets(
|
|
|
|
|
simpleConstructIdSet([[0, 1, 0], [0, 2, 0]]),
|
|
|
|
|
simpleConstructIdSet([])
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
t.group('filter out empty items (3 - end))', () => {
|
|
|
|
|
compareIdSets(
|
|
|
|
|
simpleConstructIdSet([[0, 1, 1], [0, 2, 0]]),
|
|
|
|
|
simpleConstructIdSet([[0, 1, 1]])
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
t.group('filter out empty items (4 - middle))', () => {
|
|
|
|
|
compareIdSets(
|
|
|
|
|
simpleConstructIdSet([[0, 1, 1], [0, 2, 0], [0, 3, 1]]),
|
|
|
|
|
simpleConstructIdSet([[0, 1, 1], [0, 3, 1]])
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
t.group('filter out empty items (5 - beginning))', () => {
|
|
|
|
|
compareIdSets(
|
|
|
|
|
simpleConstructIdSet([[0, 1, 0], [0, 2, 1], [0, 3, 1]]),
|
|
|
|
|
simpleConstructIdSet([[0, 2, 1], [0, 3, 1]])
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
t.group('merge of overlapping id ranges', () => {
|
|
|
|
|
compareIdSets(
|
|
|
|
|
simpleConstructIdSet([[0, 1, 2], [0, 0, 2]]),
|
|
|
|
|
simpleConstructIdSet([[0, 0, 3]])
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
t.group('construct without hole', () => {
|
|
|
|
|
compareIdSets(
|
|
|
|
|
simpleConstructIdSet([[0, 1, 2], [0, 3, 1]]),
|
|
|
|
|
simpleConstructIdSet([[0, 1, 3]])
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {t.TestCase} _tc
|
|
|
|
|
*/
|
|
|
|
|
export const testDiffing = _tc => {
|
|
|
|
|
t.group('simple case (1))', () => {
|
|
|
|
|
compareIdSets(
|
2025-04-12 17:20:21 +02:00
|
|
|
d.diffIdSet(
|
2025-04-08 20:50:20 +02:00
|
|
|
simpleConstructIdSet([[0, 1, 1], [0, 3, 1]]),
|
|
|
|
|
simpleConstructIdSet([[0, 3, 1]])
|
|
|
|
|
),
|
|
|
|
|
simpleConstructIdSet([[0, 1, 1]])
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
t.group('subset left', () => {
|
|
|
|
|
compareIdSets(
|
2025-04-12 17:20:21 +02:00
|
|
|
d.diffIdSet(
|
2025-04-08 20:50:20 +02:00
|
|
|
simpleConstructIdSet([[0, 1, 3]]),
|
|
|
|
|
simpleConstructIdSet([[0, 1, 1]])
|
|
|
|
|
),
|
|
|
|
|
simpleConstructIdSet([[0, 2, 2]])
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
t.group('subset right', () => {
|
|
|
|
|
compareIdSets(
|
2025-04-12 17:20:21 +02:00
|
|
|
d.diffIdSet(
|
2025-04-08 20:50:20 +02:00
|
|
|
simpleConstructIdSet([[0, 1, 3]]),
|
|
|
|
|
simpleConstructIdSet([[0, 3, 1]])
|
|
|
|
|
),
|
|
|
|
|
simpleConstructIdSet([[0, 1, 2]])
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
t.group('subset middle', () => {
|
|
|
|
|
compareIdSets(
|
2025-04-12 17:20:21 +02:00
|
|
|
d.diffIdSet(
|
2025-04-08 20:50:20 +02:00
|
|
|
simpleConstructIdSet([[0, 1, 3]]),
|
|
|
|
|
simpleConstructIdSet([[0, 2, 1]])
|
|
|
|
|
),
|
|
|
|
|
simpleConstructIdSet([[0, 1, 1], [0, 3, 1]])
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
t.group('overlapping left', () => {
|
|
|
|
|
compareIdSets(
|
2025-04-12 17:20:21 +02:00
|
|
|
d.diffIdSet(
|
2025-04-08 20:50:20 +02:00
|
|
|
simpleConstructIdSet([[0, 1, 3]]),
|
|
|
|
|
simpleConstructIdSet([[0, 0, 2]])
|
|
|
|
|
),
|
|
|
|
|
simpleConstructIdSet([[0, 2, 2]])
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
t.group('overlapping right', () => {
|
|
|
|
|
compareIdSets(
|
2025-04-12 17:20:21 +02:00
|
|
|
d.diffIdSet(
|
2025-04-08 20:50:20 +02:00
|
|
|
simpleConstructIdSet([[0, 1, 3]]),
|
|
|
|
|
simpleConstructIdSet([[0, 3, 5]])
|
|
|
|
|
),
|
|
|
|
|
simpleConstructIdSet([[0, 1, 2]])
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
t.group('overlapping completely', () => {
|
|
|
|
|
compareIdSets(
|
2025-04-12 17:20:21 +02:00
|
|
|
d.diffIdSet(
|
2025-04-08 20:50:20 +02:00
|
|
|
simpleConstructIdSet([[0, 1, 3]]),
|
|
|
|
|
simpleConstructIdSet([[0, 0, 5]])
|
|
|
|
|
),
|
|
|
|
|
simpleConstructIdSet([])
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
t.group('overlapping into new range', () => {
|
|
|
|
|
compareIdSets(
|
2025-04-12 17:20:21 +02:00
|
|
|
d.diffIdSet(
|
2025-04-08 20:50:20 +02:00
|
|
|
simpleConstructIdSet([[0, 1, 3], [0, 5, 2]]),
|
|
|
|
|
simpleConstructIdSet([[0, 0, 6]])
|
|
|
|
|
),
|
|
|
|
|
simpleConstructIdSet([[0, 6, 1]])
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {t.TestCase} tc
|
|
|
|
|
*/
|
|
|
|
|
export const testRepeatRandomDiffing = tc => {
|
|
|
|
|
const clients = 4
|
|
|
|
|
const clockRange = 100
|
2025-04-12 17:20:21 +02:00
|
|
|
const ds1 = createRandomIdSet(tc.prng, clients, clockRange)
|
|
|
|
|
const ds2 = createRandomIdSet(tc.prng, clients, clockRange)
|
2025-04-08 20:50:20 +02:00
|
|
|
const merged = d.mergeIdSets([ds1, ds2])
|
2025-04-12 17:20:21 +02:00
|
|
|
const e1 = d.diffIdSet(ds1, ds2)
|
|
|
|
|
const e2 = d.diffIdSet(merged, ds2)
|
2025-04-08 20:50:20 +02:00
|
|
|
compareIdSets(e1, e2)
|
|
|
|
|
}
|
2025-04-12 14:44:37 +02:00
|
|
|
|
2025-04-30 22:12:09 +02:00
|
|
|
/**
|
|
|
|
|
* @param {t.TestCase} tc
|
|
|
|
|
*/
|
|
|
|
|
export const testRepeatRandomDeletes = tc => {
|
|
|
|
|
const clients = 1
|
|
|
|
|
const clockRange = 100
|
|
|
|
|
const idset = createRandomIdSet(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 = d.createIdSet()
|
|
|
|
|
idsetOfDeletes.add(client, clock, len)
|
|
|
|
|
const diffed = d.diffIdSet(idset, idsetOfDeletes)
|
|
|
|
|
idset.delete(client, clock, len)
|
|
|
|
|
for (let i = 0; i < len; i++) {
|
|
|
|
|
t.assert(!idset.has(client, clock + i))
|
|
|
|
|
}
|
|
|
|
|
compareIdSets(idset, diffed)
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-12 14:44:37 +02:00
|
|
|
/**
|
|
|
|
|
* @param {t.TestCase} tc
|
|
|
|
|
*/
|
|
|
|
|
export const testRepeatMergingMultipleIdsets = tc => {
|
|
|
|
|
const clients = 4
|
|
|
|
|
const clockRange = 100
|
|
|
|
|
/**
|
|
|
|
|
* @type {Array<d.IdSet>}
|
|
|
|
|
*/
|
|
|
|
|
const idss = []
|
|
|
|
|
for (let i = 0; i < 3; i++) {
|
2025-04-12 17:20:21 +02:00
|
|
|
idss.push(createRandomIdSet(tc.prng, clients, clockRange))
|
2025-04-12 14:44:37 +02:00
|
|
|
}
|
|
|
|
|
const merged = d.mergeIdSets(idss)
|
|
|
|
|
const mergedReverse = d.mergeIdSets(idss.reverse())
|
|
|
|
|
compareIdSets(merged, mergedReverse)
|
|
|
|
|
const composed = d.createIdSet()
|
|
|
|
|
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 = idss.some(ids => ids.hasId(new ID(iclient, iclock)))
|
2025-04-12 14:44:37 +02:00
|
|
|
t.assert(mergedHas === oneHas)
|
2025-04-12 16:12:00 +02:00
|
|
|
if (oneHas) {
|
|
|
|
|
d.addToIdSet(composed, iclient, iclock, 1)
|
|
|
|
|
}
|
2025-04-12 14:44:37 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
compareIdSets(merged, composed)
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-12 17:20:21 +02:00
|
|
|
/**
|
|
|
|
|
* @param {t.TestCase} tc
|
|
|
|
|
*/
|
|
|
|
|
export const testRepeatRandomDiffing2 = tc => {
|
|
|
|
|
const clients = 4
|
|
|
|
|
const clockRange = 100
|
|
|
|
|
const ids1 = createRandomIdSet(tc.prng, clients, clockRange)
|
|
|
|
|
const ids2 = createRandomIdSet(tc.prng, clients, clockRange)
|
|
|
|
|
const idsExclude = createRandomIdSet(tc.prng, clients, clockRange)
|
|
|
|
|
const merged = d.mergeIdSets([ids1, ids2])
|
|
|
|
|
const mergedExcluded = d.diffIdSet(merged, idsExclude)
|
|
|
|
|
const e1 = d.diffIdSet(ids1, idsExclude)
|
|
|
|
|
const e2 = d.diffIdSet(ids2, idsExclude)
|
|
|
|
|
const excludedMerged = d.mergeIdSets([e1, e2])
|
|
|
|
|
compareIdSets(mergedExcluded, excludedMerged)
|
|
|
|
|
}
|
2025-05-09 20:34:18 +02:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {t.TestCase} tc
|
|
|
|
|
*/
|
|
|
|
|
export const testrepeatRandomIntersects = tc => {
|
|
|
|
|
const clients = 4
|
|
|
|
|
const clockRange = 100
|
|
|
|
|
const ids1 = createRandomIdSet(tc.prng, clients, clockRange)
|
|
|
|
|
const ids2 = createRandomIdSet(tc.prng, clients, clockRange)
|
|
|
|
|
const intersected = d.intersectSets(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))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
const diffed1 = d.diffIdSet(ids1, ids2)
|
|
|
|
|
const altDiffed1 = d.diffIdSet(ids1, intersected)
|
|
|
|
|
compareIdSets(diffed1, altDiffed1)
|
|
|
|
|
}
|