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

92.72% Statements 51/55
77.77% Branches 14/18
100% Functions 10/10
100% Lines 41/41

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 846x 6x     6x               6x           6x 90x     21485x 21485x   977086x 21485x 21485x       7265x 7265x 7183x 7183x 5802x       5717x 5717x 5626x 5626x 3009x       2877x 2677x 1222x 1222x 1222x 1222x       398x 339x 159x       5884x 5884x       3100x 3100x       1659x 1659x 394x 394x 390x   1265x 1265x        
import {DelOp, InsObjOp, InsStrOp, InsBinOp, InsArrOp} 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;
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;
    Iif (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;
    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);
    }
  }
}