Files
dc/sas/sasjs/macros/mpe_execute.sas
T
dc 68b89caf63
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
fix(mpe_abortmessage): read program and state by name, and trim the prefix
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.
2026-10-30 23:20:32 +00:00

167 lines
5.9 KiB
SAS

/**
@file
@brief Runs a DC service the way a test needs it run
@details Wraps mx_execute so that every call is measurable, using only
documented mx_execute outputs:
@li the child's log is kept when mdebug is on, through outlogds, and printed
as CHILD: lines. A normal run asks for no log, so it stays quiet - flip
sasjs_mdebug for an investigation run instead of editing tests;
@li the child's outcome is written to outstatus (STATE, URI, ERRMSG,
PLATFORM). state=canceled means the child aborted, so a test can assert
what happened rather than inferring it from an outlib that may still hold
the previous call's tables;
@li failfast aborts the caller as soon as the child is not completed, so the
first failure is the real one and the log names it. Tests that expect an
abort pass failfast=0 and check the status row. SAS 9 reports no job state
at all (PROC STP), which core records as state=unknown; that is treated as
unmeasured rather than failed, and noted in the log.
Usage:
%mpe_execute(&appLoc/services/editors/getdata,
viyacontext=&defaultcontext,
inputfiles=&f1:sascontroltable,
outlib=web1
)
%put reaching here means the child completed;
data _null_;
set work.dc_childstatus;
putlog "child said: " state= errmsg=;
run;
@param [in] program The _PROGRAM endpoint to run
@param [in] inputfiles=(0) fileref:filename pairs, as mx_execute
@param [in] inputdatasets=(0) datasets to send as CSV, as mx_execute
@param [in] inputparams=(0) name/value dataset, as mx_execute
@param [out] outlib=(0) Output libref for the response JSON
@param [out] outref=(0) Output fileref for the raw response text
@param [in] debug= (131) The _debug value sent to the child. Numeric, as the
server requires; 131 asks for the log.
@param [in] mdebug= (&sasjs_mdebug) 1 keeps the child's log and prints it
@param [in] viyacontext= (&defaultcontext) Viya compute context
@param [out] outstatus=(work.dc_childstatus) One row per child run
@param [in] failfast= (1) Abort the caller when the child did not complete
<h4> SAS Macros </h4>
@li mp_abort.sas
@li mpe_abortmessage.sas
@li mx_execute.sas
**/
%macro mpe_execute(program
,inputfiles=0
,inputdatasets=0
,inputparams=0
,outlib=0
,outref=0
,debug=
,mdebug=&sasjs_mdebug
,viyacontext=&defaultcontext
,outstatus=work.dc_childstatus
,viyaresult=WEBOUT_JSON
,failfast=1
);
%local logds state msg;
/* Aliases: passing &program / &inputfiles ... straight into another macro that
* declares the same parameter names makes SAS resolve them against the callee
* and null them ("recursive reference to the macro variable"). Unique names
* on this side keep the values intact. */
%local a_viyaresult a_pgm a_inds a_indatasets a_inparams a_dbg a_mdbg
a_outlib a_outref a_logds a_status a_ctx;
/* numeric: the server validates _debug as a number, so anything else never asks
* for the log and outlogds comes back empty */
%if %length(&debug)=0 %then %let debug=131;
%if %length(&mdebug)=0 %then %let mdebug=0;
%let a_pgm=&program;
%let a_viyaresult=&viyaresult;
%let a_inds=&inputfiles;
%let a_indatasets=&inputdatasets;
%let a_inparams=&inputparams;
%let a_dbg=&debug;
%let a_mdbg=&mdebug;
%let a_outlib=&outlib;
%let a_outref=&outref;
%let a_status=&outstatus;
%let a_ctx=&viyacontext;
%if &mdebug>0 %then %let logds=work.dc_childlog;
%else %let logds=_null_;
%let a_logds=&logds;
/* a status row from an earlier call must not be read as this call's outcome:
a child that dies before core writes one would otherwise look completed */
%if %index(&a_status,.)>0 and %sysfunc(exist(&a_status)) %then %do;
proc datasets library=%scan(&a_status,1,.) nolist;
delete %scan(&a_status,2,.);
quit;
%end;
%mx_execute(&a_pgm
,inputfiles=&a_inds
,inputdatasets=&a_indatasets
,inputparams=&a_inparams
,debug=&a_dbg
,mdebug=&a_mdbg
,viyacontext=&a_ctx
,outlib=&a_outlib
,outref=&a_outref
,outlogds=&a_logds
,outstatus=&a_status
,viyaresult=&a_viyaresult
)
/* the child's own log, for an investigation run */
%if &mdebug>0 %then %do;
data _null_;
set &a_status;
putlog "DC_EXECUTE: " state= errmsg=;
run;
%if %sysfunc(exist(work.dc_childlog)) %then %do;
data _null_;
set work.dc_childlog;
putlog "CHILD: " line;
run;
%end;
%end;
/* fail fast - a child that did not complete must not leave the test running
* against whatever the previous call left in outlib. A missing status table
* and an empty one are both failures: state would stay blank and the caller
* would carry on inferring, which is the thing this macro exists to stop. */
%let state=;
%let msg=;
%if %sysfunc(exist(&a_status))=1 %then %do;
data _null_;
set &a_status;
call symputx('state',state,'l');
call symputx('msg',errmsg,'l');
run;
%end;
%else %let state=missing(&a_status);
%if %length(&failfast)=0 %then %let failfast=1;
%if &failfast=1 %then %do;
/* SAS 9 (PROC STP) reports no job status at all: core writes state='unknown'
* with that explanation on a call that worked, so it cannot be read as a
* failure - only as unmeasured. Everything else that is not completed is a
* failure, a blank state included. */
%if "%upcase(&state)"="UNKNOWN" %then
%put NOTE: &sysmacroname: &program returned no job status (SAS 9) - the outcome was not measured;
%else %if "%upcase(&state)" ne "COMPLETED" %then %do;
/* the child's own message is carried into the abort verbatim - see
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, msgvar=msg)
%mp_abort(
iftrue=(1=1)
,mac=&sysmacroname
,msg=%superq(_mpe_abortmsg)
)
%end;
%end;
%mend mpe_execute;