All files / json-crdt/__tests__/fuzzer Picker.ts

94.73% Statements 54/57
84.21% Branches 16/19
100% Functions 10/10
100% Lines 42/42

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 856x 6x     6x               6x           6x 90x     21110x 21110x   1096584x 21110x 21110x       7437x 7437x 7345x 7345x 5881x       5390x 5390x 5293x 5289x 2795x       2811x 2593x 1157x 1157x 1157x 1157x       385x 340x 167x 77x       5973x 5973x       2892x 2892x       1658x 1658x 421x 421x 417x   1237x 1237x        
import {DelOp, InsObjOp, InsStrOp, InsBinOp, InsArrOp, UpdArrOp} from '../../../json-crdt-patch/operations';
import {RandomJson} from '@jsonjoy.com/util/lib/json-random';
import type {JsonNode, ObjNode, ArrNode, BinNode, StrNode} from '../../nodes';
import type {Model} from '../../model/Model';
import {Fuzzer} from '@jsonjoy.com/util/lib/Fuzzer';
import type {FuzzerOptions} from './types';
 
type StringOp = typeof InsStrOp | typeof DelOp;
type BinaryOp = typeof InsBinOp | typeof DelOp;
type ArrayOp = typeof InsArrOp | typeof DelOp | typeof UpdArrOp;
type ObjectOp = typeof InsObjOp | typeof DelOp;
 
const commonKeys = ['a', 'op', 'test', 'name', '', '__proto__'];
 
/**
 * This class picks random nodes from a model and picks a random
 * operation to apply to that node.
 */
export class Picker {
  constructor(public opts: FuzzerOptions) {}
 
  public pickNode(model: Model): JsonNode | null {
    const nodes: JsonNode[] = [];
    const index = model.index;
    // biome-ignore lint: index is not iterable
    index.forEach(({v: node}) => nodes.push(node));
    Iif (!nodes.length) return null;
    return Fuzzer.pick(nodes);
  }
 
  public pickStringOperation(node: StrNode): StringOp {
    const length = node.length();
    if (!length) return InsStrOp;
    Iif (length >= this.opts.maxStringLength) return DelOp;
    if (Math.random() < this.opts.stringDeleteProbability) return DelOp;
    return InsStrOp;
  }
 
  public pickBinaryOperation(node: BinNode): BinaryOp {
    const length = node.length();
    if (!length) return InsBinOp;
    if (length >= this.opts.maxStringLength) return DelOp;
    if (Math.random() < this.opts.binaryDeleteProbability) return DelOp;
    return InsBinOp;
  }
 
  public pickObjectOperation(node: ObjNode): [key: string, opcode: ObjectOp] {
    if (!node.keys.size) return [this.generateObjectKey(), InsObjOp];
    if (Math.random() > 0.45) return [this.generateObjectKey(), InsObjOp];
    const keys = [...node.keys.keys()];
    Iif (!keys.length) return [this.generateObjectKey(), InsObjOp];
    const key = Fuzzer.pick(keys);
    return [key, DelOp];
  }
 
  public pickArrayOperation(node: ArrNode): ArrayOp {
    if (!node.length()) return InsArrOp;
    if (Math.random() > 0.45) return InsArrOp;
    if (Math.random() > 0.45) return UpdArrOp;
    else return DelOp;
  }
 
  public generateSubstring(): string {
    const length = Math.floor(Math.random() * this.opts.maxSubstringLength) + 1;
    return RandomJson.genString(length);
  }
 
  public generateBinaryData(): Uint8Array {
    const length = Math.floor(Math.random() * this.opts.maxBinaryChunkLength) + 1;
    return RandomJson.genBinary(length);
  }
 
  public generateObjectKey(): string {
    const useCommonKey = Math.random() < 0.25;
    if (useCommonKey) {
      const str = Fuzzer.pick(commonKeys);
      if (this.opts.noProtoString && str === '__proto__') return this.generateObjectKey();
      return str;
    } else {
      const length = Math.floor(Math.random() * 20) + 1;
      return RandomJson.genString(length);
    }
  }
}