2005-09-20 21:26:39 +08:00
/** \file main.c
Various bug and feature tests . Compiled and run by make test .
*/
# include "config.h"
# include <stdlib.h>
# include <stdio.h>
# include <wchar.h>
# include <string.h>
# include <unistd.h>
# include <errno.h>
# include <unistd.h>
# include <termios.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
# include <stdarg.h>
# ifdef HAVE_GETOPT_H
# include <getopt.h>
# endif
# include <signal.h>
# include <locale.h>
# include <dirent.h>
# include "util.h"
# include "common.h"
# include "proc.h"
# include "reader.h"
# include "builtin.h"
# include "function.h"
# include "complete.h"
# include "wutil.h"
# include "env.h"
# include "expand.h"
# include "parser.h"
# include "tokenizer.h"
# define LAPS 50
static int err_count = 0 ;
static void say ( wchar_t * blah , . . . )
{
va_list va ;
va_start ( va , blah ) ;
vwprintf ( blah , va ) ;
va_end ( va ) ;
wprintf ( L " \n " ) ;
}
static void err ( wchar_t * blah , . . . )
{
va_list va ;
va_start ( va , blah ) ;
err_count + + ;
wprintf ( L " Error: " ) ;
vwprintf ( blah , va ) ;
va_end ( va ) ;
wprintf ( L " \n " ) ;
}
static void ok ( )
{
wprintf ( L " OK \n " ) ;
}
static int pq_compare ( void * e1 , void * e2 )
{
return e1 - e2 ;
}
static int pq_test ( int elements )
{
int i ;
int prev ;
int * count = calloc ( sizeof ( int ) , 100 ) ;
priority_queue_t q ;
pq_init ( & q , pq_compare ) ;
for ( i = 0 ; i < elements ; i + + )
{
int foo = rand ( ) % 100 ;
// printf( "Adding %d\n", foo );
pq_put ( & q , ( void * ) foo ) ;
count [ foo ] + + ;
}
prev = 100 ;
for ( i = 0 ; i < elements ; i + + )
{
int pos = ( int ) pq_get ( & q ) ;
count [ pos ] - - ;
if ( pos > prev )
err ( L " Wrong order of elements in priority_queue_t " ) ;
prev = pos ;
}
for ( i = 0 ; i < 100 ; i + + )
{
if ( count [ i ] ! = 0 )
{
err ( L " Wrong number of elements in priority_queue_t " ) ;
}
}
}
static int stack_test ( int elements )
{
int i ;
int res = 1 ;
array_list_t s ;
al_init ( & s ) ;
for ( i = 0 ; i < elements ; i + + )
{
int foo ;
al_push ( & s , ( void * ) i ) ;
al_push ( & s , ( void * ) i ) ;
if ( ( foo = ( int ) al_pop ( & s ) ) ! = i )
{
err ( L " Unexpected data " ) ;
res = 0 ;
break ;
}
}
for ( i = 0 ; i < elements ; i + + )
{
int foo ;
if ( ( foo = ( int ) al_pop ( & s ) ) ! = ( elements - i - 1 ) )
{
err ( L " Unexpected data " ) ;
res = 0 ;
break ;
}
}
al_destroy ( & s ) ;
return res ;
}
static int hash_func ( const void * data )
{
/* srand( (int)data );
return rand ( ) ;
*/
int foo = ( int ) data ;
return 127 * ( ( foo ^ 0xefc7e214 ) ) ^ ( foo < < 11 ) ;
}
static int compare_func ( const void * key1 , const void * key2 )
{
return key1 = = key2 ;
}
static int hash_test ( int elements )
{
int i ;
int res = 1 ;
hash_table_t h ;
hash_init ( & h , hash_func , compare_func ) ;
for ( i = 1 ; i < elements + 1 ; i + + )
{
hash_put ( & h , ( void * ) i , ( void * ) 100 - i ) ;
}
for ( i = 1 ; i < elements + 1 ; i + + )
{
if ( ( int ) hash_get ( & h , ( void * ) i ) ! = ( 100 - i ) )
{
err ( L " Key %d gave data %d, expected data %d " ,
i ,
( int ) hash_get ( & h , ( void * ) i ) ,
100 - i ) ;
res = 0 ;
break ;
}
}
if ( hash_get_count ( & h ) ! = elements )
{
err ( L " Table holds %d elements, should hold %d elements " ,
hash_get_count ( & h ) ,
elements ) ;
res = 0 ;
}
for ( i = 1 ; i < elements + 1 ; i + = 2 )
{
hash_remove ( & h , ( void * ) i , 0 , 0 ) ;
}
if ( hash_get_count ( & h ) ! = ( ( elements ) / 2 ) )
{
err ( L " Table contains %d elements, should contain %d elements " ,
hash_get_count ( & h ) ,
elements / 2 ) ;
res = 0 ;
}
for ( i = 1 ; i < elements + 1 ; i + + )
{
if ( hash_contains ( & h , ( void * ) i ) ! = ( i + 1 ) % 2 )
{
if ( i % 2 )
err ( L " Key %d remains, should be deleted " ,
i ) ;
else
err ( L " Key %d does not exist " ,
i ) ;
res = 0 ;
break ;
}
}
hash_destroy ( & h ) ;
return res ;
}
static int al_test ( int sz )
{
int i ;
array_list_t l ;
al_init ( & l ) ;
al_set ( & l , 1 , ( void * ) 7 ) ;
al_set ( & l , sz , ( void * ) 7 ) ;
if ( al_get_count ( & l ) ! = maxf ( sz + 1 , 2 ) )
err ( L " Wrong number of elements in array list " ) ;
for ( i = 0 ; i < al_get_count ( & l ) ; i + + )
{
int val = ( int ) ( ( long ) al_get ( & l , i ) ) ;
if ( ( i = = 1 ) | | ( i = = sz ) )
{
if ( val ! = 7 )
err ( L " Canary changed to %d at index %d " , val , i ) ;
}
else
{
if ( val ! = 0 )
err ( L " False canary %d found at index %d " , val , i ) ;
}
}
}
static void sb_test ( )
{
string_buffer_t b ;
int res ;
sb_init ( & b ) ;
if ( res = sb_printf ( & b , L " %ls%s " , L " Testing " , " string_buffer_t " ) = = - 1 )
{
err ( L " Error %d while testing stringbuffers " , res ) ;
}
if ( ( res = sb_printf ( & b , L " %ls " , L " functionality " ) ) = = - 1 )
{
err ( L " Error %d while testing stringbuffers " , res ) ;
}
say ( ( wchar_t * ) b . buff ) ;
}
static void test_util ( )
{
int i ;
say ( L " Testing utility library " ) ;
for ( i = 0 ; i < 18 ; i + + )
{
long t1 , t2 ;
pq_test ( 1 < < i ) ;
stack_test ( 1 < < i ) ;
t1 = get_time ( ) ;
hash_test ( 1 < < i ) ;
t2 = get_time ( ) ;
if ( i > 8 )
say ( L " Hashtable uses %f microseconds per element at size %d " ,
( ( double ) ( t2 - t1 ) ) / ( 1 < < i ) ,
1 < < i ) ;
al_test ( 1 < < i ) ;
}
sb_test ( ) ;
/*
int i ;
for ( i = 2 ; i < 10000000 ; i * = 2 )
{
printf ( " %d " , i ) ;
t1 = get_time ( ) ;
if ( ! hash_test ( i ) )
exit ( 0 ) ;
t2 = get_time ( ) ;
printf ( " %d \n " , ( t2 - t1 ) / i ) ;
}
*/
}
static void test_tok ( )
{
tokenizer t ;
say ( L " Testing tokenizer " ) ;
say ( L " Testing invalid input " ) ;
tok_init ( & t , 0 , 0 ) ;
if ( tok_last_type ( & t ) ! = TOK_ERROR )
{
err ( L " Invalid input to tokenizer was undetected " ) ;
}
say ( L " Testing use of broken tokenizer " ) ;
if ( ! tok_has_next ( & t ) )
{
err ( L " tok_has_next() should return 1 once on broken tokenizer " ) ;
}
tok_next ( & t ) ;
if ( tok_last_type ( & t ) ! = TOK_ERROR )
{
err ( L " Invalid input to tokenizer was undetected " ) ;
}
/*
This should crash if there is a bug . No reliable way to detect otherwise .
*/
say ( L " Test destruction of broken tokenizer " ) ;
tok_destroy ( & t ) ;
{
wchar_t * str = L " string <redirection 2>&1 'nested \" quoted \" '(string containing subshells ){and,brackets}$as[$well (as variable arrays)] " ;
const int types [ ] =
{
TOK_STRING , TOK_REDIRECT_IN , TOK_STRING , TOK_REDIRECT_FD , TOK_STRING , TOK_STRING , TOK_END
}
;
int i ;
say ( L " Test correct tokenization " ) ;
for ( i = 0 , tok_init ( & t , str , 0 ) ; i < ( sizeof ( types ) / sizeof ( int ) ) ; i + + , tok_next ( & t ) )
{
if ( types [ i ] ! = tok_last_type ( & t ) )
{
err ( L " Tokenization error: " ) ;
wprintf ( L " Token number %d of string \n '%ls' \n , expected token type %ls, got token '%ls' of type %ls \n " ,
i + 1 ,
str ,
tok_get_desc ( types [ i ] ) ,
tok_last ( & t ) ,
tok_get_desc ( tok_last_type ( & t ) ) ) ;
}
}
}
}
static void test_parser ( )
{
say ( L " Testing parser " ) ;
say ( L " Testing null input to parser " ) ;
if ( ! parser_test ( 0 , 0 ) )
{
err ( L " Null input to parser_test undetected " ) ;
}
say ( L " Testing block nesting " ) ;
if ( ! parser_test ( L " if; end " , 0 ) )
{
err ( L " Incomplete if statement undetected " ) ;
}
if ( ! parser_test ( L " if test; echo " , 0 ) )
{
err ( L " Missing end undetected " ) ;
}
if ( ! parser_test ( L " if test; end; end " , 0 ) )
{
err ( L " Unbalanced end undetected " ) ;
}
say ( L " Testing detection of invalid use of builtin commands " ) ;
if ( ! parser_test ( L " case foo " , 0 ) )
{
err ( L " 'case' command outside of block context undetected " ) ;
}
if ( ! parser_test ( L " switch ggg; if true; case foo;end;end " , 0 ) )
{
err ( L " 'case' command outside of switch block context undetected " ) ;
}
if ( ! parser_test ( L " else " , 0 ) )
{
err ( L " 'else' command outside of conditional block context undetected " ) ;
}
if ( ! parser_test ( L " break " , 0 ) )
{
err ( L " 'break' command outside of loop block context undetected " ) ;
}
if ( ! parser_test ( L " exec ls|less " , 0 ) | | ! parser_test ( L " echo|return " , 0 ) )
{
err ( L " Invalid pipe command undetected " ) ;
}
say ( L " Testing basic evaluation " ) ;
if ( ! eval ( 0 , 0 , TOP ) )
{
err ( L " Null input when evaluating undetected " ) ;
}
if ( ! eval ( L " ls " , 0 , WHILE ) )
{
err ( L " Invalid block mode when evaluating undetected " ) ;
}
}
/**
Perform parameter expantion and test if the output equals the zero - terminated parameter list supplied .
\ param in the string to expand
\ param flags the flags to send to expand_string
*/
static int expand_test ( const wchar_t * in , int flags , . . . )
{
array_list_t out ;
va_list va ;
int i = 0 ;
int res = 1 ;
wchar_t * arg ;
al_init ( & out ) ;
expand_string ( wcsdup ( in ) , & out , flags ) ;
va_start ( va , flags ) ;
while ( ( arg = va_arg ( va , wchar_t * ) ) ! = 0 )
{
if ( al_get_count ( & out ) = = i )
{
res = 0 ;
break ;
}
if ( wcscmp ( al_get ( & out , i ) , arg ) ! = 0 )
{
res = 0 ;
break ;
}
i + + ;
}
va_end ( va ) ;
al_foreach ( & out , ( void ( * ) ( const void * ) ) & free ) ;
return res ;
}
static void test_expand ( )
{
say ( L " Testing parameter expantion " ) ;
if ( ! expand_test ( L " foo " , 0 , L " foo " , 0 ) )
{
err ( L " Strings do not expand to themselves " ) ;
}
if ( ! expand_test ( L " a{b,c,d}e " , 0 , L " abe " , L " ace " , L " ade " , 0 ) )
{
err ( L " Bracket expantion is broken " ) ;
}
if ( ! expand_test ( L " a* " , EXPAND_SKIP_WILDCARDS , L " a* " , 0 ) )
{
err ( L " Cannot skip wildcard expantion " ) ;
}
}
void perf_complete ( )
{
wchar_t c ;
array_list_t out ;
long long t1 , t2 ;
int matches = 0 ;
double t ;
wchar_t str [ 3 ] =
{
0 , 0 , 0
}
;
int i ;
say ( L " Testing completion performance " ) ;
al_init ( & out ) ;
reader_push ( L " " ) ;
say ( L " Here we go " ) ;
t1 = get_time ( ) ;
for ( c = L ' a ' ; c < = L ' z ' ; c + + )
{
str [ 0 ] = c ;
reader_set_buffer ( str , 0 ) ;
complete ( str , & out ) ;
matches + = al_get_count ( & out ) ;
al_foreach ( & out , ( void ( * ) ( const void * ) ) & free ) ;
al_truncate ( & out , 0 ) ;
}
t2 = get_time ( ) ;
t = ( double ) ( t2 - t1 ) / ( 1000000 * 26 ) ;
say ( L " One letter command completion took %f seconds per completion, %f microseconds/match " , t , ( double ) ( t2 - t1 ) / matches ) ;
matches = 0 ;
t1 = get_time ( ) ;
for ( i = 0 ; i < LAPS ; i + + )
{
str [ 0 ] = ' a ' + ( rand ( ) % 26 ) ;
str [ 1 ] = ' a ' + ( rand ( ) % 26 ) ;
reader_set_buffer ( str , 0 ) ;
complete ( str , & out ) ;
matches + = al_get_count ( & out ) ;
al_foreach ( & out , ( void ( * ) ( const void * ) ) & free ) ;
al_truncate ( & out , 0 ) ;
}
t2 = get_time ( ) ;
t = ( double ) ( t2 - t1 ) / ( 1000000 * LAPS ) ;
say ( L " Two letter command completion took %f seconds per completion, %f microseconds/match " , t , ( double ) ( t2 - t1 ) / matches ) ;
al_destroy ( & out ) ;
reader_pop ( ) ;
}
int main ( int argc , char * * argv )
{
say ( L " Testing low-level functionality " ) ;
say ( L " Lines beginning with 'fish:' are not errors, they are warning messages \n generated by the fish parser library when given broken input, and can be \n ignored. All errors begin with 'Error:'. " ) ;
2005-10-03 21:09:37 +08:00
exec_init ( ) ;
2005-09-20 21:26:39 +08:00
parser_init ( ) ;
function_init ( ) ;
builtin_init ( ) ;
complete_init ( ) ;
reader_init ( ) ;
2005-10-03 21:09:37 +08:00
env_init ( ) ;
2005-09-20 21:26:39 +08:00
test_util ( ) ;
test_tok ( ) ;
test_parser ( ) ;
test_expand ( ) ;
say ( L " Encountered %d errors in low-level tests " , err_count ) ;
/*
Skip performance tests for now , since they seem to hang when running from inside make ( ? )
*/
// say( L"Testing performance" );
// perf_complete();
2005-10-03 21:09:37 +08:00
env_destroy ( ) ;
reader_destroy ( ) ;
2005-09-20 21:26:39 +08:00
parser_destroy ( ) ;
function_destroy ( ) ;
builtin_destroy ( ) ;
complete_destroy ( ) ;
wutil_destroy ( ) ;
2005-10-03 21:09:37 +08:00
exec_destroy ( ) ;
2005-09-20 21:26:39 +08:00
}