fix(mpe_abortmessage): read program and state by name, and trim the prefix
Build / Build-and-ng-test (pull_request) Successful in 4m52s
Lighthouse Checks / lighthouse (pull_request) Successful in 17m58s
Build / Build-and-test-development (pull_request) Successful in 29m26s

Three things, one of them a bug the review question uncovered.

- msgvar is passed explicitly at the call site, so renaming the message variable
  fails loudly instead of quietly producing an abort text with no child message.
- program and state are read with symget rather than interpolated into the data
  step, so a % or & in an endpoint name can no longer be macro-resolved, and a
  quote cannot break the literal.
- _prefix is trimmed before the message is appended. Declared as a character
  variable it arrives padded to length, so the child's message was landing past
  the buffer end and disappearing - the wrapper text was right and the message
  was simply not there. That showed up as the comma and blank cases failing while
  the "names the program state" case passed.
This commit is contained in:
dc committed 2026-10-30 23:20:32 +00:00
1 parent 7b09ade97c
commit 68b89caf63
3 files changed
+18 -6

No files matched your search

+11 -3
View File
@@ -28,12 +28,20 @@
data _null_;
/* explicit lengths: an inline symget() is typed by the data step default,
which truncates a long child message */
length _msg $32767 _out $32767;
length _prefix $32767 _msg $32767 _out $32767;
/* every value is read by name: interpolating program or state into this
data step would let a %, & or quote in an endpoint name be macro-resolved
or break the literal */
/* cat, not cats: cats strips the blanks around each argument, which would
close up the space before 'did' */
_prefix=cat(symget('program'),' did not complete: state=',symget('state'));
_msg=symget("&msgvar");
/* plain concatenation, not CATX: the child's message is kept byte for
byte, blank and all, so an exact comparison downstream can rely on it */
if missing(_msg) then _out="&program did not complete: state=&state";
else _out="&program did not complete: state=&state "||_msg;
/* trim the prefix: it is declared as a character variable, so it arrives
padded to length and the child's message would land past the buffer end */
if missing(_msg) then _out=trim(_prefix);
else _out=trim(_prefix)||' '||_msg;
call symputx('_mpe_abortmsg',_out,'g');
run;
%mend mpe_abortmessage;
+1 -1
View File
@@ -154,7 +154,7 @@
mpe_abortmessage, which builds the text so a comma in the child's message
cannot split the mp_abort call into extra parameters, and owns the global
it writes the text to (_mpe_abortmsg) rather than taking a name */
%mpe_abortmessage(program=&program, state=&state)
%mpe_abortmessage(program=&program, state=&state, msgvar=msg)
%mp_abort(
iftrue=(1=1)
,mac=&sysmacroname
+6 -2
View File
@@ -134,13 +134,17 @@ run;
* taking its value - a value argument would be split before the macro ran.
*/
/* a plain %let: %str would store its mask characters in the value, and the
macro reads the value with symget() */
macro reads the value with symget(). The same text is kept in csvmsg so both
sides of the comparison below go through %superq - the macro's value carries
its own quoting around the comma, and comparing that against a %str-masked
literal does not match */
%let msg=alpha, beta and gamma are not valid;
%let csvmsg=alpha, beta and gamma are not valid;
%mpe_abortmessage(program=/services/example, state=ABORTED)
%mp_assert(
iftrue=(%index(%superq(_mpe_abortmsg),%str(alpha, beta and gamma are not valid))>0),
iftrue=(%index(%superq(_mpe_abortmsg),%superq(csvmsg))>0),
desc=%str(Checking a comma in the child message survives into the abort text),
outds=work.test_results
)