Files
dc/sas/sasjs/macros/mpe_execute.sas
T
dc 33efc5ae1e
Build / Build-and-ng-test (pull_request) Successful in 5m42s
Lighthouse Checks / lighthouse (pull_request) Successful in 18m48s
Build / Build-and-test-development (pull_request) Failing after 13m0s
chore(lint): enforce an 80 character line and give the oversize files exact ceilings
sas/.sasjslint takes maxLineLength from 180 to 80 - the linter's own default -
and promotes it to "error".

53 of the 258 .sas files already hold a line longer than 80. Rewrapping them
would be a large, reviewable-only-in-theory diff, so each keeps its current
longest line as an exact ceiling via a per-file override, e.g.
@sasjslint {"maxLineLength": 95} in services/validations/tables_all.sas. A
file with no override is held to 80, and an overridden file cannot grow past
the length it has today: the ceiling is the file's longest line, not a round
number.

Both directions were checked: a 101 character line added to an
un-overridden file fails with "Line exceeds maximum length by 21 characters",
and a 114 character line added to macros/mpe_execute.sas (ceiling 101) fails
by 13. `npm run lint:sas` is clean with the setting in force.
2026-11-01 10:21:06 +00:00

187 lines
6.6 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 mx_execute.sas
@sasjslint {"maxLineLength": 101}
**/
%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. */
%global _mpe_abortmsg;
%let _mpe_abortmsg=;
%let state=;
%let msg=;
%if %sysfunc(exist(&a_status))=1 %then %do;
data _null_;
length _prog $32767 _state $32767 _prefix $32767 _childmsg $32767
_out $32767 _room 8;
set &a_status;
call symputx('state',state,'l');
call symputx('msg',errmsg,'l');
/* the text the caller sees is assembled here rather than with %let: the
child's message is arbitrary text, and a %, an & or a quote in it would
be macro resolved on the way into a symbol. symget/symputx hand it over
untouched, and a message already at the 32767 limit is cut to the room
left rather than overflowed by the join. */
if upcase(state) ne 'COMPLETED' then do;
_prog=symget('a_pgm');
_state=symget('state');
_childmsg=symget('msg');
_prefix=cat(trim(_prog),' did not complete: state=',trim(_state));
_room=32767-length(trim(_prefix))-2;
if _room<1 then _room=0;
if length(_childmsg)>_room then _childmsg=substr(_childmsg,1,_room);
if missing(_childmsg) then _out=trim(_prefix);
else _out=trim(_prefix)||' '||_childmsg;
call symputx('_mpe_abortmsg',_out,'g');
end;
run;
%end;
%else %do;
%let state=missing(&a_status);
%let _mpe_abortmsg=&a_pgm did not complete: state=&state;
%end;
%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;
%mp_abort(
iftrue=(1=1)
,mac=&sysmacroname
,msg=%superq(_mpe_abortmsg)
)
%end;
%end;
%mend mpe_execute;