Addresses the review on #412. The prefix comparison is gone instead of guarded. mp_abort appends "Log Extract:" and the surrounding log to every message it returns; the capture now cuts at that marker and strips the escaped newlines before it, so the comparison is an exact match again. That removes the hole the reviewer found - a blank expectation can no longer pass, because %length 0 no longer short-circuits anything - and it keeps the expectations strict, so a change in the child's wording still fails the test rather than sliding past on a matching prefix. mpe_execute: the assembled message declares its own length. An inline symget() is typed by the data step default, which would truncate a long child message before it reached the abort. The expected literals in stagedata.test.4 are unchanged by this commit: with exact matching restored, stagedata passes 5/5, which is the confirmation the reviewer asked for - they are the child's text, not text adjusted to fit the observed log. Full suite 64/64.
175 lines
6.2 KiB
SAS
175 lines
6.2 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
|
|
|
|
**/
|
|
|
|
%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 interpolated into the abort, and it can
|
|
contain commas - which would split this call into extra positional
|
|
parameters. Assembling the text in a data step and passing it through
|
|
%superq keeps it as one argument without leaving macro mask characters
|
|
in what the user reads. */
|
|
%local abortmsg;
|
|
data _null_;
|
|
/* explicit length: an inline symget() is typed by the data step default,
|
|
which truncates a long child message */
|
|
length _abortmsg $32767;
|
|
_abortmsg=catx(' ',"&program did not complete: state=&state"
|
|
,symget('msg'));
|
|
call symputx('abortmsg',_abortmsg,'l');
|
|
run;
|
|
%mp_abort(
|
|
iftrue=(1=1)
|
|
,mac=&sysmacroname
|
|
,msg=%superq(abortmsg)
|
|
)
|
|
%end;
|
|
%end;
|
|
|
|
%mend mpe_execute;
|