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.
187 lines
6.6 KiB
SAS
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;
|