Files
dc/sas/sasjs/macros/mpe_derived.test.sas
T
dc 7830b7bcfa
Build / Build-and-ng-test (pull_request) Successful in 5m25s
Build / Build-and-test-development (pull_request) Canceled after 0s
Lighthouse Checks / lighthouse (pull_request) Canceled after 3m52s
fix(derived): compare the outref as text, prove the file is empty
Review: '%if &outref ne 0' handed %EVAL the text 'derref5 ne 0', which needs a
numeric operand - so the catalog branch failed in exactly the case it was added
for, on every caller that passes a fileref.  Quoted, the default 0 still skips
the file and a fileref creates it.

The test now proves both halves: fopen tells us the file was created, and a
read loop counted zero records tells us it is empty.  A single INPUT is no
good - on an empty file it stops the step before the symputx, which is why the
first version recorded a FAIL.

The outvars placeholder keeps 'keep=name'; the comment records why that is the
whole column set: the engine builds the dataset in exactly two places and both
project NAME, and the only projection in the SAS tree is getdata.sas.
2026-10-27 21:08:22 +00:00

344 lines
7.8 KiB
SAS

/**
@file
@brief testing the mpe_derived macro
@details Covers the generated statements, the view, the metadata the callers
consume, and the dependency rules.
The configuration checks abort the run when they fail, so they are not
exercised here - this suite covers the behaviour that callers depend on.
<h4> SAS Macros </h4>
@li mf_existds.sas
@li mp_assert.sas
@li mp_assertcols.sas
@li mp_assertdsobs.sas
@li mp_assertscope.sas
@li mpe_derived.sas
@author 4GL Apps Ltd
@copyright 4GL Apps Ltd. This code may only be used within Data Controller
and may not be re-distributed or re-sold without the express permission of
4GL Apps Ltd.
**/
/* a source table, and a table with no derived variables */
data work.class;
set sashelp.class;
run;
data work.noconfig;
set sashelp.class;
run;
/* clean up any config left behind by an earlier run */
proc sql;
delete from &dclib..mpe_derived where upcase(libref)='WORK';
quit;
/**
* Test 1 - statements, view, chaining
*/
proc sql;
insert into &dclib..mpe_derived set
tx_from=0
,tx_to='31DEC5999:23:59:59'dt
,libref='WORK'
,dsn='CLASS'
,var_nm='BAND'
,var_deps='AGE'
,var_type='C'
,var_len=8
,var_fmt=''
,var_label='Age band'
,var_logic="ifc(AGE>13,'TEEN','JUNIOR')"
,active=1;
insert into &dclib..mpe_derived set
tx_from=0
,tx_to='31DEC5999:23:59:59'dt
,libref='WORK'
,dsn='CLASS'
,var_nm='BAND_AGE'
,var_deps='BAND AGE'
,var_type='C'
,var_len=16
,var_fmt=''
,var_logic="cats(BAND,'-',AGE)"
,active=1;
insert into &dclib..mpe_derived set
tx_from=0
,tx_to='31DEC5999:23:59:59'dt
,libref='WORK'
,dsn='CLASS'
,var_nm='DOUBLE_AGE'
,var_deps='AGE'
,var_type='N'
,var_len=8
,var_fmt=''
,var_logic='AGE * 2'
,active=1;
quit;
/* lower case libds on purpose - the config match must be case insensitive */
/* the setup SQL above creates the SQL automatic macro variables, so the
snapshot is taken here - the check is for the macro's own leakage */
%mp_assertscope(SNAPSHOT)
%mpe_derived(libds=work.class,
dclib=&dclib,
outref=derref1,
outview=work.vw_derived,
outvars=work.derived_vars
)
%mp_assertdsobs(work.derived_vars,
desc=Test1 - one row per derived variable,
test=EQUALS 3,
outds=work.test_results
)
%mp_assertcols(work.vw_derived,
desc=Test1 - the view carries the derived columns,
cols=BAND BAND_AGE DOUBLE_AGE,
test=ALL,
outds=work.test_results
)
data work.chk1;
set work.vw_derived;
where BAND='TEEN';
run;
%mp_assertdsobs(work.chk1,
desc=Test1 - the filter on a derived column selects rows,
test=HASOBS,
outds=work.test_results
)
/* the chained value only resolves if the statements run in order */
data work.chk1b;
set work.vw_derived;
where BAND='TEEN';
if BAND_AGE ne cats(BAND,'-',AGE) then output;
run;
%mp_assertdsobs(work.chk1b,
desc=Test1 - a derived variable can reference an earlier one,
test=EQUALS 0,
outds=work.test_results
)
data work.chk1c;
set work.vw_derived;
if DOUBLE_AGE ne AGE*2 then output;
run;
%mp_assertdsobs(work.chk1c,
desc=Test1 - the numeric derived value is correct,
test=EQUALS 0,
outds=work.test_results
)
/* proc contents on a view reports a format name without its width, so the
format is compared on the name only */
proc contents noprint data=work.vw_derived
out=work.vw_cols(keep=name label format);
run;
data work.chk1d;
set work.vw_cols;
where name='BAND' and label='Age band' and format=:'$';
run;
%mp_assertdsobs(work.chk1d,
desc=Test1 - the label and default format are applied,
test=EQUALS 1,
outds=work.test_results
)
/**
* Test 2 - a configured format is used as-is
*/
proc sql;
insert into &dclib..mpe_derived set
tx_from=0
,tx_to='31DEC5999:23:59:59'dt
,libref='WORK'
,dsn='CLASS'
,var_nm='FLAG_DT'
,var_deps=''
,var_type='N'
,var_len=8
,var_fmt='DATE9.'
,var_logic='mdy(1,1,2020)'
,active=1;
quit;
%mpe_derived(libds=WORK.CLASS,
dclib=&dclib,
outref=derref2,
outview=work.vw_derived2,
outvars=work.derived_vars2
)
/* asserted on the emitted statement - proc contents on a view does not
report the format in full */
data work.chk2;
infile derref2 truncover;
input line $4000.;
if index(line,'format=DATE9.')>0 then output;
run;
%mp_assertdsobs(work.chk2,
desc=Test2 - the configured format is applied,
test=EQUALS 1,
outds=work.test_results
)
data work.chk2b;
set work.vw_derived2;
if flag_dt ne mdy(1,1,2020) then output;
stop;
run;
%mp_assertdsobs(work.chk2b,
desc=Test2 - the numeric value is correct,
test=EQUALS 0,
outds=work.test_results
)
/**
* Test 3 - no configuration
*/
%mpe_derived(libds=WORK.NOCONFIG,
dclib=&dclib,
outref=derref3,
outview=work.vw_derived3,
outvars=work.derived_vars3
)
%mp_assertdsobs(work.derived_vars3,
desc=Test3 - no derived variables returned,
test=EQUALS 0,
outds=work.test_results
)
%mp_assert(iftrue=(%mf_existds(libds=work.vw_derived3)=0),
desc=Test3 - no view is built when there is no config,
outds=work.test_results
)
data _null_;
infile derref3 end=eof;
input;
if not eof or cats(_infile_) ne '' then do;
putlog 'ERR' 'OR: derived statement file is not empty';
call symputx('emptycheck','0','l');
end;
run;
%mp_assert(iftrue=(%symexist(emptycheck)=0),
desc=Test3 - no statements are generated,
outds=work.test_results
)
/**
* Test 4 - the wrapper scopes the config: only the current, active row of a
* table is used
*/
proc sql;
insert into &dclib..mpe_derived set
tx_from=0
,tx_to='01JAN2020:00:00:00'dt
,libref='WORK'
,dsn='CLASS'
,var_nm='CLOSED_OFF'
,var_deps='AGE'
,var_type='N'
,var_len=8
,var_fmt=''
,var_logic='AGE + 1'
,active=1;
insert into &dclib..mpe_derived set
tx_from=0
,tx_to='31DEC5999:23:59:59'dt
,libref='WORK'
,dsn='CLASS'
,var_nm='NOT_ACTIVE'
,var_deps='AGE'
,var_type='N'
,var_len=8
,var_fmt=''
,var_logic='AGE + 2'
,active=0;
quit;
%mpe_derived(libds=WORK.CLASS,
dclib=&dclib,
outref=derref4,
outview=work.vw_derived4,
outvars=work.derived_vars4
)
data work.chk4;
set work.derived_vars4;
where name in ('CLOSED_OFF','NOT_ACTIVE');
run;
%mp_assertdsobs(work.chk4,
desc=Test4 - a closed version and an inactive row are ignored,
test=EQUALS 0,
outds=work.test_results
)
/* tidy up so repeated runs start clean */
proc sql;
delete from &dclib..mpe_derived where upcase(libref)='WORK';
quit;
%mp_assertscope(COMPARE,ignorelist=_SASJS_WEBOUT_HEADERS,outds=work.test_results)
/* Test 5 - a format catalog is not a dataset: no derived variables, no abort.
Callers pass LIBREF.CATALOGNAME-FC (validatefilter, getrawdata, viewdata), and
the engine's existence check would otherwise end the service. */
proc format library=work.dcfmts;
value $band 'A'='Alpha';
run;
%mpe_derived(libds=work.DCFMTS-FC,
dclib=&dclib,
outref=derref5,
outvars=work.derived_vars5
)
%mp_assertdsobs(work.derived_vars5,
desc=Test5 - a format catalog yields no derived variables,
test=EQUALS 0,
outds=work.test_results
)
/* The branch has to create the statement file even though it writes nothing,
because callers %include it unconditionally. Two things to prove: the branch created the file, and it left it empty.
fopen answers the first (0 when the fileref cannot be opened), the loop the
second - a single INPUT would stop the step at once on an empty file and never
reach the symputx. */
%let deropen=0;
%let derlines=-1;
data _null_;
did=fopen('derref5');
call symputx('deropen',ifn(did>0,1,0),'l');
if did>0 then rc=fclose(did);
run;
data _null_;
infile derref5 end=eof;
n=0;
do while(not eof);
input;
n+1;
end;
call symputx('derlines',n,'l');
run;
%mp_assert(
iftrue=(&deropen=1 and &derlines=0),
desc=Test5 - a format catalog leaves an empty statement file,
outds=work.test_results
)