covertable
Version:
Efficient TypeScript library for pairwise testing, generating minimal covering arrays with constraint support.
2 lines (1 loc) • 14.6 kB
JavaScript
"use strict";class x extends Error{constructor(t,e=[]){super(t),this.uncoveredPairs=e}}const b=(i,t,e=1)=>Array.from({length:(t-i-1)/e+1},(s,n)=>i+n*e),T=(...i)=>{const t=i[0].length;return b(0,t).map(e=>i.map(s=>s[e]))},k=(i,t)=>{const e=t-1,s=[],n=b(0,t);for(;n[0]<i.length-e;){s.push(n.map(r=>i[r])),n[e]++;for(let r=e;r>0;r--)n[r]>=i.length-(e-r)&&n[r-1]++;for(let r=1;r<=e;r++)n[r]>=i.length-(e-r)&&(n[r]=n[r-1]+1)}return s},A=(...i)=>{const t=[],e=(s,n)=>{if(s.length===i.length){t.push(s);return}for(let r of i[n])e([...s,r],n+1)};return e([],0),t},B=i=>Array.isArray(i)?i.length:Object.keys(i).length,S=i=>Array.isArray(i)?i.map((t,e)=>[e,t]):i instanceof Map?[...i.entries()]:[...Object.entries(i)],U=(i,t)=>{const e=i.map(s=>t.get(s)||0);return T(e,i)},M=(i,t)=>i>t?1:-1,v=i=>{const t=i.reduce((e,s)=>e*s);return Number.isSafeInteger(t)?t:i.sort(M).toString()},W=i=>{if(i%2===0)return!1;let t=b(3,Math.sqrt(i)+1,2);for(;t.length>0;){const e=t[0];if(i%e===0)return!1;t=t.filter(s=>s%e!==0)}return!0};function*$(){yield 2;for(let i=3;;i+=2)W(i)&&(yield i)}const F=i=>{let t=2166136261;for(let e=0;e<i.length;e++)t^=i.charCodeAt(e),t=Math.imul(t,16777619);return(t>>>0).toString(16).padStart(8,"0")};function q(i,t){const{salt:e,indices:s}=t,n=(r,o)=>{const l=`${r.map(c=>s.get(c))} ${e}`,a=`${o.map(c=>s.get(c))} ${e}`;return F(l)>F(a)?1:-1};return i.sort(n)}const D=(i,t,e,s)=>{let n=0;for(const r of e)if(r===2){const o=[...i],l=o.length;for(let a=0;a<l-1;a++){const c=o[a];for(let h=a+1;h<l;h++){const f=c*o[h];t.has(f)&&(!s||!s.has(f))&&n++}}}else for(let o of k([...i],r)){const l=v(o);t.has(l)&&(!s||!s.has(l))&&n++}return n};function*E(i){var e,s;const t=(((s=(e=i.options)==null?void 0:e.constraints)==null?void 0:s.length)??0)>0;for(;;){let n=null,r=null;const o=new Set;if(i.row.size>0)for(const l of i.allStrengths)for(const a of k([...i.row.values()],l))o.add(v(a));for(const[l,a]of i.incomplete.entries()){const c=i.row.size;if(i.isFilled(i.row))break;if(o.has(l)||i.row.invalidPairs.has(l))continue;const h=c===0?a.length:i.isCompatible(a);if(h===null||h===0)continue;let f=h;if(t){const m=i.storable(i.getCandidate(a));if(m===null||m===0)continue;f=m}const u=Math.abs(f),{tolerance:y=0}=i.options,g=D(new Set([...i.row.values(),...a]),i.incomplete,i.allStrengths,o);if(g+y>c*u){r=a;break}(n===null||n<g)&&(n=g,r=a)}if(r===null)break;yield r}}function L(i,t){const e=t.split(".");let s=i;for(const n of e){if(s==null)return;s=s[n]}return s}const V={add:(i,t)=>i+t,sub:(i,t)=>i-t,mul:(i,t)=>i*t,div:(i,t)=>i/t,mod:(i,t)=>i%t,pow:(i,t)=>i**t};function K(i,t){if(typeof t=="string")return L(i,t);const e=K(i,t.left);if(e===void 0)return;const s="right"in t?K(i,t.right):t.value;if(s===void 0)return;const n=V[t.operator];return n(e,s)}function j(i){if(typeof i=="string")return new Set([i.split(".")[0]]);const t=j(i.left);if("right"in i)for(const e of j(i.right))t.add(e);return t}function O(i){switch(i.operator){case"not":return O(i.condition);case"and":case"or":{const t=new Set;for(const e of i.conditions)for(const s of O(e))t.add(s);return t}case"fn":return new Set(i.requires);default:{const t=j(i.left);if("right"in i)for(const e of j(i.right))t.add(e);return t}}}const R={eq:(i,t)=>i===t,ne:(i,t)=>i!==t,gt:(i,t)=>i>t,lt:(i,t)=>i<t,gte:(i,t)=>i>=t,lte:(i,t)=>i<=t,in:(i,t)=>t.has(i)};function d(i,t,e={}){switch(i.operator){case"not":{const s=d(i.condition,t,e);return s===null?null:!s}case"and":{let s=!1;for(const n of i.conditions){const r=d(n,t,e);if(r===!1)return!1;r===null&&(s=!0)}return s?null:!0}case"or":{let s=!1;for(const n of i.conditions){const r=d(n,t,e);if(r===!0)return!0;r===null&&(s=!0)}return s?null:!1}case"fn":{for(const s of i.requires)if(L(t,s)===void 0)return null;return i.evaluate(t)}case"in":{const s=K(t,i.left);return s===void 0?null:(e.in??R.in)(s,i.values)}default:{const s=K(t,i.left);if(s===void 0)return null;let n;if("right"in i){if(n=K(t,i.right),n===void 0)return null}else n=i.value;return(e[i.operator]??R[i.operator])(s,n)}}}class p extends Map{constructor(t){super(),this.invalidPairs=new Set;for(const[e,s]of t)this.set(e,s)}getPairKey(...t){const e=[...this.values(),...t];return v(e)}copy(t){for(let[e,s]of t.entries())this.set(e,s)}}class _{constructor(t,e={}){this.factors=t,this.options=e,this.serials=new Map,this.parents=new Map,this.indices=new Map,this.incomplete=new Map,this.rejected=new Set,this._totalPairs=0,this._prunedPairs=0,this._rowCount=0,this._uncoveredPairs=[],this._completions={},this.constraints=[],this.constraintsByKey=new Map,this.passedIndexes=new Set,this.comparer=e.comparer??{},this.serialize(t),this.factorLength=B(t),this.factorIsArray=t instanceof Array,this.resolveConstraints(),this.setIncomplete(),this._totalPairs=this.incomplete.size,this.row=new p([]);for(const[s,n]of this.incomplete.entries()){const r=this.getCandidate(n);if(this.storableCheck(r)===!1)this.incomplete.delete(s);else if(this.constraints.length>0){const o=new p(r);this.forwardCheck(o)||this.incomplete.delete(s)}}this._prunedPairs=this._totalPairs-this.incomplete.size}get stats(){return{totalPairs:this._totalPairs,prunedPairs:this._prunedPairs,coveredPairs:this._totalPairs-this._prunedPairs-this.incomplete.size,progress:this._totalPairs===0?0:1-this.incomplete.size/this._totalPairs,rowCount:this._rowCount,uncoveredPairs:this._uncoveredPairs,completions:this._completions}}static normalizeCondition(t){return t.operator==="in"&&Array.isArray(t.values)?{...t,values:new Set(t.values)}:t.operator==="and"||t.operator==="or"?{...t,conditions:t.conditions.map(_.normalizeCondition)}:t.operator==="not"?{...t,condition:_.normalizeCondition(t.condition)}:t}resolveConstraints(){const t=this.options.constraints??[];for(let e=0;e<t.length;e++){const s=_.normalizeCondition(t[e]),n=O(s);this.constraints.push({condition:s,keys:n});for(const r of n){let o=this.constraintsByKey.get(r);o||(o=new Set,this.constraintsByKey.set(r,o)),o.add(e)}}}serialize(t){let e=0;const s=$();S(t).map(([n,r])=>{const o=B(r),l=[];b(e,e+o).map(a=>{const c=s.next().value;l.push(c),this.parents.set(c,n),this.indices.set(c,a)}),this.serials.set(n,l),e+=o})}setIncomplete(){const{sorter:t=q,salt:e=""}=this.options,s=[],n=S(this.serials).map(([a,c])=>a),r=this.options.subModels??[],o=r.map(a=>new Set(a.fields)),l=a=>o.some(c=>a.every(h=>c.has(h)));for(const a of k(n,this.strength)){if(l(a))continue;const c=b(0,this.strength).map(h=>this.serials.get(a[h]));for(let h of A(...c))s.push(h.sort(M))}for(const a of r)for(const c of k(a.fields,a.strength)){const h=b(0,a.strength).map(f=>this.serials.get(c[f]));for(let f of A(...h))s.push(f.sort(M))}for(let a of t(s,{salt:e,indices:this.indices}))this.incomplete.set(v(a),a)}setPair(t){const e=this.getCandidate(t),s=new p([...this.row.entries(),...e]),n=this.toObject(s);for(let r=0;r<this.constraints.length;r++){if(this.passedIndexes.has(r))continue;if(d(this.constraints[r].condition,n,this.comparer)===!1)return!1}if(!this.forwardCheck(s))return!1;for(const[r,o]of e)this.row.set(r,o);return this.markPassedConstraints(this.row),!0}consumePairs(t){for(const e of this.allStrengths)for(let s of k([...t.values()],e)){const n=v(s);this.incomplete.delete(n)}}getCandidate(t){return U(t,this.parents)}isCompatible(t){let e=0;for(const s of t){const n=this.parents.get(s),r=this.row.get(n);if(typeof r>"u")e++;else if(r!==s)return null}return e}storableCheck(t,e=this.row){if(this.constraints.length===0)return!0;let s=null;for(let n=0;n<this.constraints.length;n++){if(this.passedIndexes.has(n))continue;if(s===null){const o=new p([...e.entries(),...t]);s=this.toObject(o)}if(d(this.constraints[n].condition,s,this.comparer)===!1)return!1}return!0}storable(t,e=this.row){let s=0;for(let[r,o]of t){let l=e.get(r);if(typeof l>"u")s++;else if(l!=o)return null}return this.storableCheck(t,e)===!1?null:s}markPassedConstraints(t){if(this.constraints.length===0)return!0;let e=null;for(let s=0;s<this.constraints.length;s++){if(this.passedIndexes.has(s))continue;e===null&&(e=this.toObject(t));const n=d(this.constraints[s].condition,e,this.comparer);if(n===!0)this.passedIndexes.add(s);else if(n===!1)return!1}return!0}forwardCheck(t){if(this.constraints.length===0)return!0;const e=new Map;for(const[o,l]of this.serials.entries())t.has(o)||e.set(o,[...l]);if(e.size===0)return!0;const s=new Set(this.constraints.map((o,l)=>l)),n=new p([...t.entries()]);let r=!0;for(;r;){r=!1;for(const[o,l]of e.entries()){if(l.length===0)return!1;const a=this.constraintsByKey.get(o);if(!a)continue;let c=!1;for(const f of a)if(s.has(f)){c=!0;break}if(!c)continue;const h=[];for(const f of l){n.set(o,f);const u=this.toObject(n);let y=!0;for(const g of a)if(!this.passedIndexes.has(g)&&d(this.constraints[g].condition,u,this.comparer)===!1){y=!1;break}y&&h.push(f)}if(n.delete(o),h.length===0)return!1;if(h.length<l.length&&(e.set(o,h),r=!0,h.length===1)){n.set(o,h[0]),e.delete(o);const f=this.constraintsByKey.get(o);if(f)for(const u of f)s.add(u)}if(h.length>1)for(const[f,u]of e.entries()){if(f===o)continue;const y=this.constraintsByKey.get(f);if(!y)continue;let g=!1;for(const w of y)if(a.has(w)){g=!0;break}if(!g)continue;const m=new Set;for(const w of h){n.set(o,w);for(const C of u){if(m.has(C))continue;n.set(f,C);const N=this.toObject(n);let I=!0;for(const z of y)if(!this.passedIndexes.has(z)&&d(this.constraints[z].condition,N,this.comparer)===!1){I=!1;break}I&&m.add(C)}n.delete(f)}n.delete(o);const P=u.filter(w=>m.has(w));if(P.length===0)return!1;if(P.length<u.length&&(e.set(f,P),r=!0,P.length===1)){n.set(f,P[0]),e.delete(f);const w=this.constraintsByKey.get(f);if(w)for(const C of w)s.add(C)}}}}return!0}isFilled(t){return t.size===this.factorLength}toMap(t){const e=new Map;for(let[s,n]of t.entries()){const r=this.indices.get(n),o=this.indices.get(this.serials.get(s)[0]);e.set(s,this.factors[s][r-o])}return e}toObject(t){const e={};for(let[s,n]of this.toMap(t).entries())e[s]=n;return e}reset(){this.row=new p([]),this.passedIndexes.clear()}restore(){const t=this.row;if(this.row=new p([]),this.passedIndexes.clear(),this.factorIsArray){const e=this.toMap(t);return S(e).sort((s,n)=>s[0]>n[0]?1:-1).map(([s,n])=>n)}return this.toObject(t)}close(){const t=S(this.serials),e=[];for(const[a,c]of t)this.row.has(a)||e.push({key:a,values:this.orderByWeight(a,c)});if(e.length===0)return this.passedIndexes.clear(),this.markPassedConstraints(this.row),this.isComplete?!0:{conflictKeys:this.findConflictKeys()};const s=new p([...this.row.entries()]),n=e.length,r=new Array(n).fill(0);let o=0,l=null;for(s.set(e[0].key,e[0].values[0]);;){const a=e[o],c=a.values[r[o]],h=[[a.key,c]];if(this.storable(h,s)!==null)if(o===n-1){if(this.row.copy(s),this.passedIndexes.clear(),this.markPassedConstraints(this.row),this.isComplete)return!0;l=this.findConflictKeys()}else{o++,r[o]=0,s.set(e[o].key,e[o].values[0]);continue}for(;;){if(r[o]++,r[o]<e[o].values.length){s.set(e[o].key,e[o].values[r[o]]);break}if(s.delete(e[o].key),o--,o<0)return this.reset(),{conflictKeys:l}}}}get strength(){return this.options.strength||2}get allStrengths(){const t=new Set([this.strength]);for(const e of this.options.subModels??[])t.add(e.strength);return[...t]}valueToSerial(t,e){const s=this.factors[t];if(!s)return null;const n=s.indexOf(e);if(n===-1)return null;const r=this.serials.get(t);return r?r[n]:null}applyPreset(t){const e=[];for(const[s,n]of S(t)){const r=this.valueToSerial(s,n);if(r===null)return!1;e.push([s,r])}if(e.length===0)return!1;for(const[s,n]of e)this.row.set(s,n);return!0}orderByWeight(t,e){const s=this.options.weights;if(!s)return e;const n=s[t];if(!n)return e;const r=this.indices.get(e[0]);return[...e].sort((o,l)=>{const a=n[this.indices.get(o)-r]??1;return(n[this.indices.get(l)-r]??1)-a})}get isComplete(){if(!this.isFilled(this.row))return!1;if(this.constraints.length===0)return!0;const t=this.toObject(this.row);for(let e=0;e<this.constraints.length;e++){if(this.passedIndexes.has(e))continue;if(d(this.constraints[e].condition,t,this.comparer)===!1)return!1}return!0}findConflictKeys(){if(!this.isFilled(this.row))return null;const t=this.toObject(this.row);for(let e=0;e<this.constraints.length;e++)if(!this.passedIndexes.has(e)&&d(this.constraints[e].condition,t,this.comparer)===!1)return this.constraints[e].keys;return null}diagnoseUncoveredPairs(){const t=[];for(const[,e]of this.incomplete.entries()){const s=this.getCandidate(e),n={};for(const[a,c]of s){const h=this.indices.get(c),f=this.serials.get(a),u=this.indices.get(f[0]);n[a]=this.factors[a][h-u]}const r=new p(s),o=this.toObject(r),l=[];for(let a=0;a<this.constraints.length;a++)d(this.constraints[a].condition,o,this.comparer)===!1&&l.push(a);if(l.length===0){const a=new Set(Object.keys(n));for(let c=0;c<this.constraints.length;c++)for(const h of this.constraints[c].keys)if(a.has(h)){l.push(c);break}}t.push({pair:n,constraints:l})}return t}recordCompletions(t,e){for(const[s,n]of t.entries()){if(e.has(s))continue;const r=this.indices.get(n),o=this.serials.get(s),l=this.indices.get(o[0]),a=String(this.factors[s][r-l]),c=String(s);this._completions[c]||(this._completions[c]={}),this._completions[c][a]=(this._completions[c][a]??0)+1}}get progress(){return this.stats.progress}make(){return[...this.makeAsync()]}*makeAsync(){const{criterion:t=E,presets:e=[]}=this.options;for(const n of e){if(!this.applyPreset(n))continue;const r=new Set(this.row.keys());try{this.close()===!0&&(this.recordCompletions(this.row,r),this.consumePairs(this.row),this._rowCount++,yield this.restore())}catch(o){if(o instanceof x)this.row=new p([]),this.passedIndexes.clear();else throw o}}let s=0;for(;this.incomplete.size;){for(let r of t(this)){if(this.isFilled(this.row))break;const o=v(r);this.row.invalidPairs.has(o)||this.setPair(r)||this.row.invalidPairs.add(o)}const n=new Set(this.row.keys());try{if(this.close()===!0)this.recordCompletions(this.row,n),this.consumePairs(this.row),this._rowCount++,yield this.restore(),s=0;else{if(s++,s>this.incomplete.size)break;const o=this.row.invalidPairs;for(const l of this.allStrengths)for(let a of k([...this.row.values()],l))o.add(v(a));this.reset(),this.rejected.clear();for(const l of o)this.row.invalidPairs.add(l)}}catch(r){if(r instanceof x){this.reset(),this.rejected.clear();continue}throw r}}for(const[,n]of[...this.incomplete.entries()]){if(this.reset(),!this.setPair(n))continue;const r=new Set(this.row.keys());this.close()===!0?(this.recordCompletions(this.row,r),this.consumePairs(this.row),this._rowCount++,yield this.restore()):this.reset()}this.incomplete.size>0&&(this._uncoveredPairs=this.diagnoseUncoveredPairs())}}exports.Controller=_;exports.NeverMatch=x;exports.greedy=E;exports.hash=q;