Files
yjs/tests/AttributionManager.tests.js

136 lines
4.3 KiB
JavaScript
Raw Normal View History

2025-04-12 14:44:37 +02:00
import * as t from 'lib0/testing'
import * as am from '../src/utils/AttributionManager.js'
import * as prng from 'lib0/prng'
import * as math from 'lib0/math'
import { compareAttributionManagers, createAttributionManager, ID } from './testHelper.js'
/**
* @template T
* @param {Array<[number, number, number, Array<T>]>} ops
*/
const simpleConstructAttrs = ops => {
const attrs = createAttributionManager()
ops.forEach(op => {
attrs.add(op[0], op[1], op[2], op[3])
})
return attrs
}
/**
* @template T
* @param {prng.PRNG} gen
* @param {number} clients
* @param {number} clockRange (max clock - exclusive - by each client)
* @param {Array<T>} attrChoices (max clock - exclusive - by each client)
* @return {am.AttributionManager<T>}
*/
const createRandomAttributionManager = (gen, clients, clockRange, attrChoices) => {
const maxOpLen = 5
const numOfOps = math.ceil((clients * clockRange) / maxOpLen)
const attrMngr = createAttributionManager()
for (let i = 0; i < numOfOps; i++) {
const client = prng.uint32(gen, 0, clients - 1)
const clockStart = prng.uint32(gen, 0, clockRange)
const len = prng.uint32(gen, 0, clockRange - clockStart)
const attrs = [prng.oneOf(gen, attrChoices)]
2025-04-12 16:12:00 +02:00
// maybe add another attr
2025-04-12 14:44:37 +02:00
if (prng.bool(gen)) {
2025-04-12 16:12:00 +02:00
const a = prng.oneOf(gen, attrChoices)
if (attrs.find((attr => attr === a)) == null) {
attrs.push(a)
}
2025-04-12 14:44:37 +02:00
}
attrMngr.add(client, clockStart, len, attrs)
}
return attrMngr
}
/**
* @param {t.TestCase} _tc
*/
export const testAmMerge = _tc => {
const attrs = [42]
t.group('filter out empty items (1))', () => {
compareAttributionManagers(
simpleConstructAttrs([[0, 1, 0, attrs]]),
simpleConstructAttrs([])
)
})
t.group('filter out empty items (2))', () => {
compareAttributionManagers(
simpleConstructAttrs([[0, 1, 0, attrs], [0, 2, 0, attrs]]),
simpleConstructAttrs([])
)
})
t.group('filter out empty items (3 - end))', () => {
compareAttributionManagers(
simpleConstructAttrs([[0, 1, 1, attrs], [0, 2, 0, attrs]]),
simpleConstructAttrs([[0, 1, 1, attrs]])
)
})
t.group('filter out empty items (4 - middle))', () => {
compareAttributionManagers(
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))', () => {
compareAttributionManagers(
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', () => {
compareAttributionManagers(
simpleConstructAttrs([[0, 1, 2, attrs], [0, 0, 2, attrs]]),
simpleConstructAttrs([[0, 0, 3, attrs]])
)
})
t.group('construct without hole', () => {
compareAttributionManagers(
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', () => {
compareAttributionManagers(
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
*/
export const testRepeatMergingMultipleAttrManagers = tc => {
const clients = 4
2025-04-12 16:12:00 +02:00
const clockRange = 5
2025-04-12 14:44:37 +02:00
/**
* @type {Array<am.AttributionManager<number>>}
*/
const sets = []
for (let i = 0; i < 3; i++) {
sets.push(createRandomAttributionManager(tc.prng, clients, clockRange, [1, 2, 3]))
}
const merged = am.mergeAttributionManagers(sets)
const mergedReverse = am.mergeAttributionManagers(sets.reverse())
compareAttributionManagers(merged, mergedReverse)
const composed = am.createAttributionManager()
for (let iclient = 0; iclient < clients; iclient++) {
for (let iclock = 0; iclock < clockRange + 42; iclock++) {
const mergedHas = merged.has(new ID(iclient, iclock))
const oneHas = sets.some(ids => ids.has(new ID(iclient, iclock)))
t.assert(mergedHas === oneHas)
const mergedAttrs = merged.slice(new ID(iclient, iclock), 1)
if (mergedAttrs) {
mergedAttrs.forEach(a => {
composed.add(iclient, a.clock, a.len, a.attrs)
})
}
}
}
compareAttributionManagers(merged, composed)
}