From 0a52d20c5fc27b4adde5fc8ed6f39a58c48f62ff Mon Sep 17 00:00:00 2001 From: "dc@4gl.io" Date: Tue, 27 Oct 2026 14:49:55 +0000 Subject: [PATCH] fix(mpe_assert_abort): take the abort's own MSG, tolerate a missing response Three defects in the scan: - 's/.*"MSG" *: *"([^"]*)".*/$1/' is greedy, so a response carrying more than one MSG compared the last one rather than the abort's. The pattern is now matched with prxmatch and the capture read with prxposn, which returns the first match at or after the sasjsAbort marker. - the whole response was concatenated into $32767, so a longer response truncated silently and an abort near the tail was missed. The file is now scanned a line at a time and the scan stops at the first MSG. - 'infile &outref' had no fileref check, so a child that died before writing a response raised an unassigned-fileref error instead of recording a FAIL. --- sas/sasjs/macros/mpe_assert_abort.sas | 54 +++++++++++++++++++-------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/sas/sasjs/macros/mpe_assert_abort.sas b/sas/sasjs/macros/mpe_assert_abort.sas index 185c777..b5b0243 100644 --- a/sas/sasjs/macros/mpe_assert_abort.sas +++ b/sas/sasjs/macros/mpe_assert_abort.sas @@ -7,6 +7,14 @@ match it, so the assertion proves the service aborted for the expected reason rather than for any reason at all. + The response is scanned a line at a time and only the first MSG that follows + the sasjsAbort marker is taken: a single 32767 buffer both truncated longer + responses and, with a leading `.*`, returned the last MSG in the text rather + than the abort's own. + + An unassigned outref (a child that died before writing a response) records a + FAIL rather than raising an unassigned-fileref error in the data step. + Usage: %mpe_execute(&_program, @@ -33,31 +41,45 @@ **/ %macro mpe_assert_abort(desc, outref, msg=0, outds=work.test_results); -%local ok seen; +%local ok seen abortseen; %let ok=0; %let seen=; +%let abortseen=0; +%if %sysfunc(fileref(&outref))>0 %then %do; + %put WARNING: &sysmacroname: fileref &outref is not assigned - the child left no response to read; +%end; +%else %do; data _null_; - length resp $32767 got $256; - retain resp ''; + length line $32767 got $256; + retain prxid 0 inabort 0 found 0 abortseen 0; + if _n_=1 then prxid=prxparse('/"MSG" *: *"([^"]*)"/'); infile &outref end=eof lrecl=32767; - /* the abort object can span lines, so read the whole response first */ do while(not eof); input; - resp=cats(resp,_infile_); - end; - if index(resp,'sasjsAbort')>0 then do; - if prxmatch('/"MSG" *: *"([^"]*)"/',resp) then - got=prxchange('s/.*"MSG" *: *"([^"]*)".*/$1/',-1,resp); - call symputx('seen',got); - %if "%superq(msg)" ne "0" %then %do; - if got="%superq(msg)" then call symputx('ok',1); - %end; - %else %do; - call symputx('ok',1); - %end; + line=_infile_; + if index(line,'sasjsAbort')>0 then do; + abortseen=1; + inabort=1; + end; + /* first MSG at or after the abort marker - stops at the first found */ + if inabort=1 and found=0 then do; + if prxmatch(prxid,line)>0 then do; + got=prxposn(prxid,1,line); + found=1; + end; + end; end; + call symputx('abortseen',abortseen,'l'); + if found=1 then call symputx('seen',got,'l'); + stop; run; +%end; + +%if &abortseen=1 %then %do; + %if "%superq(msg)"="0" %then %let ok=1; + %else %if "&seen"="%superq(msg)" %then %let ok=1; +%end; %if "&seen" ne "" %then %put NOTE: &sysmacroname: sasjsAbort MSG=&seen; %mp_assert(