Two review points. The strip looped over trailing \n and \r escapes, so a message that itself ended in an escaped newline lost that escape along with the separator and the exact comparison failed. It now removes one fixed tail: the six characters the separator becomes once mp_abort doubles the backslashes, or four if the extract is not doubled. Anything the message itself ended with is left for the unescape. mpe_execute sets _mpe_abortmsg before calling mpe_abortmessage, so a build where the helper is not on the autocall path still aborts with the program and state rather than an empty message. Full suite 64/64, mpe_assert_abort PASS, sasjs cbd before the run.
171 lines
6.1 KiB
SAS
171 lines
6.1 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.
|
|
The %let is the fallback for a build where the helper is not on the
|
|
autocall path: the abort still names the program and state rather than
|
|
arriving blank. */
|
|
%let _mpe_abortmsg=%superq(program) did not complete: state=%superq(state);
|
|
%mpe_abortmessage(program=&program, state=&state, msgvar=msg)
|
|
%mp_abort(
|
|
iftrue=(1=1)
|
|
,mac=&sysmacroname
|
|
,msg=%superq(_mpe_abortmsg)
|
|
)
|
|
%end;
|
|
%end;
|
|
|
|
%mend mpe_execute;
|