;;; ekp-fuzz.el --- property-based stress test for ekp -*- lexical-binding: t; -*- (require 'ekp) (require 'cl-lib) (ekp-c-module-load) (unless ekp-c-module-loaded (error "C module required for parity fuzz")) (defvar fuzz--seed 42) (defun fuzz--rand (n) ; deterministic LCG so failures are reproducible (setq fuzz--seed (mod (+ (* fuzz--seed 1103515245) 12345) 2147483648)) (mod fuzz--seed n)) (defconst fuzz--cjk "中文排版是门艺术需要考虑标点悬挂避头尾规则同时兼顾美观") (defconst fuzz--words '("the" "quick" "hyphenation" "emergency" "extraordinary" "a" "of" "supercalifragilisticexpialidocious" "bcdfghjklmnpqrstvwxz" "word!" "(paren)" "don't" "test," "end." "«quoted»" "naïve" "Full" "123")) (defconst fuzz--puncts '("," "。" "、" "《" "》" "「" "」" "!" "?")) (defconst fuzz--policy-tokens '("https://example.test/a_b" "src/core/file_name.el" "processKeyword42" "3.14MB" "100px")) (defun fuzz--maybe-policy-propertize (token) "Return TOKEN with deterministic public policy annotations sometimes." (pcase (fuzz--rand 8) (0 (propertize token 'ekp-break-policy 'normal)) (1 (propertize token 'ekp-break-policy 'hyphenate)) (2 (propertize token 'ekp-break-policy 'no-hyphen)) (3 (propertize token 'ekp-no-break t)) (4 (propertize token 'face 'ekp-fuzz-inline-code)) (_ token))) (defun fuzz--gen-string () "Random mixed paragraph of 5-60 tokens." (let ((n (+ 5 (fuzz--rand 56))) (parts nil)) (dotimes (_ n) (pcase (fuzz--rand 11) ;; latin word ((or 0 1 2 3) (push (fuzz--maybe-policy-propertize (nth (fuzz--rand (length fuzz--words)) fuzz--words)) parts) (push " " parts)) ;; CJK run ((or 4 5 6 7) (let ((len (1+ (fuzz--rand 6))) (start (fuzz--rand (- (length fuzz--cjk) 7)))) (push (substring fuzz--cjk start (+ start len)) parts))) ;; CJK punct (8 (push (nth (fuzz--rand (length fuzz--puncts)) fuzz--puncts) parts)) ;; spaces / zwsp (9 (push (if (= 0 (fuzz--rand 3)) "​" " ") parts)) ;; built-in policy token categories (10 (push (fuzz--maybe-policy-propertize (nth (fuzz--rand (length fuzz--policy-tokens)) fuzz--policy-tokens)) parts) (push " " parts)))) (string-trim (apply #'concat (nreverse parts))))) (defun fuzz--content (s) (replace-regexp-in-string "[ \t\n​-]+" "" (substring-no-properties s))) (ekp-param-set 5 2 1 4 2 1 0 3 0) (let ((cases 300) (fails 0)) (dotimes (i cases) (let* ((s (fuzz--gen-string)) (w (+ 1 (fuzz--rand 300)))) (unless (string-blank-p s) (condition-case err (let (el cr) (setq ekp-use-c-module nil) (ekp-clear-caches) (setq el (ekp-pixel-justify s w)) (setq ekp-use-c-module t) (ekp-clear-caches) (setq cr (ekp-pixel-justify s w)) ;; ① parity (unless (equal el cr) (cl-incf fails) (message "PARITY FAIL #%d w=%d s=%S" i w s)) ;; ② content preservation (unless (equal (fuzz--content el) (fuzz--content s)) (cl-incf fails) (message "CONTENT FAIL #%d w=%d s=%S" i w s)) ;; ③ finite cost (unless (numberp (ekp-total-cost s w)) (cl-incf fails) (message "COST FAIL #%d w=%d" i w))) (error (cl-incf fails) (message "ERROR #%d w=%d s=%S err=%S" i w s err)))))) (message "fuzz done: %d cases, %d failures" cases fails) (kill-emacs (if (> fails 0) 1 0)))