diff --git a/.gitignore b/.gitignore index 2d13a2c9..8c53ec64 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ __pycache__ .ropeproject report.xml +/base/pop/getpoploglib/scrapbook/ diff --git a/base/pop/getpoploglib/auto/assert.p b/base/pop/getpoploglib/auto/assert.p new file mode 100644 index 00000000..d2c0b44a --- /dev/null +++ b/base/pop/getpoploglib/auto/assert.p @@ -0,0 +1 @@ +uses define_unittest; \ No newline at end of file diff --git a/base/pop/getpoploglib/auto/define_unittest.p b/base/pop/getpoploglib/auto/define_unittest.p new file mode 100644 index 00000000..3a7f0889 --- /dev/null +++ b/base/pop/getpoploglib/auto/define_unittest.p @@ -0,0 +1,424 @@ +compile_mode :pop11 +strict; + +section $-unittest => + define_testsuite ;;; used for defining test collections + define_unittest ;;; used for defining unit tests + assert ;;; used for defining unit tests + expect_mishap ;;; used for defining unit tests + with_data ;;; used for defining unit tests with scenario data + register_unittest ;;; exported because of code-planting +; + + +constant unittest_suffix = '.test.p'; + +;;; --- expecting mishaps --- + +vars mishap_happened = false; + +vars _expect_mishap = false; + +define global active:1 expect_mishap(); + _expect_mishap; +enddefine; + +define updaterof active:1 expect_mishap( saved ); + saved -> _expect_mishap; + if not(saved) and not( mishap_happened ) do + mishap( 'Expected mishap was skipped', [] ) + endif; +enddefine; + +;;; --- test discovery and execution --- + +vars unittest_passes = undef; ;;; Part of test-execution. +vars unittest_failures = undef; ;;; Part of test-execution. +vars current_unittest = undef; ;;; Part of test-execution. + +;;; Part of test-discovery, although may be re-invoked during execution. +;;; At top-level do nothing. This will be dlocalised during test-discovery. +define vars register_unittest( u ); +enddefine; + +;;; Part of test-execution. +;;; This is defensive - we only want to run unit tests inside an appropriate +;;; dynamic context. +define vars run_unittest( p ); + mishap( 'Trying to trace unit tests with context (this should never happen)', [] ) +enddefine; + +defclass failureinfo { + failureinfo_unittest, + failureinfo_message, + failureinfo_idstring, + failureinfo_argv +}; + +;;; Part of test-execution. +;;; This will only be invoked at top-level and hence outside of a test +;;; context, so it is OK for it to simply mishap. +define vars fail_unittest( info ); + mishap( 'Unittest failed', [] ) +enddefine; + +define fail_unittest_during_execution( info ); + unittest_failures( info ); + exitfrom( run_unittest ) +enddefine; + +;;; This is the normal way to run a unit test & we will dlocalise run_unittest +;;; to its value. +define run_unittest_during_execution( p ); + + define lconstant is_expected_mishap( mess, idstring, severity, args ); + if isstartstring( 'unittest-assert:', idstring ) then + false + elseif expect_mishap == true then + true + elseif expect_mishap.isstring then + idstring = expect_mishap or mess = expect_mishap + elseif expect_mishap.isregexp then + expect_mishap( 1, idstring, false, false ) or expect_mishap( 1, mess, false, false ) + elseif expect_mishap.isprocedure and not( expect_mishap.isregexp ) then + expect_mishap( mess, idstring, severity, args ) + else + false + endif; + enddefine; + + define dlocal pop_exception_final( N, mess, idstring, severity ); + returnunless( severity == `E` or severity == `R` )( false ); + true -> mishap_happened; + lvars args = conslist( N ); + lvars is_expected = is_expected_mishap( mess, idstring, severity, args ); + if is_expected then + exitto( run_unittest ); + else + chain( consfailureinfo( current_unittest, mess, idstring, args ), fail_unittest ) + endif; + enddefine; + + dlocal current_unittest = p; + dlocal mishap_happened = false; + p(); + unittest_passes( p ); +enddefine; + +define discover_unittests( p ); + dlocal register_unittest = new_list_builder(); + erasenum(#| p() |#); + return( register_unittest( termin ) ) +enddefine; + +define run_all_unittests( unittest_list ); + dlocal unittest_passes = new_list_builder(); + dlocal unittest_failures = new_list_builder(); + [% applist( unittest_list, run_unittest ) %] -> _; ;;; defensive. + ( unittest_passes( termin ), unittest_failures( termin ) ) +enddefine; + + +;;; --- Discovery --- + +;;; This is a class to collect the results from discovery - especially inside VED. +defclass discovered { + discovered_unittests, + discovered_files_cache +}; + +define newdiscovered( unittests ); + consdiscovered( unittests, false ) +enddefine; + +define discovered_files( d ); + if d.discovered_files_cache then + d.discovered_files_cache + else + lvars t = ( + newanyproperty( + [], 8, 1, 8, + syshash, nonop =, "perm", + false, false + ) + ); + lvars u; + for u in d.discovered_unittests do + lvars ( parent, linenum ) = pdorigin( u ); + if parent.isstring do + true -> t( parent ) + endif + endfor; + nc_listsort( [% fast_appproperty( t, erase ) %], alphabefore ) ->> d.discovered_files_cache; + endif +enddefine; + + +;;; --- Syntax --- + +vars procedure unittest_sysVARS = sysVARS; + +define read_declaration( defdec ) -> ( pdrname, props, is_global, declarator ); + lvars attributes = ( + [% + repeat + lvars item = nextreaditem(); + if item == termin then + mishap( 'Unexpected end of input in unittest definition', [] ) + endif; + lvars id = identprops( item ); + quitunless( id.isword ); + quitunless( isstartstring( "syntax", id ) ); + quitif( item == ";" or item == "(" ); + readitem() + endrepeat + %] + ); + readitem() -> pdrname; + + false -> is_global; + defdec -> declarator; + + lvars a; + for a in attributes do + if a == "global" then + true -> is_global + elseif a == "lconstant" then + sysLCONSTANT -> declarator; + elseif a == "lvars" then + sysLVARS -> declarator; + elseif a == "constant" then + sysCONSTANT -> declarator; + elseif a == "vars" then + sysVARS -> declarator; + else + mishap( 'Unexpected syntax word', [^a] ) + endif + endfor; + + if pdrname == ";" or item == "(" then + pdrname :: proglist -> proglist; + sysNEW_LVAR() -> pdrname; + false -> is_global; + procedure( w, n ); endprocedure -> declarator; + "anonymous_unittest" -> props; + else + pdrname -> props + endif; +enddefine; + +define core_define_unittest(); + lvars ( pdrname, props, is_global, declarator ) = read_declaration( unittest_sysVARS ); + + lvars captured_popfilename = popfilename or vedpathname; + lvars captured_poplinenum = poplinenum; + + declarator( pdrname, 0 ); + if is_global then sysGLOBAL( pdrname, is_global ) endif; + sysPROCEDURE( props, 0 ); + dlocal unittest_sysVARS = sysLVARS; + sysLOCAL( "ident $-unittest$-mishap_happened" ); + ;;; Main body. + sysCALLQ( pop11_comp_procedure( "enddefine", false, pdrname and pdrname >< "_body" or "unittest_body" ) ); + sysPASSIGN( sysENDPROCEDURE(), pdrname ); + + sysPUSHQ( captured_popfilename ); + sysPUSHQ( captured_poplinenum ); + sysPUSH( pdrname ); + sysUCALL( "pdorigin" ); + + return( pdrname ); +enddefine; + +define :define_form global unittest; + lvars pdrname = core_define_unittest(); + sysPUSH( pdrname ); + sysCALL( "register_unittest" ); +enddefine; + +;;; with_data +;;; [0 1] +;;; [1 2] +;;; define :unittest foo( x, y ); +;;; enddefine; +define syntax with_data; + + define register_closures( data, pdr ); + lvars d; + for d in data do + register_unittest( pdr(% d.explode %) ) + endfor + enddefine; + + dlocal pop_new_lvar_list; + lvars data = sysNEW_LVAR(); + sysPUSH( "popstackmark" ); + until pop11_try_nextitem( "define" ) do + pop11_comp_expr(); + enduntil; + pop11_need_nextreaditem( ":" ) -> _; + pop11_need_nextreaditem( "unittest" ) -> _; + sysCALL( "sysconslist" ); + sysPOP( data ); + lvars pdrname = core_define_unittest(); + sysPUSH( data ); + sysPUSH( pdrname ); + sysCALLQ( register_closures ); +enddefine; + + +;;; --- Syntax: testsuite + +define :define_form global testsuite; + lvars ( pdrname, props, is_global, declarator ) = read_declaration( unittest_sysVARS ); + pop11_need_nextreaditem( ";" ) -> _; + declarator( pdrname, 0 ); + if is_global then sysGLOBAL( pdrname, is_global ) endif; + sysPROCEDURE( props, 0 ); + dlocal unittest_sysVARS = sysLVARS; + pop11_comp_stmnt_seq_to( "enddefine" ) -> _; + sysPASSIGN( sysENDPROCEDURE(), pdrname ); + sysCALL( pdrname ); +enddefine; + + +;;; --- Syntax: assert --- + +;;; Return a copy of the expanded portion of a partially expanded +;;; dynamic list without causing any further expansion. +define lconstant only_expanded( L ); + [% + while L.ispair and not( L.fast_back.isprocedure ) do + L.fast_destpair -> L + endwhile; + %] +enddefine; + +;;; -peek_expr_to- does not consume any input nor plant any code but expands -proglist- +;;; by exactly one Pop-11 expression and returns the expanded portion. +define lconstant peek_expr_to( closing_keyword ); + dlocal pop_syntax_only = true; + dlocal proglist_state; + lvars old_proglist = proglist; + pop11_comp_expr_to( closing_keyword ) -> _; + old_proglist.only_expanded; +enddefine; + +define global syntax assert(); + + define lconstant check_assertion( N, filename, linenum, expr ); + if N == 0 then + mishap( 0, 'No results from assertion', 'unittest-assert:stack-empty' ) + elseif N > 1 then + mishap( N, 'Too many results from assertion', 'unittest-assert:stack-many' ) + else + lvars result = (); + unless result do + mishap( #| filename, linenum, expr |#, 'Assertion failed', 'unittest-assert:unittest-fail' ) + endunless + endif + enddefine; + + dlocal pop_new_lvar_list; + lvars t = sysNEW_LVAR(); + sysCALL( "stacklength" ); + sysPOP( t ); + lvars expr = peek_expr_to( ";" ); + pop11_comp_expr(); + sysCALL( "stacklength" ); + sysPUSH( t ); + sysCALL( "fi_-" ); + sysPUSHQ( popfilename ); + sysPUSHQ( poplinenum ); + sysPUSHQ( expr ); + sysCALLQ( check_assertion ); +enddefine; + + +;;; --- Common reporting --- + +define r_pdprops( u ); + while u.isclosure and not( u.pdprops ) do + u.pdpart -> u + endwhile; + lvars props = u.pdprops; + while props.islist and not( null( props ) ) do + props.hd -> props + endwhile; + props; +enddefine; + +define pr_show_failures( passes, failures ); + dlocal poplinewidth = false; + dlocal pop_pr_quotes = false; + nprintf( 'Test results at: ' <> sysdaytime() ); + nl(1); + + lvars i, n; + for i, n in failures, list_from(1) do + lvars ( u, msg, idstring, args ) = i.destfailureinfo; + if idstring = 'unittest-assert:unittest-fail' then + lvars name = u.r_pdprops; + nprintf( '%p.\tFailed : %p', [^n ^name] ); + if u.isclosure then + nprintf( '\tData : %p', [[% u.explode %]]) + endif; + lvars (filename, linenumber, assert_expr, _n) = args.destlist; + if hasstartstring( filename, current_directory ) then + allbutfirst( datalength(current_directory) + 1, filename ) -> filename; + endif; + printf( '\tExpression: ' ); + applist( [assert ^^assert_expr], procedure(); dlocal pop_pr_quotes = true; spr() endprocedure ); + nl(1); + nprintf( '\tLine num : %p', [ ^linenumber ] ); + nprintf( '\tFile name : %p', [ ^filename ] ); + else + lvars name = u.r_pdprops; + lvars ( parent, linenum ) = u.pdorigin; + nprintf( '%p.\tUnit test : %p', [^n ^name] ); + nprintf( '\tMessage : %p', [ ^msg ] ); + unless args.null do + npr( '\tInvolving : ' ); + lvars a; + for a in args do + nprintf( '\t *\t%p', [^a] ) + endfor; + endunless; + if parent then + if linenum then + nprintf( '\tLine num : %p', [ ^linenum ] ); + endif; + if parent.isstring then + if hasstartstring( parent, current_directory ) then + allbutfirst( datalength(current_directory) + 1, parent ) -> parent; + endif; + nprintf( '\tFrom : %p', [ ^parent ] ); + endif; + endif; + endif; + nl( 1 ); + endfor; +enddefine; + +define pr_show_discovered( d ); + dlocal poplinewidth = false; + dlocal pop_pr_quotes = false; + nprintf( 'Test discovery at: ' <> sysdaytime() ); + nl( 1 ); + + nprintf( 'Files compiled during discovery (total of %p)', [% d.discovered_files.length %] ); + lvars file, n; + for file, n in d.discovered_files, list_from(1) do + nprintf( '%p. %p', [% n, file %] ); + endfor; + nl(1); + + nprintf( 'Unittests discovered (total of %p)', [% d.discovered_unittests.length %] ); + lvars u, n; + for u, n in d.discovered_unittests, list_from(1) do + nprintf( '%p. %p', [% n, u %] ); + endfor; + nl(1); + +enddefine; + +endsection; diff --git a/base/pop/getpoploglib/auto/expect_mishap.p b/base/pop/getpoploglib/auto/expect_mishap.p new file mode 100644 index 00000000..d2c0b44a --- /dev/null +++ b/base/pop/getpoploglib/auto/expect_mishap.p @@ -0,0 +1 @@ +uses define_unittest; \ No newline at end of file diff --git a/base/pop/getpoploglib/auto/list_from.p b/base/pop/getpoploglib/auto/list_from.p new file mode 100644 index 00000000..92b63e6b --- /dev/null +++ b/base/pop/getpoploglib/auto/list_from.p @@ -0,0 +1,11 @@ +compile_mode :pop11 +strict; + +section; + +;;; list_from( n: num ) -> [ n, n+1, ... ] +;;; list_from( item, p: item -> item ) -> [ item, p(item), p(p(item)), ... ] +define global list_from() with_nargs 1; + repeater_from( /* take args from stack */ ).pdtolist +enddefine; + +endsection; diff --git a/base/pop/getpoploglib/auto/new_list_builder.p b/base/pop/getpoploglib/auto/new_list_builder.p new file mode 100644 index 00000000..a3305616 --- /dev/null +++ b/base/pop/getpoploglib/auto/new_list_builder.p @@ -0,0 +1,66 @@ +compile_mode :pop11 +strict; + +section; + +define lconstant list_builder( item, first_pair_ref, last_pair_ref ); + if item == termin then + lvars first_pair = fast_cont( first_pair_ref ); + lvars result = fast_back( first_pair ); + nil -> fast_back( first_pair ); + first_pair -> fast_cont( last_pair_ref ); + result + else + lvars last_pair = fast_cont( last_pair_ref ); + conspair( item, nil ) ->> fast_back( last_pair ) -> fast_cont( last_pair_ref ) + endif +enddefine; + +define global new_list_builder(); + lvars first_pair = conspair( _, nil ); + lvars last_pair = first_pair; + list_builder(% consref( first_pair ), consref( last_pair ) %) +enddefine; + +define global is_list_builder( p ); + p.isclosure and p.pdpart == list_builder +enddefine; + +define global list_builder_copylist( p ); + if p.is_list_builder then + copylist( frozval( 1, p ).cont.back ) + else + mishap( 'List builder required', [^p] ) + endif +enddefine; + +define global list_builder_push_front( item, p ); + if p.is_list_builder then + lvars r = frozval( 1, p ); + lvars s = frozval( 2, p ); + lvars same = r.cont == s.cont; + conspair( item, r.cont.back ) -> r.cont.back; + if same then + r.cont.back -> s.cont + endif + else + mishap( 'List builder required', [^p] ) + endif +enddefine; + +define global list_builder_push_back( item, p ); + if p.is_list_builder then + p( item ) + else + mishap( 'List builder required', [^p] ) + endif +enddefine; + +define global list_builder_newlist( p ); + if p.is_list_builder then + p( termin ) + else + mishap( 'List builder required', [^p] ) + endif +enddefine; + +endsection; diff --git a/base/pop/getpoploglib/auto/pdorigin.p b/base/pop/getpoploglib/auto/pdorigin.p new file mode 100644 index 00000000..10c4d814 --- /dev/null +++ b/base/pop/getpoploglib/auto/pdorigin.p @@ -0,0 +1,23 @@ +compile_mode :pop11 +strict; + +#_TERMIN_IF DEF pdorigin + +section; + +lconstant procedure pdorigin_table = + newanyproperty( + [], 8, 1, 8, + false, false, "tmpval", + conspair( false, false ), + false + ); + +define global constant procedure pdorigin( procedure p ); + pdorigin_table( p ).destpair +enddefine; + +define updaterof pdorigin( file, linenum, procedure p ); + conspair( file, linenum ) -> pdorigin_table( p ) +enddefine; + +endsection; diff --git a/base/pop/getpoploglib/auto/repeater_from.p b/base/pop/getpoploglib/auto/repeater_from.p new file mode 100644 index 00000000..c682926b --- /dev/null +++ b/base/pop/getpoploglib/auto/repeater_from.p @@ -0,0 +1,25 @@ +compile_mode :pop11 +strict; + +section; + +;;; repeater_from( n: num ) +;;; repeater_from( item, p: item -> item ) +define global repeater_from( n ); + if n.isnumber then + procedure(); + n; + n + 1 -> n; + endprocedure + elseif n.isprocedure then + lvars p = n; + lvars n = (); + procedure(); + n; + p( n ) -> n + endprocedure + else + mishap( 'Unexpected argument', [^n] ) + endif +enddefine; + +endsection; diff --git a/base/pop/getpoploglib/auto/split_by_spaces.p b/base/pop/getpoploglib/auto/split_by_spaces.p new file mode 100644 index 00000000..d6002b15 --- /dev/null +++ b/base/pop/getpoploglib/auto/split_by_spaces.p @@ -0,0 +1,60 @@ +compile_mode :pop11 +strict; + +section; + +;;; +;;; split_by_spaces( s, [maxsplit], [constructor] ) -> constructor( N ) +;;; +define global procedure split_by_spaces() with_props 1; + lvars s, maxsplit, constructor; + + false -> maxsplit; + consvector -> constructor; + + if dup().isprocedure then + () -> constructor + endif; + + if dup().isinteger or dup() == false or dup() == _ do + () -> maxsplit; + if maxsplit == _ then + false -> maxsplit + endif + endif; + + () -> s; + unless s.isstring then + mishap( 'Invalid argument for splitstring', [ ^s ] ) + endunless; + if maxsplit == false or maxsplit == _ do + ;;; Stop maxsplit ever being a limit by making it 'very big'. + datalength(s) + 1 -> maxsplit; + endif; + + constructor(#| + lvars position = 1; + lvars count = 0; + repeat + skipchar( ` `, position, s ) -> position; + quitunless( position ); + + if count >= maxsplit then + substring( position, s.datalength - position + 1, s ); + quitloop + endif; + + lvars n = locchar( ` `, position, s); + if n then + substring( position, n - position, s ); + count fi_+ 1 -> count; + n -> position; + else + lvars len = s.datalength - position + 1; + substring( position, len, s ); + quitloop + endif + endrepeat + |#) +enddefine; + +endsection; diff --git a/base/pop/getpoploglib/auto/splitstring.p b/base/pop/getpoploglib/auto/splitstring.p new file mode 100644 index 00000000..dea5ead4 --- /dev/null +++ b/base/pop/getpoploglib/auto/splitstring.p @@ -0,0 +1,97 @@ +compile_mode :pop11 +strict; + +section; + +;;; +;;; splitstring( string, separator ) -> vector +;;; splitstring( string, separator, maxsplit: int_or_false, constructor: procedure ) -> constructor( N ) +;;; +define global splitstring() with_props 2; + lvars s, sep, maxsplit, procedure constructor; + + ;;; Set default values for optionals. + consvector -> constructor; + false -> maxsplit; + + ;;; Decode optional arguments directly from the stack. + if not(dup().isregexp) and dup().isprocedure then + () -> ( maxsplit, constructor ); + endif; + + () -> ( s, sep ); + + if sep.isstring then + if sep.datalength == 1 then + ;;; Take advantage of locchar. + subscrs( 1, sep ) -> sep + endif + elseunless sep.isinteger or sep.isregexp then + mishap( 'Invalid separator for split', [ ^sep ] ) + endif; + + unless s.isstring then + mishap( 'Invalid argument for splitstring', [ ^s ] ) + endunless; + + if maxsplit == false or maxsplit == _ do + ;;; Stop maxsplit ever being a limit by making it 'big enough'. + datalength(s) + 1 -> maxsplit; + endif; + + ;;; Deal separately with regular expressions and string/integer separators. + if sep.isregexp then + constructor(#| + lvars position = 1; + lvars count = 0; + lvars s_len = datalength( s ); + repeat + if position > s_len then + ''; + quitloop + endif; + lvars ( n, sep_n ) = if maxsplit > count then sep( position, s, false, false ) else false, false endif; + if n then + substring( position, n - position, s ); + count fi_+ 1 -> count; + n + sep_n -> position; + else + lvars len = s_len fi_- position fi_+ 1; + substring( position, s.datalength - position + 1, s ); + quitloop + endif + endrepeat + |#) + else + lvars ( procedure finder, sep_n ) = ( + if sep.isstring then + lvars d = sep.datalength; + if d == 0 then + mishap( 'Invalid empty separator argument', [^sep] ) + endif; + ( issubstring, datalength(sep) ) + elseif sep.isinteger then + ( locchar, 1 ) + else + mishap( 'Unexpected separator', [^sep] ) + endif + ); + constructor(#| + lvars position = 1; + lvars count = 0; + repeat + lvars n = (maxsplit > count) and finder( sep, position, s ); + if n then + substring( position, n - position, s ); + count fi_+ 1 -> count; + n + sep_n -> position; + else + lvars len = s.datalength - position + 1; + substring( position, len, s ); + quitloop + endif + endrepeat + |#) + endif +enddefine; + +endsection; diff --git a/base/pop/getpoploglib/auto/unittests_discover.p b/base/pop/getpoploglib/auto/unittests_discover.p new file mode 100644 index 00000000..46d73f8b --- /dev/null +++ b/base/pop/getpoploglib/auto/unittests_discover.p @@ -0,0 +1 @@ +uses unittests_run; diff --git a/base/pop/getpoploglib/auto/unittests_run.p b/base/pop/getpoploglib/auto/unittests_run.p new file mode 100644 index 00000000..79441ba8 --- /dev/null +++ b/base/pop/getpoploglib/auto/unittests_run.p @@ -0,0 +1,47 @@ +compile_mode :pop11 +strict; + +uses define_unittest; + +section $-unittest => + unittests_discover ;;; performs test-discovery at Pop-11 prompt + unittests_run ;;; performs test discovery-and-execution at Pop-11 prompt +; + +define find_unittest_files( location ); + if hasendstring( location, unittest_suffix ) then + [% location %] + elseif hasendstring( location, '.p' ) then + lvars dir = sys_fname_path( location ); + lvars name = sys_fname_nam( location ) <> unittest_suffix; + sys_file_match( name, dir dir_>< '../*/', false, false ).pdtolist; + elseif sysisdirectory( location ) then + lvars folder = sys_fname_path( location ); + sys_file_match( folder dir_>< '.../', '*' <> unittest_suffix, false, false ).pdtolist + else + [] + endif.expandlist +enddefine; + +define find_unittests( location ); + lvars files = find_unittest_files( location ); + lvars tests = applist(% files, loadcompiler %).discover_unittests; + return( newdiscovered( tests ) ) +enddefine; + +define unittests_discover( location ); + lvars d = find_unittests( location ); + pr_show_discovered( d ); +enddefine; + +define unittests_run( location ); + lvars d = find_unittests( location ); + + dlocal run_unittest = run_unittest_during_execution; + dlocal fail_unittest = fail_unittest_during_execution; + lvars disco = d.discovered_unittests; + lvars ( passes, failures ) = run_all_unittests( disco ); + + pr_show_failures( passes, failures ); +enddefine; + +endsection; diff --git a/base/pop/getpoploglib/auto/ved_discover.p b/base/pop/getpoploglib/auto/ved_discover.p new file mode 100644 index 00000000..37590841 --- /dev/null +++ b/base/pop/getpoploglib/auto/ved_discover.p @@ -0,0 +1 @@ +uses ved_test; diff --git a/base/pop/getpoploglib/auto/ved_test.p b/base/pop/getpoploglib/auto/ved_test.p new file mode 100644 index 00000000..1b367ca4 --- /dev/null +++ b/base/pop/getpoploglib/auto/ved_test.p @@ -0,0 +1,100 @@ +compile_mode :pop11 +strict; + +uses define_unittest; + +section $-unittest => + ved_test ;;; discovery and execution + ved_discover ;;; performs test-discovery +; + +;;; -- VED integration --- + +define select_scope_for_vedargument(); + if vedargument = '' then + if hasendstring( vedcurrent, unittest_suffix ) then + ved_l1 + elseif hasendstring( vedcurrent, '.p' ) then + lvars dir = sys_fname_path( vedcurrent ); + lvars name = sys_fname_nam( vedcurrent ) <> unittest_suffix; + procedure(); + lvars file; + for file in sys_file_match( name, dir dir_>< '../*/', false, false ).pdtolist do + vedputmessage( 'COMPILING ' >< file ); + pop11_compile( file ) + endfor + endprocedure + else + mishap( 'No tests found', [vedargument ^vedargument vedcurrent ^vedcurrent] ) + endif + elseif sysisdirectory( vedargument ) then + procedure( folder ); + lvars file; + for file in sys_file_match( folder dir_>< '.../', '*' <> unittest_suffix, false, false ).pdtolist do + vedputmessage( 'COMPILING ' >< file ); + pop11_compile( file ) + endfor + endprocedure(% vedargument %) + else + identfn(% [] %) + endif +enddefine; + +define test_discovery_in_ved(); + newdiscovered( discover_unittests( select_scope_for_vedargument() ) ) +enddefine; + +define show_failures( passes, failures ); + dlocal vedpositionstack; + + vededit( '*TEST RESULTS*', procedure(); vedhelpdefaults(); false -> vedbreak; endprocedure ); + ved_clear(); + vedpositionpush(); + dlocal cucharout = vedcharinsert; + + pr_show_failures( passes, failures ); + + vedpositionpop(); +enddefine; + + +define ved_discover(); + dlocal vedpositionstack; + lvars d = test_discovery_in_ved(); + + vededit( '*TESTS DISCOVERED*', procedure(); vedhelpdefaults(); false -> vedbreak; endprocedure ); + ved_clear(); + vedpositionpush(); + + dlocal cucharout = vedcharinsert; + + pr_show_discovered( d ); + + vedpositionpop(); +enddefine; + +define ved_test(); + lvars d = test_discovery_in_ved(); + dlocal run_unittest = run_unittest_during_execution; + dlocal fail_unittest = fail_unittest_during_execution; + lvars disco = d.discovered_unittests; + lvars ( passes, failures ) = run_all_unittests( disco ); + + unless null(failures) then + show_failures( passes, failures ) + endunless; + + lvars n_passes = passes.length; + lvars n_failures = failures.length; + dlocal pop_pr_quotes = false; + sprintf( + '%p pass%p, %p failure%p', + [% + n_passes, + if n_passes == 1 then '' else 'es' endif, ;;; singular v plural + n_failures, + if n_failures == 1 then '' else 's' endif ;;; singular v plural + %] + ).vedputmessage; +enddefine; + +endsection; diff --git a/base/pop/getpoploglib/auto/with_data.p b/base/pop/getpoploglib/auto/with_data.p new file mode 100644 index 00000000..d2c0b44a --- /dev/null +++ b/base/pop/getpoploglib/auto/with_data.p @@ -0,0 +1 @@ +uses define_unittest; \ No newline at end of file diff --git a/base/pop/getpoploglib/examples/.gitignore b/base/pop/getpoploglib/examples/.gitignore deleted file mode 100644 index e69de29b..00000000 diff --git a/base/pop/getpoploglib/help/list_from b/base/pop/getpoploglib/help/list_from new file mode 100644 index 00000000..b7196c98 --- /dev/null +++ b/base/pop/getpoploglib/help/list_from @@ -0,0 +1,41 @@ +HELP LIST_FROM Stephen Leach, Oct 2021 + + list_from( n: num ) -> [ n, n+1, n+2, ... ] + list_from( n: item, procedure p ) -> [ n, p(n), p(p(n)), ... ] + +This function is used to construct non-terminating lists. If called with a +single numerical argument it returns a list whose first member is n, and +whose next member is n+1, then n+2 etc. For example: + + Setpop + : vars list = list_from(1); + : list => + ** [...] + : list(1) => + ** 1 + : list(2) => + ** 2 + : list(3) => + ** 3 + : list => + ** [1 2 3 ...] + : + +If it is called with a procedure then the list that is returned contains +n, p(n), p(p(n)), etc. For example: + + Setpop + : vars L = list_from( 1, nonop *(% 2 %) ); + : L => + ** [...] + : L(1) => + ** 1 + : L(2) => + ** 2 + : L(3) => + ** 4 + : L(4) => + ** 8 + : L => + ** [1 2 4 8 ...] + : diff --git a/base/pop/getpoploglib/help/new_list_builder b/base/pop/getpoploglib/help/new_list_builder new file mode 100644 index 00000000..e530f7fa --- /dev/null +++ b/base/pop/getpoploglib/help/new_list_builder @@ -0,0 +1,81 @@ +HELP NEW_LIST_BUILDER Stephen Leach, Sept 2021 + + uses new_list_builder + + new_list_builder() -> builder + builder( item ) + builder( termin ) -> list + + is_list_builder( item ) -> bool + list_builder_push_front( item, builder ) + list_builder_push_back( item, builder ) + list_builder_copylist( builder ) -> list + list_builder_newlist( builder ) -> list + + + CONTENTS - (Use g to access required sections) + + -- Introduction + -- Predicates on List Builders + -- Constructing List Builders + -- Manipulating List Builders + + +-- Introduction ------------------------------------------------------- + +A list-builder is a consumer procedure that constructs a list from all the +items that it is applied to. It is particularly useful for building lists +by adding to the end of the list, although you can also add to the start of +the list as an alternative. + +The builder returns the list when it is applied to the special item . +For example: + + + Setpop + : vars b = new_list_builder(); + : b( 1 ); + : b( true ); + : b( 'three' ); + : b( termin ) => + ** [1 three] + : + + +-- Predicates on List Builders ---------------------------------------- + +List builders are closures so both isprocedure and isclosure is true +for any list builder. + +is_list_builder( item ) -> bool + Returns true if item is a list builder, false if not. + + +-- Constructing List Builders ----------------------------------------- + + +new_list_builder() -> list_builder + Returns a new, empty list builder. + + +-- Manipulating List Builders ----------------------------------------- + +list_builder_push_front( item, builder ) + Adds an item to the start of the list that is being built. + + +list_builder_push_back( item, builder ) + Adds an item to the end of the list that is being built. This + is the same as calling builder( item ) + + +list_builder_copylist( builder ) -> list + This takes a copy of the in-progress list that is being built. + Each time you take a copy you get a completely new list. + + +list_builder_newlist( builder ) -> list + This returns the in-progress list _and_ resets the builder back + to its initial empty state. + +--- Copyright GetPoplog (c) 2021. diff --git a/base/pop/getpoploglib/help/repeater_from b/base/pop/getpoploglib/help/repeater_from new file mode 100644 index 00000000..37ac5dab --- /dev/null +++ b/base/pop/getpoploglib/help/repeater_from @@ -0,0 +1,34 @@ +HELP REPEATER_FROM Stephen Leach, Oct 2021 + + repeater_from( n: num ) -> r + repeater_from( n: item, procedure p ) -> r + +This function is used to construct non-terminating repeaters. If called with a +single numerical argument it returns a repeater that generates n, then n+1, +then n+2 etc. For example: + + Setpop + : vars r = repeater_from(1); + : r() => + ** 1 + : r() => + ** 2 + : r() => + ** 3 + : + + +If it is called with a procedure then the repeater that is returned, when called, +will generate n, p(n), p(p(n)), etc. For example: + + Setpop + : vars r = repeater_from( false, not ); + : r() => + ** + : r() => + ** + : r() => + ** + : r() => + ** + : diff --git a/base/pop/getpoploglib/help/splitstring b/base/pop/getpoploglib/help/splitstring new file mode 100644 index 00000000..5ac392dc --- /dev/null +++ b/base/pop/getpoploglib/help/splitstring @@ -0,0 +1,33 @@ +HELP SPLITSTRING Stephen Leach, Sep 2021 + + splitstring( string, separator ) -> vector + splitstring( string, separator, maxsplit: int_or_false, constructor: procedure ) -> constructor( N ) + +In the first form, -splitstring- returns a vector of the sub-strings in the +-string-, using -sep- as the delimiter. The delimiter can be specified as a +string, character or regexp. For example: + + Setpop + : splitstring( 'abc,def,ghi', `,` ) => + ** {'abc' 'def' 'ghi'} + : + +In the second form, a limit on the number of splits can be specified and also +a different constructor from -consvector-. The constructor should be a +procedure that expects the top of the stack to be a count of the items beneath +it. + +If maxsplit is given as an integer, at most maxsplit splits are done (thus, +the list will have at most maxsplit+1 elements). If maxsplit is not specified +or -false- or -pop_undef-, then there is no limit on the number of splits (all +possible splits are made). + +For example: + + Setpop + : splitstring( 'abc,def,ghi', `,`, 1, conslist ) => + ** ['abc' 'def,ghi'] + : + : splitstring( 'abc,def,ghi', `,`, _, conslist ) => + ** ['abc' 'def' 'ghi'] + : diff --git a/base/pop/getpoploglib/unittests/new_list_builder.test.p b/base/pop/getpoploglib/unittests/new_list_builder.test.p new file mode 100644 index 00000000..0450d963 --- /dev/null +++ b/base/pop/getpoploglib/unittests/new_list_builder.test.p @@ -0,0 +1,41 @@ + +define :unittest test_new_list_builder; + + define :unittest empty; + ;;; Arrange + lvars b = new_list_builder(); + ;;; Act + lvars L = b( termin ); + lvars M = b( termin ); + ;;; Assert + assert L == nil; + assert M == nil; + enddefine; + + define :unittest some; + ;;; Arrange + lvars b = new_list_builder(); + lvars items = [ a b c ]; + applist( items, b ); + ;;; Act + lvars L = b( termin ); + lvars M = b( termin ); + ;;; Assert + assert L = items; + assert L /== items; + assert M == nil; + enddefine; + + define :unittest is; + ;;; Arrange + lvars banana = "banana"; + lvars b = new_list_builder(); + ;;; Act + lvars banana_is = is_list_builder( banana ); + lvars b_is = is_list_builder( b ); + ;;; Assert + assert banana_is.not; + assert b_is; + enddefine; + +enddefine; diff --git a/base/pop/getpoploglib/unittests/splitstring.test.p b/base/pop/getpoploglib/unittests/splitstring.test.p new file mode 100644 index 00000000..d5a29f54 --- /dev/null +++ b/base/pop/getpoploglib/unittests/splitstring.test.p @@ -0,0 +1,72 @@ +;;; --- Anonymous tests --- + +define :unittest; + ;;; Act + lvars actual = splitstring( 'abc|def|ghi', '|' ); + ;;; Assert + assert { 'abc' 'def' 'ghi' } = actual; +enddefine; + +;;; --- with_data tests --- + +with_data + [ '' ',' {''} ] + [ '' ',' ^false ^consvector {''} ] + [ 'abc' ',' {'abc'} ] + [ ',' ',' {'' ''} ] + [ ',' `,` {'' ''} ] + [ 'abc,' ',' {'abc' ''} ] + [ 'abc--def' '--' {'abc' 'def'} ] + [ 'abc----def' '--' {'abc' '' 'def'} ] + [ 'abc----def' '--' {'abc' '' 'def'} ] + [ 'abc--def--ghi' '--' {'abc' 'def' 'ghi'} ] + [ '---abc--def--ghi' '--' {'' '-abc' 'def' 'ghi'} ] + [ 'a b c' ' ' ^false ^conslist ['a' 'b' 'c'] ] + [ 'a b c' ' ' 1 ^conslist ['a' 'b c'] ] +define :unittest( expected ); + ;;; Act + lvars actual = splitstring( /*stack*/ ); + ;;; Assert + assert actual = expected; +enddefine; + +;;; --- Named tests --- + +define :unittest test_splitstring_nested; + + define :unittest test_splitstring_regexp(); + ;;; Arrange + lvars ( _, regexp_p ) = regexp_compile( '@[,;@]' ); + ;;; Act + lvars actual = splitstring( 'abc,def;ghi', regexp_p ); + ;;; Assert + assert actual = { 'abc' 'def' 'ghi' }; + enddefine; + + define :unittest test_splitstring_regexp_conslist(); + ;;; Arrange + lvars ( _, regexp_p ) = regexp_compile( '@[,;@]' ); + ;;; Act + lvars actual = splitstring( 'abc,def;ghi', regexp_p, false, conslist ); + ;;; Assert + assert actual = [ 'abc' 'def' 'ghi' ]; + enddefine; + + define :unittest test_splitstring_regexp_conslist_max1(); + ;;; Arrange + lvars ( _, regexp_p ) = regexp_compile( '@[,;@]' ); + ;;; Act + lvars actual = splitstring( 'abc,def;ghi', regexp_p, 1, conslist ); + ;;; Assert + assert actual = [ 'abc' 'def;ghi' ]; + enddefine; + +enddefine; + + +;;; --- expect_mishap tests --- + +define :unittest; + dlocal expect_mishap = true; + splitstring( "wordsarenotstrings", `,` ) -> _; +enddefine;