@@ -3,9 +3,12 @@ import assert from 'node:assert/strict';
33import fc from 'fast-check' ;
44import { readFileSync } from 'node:fs' ;
55import * as ohm from 'ohm-js' ;
6+ import process from 'node:process' ;
67
78import { scriptRel , wasmMatcherForGrammar } from './_helpers.js' ;
89
10+ const verbose = process . argv . slice ( 2 ) . includes ( '--verbose' ) ;
11+
912const grammarSource = readFileSync ( scriptRel ( 'data/liquid-html.ohm' ) , 'utf8' ) ;
1013const ns = ohm . grammars ( grammarSource ) ;
1114
@@ -58,30 +61,34 @@ const validInput = `{% comment %}
5861
5962// Take some valid input, randomly corrupt it, and then check that the
6063// rightmostFailurePosition is the same as the JS parser reports.
61- const checkFailurePos = matcher =>
62- fc . property (
63- fc . nat ( ) , // Position to corrupt
64- fc . integer ( { min : 1 , max : 20 } ) , // Number of characters to corrupt
65- ( posSeed , numChars ) => {
66- const pos = posSeed % Math . max ( 1 , validInput . length - numChars ) ;
67-
68- // Remove a slice of random amount of characters
69- const newInput = validInput . slice ( 0 , pos ) + validInput . slice ( pos + numChars ) ;
70-
71- matcher . setInput ( newInput ) ;
72- fc . pre ( matcher . match ( ) === 0 ) ;
73-
74- return (
75- matcher . getRightmostFailurePosition ( ) ===
76- ns . LiquidHTML . match ( newInput ) . getRightmostFailurePosition ( )
77- ) ;
78- } ,
79- ) ;
80-
81- // eslint-disable-next-line ava/no-skip-test
64+ function arbitraryEdit ( input ) {
65+ return fc
66+ . tuple (
67+ fc . nat ( { max : input . length - 1 } ) , // Position to edit
68+ fc . integer ( { min : 1 , max : 20 } ) // Number of characters to delete
69+ )
70+ . map ( ( [ pos , numDeleted ] ) => {
71+ return input . slice ( 0 , pos ) + input . slice ( pos + numDeleted ) ;
72+ } ) ;
73+ }
74+
75+ // A fast-check property that checks that:
76+ // - for some randomly-corrupted input, which fails to parse
77+ // - the rightmostFailurePosition reported by a JS matcher and a Wasm matcher
78+ // is the same.
79+ const sameFailurePos = ( t , wasmMatcher ) =>
80+ fc . property ( arbitraryEdit ( validInput ) , input => {
81+ wasmMatcher . setInput ( input ) ;
82+ fc . pre ( wasmMatcher . match ( ) === 0 ) ;
83+ assert . equal (
84+ ns . LiquidHTML . match ( input ) . getRightmostFailurePosition ( ) ,
85+ wasmMatcher . getRightmostFailurePosition ( )
86+ ) ;
87+ } ) ;
88+
8289test ( 'failure pos (fast-check)' , async t => {
8390 const m = await wasmMatcherForGrammar ( ns . LiquidHTML ) ;
84- t . notThrows ( ( ) => fc . assert ( checkFailurePos ( m ) , { numRuns : 50 } ) ) ;
91+ t . notThrows ( ( ) => fc . assert ( sameFailurePos ( t , m ) , { verbose , includeErrorInReport : true } ) ) ;
8592} ) ;
8693
8794test ( 'failure pos: basic 1' , async t => {
@@ -177,3 +184,11 @@ test('failure pos: space skipping', async t => {
177184 t . is ( failurePos ( wasmMatcher , '9 /* bad' ) , 2 ) ;
178185 }
179186} ) ;
187+
188+ test ( 'fast-check zoo' , async t => {
189+ const jsMatcher = ns . LiquidHTML . matcher ( ) ;
190+ const wasmMatcher = await wasmMatcherForGrammar ( ns . LiquidHTML ) ;
191+
192+ const input = '< {% if swatch_value %}' ;
193+ t . is ( failurePos ( wasmMatcher , input ) , failurePos ( jsMatcher , input ) ) ;
194+ } ) ;
0 commit comments