2005-10-24 23:26:25 +08:00
/** \file fish_tests.c
2012-11-18 18:23:22 +08:00
Various bug and feature tests . Compiled and run by make test .
2005-09-20 21:26:39 +08:00
*/
# include "config.h"
2006-02-28 21:17:16 +08:00
2005-09-20 21:26:39 +08:00
# 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>
2012-03-06 00:27:31 +08:00
# include <sys/wait.h>
2005-09-20 21:26:39 +08:00
# include <fcntl.h>
2012-01-28 03:43:45 +08:00
# include <stdarg.h>
# include <assert.h>
2012-03-07 16:54:01 +08:00
# include <iostream>
# include <string>
# include <sstream>
2012-01-28 03:43:45 +08:00
# include <algorithm>
2012-03-07 16:54:01 +08:00
# include <iterator>
2005-09-20 21:26:39 +08:00
# ifdef HAVE_GETOPT_H
# include <getopt.h>
# endif
# include <signal.h>
# include <locale.h>
# include <dirent.h>
2007-09-24 04:59:18 +08:00
# include <time.h>
2005-09-20 21:26:39 +08:00
2006-02-28 21:17:16 +08:00
# include "fallback.h"
2005-09-20 21:26:39 +08:00
# include "util.h"
2006-02-28 21:17:16 +08:00
2005-09-20 21:26:39 +08:00
# include "common.h"
# include "proc.h"
# include "reader.h"
# include "builtin.h"
# include "function.h"
2012-01-28 03:43:45 +08:00
# include "autoload.h"
2005-09-20 21:26:39 +08:00
# include "complete.h"
# include "wutil.h"
# include "env.h"
# include "expand.h"
# include "parser.h"
# include "tokenizer.h"
2005-10-27 20:20:03 +08:00
# include "output.h"
# include "exec.h"
# include "event.h"
2007-05-11 03:11:28 +08:00
# include "path.h"
2012-02-06 08:42:24 +08:00
# include "history.h"
2012-02-19 15:26:39 +08:00
# include "highlight.h"
2012-03-01 09:55:50 +08:00
# include "iothread.h"
# include "postfork.h"
# include "signal.h"
2012-07-07 05:34:53 +08:00
# include "highlight.h"
2013-06-10 05:21:24 +08:00
# include "parse_tree.h"
# include "parse_exec.h"
2012-07-07 05:34:53 +08:00
2007-09-24 04:59:18 +08:00
/**
The number of tests to run
*/
2012-02-16 16:24:27 +08:00
//#define ESCAPE_TEST_COUNT 1000000
# define ESCAPE_TEST_COUNT 10000
2007-09-24 04:59:18 +08:00
/**
The average length of strings to unescape
*/
# define ESCAPE_TEST_LENGTH 100
/**
The higest character number of character to try and escape
*/
# define ESCAPE_TEST_CHAR 4000
2005-10-24 23:26:25 +08:00
/**
Number of laps to run performance testing loop
*/
2005-09-20 21:26:39 +08:00
# define LAPS 50
2006-06-20 08:50:10 +08:00
/**
The result of one of the test passes
*/
# define NUM_ANS L"-7 99999999 1234567 deadbeef DEADBEEFDEADBEEF"
2005-10-24 23:26:25 +08:00
/**
Number of encountered errors
*/
2005-09-20 21:26:39 +08:00
static int err_count = 0 ;
2005-10-24 23:26:25 +08:00
/**
Print formatted output
*/
2012-11-19 08:30:30 +08:00
static void say ( const wchar_t * blah , . . . )
2005-09-20 21:26:39 +08:00
{
2012-11-19 08:30:30 +08:00
va_list va ;
va_start ( va , blah ) ;
vwprintf ( blah , va ) ;
va_end ( va ) ;
wprintf ( L " \n " ) ;
2005-09-20 21:26:39 +08:00
}
2005-10-24 23:26:25 +08:00
/**
Print formatted error string
*/
2012-11-19 08:30:30 +08:00
static void err ( const wchar_t * blah , . . . )
2005-09-20 21:26:39 +08:00
{
2012-11-19 08:30:30 +08:00
va_list va ;
va_start ( va , blah ) ;
err_count + + ;
wprintf ( L " Error: " ) ;
vwprintf ( blah , va ) ;
va_end ( va ) ;
wprintf ( L " \n " ) ;
2005-09-20 21:26:39 +08:00
}
2007-09-24 04:59:18 +08:00
/**
Test the escaping / unescaping code by escaping / unescaping random
strings and verifying that the original string comes back .
*/
static void test_escape ( )
{
2012-11-19 08:30:30 +08:00
int i ;
wcstring sb ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
say ( L " Testing escaping and unescaping " ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
for ( i = 0 ; i < ESCAPE_TEST_COUNT ; i + + )
2012-11-18 18:23:22 +08:00
{
2012-11-19 08:30:30 +08:00
const wchar_t * o , * e , * u ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
sb . clear ( ) ;
while ( rand ( ) % ESCAPE_TEST_LENGTH )
{
sb . push_back ( ( rand ( ) % ESCAPE_TEST_CHAR ) + 1 ) ;
}
o = ( const wchar_t * ) sb . c_str ( ) ;
e = escape ( o , 1 ) ;
u = unescape ( e , 0 ) ;
if ( ! o | | ! e | | ! u )
{
err ( L " Escaping cycle of string %ls produced null pointer on %ls " , o , e ? L " unescaping " : L " escaping " ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
}
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
if ( wcscmp ( o , u ) )
{
err ( L " Escaping cycle of string %ls produced different string %ls " , o , u ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
}
free ( ( void * ) e ) ;
free ( ( void * ) u ) ;
}
2012-03-01 03:27:14 +08:00
}
2007-09-24 04:59:18 +08:00
2012-11-19 08:30:30 +08:00
static void test_format ( void )
{
say ( L " Testing formatting functions " ) ;
struct
{
unsigned long long val ;
const char * expected ;
} tests [ ] =
{
2012-03-01 03:27:14 +08:00
{ 0 , " empty " } ,
{ 1 , " 1B " } ,
{ 2 , " 2B " } ,
{ 1024 , " 1kB " } ,
{ 1870 , " 1.8kB " } ,
{ 4322911 , " 4.1MB " }
} ;
size_t i ;
2012-11-19 08:30:30 +08:00
for ( i = 0 ; i < sizeof tests / sizeof * tests ; i + + )
{
2012-03-01 03:27:14 +08:00
char buff [ 128 ] ;
format_size_safe ( buff , tests [ i ] . val ) ;
2012-11-19 08:30:30 +08:00
assert ( ! strcmp ( buff , tests [ i ] . expected ) ) ;
2012-03-01 03:27:14 +08:00
}
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
for ( int j = - 129 ; j < = 129 ; j + + )
{
2012-03-01 03:27:14 +08:00
char buff1 [ 128 ] , buff2 [ 128 ] ;
2012-03-04 07:20:30 +08:00
format_long_safe ( buff1 , j ) ;
2012-03-01 03:27:14 +08:00
sprintf ( buff2 , " %d " , j ) ;
2012-11-19 08:30:30 +08:00
assert ( ! strcmp ( buff1 , buff2 ) ) ;
2012-03-04 07:20:30 +08:00
}
2012-11-18 18:23:22 +08:00
2012-03-04 07:20:30 +08:00
long q = LONG_MIN ;
char buff1 [ 128 ] , buff2 [ 128 ] ;
format_long_safe ( buff1 , q ) ;
sprintf ( buff2 , " %ld " , q ) ;
2012-11-19 08:30:30 +08:00
assert ( ! strcmp ( buff1 , buff2 ) ) ;
2012-11-18 18:23:22 +08:00
2007-09-24 04:59:18 +08:00
}
/**
Test wide / narrow conversion by creating random strings and
verifying that the original string comes back thorugh double
conversion .
*/
static void test_convert ( )
{
2012-11-19 08:30:30 +08:00
/* char o[] =
{
- 17 , - 128 , - 121 , - 68 , 0
}
;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
wchar_t * w = str2wcs ( o ) ;
char * n = wcs2str ( w ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
int i ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
for ( i = 0 ; o [ i ] ; i + + )
{
bitprint ( o [ i ] ) ; ;
//wprintf(L"%d ", o[i]);
}
wprintf ( L " \n " ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
for ( i = 0 ; w [ i ] ; i + + )
{
wbitprint ( w [ i ] ) ; ;
//wprintf(L"%d ", w[i]);
}
wprintf ( L " \n " ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
for ( i = 0 ; n [ i ] ; i + + )
{
bitprint ( n [ i ] ) ; ;
//wprintf(L"%d ", n[i]);
}
wprintf ( L " \n " ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
return ;
*/
2007-09-24 04:59:18 +08:00
2012-11-19 08:30:30 +08:00
int i ;
std : : vector < char > sb ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
say ( L " Testing wide/narrow string conversion " ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
for ( i = 0 ; i < ESCAPE_TEST_COUNT ; i + + )
{
const char * o , * n ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
char c ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
sb . clear ( ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
while ( rand ( ) % ESCAPE_TEST_LENGTH )
{
c = rand ( ) ;
2012-03-04 14:48:21 +08:00
sb . push_back ( c ) ;
2012-11-19 08:30:30 +08:00
}
c = 0 ;
2012-03-04 14:48:21 +08:00
sb . push_back ( c ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
o = & sb . at ( 0 ) ;
2012-12-20 05:31:06 +08:00
const wcstring w = str2wcstring ( o ) ;
n = wcs2str ( w . c_str ( ) ) ;
2012-11-18 18:23:22 +08:00
2012-12-20 05:31:06 +08:00
if ( ! o | | ! n )
2012-11-19 08:30:30 +08:00
{
2012-12-20 05:31:06 +08:00
err ( L " Line %d - Conversion cycle of string %s produced null pointer on %s " , __LINE__ , o , L " wcs2str " ) ;
2012-11-19 08:30:30 +08:00
}
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
if ( strcmp ( o , n ) )
{
err ( L " Line %d - %d: Conversion cycle of string %s produced different string %s " , __LINE__ , i , o , n ) ;
}
free ( ( void * ) n ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
}
2012-12-20 05:31:06 +08:00
}
/* Verify correct behavior with embedded nulls */
static void test_convert_nulls ( void )
{
say ( L " Testing embedded nulls in string conversion " ) ;
const wchar_t in [ ] = L " AAA \0 BBB " ;
const size_t in_len = ( sizeof in / sizeof * in ) - 1 ;
const wcstring in_str = wcstring ( in , in_len ) ;
std : : string out_str = wcs2string ( in_str ) ;
if ( out_str . size ( ) ! = in_len )
{
err ( L " Embedded nulls mishandled in wcs2string " ) ;
}
for ( size_t i = 0 ; i < in_len ; i + + )
{
if ( in [ i ] ! = out_str . at ( i ) )
{
err ( L " Embedded nulls mishandled in wcs2string at index %lu " , ( unsigned long ) i ) ;
}
}
wcstring out_wstr = str2wcstring ( out_str ) ;
if ( out_wstr . size ( ) ! = in_len )
{
err ( L " Embedded nulls mishandled in str2wcstring " ) ;
}
for ( size_t i = 0 ; i < in_len ; i + + )
{
if ( in [ i ] ! = out_wstr . at ( i ) )
{
err ( L " Embedded nulls mishandled in str2wcstring at index %lu " , ( unsigned long ) i ) ;
}
}
2007-09-24 04:59:18 +08:00
}
2005-10-24 23:26:25 +08:00
/**
Test the tokenizer
*/
2005-09-20 21:26:39 +08:00
static void test_tok ( )
{
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
say ( L " Testing tokenizer " ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
say ( L " Testing invalid input " ) ;
2012-11-22 09:48:35 +08:00
tokenizer_t t ( NULL , 0 ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
if ( tok_last_type ( & t ) ! = TOK_ERROR )
{
err ( L " Invalid input to tokenizer was undetected " ) ;
}
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
say ( L " Testing use of broken tokenizer " ) ;
if ( ! tok_has_next ( & t ) )
{
err ( L " tok_has_next() should return 1 once on broken tokenizer " ) ;
}
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
tok_next ( & t ) ;
if ( tok_last_type ( & t ) ! = TOK_ERROR )
2012-11-18 18:23:22 +08:00
{
2012-11-19 08:30:30 +08:00
err ( L " Invalid input to tokenizer was undetected " ) ;
2012-11-18 18:23:22 +08:00
}
2012-11-19 08:30:30 +08:00
/*
This should crash if there is a bug . No reliable way to detect otherwise .
*/
say ( L " Test destruction of broken tokenizer " ) ;
2012-11-18 18:23:22 +08:00
{
2012-11-19 08:30:30 +08:00
const wchar_t * str = L " string <redirection 2>&1 'nested \" quoted \" '(string containing subshells ){and,brackets}$as[$well (as variable arrays)] not_a_redirect^ ^ ^^is_a_redirect " ;
const int types [ ] =
{
TOK_STRING , TOK_REDIRECT_IN , TOK_STRING , TOK_REDIRECT_FD , TOK_STRING , TOK_STRING , TOK_STRING , TOK_REDIRECT_OUT , TOK_REDIRECT_APPEND , TOK_STRING , TOK_END
2012-11-22 09:48:35 +08:00
} ;
2012-11-19 08:30:30 +08:00
say ( L " Test correct tokenization " ) ;
2012-11-24 03:12:22 +08:00
2012-11-22 09:48:35 +08:00
tokenizer_t t ( str , 0 ) ;
2012-11-24 03:12:22 +08:00
for ( size_t i = 0 ; i < sizeof types / sizeof * types ; i + + , tok_next ( & t ) )
{
2012-11-19 08:30:30 +08:00
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 ) ) ) ;
}
}
2012-11-18 18:23:22 +08:00
}
2012-03-01 09:55:50 +08:00
}
2005-09-20 21:26:39 +08:00
2012-11-19 08:30:30 +08:00
static int test_fork_helper ( void * unused )
{
2012-03-01 09:55:50 +08:00
size_t i ;
2013-01-10 01:36:25 +08:00
for ( i = 0 ; i < 1000 ; i + + )
2012-11-19 08:30:30 +08:00
{
2013-01-10 01:36:25 +08:00
//delete [](new char[4 * 1024 * 1024]);
2013-01-13 04:55:23 +08:00
for ( int j = 0 ; j < 1024 ; j + + )
{
2013-01-10 01:36:25 +08:00
strerror ( j ) ;
}
2012-03-01 09:55:50 +08:00
}
return 0 ;
}
2012-11-19 08:30:30 +08:00
static void test_fork ( void )
{
2013-01-10 01:36:25 +08:00
return ;
2012-03-01 09:55:50 +08:00
say ( L " Testing fork " ) ;
size_t i , max = 100 ;
2012-11-19 08:30:30 +08:00
for ( i = 0 ; i < 100 ; i + + )
{
2012-03-01 09:55:50 +08:00
printf ( " %lu / %lu \n " , i + 1 , max ) ;
/* Do something horrible to try to trigger an error */
# define THREAD_COUNT 8
2013-01-10 01:36:25 +08:00
# define FORK_COUNT 10
2012-03-01 09:55:50 +08:00
# define FORK_LOOP_COUNT 16
signal_block ( ) ;
2012-11-19 08:30:30 +08:00
for ( size_t i = 0 ; i < THREAD_COUNT ; i + + )
{
2012-03-01 09:55:50 +08:00
iothread_perform < void > ( test_fork_helper , NULL , NULL ) ;
}
2012-11-19 08:30:30 +08:00
for ( size_t q = 0 ; q < FORK_LOOP_COUNT ; q + + )
{
2012-03-07 16:54:01 +08:00
pid_t pids [ FORK_COUNT ] ;
2012-11-19 08:30:30 +08:00
for ( size_t i = 0 ; i < FORK_COUNT ; i + + )
{
2012-03-01 09:55:50 +08:00
pid_t pid = execute_fork ( false ) ;
2012-11-19 08:30:30 +08:00
if ( pid > 0 )
{
2012-03-01 09:55:50 +08:00
/* Parent */
pids [ i ] = pid ;
2012-11-19 08:30:30 +08:00
}
else if ( pid = = 0 )
{
2012-03-01 09:55:50 +08:00
/* Child */
2013-01-10 01:36:25 +08:00
//new char[4 * 1024 * 1024];
2013-01-13 04:55:23 +08:00
for ( size_t i = 0 ; i < 1024 * 16 ; i + + )
{
for ( int j = 0 ; j < 256 ; j + + )
{
2013-01-10 01:36:25 +08:00
strerror ( j ) ;
}
}
2012-03-01 09:55:50 +08:00
exit_without_destructors ( 0 ) ;
2012-11-19 08:30:30 +08:00
}
else
{
2012-03-01 09:55:50 +08:00
perror ( " fork " ) ;
2012-11-18 18:23:22 +08:00
}
2012-03-01 09:55:50 +08:00
}
2012-11-19 08:30:30 +08:00
for ( size_t i = 0 ; i < FORK_COUNT ; i + + )
{
2012-03-01 09:55:50 +08:00
int status = 0 ;
2012-11-19 08:30:30 +08:00
if ( pids [ i ] ! = waitpid ( pids [ i ] , & status , 0 ) )
{
2012-03-01 09:55:50 +08:00
perror ( " waitpid " ) ;
assert ( 0 ) ;
}
assert ( WIFEXITED ( status ) & & 0 = = WEXITSTATUS ( status ) ) ;
}
}
iothread_drain_all ( ) ;
signal_unblock ( ) ;
}
# undef FORK_COUNT
2005-09-20 21:26:39 +08:00
}
2005-10-24 23:26:25 +08:00
/**
Test the parser
*/
2005-09-20 21:26:39 +08:00
static void test_parser ( )
{
2012-11-19 08:30:30 +08:00
say ( L " Testing parser " ) ;
parser_t parser ( PARSER_TYPE_GENERAL , true ) ;
say ( L " Testing null input to parser " ) ;
2013-01-25 06:59:52 +08:00
if ( ! parser . test ( NULL ) )
2012-11-19 08:30:30 +08:00
{
err ( L " Null input to parser.test undetected " ) ;
}
say ( L " Testing block nesting " ) ;
2013-01-25 06:59:52 +08:00
if ( ! parser . test ( L " if; end " ) )
2012-11-19 08:30:30 +08:00
{
err ( L " Incomplete if statement undetected " ) ;
}
2013-01-25 06:59:52 +08:00
if ( ! parser . test ( L " if test; echo " ) )
2012-11-19 08:30:30 +08:00
{
err ( L " Missing end undetected " ) ;
}
2013-01-25 06:59:52 +08:00
if ( ! parser . test ( L " if test; end; end " ) )
2012-11-19 08:30:30 +08:00
{
err ( L " Unbalanced end undetected " ) ;
}
say ( L " Testing detection of invalid use of builtin commands " ) ;
2013-01-25 06:59:52 +08:00
if ( ! parser . test ( L " case foo " ) )
2012-11-19 08:30:30 +08:00
{
err ( L " 'case' command outside of block context undetected " ) ;
}
2013-01-25 06:59:52 +08:00
if ( ! parser . test ( L " switch ggg; if true; case foo;end;end " ) )
2012-11-19 08:30:30 +08:00
{
err ( L " 'case' command outside of switch block context undetected " ) ;
}
2013-01-25 06:59:52 +08:00
if ( ! parser . test ( L " else " ) )
2012-11-19 08:30:30 +08:00
{
err ( L " 'else' command outside of conditional block context undetected " ) ;
}
2013-01-25 06:59:52 +08:00
if ( ! parser . test ( L " else if " ) )
2012-11-19 08:30:30 +08:00
{
err ( L " 'else if' command outside of conditional block context undetected " ) ;
}
2013-01-25 06:59:52 +08:00
if ( ! parser . test ( L " if false; else if; end " ) )
2012-11-19 08:30:30 +08:00
{
err ( L " 'else if' missing command undetected " ) ;
}
2013-01-25 06:59:52 +08:00
if ( ! parser . test ( L " break " ) )
2012-11-19 08:30:30 +08:00
{
err ( L " 'break' command outside of loop block context undetected " ) ;
}
2013-01-25 06:59:52 +08:00
if ( ! parser . test ( L " exec ls|less " ) | | ! parser . test ( L " echo|return " ) )
2012-11-19 08:30:30 +08:00
{
err ( L " Invalid pipe command undetected " ) ;
}
say ( L " Testing basic evaluation " ) ;
2012-01-28 03:43:45 +08:00
#if 0
/* This fails now since the parser takes a wcstring&, and NULL converts to wchar_t * converts to wcstring which crashes (thanks C++) */
2012-11-19 08:30:30 +08:00
if ( ! parser . eval ( 0 , 0 , TOP ) )
{
err ( L " Null input when evaluating undetected " ) ;
}
2012-01-28 03:43:45 +08:00
# endif
2012-11-19 08:30:30 +08:00
if ( ! parser . eval ( L " ls " , io_chain_t ( ) , WHILE ) )
{
err ( L " Invalid block mode when evaluating undetected " ) ;
}
2012-01-28 03:43:45 +08:00
}
2012-11-19 08:30:30 +08:00
class lru_node_test_t : public lru_node_t
{
public :
2012-02-06 14:48:43 +08:00
lru_node_test_t ( const wcstring & tmp ) : lru_node_t ( tmp ) { }
} ;
2012-11-19 08:30:30 +08:00
class test_lru_t : public lru_cache_t < lru_node_test_t >
{
public :
2012-02-06 14:48:43 +08:00
test_lru_t ( ) : lru_cache_t < lru_node_test_t > ( 16 ) { }
2012-11-18 18:23:22 +08:00
2012-02-06 14:48:43 +08:00
std : : vector < lru_node_test_t * > evicted_nodes ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
virtual void node_was_evicted ( lru_node_test_t * node )
{
2012-01-28 03:43:45 +08:00
assert ( find ( evicted_nodes . begin ( ) , evicted_nodes . end ( ) , node ) = = evicted_nodes . end ( ) ) ;
evicted_nodes . push_back ( node ) ;
}
} ;
2012-11-19 08:30:30 +08:00
static void test_lru ( void )
{
say ( L " Testing LRU cache " ) ;
2012-11-18 18:23:22 +08:00
2012-01-28 03:43:45 +08:00
test_lru_t cache ;
2012-02-06 14:48:43 +08:00
std : : vector < lru_node_test_t * > expected_evicted ;
2012-01-28 03:43:45 +08:00
size_t total_nodes = 20 ;
2012-11-19 08:30:30 +08:00
for ( size_t i = 0 ; i < total_nodes ; i + + )
{
2012-01-28 03:43:45 +08:00
assert ( cache . size ( ) = = std : : min ( i , ( size_t ) 16 ) ) ;
2012-03-07 16:54:01 +08:00
lru_node_test_t * node = new lru_node_test_t ( to_string ( i ) ) ;
2012-01-28 03:43:45 +08:00
if ( i < 4 ) expected_evicted . push_back ( node ) ;
// Adding the node the first time should work, and subsequent times should fail
assert ( cache . add_node ( node ) ) ;
assert ( ! cache . add_node ( node ) ) ;
}
assert ( cache . evicted_nodes = = expected_evicted ) ;
cache . evict_all_nodes ( ) ;
assert ( cache . evicted_nodes . size ( ) = = total_nodes ) ;
2012-11-19 08:30:30 +08:00
while ( ! cache . evicted_nodes . empty ( ) )
{
2012-01-28 03:43:45 +08:00
lru_node_t * node = cache . evicted_nodes . back ( ) ;
cache . evicted_nodes . pop_back ( ) ;
delete node ;
}
2005-09-20 21:26:39 +08:00
}
2012-11-18 18:23:22 +08:00
2005-09-20 21:26:39 +08:00
/**
2005-12-07 23:57:17 +08:00
Perform parameter expansion and test if the output equals the zero - terminated parameter list supplied .
2005-09-20 21:26:39 +08:00
\ param in the string to expand
\ param flags the flags to send to expand_string
*/
2012-11-19 08:30:30 +08:00
static int expand_test ( const wchar_t * in , int flags , . . . )
2005-09-20 21:26:39 +08:00
{
2012-11-19 08:30:30 +08:00
std : : vector < completion_t > output ;
va_list va ;
size_t i = 0 ;
int res = 1 ;
wchar_t * arg ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
if ( expand_string ( in , output , flags ) )
{
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
}
2012-11-18 18:23:22 +08:00
2012-10-16 09:16:47 +08:00
#if 0
for ( size_t idx = 0 ; idx < output . size ( ) ; idx + + )
{
printf ( " %ls \n " , output . at ( idx ) . completion . c_str ( ) ) ;
}
# endif
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
va_start ( va , flags ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
while ( ( arg = va_arg ( va , wchar_t * ) ) ! = 0 )
2012-11-18 18:23:22 +08:00
{
2012-11-19 08:30:30 +08:00
if ( output . size ( ) = = i )
{
res = 0 ;
break ;
}
2012-11-18 18:23:22 +08:00
2012-02-06 08:42:24 +08:00
if ( output . at ( i ) . completion ! = arg )
2012-11-19 08:30:30 +08:00
{
res = 0 ;
break ;
}
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
i + + ;
}
va_end ( va ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
return res ;
2012-11-18 18:23:22 +08:00
2005-09-20 21:26:39 +08:00
}
2005-10-24 23:26:25 +08:00
/**
2005-12-07 23:57:17 +08:00
Test globbing and other parameter expansion
2005-10-24 23:26:25 +08:00
*/
2005-09-20 21:26:39 +08:00
static void test_expand ( )
{
2012-11-19 08:30:30 +08:00
say ( L " Testing parameter expansion " ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
if ( ! expand_test ( L " foo " , 0 , L " foo " , 0 ) )
{
err ( L " Strings do not expand to themselves " ) ;
}
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
if ( ! expand_test ( L " a{b,c,d}e " , 0 , L " abe " , L " ace " , L " ade " , 0 ) )
{
err ( L " Bracket expansion is broken " ) ;
}
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
if ( ! expand_test ( L " a* " , EXPAND_SKIP_WILDCARDS , L " a* " , 0 ) )
{
err ( L " Cannot skip wildcard expansion " ) ;
}
2012-11-18 18:23:22 +08:00
2012-10-16 09:16:47 +08:00
if ( system ( " mkdir -p /tmp/fish_expand_test/ " ) ) err ( L " mkdir failed " ) ;
if ( system ( " touch /tmp/fish_expand_test/.foo " ) ) err ( L " touch failed " ) ;
if ( system ( " touch /tmp/fish_expand_test/bar " ) ) err ( L " touch failed " ) ;
2012-11-18 18:23:22 +08:00
2012-10-16 09:16:47 +08:00
// This is checking that .* does NOT match . and .. (https://github.com/fish-shell/fish-shell/issues/270). But it does have to match literal components (e.g. "./*" has to match the same as "*"
2012-11-19 08:30:30 +08:00
if ( ! expand_test ( L " /tmp/fish_expand_test/.* " , 0 , L " /tmp/fish_expand_test/.foo " , 0 ) )
{
err ( L " Expansion not correctly handling dotfiles " ) ;
}
if ( ! expand_test ( L " /tmp/fish_expand_test/./.* " , 0 , L " /tmp/fish_expand_test/./.foo " , 0 ) )
{
err ( L " Expansion not correctly handling literal path components in dotfiles " ) ;
}
2012-10-16 09:16:47 +08:00
2013-05-26 06:41:18 +08:00
system ( " rm -Rf /tmp/fish_expand_test " ) ;
}
static void test_fuzzy_match ( void )
{
say ( L " Testing fuzzy string matching " ) ;
if ( string_fuzzy_match_string ( L " " , L " " ) . type ! = fuzzy_match_exact ) err ( L " test_fuzzy_match failed on line %ld " , __LINE__ ) ;
if ( string_fuzzy_match_string ( L " alpha " , L " alpha " ) . type ! = fuzzy_match_exact ) err ( L " test_fuzzy_match failed on line %ld " , __LINE__ ) ;
if ( string_fuzzy_match_string ( L " alp " , L " alpha " ) . type ! = fuzzy_match_prefix ) err ( L " test_fuzzy_match failed on line %ld " , __LINE__ ) ;
if ( string_fuzzy_match_string ( L " ALPHA! " , L " alPhA! " ) . type ! = fuzzy_match_case_insensitive ) err ( L " test_fuzzy_match failed on line %ld " , __LINE__ ) ;
if ( string_fuzzy_match_string ( L " alPh " , L " ALPHA! " ) . type ! = fuzzy_match_prefix_case_insensitive ) err ( L " test_fuzzy_match failed on line %ld " , __LINE__ ) ;
if ( string_fuzzy_match_string ( L " LPH " , L " ALPHA! " ) . type ! = fuzzy_match_substring ) err ( L " test_fuzzy_match failed on line %ld " , __LINE__ ) ;
if ( string_fuzzy_match_string ( L " AA " , L " ALPHA! " ) . type ! = fuzzy_match_subsequence_insertions_only ) err ( L " test_fuzzy_match failed on line %ld " , __LINE__ ) ;
if ( string_fuzzy_match_string ( L " BB " , L " ALPHA! " ) . type ! = fuzzy_match_none ) err ( L " test_fuzzy_match failed on line %ld " , __LINE__ ) ;
2005-09-20 21:26:39 +08:00
}
2012-03-07 16:54:01 +08:00
/** Test path functions */
2007-05-11 03:11:28 +08:00
static void test_path ( )
{
2012-11-19 08:30:30 +08:00
say ( L " Testing path functions " ) ;
2007-05-11 03:11:28 +08:00
2012-02-08 13:23:12 +08:00
wcstring path = L " //foo//////bar/ " ;
wcstring canon = path ;
path_make_canonical ( canon ) ;
2012-11-19 08:30:30 +08:00
if ( canon ! = L " /foo/bar " )
{
err ( L " Bug in canonical PATH code " ) ;
}
2012-11-18 18:23:22 +08:00
2012-06-05 03:00:59 +08:00
path = L " / " ;
path_make_canonical ( path ) ;
if ( path ! = L " / " )
{
2012-11-19 08:30:30 +08:00
err ( L " Bug in canonical PATH code " ) ;
2012-06-05 03:00:59 +08:00
}
2007-05-11 03:11:28 +08:00
}
2012-12-23 04:21:31 +08:00
enum word_motion_t
{
2012-12-21 09:37:09 +08:00
word_motion_left ,
word_motion_right
} ;
static void test_1_word_motion ( word_motion_t motion , move_word_style_t style , const wcstring & test )
{
wcstring command ;
std : : set < size_t > stops ;
2012-12-23 04:21:31 +08:00
2012-12-21 09:37:09 +08:00
// Carets represent stops and should be cut out of the command
2012-12-23 04:21:31 +08:00
for ( size_t i = 0 ; i < test . size ( ) ; i + + )
{
2012-12-21 09:37:09 +08:00
wchar_t wc = test . at ( i ) ;
if ( wc = = L ' ^ ' )
{
stops . insert ( command . size ( ) ) ;
}
else
{
command . push_back ( wc ) ;
}
}
2012-12-23 04:21:31 +08:00
2012-12-21 09:37:09 +08:00
size_t idx , end ;
if ( motion = = word_motion_left )
{
idx = command . size ( ) ;
end = 0 ;
}
else
{
idx = 0 ;
end = command . size ( ) ;
}
2012-12-23 04:21:31 +08:00
2012-12-21 09:37:09 +08:00
move_word_state_machine_t sm ( style ) ;
while ( idx ! = end )
{
size_t char_idx = ( motion = = word_motion_left ? idx - 1 : idx ) ;
wchar_t wc = command . at ( char_idx ) ;
bool will_stop = ! sm . consume_char ( wc ) ;
//printf("idx %lu, looking at %lu (%c): %d\n", idx, char_idx, (char)wc, will_stop);
bool expected_stop = ( stops . count ( idx ) > 0 ) ;
if ( will_stop ! = expected_stop )
{
wcstring tmp = command ;
tmp . insert ( idx , L " ^ " ) ;
const char * dir = ( motion = = word_motion_left ? " left " : " right " ) ;
if ( will_stop )
{
err ( L " Word motion: moving %s, unexpected stop at idx %lu: '%ls' " , dir , idx , tmp . c_str ( ) ) ;
}
else if ( ! will_stop & & expected_stop )
{
err ( L " Word motion: moving %s, should have stopped at idx %lu: '%ls' " , dir , idx , tmp . c_str ( ) ) ;
}
}
// We don't expect to stop here next time
if ( expected_stop )
{
stops . erase ( idx ) ;
}
if ( will_stop )
{
sm . reset ( ) ;
}
else
{
idx + = ( motion = = word_motion_left ? - 1 : 1 ) ;
}
}
}
/** Test word motion (forward-word, etc.). Carets represent cursor stops. */
static void test_word_motion ( )
{
say ( L " Testing word motion " ) ;
test_1_word_motion ( word_motion_left , move_word_style_punctuation , L " ^echo ^hello_^world.^txt " ) ;
test_1_word_motion ( word_motion_right , move_word_style_punctuation , L " echo^ hello^_world^.txt^ " ) ;
2012-12-23 04:21:31 +08:00
2012-12-21 09:37:09 +08:00
test_1_word_motion ( word_motion_left , move_word_style_punctuation , L " echo ^foo_^foo_^foo/^/^/^/^/^ " ) ;
test_1_word_motion ( word_motion_right , move_word_style_punctuation , L " echo^ foo^_foo^_foo^/^/^/^/^/ ^ " ) ;
2012-12-23 04:21:31 +08:00
2012-12-21 09:37:09 +08:00
test_1_word_motion ( word_motion_left , move_word_style_path_components , L " ^/^foo/^bar/^baz/ " ) ;
test_1_word_motion ( word_motion_left , move_word_style_path_components , L " ^echo ^--foo ^--bar " ) ;
test_1_word_motion ( word_motion_left , move_word_style_path_components , L " ^echo ^hi ^> /^dev/^null " ) ;
2012-12-23 04:21:31 +08:00
2012-12-21 09:37:09 +08:00
test_1_word_motion ( word_motion_left , move_word_style_path_components , L " ^echo /^foo/^bar{^aaa,^bbb,^ccc}^bak/ " ) ;
}
2012-05-14 11:19:02 +08:00
/** Test is_potential_path */
static void test_is_potential_path ( )
{
say ( L " Testing is_potential_path " ) ;
2012-11-19 08:30:30 +08:00
if ( system ( " rm -Rf /tmp/is_potential_path_test/ " ) )
{
2012-05-14 11:19:02 +08:00
err ( L " Failed to remove /tmp/is_potential_path_test/ " ) ;
}
2012-11-18 18:23:22 +08:00
2012-05-14 11:19:02 +08:00
/* Directories */
if ( system ( " mkdir -p /tmp/is_potential_path_test/alpha/ " ) ) err ( L " mkdir failed " ) ;
if ( system ( " mkdir -p /tmp/is_potential_path_test/beta/ " ) ) err ( L " mkdir failed " ) ;
2012-11-18 18:23:22 +08:00
2012-05-14 11:19:02 +08:00
/* Files */
if ( system ( " touch /tmp/is_potential_path_test/aardvark " ) ) err ( L " touch failed " ) ;
if ( system ( " touch /tmp/is_potential_path_test/gamma " ) ) err ( L " touch failed " ) ;
2012-11-18 18:23:22 +08:00
2012-05-14 11:19:02 +08:00
const wcstring wd = L " /tmp/is_potential_path_test/ " ;
const wcstring_list_t wds ( 1 , wd ) ;
2012-11-18 18:23:22 +08:00
2012-05-14 11:19:02 +08:00
wcstring tmp ;
2012-07-07 05:34:53 +08:00
assert ( is_potential_path ( L " al " , wds , PATH_REQUIRE_DIR , & tmp ) & & tmp = = L " alpha/ " ) ;
assert ( is_potential_path ( L " alpha/ " , wds , PATH_REQUIRE_DIR , & tmp ) & & tmp = = L " alpha/ " ) ;
assert ( is_potential_path ( L " aard " , wds , 0 , & tmp ) & & tmp = = L " aardvark " ) ;
2012-11-18 18:23:22 +08:00
2012-07-07 05:34:53 +08:00
assert ( ! is_potential_path ( L " balpha/ " , wds , PATH_REQUIRE_DIR , & tmp ) ) ;
assert ( ! is_potential_path ( L " aard " , wds , PATH_REQUIRE_DIR , & tmp ) ) ;
assert ( ! is_potential_path ( L " aarde " , wds , PATH_REQUIRE_DIR , & tmp ) ) ;
assert ( ! is_potential_path ( L " aarde " , wds , 0 , & tmp ) ) ;
2012-11-18 18:23:22 +08:00
2012-07-07 05:34:53 +08:00
assert ( is_potential_path ( L " /tmp/is_potential_path_test/aardvark " , wds , 0 , & tmp ) & & tmp = = L " /tmp/is_potential_path_test/aardvark " ) ;
assert ( is_potential_path ( L " /tmp/is_potential_path_test/al " , wds , PATH_REQUIRE_DIR , & tmp ) & & tmp = = L " /tmp/is_potential_path_test/alpha/ " ) ;
assert ( is_potential_path ( L " /tmp/is_potential_path_test/aardv " , wds , 0 , & tmp ) & & tmp = = L " /tmp/is_potential_path_test/aardvark " ) ;
2012-11-18 18:23:22 +08:00
2012-07-07 05:34:53 +08:00
assert ( ! is_potential_path ( L " /tmp/is_potential_path_test/aardvark " , wds , PATH_REQUIRE_DIR , & tmp ) ) ;
assert ( ! is_potential_path ( L " /tmp/is_potential_path_test/al/ " , wds , 0 , & tmp ) ) ;
assert ( ! is_potential_path ( L " /tmp/is_potential_path_test/ar " , wds , 0 , & tmp ) ) ;
2012-11-18 18:23:22 +08:00
2012-07-07 05:34:53 +08:00
assert ( is_potential_path ( L " /usr " , wds , PATH_REQUIRE_DIR , & tmp ) & & tmp = = L " /usr/ " ) ;
2012-05-14 11:49:14 +08:00
2012-05-14 11:19:02 +08:00
}
2012-03-07 16:54:01 +08:00
/** Test the 'test' builtin */
2012-11-19 08:30:30 +08:00
int builtin_test ( parser_t & parser , wchar_t * * argv ) ;
2013-01-05 17:30:03 +08:00
static bool run_one_test_test ( int expected , wcstring_list_t & lst , bool bracket )
2012-11-19 08:30:30 +08:00
{
2012-05-08 03:55:13 +08:00
parser_t parser ( PARSER_TYPE_GENERAL , true ) ;
2012-03-07 16:54:01 +08:00
size_t i , count = lst . size ( ) ;
2013-01-05 17:30:03 +08:00
wchar_t * * argv = new wchar_t * [ count + 3 ] ;
argv [ 0 ] = ( wchar_t * ) ( bracket ? L " [ " : L " test " ) ;
2012-11-19 08:30:30 +08:00
for ( i = 0 ; i < count ; i + + )
{
2012-03-07 16:54:01 +08:00
argv [ i + 1 ] = ( wchar_t * ) lst . at ( i ) . c_str ( ) ;
}
2013-01-05 17:30:03 +08:00
if ( bracket )
{
argv [ i + 1 ] = ( wchar_t * ) L " ] " ;
i + + ;
}
2012-03-07 16:54:01 +08:00
argv [ i + 1 ] = NULL ;
int result = builtin_test ( parser , argv ) ;
delete [ ] argv ;
return expected = = result ;
}
2012-11-19 08:30:30 +08:00
static bool run_test_test ( int expected , const wcstring & str )
{
2012-03-07 16:54:01 +08:00
using namespace std ;
wcstring_list_t lst ;
wistringstream iss ( str ) ;
copy ( istream_iterator < wcstring , wchar_t , std : : char_traits < wchar_t > > ( iss ) ,
2012-11-19 08:30:30 +08:00
istream_iterator < wstring , wchar_t , std : : char_traits < wchar_t > > ( ) ,
back_inserter < vector < wcstring > > ( lst ) ) ;
2013-01-08 18:39:22 +08:00
2013-01-05 17:30:03 +08:00
bool bracket = run_one_test_test ( expected , lst , true ) ;
bool nonbracket = run_one_test_test ( expected , lst , false ) ;
assert ( bracket = = nonbracket ) ;
return nonbracket ;
}
static void test_test_brackets ( )
{
// Ensure [ knows it needs a ]
parser_t parser ( PARSER_TYPE_GENERAL , true ) ;
2013-01-08 18:39:22 +08:00
2013-01-05 17:30:03 +08:00
const wchar_t * argv1 [ ] = { L " [ " , L " foo " , NULL } ;
assert ( builtin_test ( parser , ( wchar_t * * ) argv1 ) ! = 0 ) ;
2013-01-08 18:39:22 +08:00
2013-01-05 17:30:03 +08:00
const wchar_t * argv2 [ ] = { L " [ " , L " foo " , L " ] " , NULL } ;
assert ( builtin_test ( parser , ( wchar_t * * ) argv2 ) = = 0 ) ;
2013-01-08 18:39:22 +08:00
2013-01-05 17:30:03 +08:00
const wchar_t * argv3 [ ] = { L " [ " , L " foo " , L " ] " , L " bar " , NULL } ;
assert ( builtin_test ( parser , ( wchar_t * * ) argv3 ) ! = 0 ) ;
2012-03-07 16:54:01 +08:00
}
2012-11-19 08:30:30 +08:00
static void test_test ( )
{
2012-03-07 16:54:01 +08:00
say ( L " Testing test builtin " ) ;
2013-01-05 17:30:03 +08:00
test_test_brackets ( ) ;
2012-05-21 03:58:03 +08:00
2012-03-07 16:54:01 +08:00
assert ( run_test_test ( 0 , L " 5 -ne 6 " ) ) ;
assert ( run_test_test ( 0 , L " 5 -eq 5 " ) ) ;
assert ( run_test_test ( 0 , L " 0 -eq 0 " ) ) ;
assert ( run_test_test ( 0 , L " -1 -eq -1 " ) ) ;
assert ( run_test_test ( 0 , L " 1 -ne -1 " ) ) ;
assert ( run_test_test ( 1 , L " -1 -ne -1 " ) ) ;
assert ( run_test_test ( 0 , L " abc != def " ) ) ;
assert ( run_test_test ( 1 , L " abc = def " ) ) ;
assert ( run_test_test ( 0 , L " 5 -le 10 " ) ) ;
assert ( run_test_test ( 0 , L " 10 -le 10 " ) ) ;
assert ( run_test_test ( 1 , L " 20 -le 10 " ) ) ;
assert ( run_test_test ( 0 , L " -1 -le 0 " ) ) ;
assert ( run_test_test ( 1 , L " 0 -le -1 " ) ) ;
assert ( run_test_test ( 0 , L " 15 -ge 10 " ) ) ;
assert ( run_test_test ( 0 , L " 15 -ge 10 " ) ) ;
assert ( run_test_test ( 1 , L " ! 15 -ge 10 " ) ) ;
assert ( run_test_test ( 0 , L " ! ! 15 -ge 10 " ) ) ;
assert ( run_test_test ( 0 , L " 0 -ne 1 -a 0 -eq 0 " ) ) ;
assert ( run_test_test ( 0 , L " 0 -ne 1 -a -n 5 " ) ) ;
assert ( run_test_test ( 0 , L " -n 5 -a 10 -gt 5 " ) ) ;
assert ( run_test_test ( 0 , L " -n 3 -a -n 5 " ) ) ;
2012-11-18 18:23:22 +08:00
2012-03-07 16:54:01 +08:00
/* test precedence:
' 0 = = 0 | | 0 = = 1 & & 0 = = 2 '
should be evaluated as :
' 0 = = 0 | | ( 0 = = 1 & & 0 = = 2 ) '
and therefore true . If it were
' ( 0 = = 0 | | 0 = = 1 ) & & 0 = = 2 '
it would be false . */
assert ( run_test_test ( 0 , L " 0 = 0 -o 0 = 1 -a 0 = 2 " ) ) ;
assert ( run_test_test ( 0 , L " -n 5 -o 0 = 1 -a 0 = 2 " ) ) ;
2012-03-16 11:40:31 +08:00
assert ( run_test_test ( 1 , L " ( 0 = 0 -o 0 = 1 ) -a 0 = 2 " ) ) ;
2012-11-18 18:23:22 +08:00
assert ( run_test_test ( 0 , L " 0 = 0 -o ( 0 = 1 -a 0 = 2 ) " ) ) ;
2012-03-07 16:54:01 +08:00
/* A few lame tests for permissions; these need to be a lot more complete. */
assert ( run_test_test ( 0 , L " -e /bin/ls " ) ) ;
assert ( run_test_test ( 1 , L " -e /bin/ls_not_a_path " ) ) ;
assert ( run_test_test ( 0 , L " -x /bin/ls " ) ) ;
assert ( run_test_test ( 1 , L " -x /bin/ls_not_a_path " ) ) ;
assert ( run_test_test ( 0 , L " -d /bin/ " ) ) ;
assert ( run_test_test ( 1 , L " -d /bin/ls " ) ) ;
2012-11-18 18:23:22 +08:00
2012-05-21 03:58:03 +08:00
/* This failed at one point */
2012-03-07 17:13:24 +08:00
assert ( run_test_test ( 1 , L " -d /bin -a 5 -eq 3 " ) ) ;
assert ( run_test_test ( 0 , L " -d /bin -o 5 -eq 3 " ) ) ;
assert ( run_test_test ( 0 , L " -d /bin -a ! 5 -eq 3 " ) ) ;
2012-11-18 18:23:22 +08:00
2012-05-21 03:58:03 +08:00
/* We didn't properly handle multiple "just strings" either */
assert ( run_test_test ( 0 , L " foo " ) ) ;
assert ( run_test_test ( 0 , L " foo -a bar " ) ) ;
2013-01-08 18:39:22 +08:00
2013-01-05 17:30:03 +08:00
/* These should be errors */
assert ( run_test_test ( 1 , L " foo bar " ) ) ;
assert ( run_test_test ( 1 , L " foo bar baz " ) ) ;
2013-01-08 18:39:22 +08:00
2013-01-07 06:48:46 +08:00
/* This crashed */
assert ( run_test_test ( 1 , L " 1 = 1 -a = 1 " ) ) ;
2013-03-22 08:44:51 +08:00
2013-03-04 05:22:00 +08:00
/* Make sure we can treat -S as a parameter instead of an operator. https://github.com/fish-shell/fish-shell/issues/601 */
assert ( run_test_test ( 0 , L " -S = -S " ) ) ;
assert ( run_test_test ( 1 , L " ! ! ! A " ) ) ;
2012-03-07 16:54:01 +08:00
}
2012-02-13 10:05:59 +08:00
/** Testing colors */
static void test_colors ( )
{
say ( L " Testing colors " ) ;
assert ( rgb_color_t ( L " #FF00A0 " ) . is_rgb ( ) ) ;
assert ( rgb_color_t ( L " FF00A0 " ) . is_rgb ( ) ) ;
assert ( rgb_color_t ( L " #F30 " ) . is_rgb ( ) ) ;
assert ( rgb_color_t ( L " F30 " ) . is_rgb ( ) ) ;
assert ( rgb_color_t ( L " f30 " ) . is_rgb ( ) ) ;
assert ( rgb_color_t ( L " #FF30a5 " ) . is_rgb ( ) ) ;
assert ( rgb_color_t ( L " 3f30 " ) . is_none ( ) ) ;
assert ( rgb_color_t ( L " ##f30 " ) . is_none ( ) ) ;
assert ( rgb_color_t ( L " magenta " ) . is_named ( ) ) ;
assert ( rgb_color_t ( L " MaGeNTa " ) . is_named ( ) ) ;
assert ( rgb_color_t ( L " mooganta " ) . is_none ( ) ) ;
}
2007-05-11 03:11:28 +08:00
2013-03-06 12:54:16 +08:00
static void test_complete ( void )
{
say ( L " Testing complete " ) ;
const wchar_t * name_strs [ ] = { L " Foo1 " , L " Foo2 " , L " Foo3 " , L " Bar1 " , L " Bar2 " , L " Bar3 " } ;
size_t count = sizeof name_strs / sizeof * name_strs ;
const wcstring_list_t names ( name_strs , name_strs + count ) ;
2013-03-22 08:44:51 +08:00
2013-03-06 12:54:16 +08:00
complete_set_variable_names ( & names ) ;
2013-03-22 08:44:51 +08:00
2013-03-06 12:54:16 +08:00
std : : vector < completion_t > completions ;
complete ( L " $F " , completions , COMPLETION_REQUEST_DEFAULT ) ;
assert ( completions . size ( ) = = 3 ) ;
assert ( completions . at ( 0 ) . completion = = L " oo1 " ) ;
assert ( completions . at ( 1 ) . completion = = L " oo2 " ) ;
assert ( completions . at ( 2 ) . completion = = L " oo3 " ) ;
2013-06-02 16:14:26 +08:00
2013-05-26 06:41:18 +08:00
completions . clear ( ) ;
complete ( L " $1 " , completions , COMPLETION_REQUEST_DEFAULT ) ;
assert ( completions . empty ( ) ) ;
2013-06-02 16:14:26 +08:00
2013-05-26 06:41:18 +08:00
completions . clear ( ) ;
complete ( L " $1 " , completions , COMPLETION_REQUEST_DEFAULT | COMPLETION_REQUEST_FUZZY_MATCH ) ;
assert ( completions . size ( ) = = 2 ) ;
assert ( completions . at ( 0 ) . completion = = L " $Foo1 " ) ;
assert ( completions . at ( 1 ) . completion = = L " $Bar1 " ) ;
2013-06-02 16:14:26 +08:00
2013-03-06 12:54:16 +08:00
complete_set_variable_names ( NULL ) ;
}
2013-02-03 06:50:22 +08:00
static void test_1_completion ( wcstring line , const wcstring & completion , complete_flags_t flags , bool append_only , wcstring expected , long source_line )
{
// str is given with a caret, which we use to represent the cursor position
// find it
const size_t in_cursor_pos = line . find ( L ' ^ ' ) ;
assert ( in_cursor_pos ! = wcstring : : npos ) ;
line . erase ( in_cursor_pos , 1 ) ;
2013-02-04 03:38:22 +08:00
2013-02-03 06:50:22 +08:00
const size_t out_cursor_pos = expected . find ( L ' ^ ' ) ;
assert ( out_cursor_pos ! = wcstring : : npos ) ;
expected . erase ( out_cursor_pos , 1 ) ;
2013-02-04 03:38:22 +08:00
2013-02-03 06:50:22 +08:00
size_t cursor_pos = in_cursor_pos ;
wcstring result = completion_apply_to_command_line ( completion , flags , line , & cursor_pos , append_only ) ;
if ( result ! = expected )
{
fprintf ( stderr , " line %ld: %ls + %ls -> [%ls], expected [%ls] \n " , source_line , line . c_str ( ) , completion . c_str ( ) , result . c_str ( ) , expected . c_str ( ) ) ;
}
assert ( result = = expected ) ;
assert ( cursor_pos = = out_cursor_pos ) ;
}
2013-03-06 12:54:16 +08:00
static void test_completion_insertions ( )
2013-02-03 06:50:22 +08:00
{
2013-02-04 03:38:22 +08:00
# define TEST_1_COMPLETION(a, b, c, d, e) test_1_completion(a, b, c, d, e, __LINE__)
2013-03-06 12:54:16 +08:00
say ( L " Testing completion insertions " ) ;
2013-02-03 06:50:22 +08:00
TEST_1_COMPLETION ( L " foo^ " , L " bar " , 0 , false , L " foobar ^ " ) ;
TEST_1_COMPLETION ( L " foo^ baz " , L " bar " , 0 , false , L " foobar ^ baz " ) ; //we really do want to insert two spaces here - otherwise it's hidden by the cursor
TEST_1_COMPLETION ( L " 'foo^ " , L " bar " , 0 , false , L " 'foobar' ^ " ) ;
TEST_1_COMPLETION ( L " 'foo'^ " , L " bar " , 0 , false , L " 'foobar' ^ " ) ;
TEST_1_COMPLETION ( L " 'foo \\ '^ " , L " bar " , 0 , false , L " 'foo \\ 'bar' ^ " ) ;
TEST_1_COMPLETION ( L " foo \\ '^ " , L " bar " , 0 , false , L " foo \\ 'bar ^ " ) ;
2013-02-04 03:38:22 +08:00
2013-02-03 06:50:22 +08:00
// Test append only
TEST_1_COMPLETION ( L " foo^ " , L " bar " , 0 , true , L " foobar ^ " ) ;
TEST_1_COMPLETION ( L " foo^ baz " , L " bar " , 0 , true , L " foobar ^ baz " ) ;
TEST_1_COMPLETION ( L " 'foo^ " , L " bar " , 0 , true , L " 'foobar' ^ " ) ;
TEST_1_COMPLETION ( L " 'foo'^ " , L " bar " , 0 , true , L " 'foo'bar ^ " ) ;
TEST_1_COMPLETION ( L " 'foo \\ '^ " , L " bar " , 0 , true , L " 'foo \\ 'bar' ^ " ) ;
TEST_1_COMPLETION ( L " foo \\ '^ " , L " bar " , 0 , true , L " foo \\ 'bar ^ " ) ;
2013-02-04 03:38:22 +08:00
2013-02-03 06:50:22 +08:00
TEST_1_COMPLETION ( L " foo^ " , L " bar " , COMPLETE_NO_SPACE , false , L " foobar^ " ) ;
TEST_1_COMPLETION ( L " 'foo^ " , L " bar " , COMPLETE_NO_SPACE , false , L " 'foobar^ " ) ;
TEST_1_COMPLETION ( L " 'foo'^ " , L " bar " , COMPLETE_NO_SPACE , false , L " 'foobar'^ " ) ;
TEST_1_COMPLETION ( L " 'foo \\ '^ " , L " bar " , COMPLETE_NO_SPACE , false , L " 'foo \\ 'bar^ " ) ;
TEST_1_COMPLETION ( L " foo \\ '^ " , L " bar " , COMPLETE_NO_SPACE , false , L " foo \\ 'bar^ " ) ;
2013-05-26 06:41:18 +08:00
TEST_1_COMPLETION ( L " foo^ " , L " bar " , COMPLETE_REPLACES_TOKEN , false , L " bar ^ " ) ;
TEST_1_COMPLETION ( L " 'foo^ " , L " bar " , COMPLETE_REPLACES_TOKEN , false , L " bar ^ " ) ;
2013-02-03 06:50:22 +08:00
}
2012-07-07 05:34:53 +08:00
static void perform_one_autosuggestion_test ( const wcstring & command , const wcstring & wd , const wcstring & expected , long line )
{
wcstring suggestion ;
bool success = autosuggest_suggest_special ( command , wd , suggestion ) ;
if ( ! success )
{
printf ( " line %ld: autosuggest_suggest_special() failed for command %ls \n " , line , command . c_str ( ) ) ;
assert ( success ) ;
}
if ( suggestion ! = expected )
{
printf ( " line %ld: autosuggest_suggest_special() returned the wrong expected string for command %ls \n " , line , command . c_str ( ) ) ;
printf ( " actual: %ls \n " , suggestion . c_str ( ) ) ;
printf ( " expected: %ls \n " , expected . c_str ( ) ) ;
2012-11-18 18:23:22 +08:00
assert ( suggestion = = expected ) ;
2012-07-07 05:34:53 +08:00
}
}
/* Testing test_autosuggest_suggest_special, in particular for properly handling quotes and backslashes */
2012-11-19 08:30:30 +08:00
static void test_autosuggest_suggest_special ( )
{
2012-07-07 05:34:53 +08:00
if ( system ( " mkdir -p '/tmp/autosuggest_test/0foobar' " ) ) err ( L " mkdir failed " ) ;
if ( system ( " mkdir -p '/tmp/autosuggest_test/1foo bar' " ) ) err ( L " mkdir failed " ) ;
if ( system ( " mkdir -p '/tmp/autosuggest_test/2foo bar' " ) ) err ( L " mkdir failed " ) ;
if ( system ( " mkdir -p '/tmp/autosuggest_test/3foo \\ bar' " ) ) err ( L " mkdir failed " ) ;
if ( system ( " mkdir -p /tmp/autosuggest_test/4foo \\ 'bar " ) ) err ( L " mkdir failed " ) ; //a path with a single quote
if ( system ( " mkdir -p /tmp/autosuggest_test/5foo \\ \" bar " ) ) err ( L " mkdir failed " ) ; //a path with a double quote
if ( system ( " mkdir -p ~/test_autosuggest_suggest_special/ " ) ) err ( L " mkdir failed " ) ; //make sure tilde is handled
2012-11-18 18:23:22 +08:00
2012-07-07 05:34:53 +08:00
const wcstring wd = L " /tmp/autosuggest_test/ " ;
perform_one_autosuggestion_test ( L " cd /tmp/autosuggest_test/0 " , wd , L " cd /tmp/autosuggest_test/0foobar/ " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd \" /tmp/autosuggest_test/0 " , wd , L " cd \" /tmp/autosuggest_test/0foobar/ \" " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd '/tmp/autosuggest_test/0 " , wd , L " cd '/tmp/autosuggest_test/0foobar/' " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd 0 " , wd , L " cd 0foobar/ " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd \" 0 " , wd , L " cd \" 0foobar/ \" " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd '0 " , wd , L " cd '0foobar/' " , __LINE__ ) ;
2012-11-18 18:23:22 +08:00
2012-07-07 05:34:53 +08:00
perform_one_autosuggestion_test ( L " cd /tmp/autosuggest_test/1 " , wd , L " cd /tmp/autosuggest_test/1foo \\ bar/ " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd \" /tmp/autosuggest_test/1 " , wd , L " cd \" /tmp/autosuggest_test/1foo bar/ \" " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd '/tmp/autosuggest_test/1 " , wd , L " cd '/tmp/autosuggest_test/1foo bar/' " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd 1 " , wd , L " cd 1foo \\ bar/ " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd \" 1 " , wd , L " cd \" 1foo bar/ \" " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd '1 " , wd , L " cd '1foo bar/' " , __LINE__ ) ;
2012-11-18 18:23:22 +08:00
2012-07-07 05:34:53 +08:00
perform_one_autosuggestion_test ( L " cd /tmp/autosuggest_test/2 " , wd , L " cd /tmp/autosuggest_test/2foo \\ \\ bar/ " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd \" /tmp/autosuggest_test/2 " , wd , L " cd \" /tmp/autosuggest_test/2foo bar/ \" " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd '/tmp/autosuggest_test/2 " , wd , L " cd '/tmp/autosuggest_test/2foo bar/' " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd 2 " , wd , L " cd 2foo \\ \\ bar/ " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd \" 2 " , wd , L " cd \" 2foo bar/ \" " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd '2 " , wd , L " cd '2foo bar/' " , __LINE__ ) ;
2012-11-18 18:23:22 +08:00
2012-07-07 05:34:53 +08:00
perform_one_autosuggestion_test ( L " cd /tmp/autosuggest_test/3 " , wd , L " cd /tmp/autosuggest_test/3foo \\ \\ bar/ " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd \" /tmp/autosuggest_test/3 " , wd , L " cd \" /tmp/autosuggest_test/3foo \\ bar/ \" " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd '/tmp/autosuggest_test/3 " , wd , L " cd '/tmp/autosuggest_test/3foo \\ bar/' " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd 3 " , wd , L " cd 3foo \\ \\ bar/ " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd \" 3 " , wd , L " cd \" 3foo \\ bar/ \" " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd '3 " , wd , L " cd '3foo \\ bar/' " , __LINE__ ) ;
2012-11-18 18:23:22 +08:00
2012-07-07 05:34:53 +08:00
perform_one_autosuggestion_test ( L " cd /tmp/autosuggest_test/4 " , wd , L " cd /tmp/autosuggest_test/4foo \\ 'bar/ " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd \" /tmp/autosuggest_test/4 " , wd , L " cd \" /tmp/autosuggest_test/4foo'bar/ \" " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd '/tmp/autosuggest_test/4 " , wd , L " cd '/tmp/autosuggest_test/4foo \\ 'bar/' " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd 4 " , wd , L " cd 4foo \\ 'bar/ " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd \" 4 " , wd , L " cd \" 4foo'bar/ \" " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd '4 " , wd , L " cd '4foo \\ 'bar/' " , __LINE__ ) ;
2012-11-18 18:23:22 +08:00
2012-07-07 05:34:53 +08:00
perform_one_autosuggestion_test ( L " cd /tmp/autosuggest_test/5 " , wd , L " cd /tmp/autosuggest_test/5foo \\ \" bar/ " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd \" /tmp/autosuggest_test/5 " , wd , L " cd \" /tmp/autosuggest_test/5foo \\ \" bar/ \" " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd '/tmp/autosuggest_test/5 " , wd , L " cd '/tmp/autosuggest_test/5foo \" bar/' " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd 5 " , wd , L " cd 5foo \\ \" bar/ " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd \" 5 " , wd , L " cd \" 5foo \\ \" bar/ \" " , __LINE__ ) ;
perform_one_autosuggestion_test ( L " cd '5 " , wd , L " cd '5foo \" bar/' " , __LINE__ ) ;
2012-11-18 18:23:22 +08:00
2012-07-07 05:34:53 +08:00
perform_one_autosuggestion_test ( L " cd ~/test_autosuggest_suggest_specia " , wd , L " cd ~/test_autosuggest_suggest_special/ " , __LINE__ ) ;
2012-11-18 18:23:22 +08:00
2012-07-07 05:34:53 +08:00
// A single quote should defeat tilde expansion
perform_one_autosuggestion_test ( L " cd '~/test_autosuggest_suggest_specia' " , wd , L " " , __LINE__ ) ;
2012-11-18 18:23:22 +08:00
2012-07-07 05:34:53 +08:00
system ( " rm -Rf '/tmp/autosuggest_test/' " ) ;
system ( " rm -Rf ~/test_autosuggest_suggest_special/ " ) ;
2012-02-19 15:26:39 +08:00
}
2013-01-06 07:21:42 +08:00
static void test_autosuggestion_combining ( )
{
say ( L " Testing autosuggestion combining " ) ;
assert ( combine_command_and_autosuggestion ( L " alpha " , L " alphabeta " ) = = L " alphabeta " ) ;
2013-01-08 18:39:22 +08:00
2013-01-06 07:21:42 +08:00
// when the last token contains no capital letters, we use the case of the autosuggestion
assert ( combine_command_and_autosuggestion ( L " alpha " , L " ALPHABETA " ) = = L " ALPHABETA " ) ;
2013-01-08 18:39:22 +08:00
2013-01-06 07:21:42 +08:00
// when the last token contains capital letters, we use its case
assert ( combine_command_and_autosuggestion ( L " alPha " , L " alphabeTa " ) = = L " alPhabeTa " ) ;
2013-01-08 18:39:22 +08:00
2013-01-07 06:10:03 +08:00
// if autosuggestion is not longer than input, use the input's case
assert ( combine_command_and_autosuggestion ( L " alpha " , L " ALPHAA " ) = = L " ALPHAA " ) ;
assert ( combine_command_and_autosuggestion ( L " alpha " , L " ALPHA " ) = = L " alpha " ) ;
2013-01-06 07:21:42 +08:00
}
2007-05-11 03:11:28 +08:00
2005-10-24 23:26:25 +08:00
/**
Test speed of completion calculations
*/
2005-09-20 21:26:39 +08:00
void perf_complete ( )
{
2012-11-19 08:30:30 +08:00
wchar_t c ;
std : : vector < completion_t > out ;
long long t1 , t2 ;
int matches = 0 ;
double t ;
wchar_t str [ 3 ] =
{
0 , 0 , 0
}
;
int i ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
say ( L " Testing completion performance " ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
reader_push ( L " " ) ;
say ( L " Here we go " ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
t1 = get_time ( ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
for ( c = L ' a ' ; c < = L ' z ' ; c + + )
{
str [ 0 ] = c ;
reader_set_buffer ( str , 0 ) ;
2012-11-18 18:23:22 +08:00
2013-03-06 12:54:16 +08:00
complete ( str , out , COMPLETION_REQUEST_DEFAULT , NULL ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
matches + = out . size ( ) ;
2012-02-06 08:42:24 +08:00
out . clear ( ) ;
2012-11-19 08:30:30 +08:00
}
t2 = get_time ( ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
t = ( double ) ( t2 - t1 ) / ( 1000000 * 26 ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
say ( L " One letter command completion took %f seconds per completion, %f microseconds/match " , t , ( double ) ( t2 - t1 ) / matches ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
matches = 0 ;
t1 = get_time ( ) ;
for ( i = 0 ; i < LAPS ; i + + )
{
str [ 0 ] = ' a ' + ( rand ( ) % 26 ) ;
str [ 1 ] = ' a ' + ( rand ( ) % 26 ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
reader_set_buffer ( str , 0 ) ;
2012-11-18 18:23:22 +08:00
2013-03-06 12:54:16 +08:00
complete ( str , out , COMPLETION_REQUEST_DEFAULT , NULL ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
matches + = out . size ( ) ;
2012-02-06 08:42:24 +08:00
out . clear ( ) ;
2012-11-19 08:30:30 +08:00
}
t2 = get_time ( ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
t = ( double ) ( t2 - t1 ) / ( 1000000 * LAPS ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
say ( L " Two letter command completion took %f seconds per completion, %f microseconds/match " , t , ( double ) ( t2 - t1 ) / matches ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
reader_pop ( ) ;
2012-11-18 18:23:22 +08:00
2005-09-20 21:26:39 +08:00
}
2012-11-19 08:30:30 +08:00
static void test_history_matches ( history_search_t & search , size_t matches )
{
2012-02-06 08:42:24 +08:00
size_t i ;
2012-11-19 08:30:30 +08:00
for ( i = 0 ; i < matches ; i + + )
{
2012-02-06 08:42:24 +08:00
assert ( search . go_backwards ( ) ) ;
2012-02-16 16:24:27 +08:00
wcstring item = search . current_string ( ) ;
2012-02-06 08:42:24 +08:00
}
assert ( ! search . go_backwards ( ) ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
for ( i = 1 ; i < matches ; i + + )
{
2012-02-06 08:42:24 +08:00
assert ( search . go_forwards ( ) ) ;
}
assert ( ! search . go_forwards ( ) ) ;
}
2012-11-19 08:30:30 +08:00
static bool history_contains ( history_t * history , const wcstring & txt )
{
2012-04-17 11:26:50 +08:00
bool result = false ;
size_t i ;
2012-11-19 08:30:30 +08:00
for ( i = 1 ; ; i + + )
{
2012-04-17 11:26:50 +08:00
history_item_t item = history - > item_at_index ( i ) ;
if ( item . empty ( ) )
break ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
if ( item . str ( ) = = txt )
{
2012-04-17 11:26:50 +08:00
result = true ;
break ;
}
}
return result ;
}
2012-11-19 08:30:30 +08:00
class history_tests_t
{
2012-02-16 16:24:27 +08:00
public :
static void test_history ( void ) ;
2012-04-17 11:26:50 +08:00
static void test_history_merge ( void ) ;
2012-06-16 07:22:37 +08:00
static void test_history_formats ( void ) ;
2012-12-03 18:25:08 +08:00
static void test_history_speed ( void ) ;
2012-12-03 15:38:38 +08:00
static void test_history_races ( void ) ;
static void test_history_races_pound_on_history ( ) ;
2012-02-16 16:24:27 +08:00
} ;
2012-11-19 08:30:30 +08:00
static wcstring random_string ( void )
{
2012-02-16 16:24:27 +08:00
wcstring result ;
size_t max = 1 + rand ( ) % 32 ;
2012-11-19 08:30:30 +08:00
while ( max - - )
{
2012-02-16 16:24:27 +08:00
wchar_t c = 1 + rand ( ) % ESCAPE_TEST_CHAR ;
result . push_back ( c ) ;
}
return result ;
}
2012-11-19 08:30:30 +08:00
void history_tests_t : : test_history ( void )
{
say ( L " Testing history " ) ;
2012-11-18 18:23:22 +08:00
2012-02-06 08:42:24 +08:00
history_t & history = history_t : : history_with_name ( L " test_history " ) ;
2012-02-16 16:24:27 +08:00
history . clear ( ) ;
2012-02-06 08:42:24 +08:00
history . add ( L " Gamma " ) ;
2012-02-06 14:48:43 +08:00
history . add ( L " Beta " ) ;
history . add ( L " Alpha " ) ;
2012-11-18 18:23:22 +08:00
2012-02-06 08:42:24 +08:00
/* All three items match "a" */
history_search_t search1 ( history , L " a " ) ;
test_history_matches ( search1 , 3 ) ;
2012-02-16 16:24:27 +08:00
assert ( search1 . current_string ( ) = = L " Alpha " ) ;
2012-11-18 18:23:22 +08:00
2012-02-06 08:42:24 +08:00
/* One item matches "et" */
history_search_t search2 ( history , L " et " ) ;
test_history_matches ( search2 , 1 ) ;
2012-02-16 16:24:27 +08:00
assert ( search2 . current_string ( ) = = L " Beta " ) ;
2012-06-05 12:24:42 +08:00
/* Test item removal */
history . remove ( L " Alpha " ) ;
history_search_t search3 ( history , L " Alpha " ) ;
test_history_matches ( search3 , 0 ) ;
2012-11-18 18:23:22 +08:00
2012-02-16 16:24:27 +08:00
/* Test history escaping and unescaping, yaml, etc. */
std : : vector < history_item_t > before , after ;
history . clear ( ) ;
size_t i , max = 100 ;
2012-11-19 08:30:30 +08:00
for ( i = 1 ; i < = max ; i + + )
{
2012-11-18 18:23:22 +08:00
2012-02-16 16:24:27 +08:00
/* Generate a value */
2012-03-07 16:54:01 +08:00
wcstring value = wcstring ( L " test item " ) + to_string ( i ) ;
2012-11-18 18:23:22 +08:00
2012-02-24 02:29:00 +08:00
/* Maybe add some backslashes */
if ( i % 3 = = 0 )
value . append ( L " (slashies \\ \\ \\ slashies) " ) ;
2012-02-16 16:24:27 +08:00
/* Generate some paths */
2012-11-18 18:23:22 +08:00
path_list_t paths ;
2012-02-16 16:24:27 +08:00
size_t count = rand ( ) % 6 ;
2012-11-19 08:30:30 +08:00
while ( count - - )
{
2012-07-25 13:31:31 +08:00
paths . push_back ( random_string ( ) ) ;
2012-02-16 16:24:27 +08:00
}
2012-11-18 18:23:22 +08:00
2012-02-16 16:24:27 +08:00
/* Record this item */
history_item_t item ( value , time ( NULL ) , paths ) ;
before . push_back ( item ) ;
history . add ( item ) ;
}
history . save ( ) ;
2012-11-18 18:23:22 +08:00
2012-02-16 16:24:27 +08:00
/* Read items back in reverse order and ensure they're the same */
2012-11-19 08:30:30 +08:00
for ( i = 100 ; i > = 1 ; i - - )
{
2012-02-16 16:24:27 +08:00
history_item_t item = history . item_at_index ( i ) ;
assert ( ! item . empty ( ) ) ;
after . push_back ( item ) ;
}
assert ( before . size ( ) = = after . size ( ) ) ;
2012-11-19 08:30:30 +08:00
for ( size_t i = 0 ; i < before . size ( ) ; i + + )
{
2012-02-16 16:24:27 +08:00
const history_item_t & bef = before . at ( i ) , & aft = after . at ( i ) ;
assert ( bef . contents = = aft . contents ) ;
assert ( bef . creation_timestamp = = aft . creation_timestamp ) ;
assert ( bef . required_paths = = aft . required_paths ) ;
}
2012-11-18 18:23:22 +08:00
2012-04-17 11:26:50 +08:00
/* Clean up after our tests */
history . clear ( ) ;
}
// wait until the next second
2012-11-19 08:30:30 +08:00
static void time_barrier ( void )
{
2012-04-17 11:26:50 +08:00
time_t start = time ( NULL ) ;
2012-11-19 08:30:30 +08:00
do
{
2012-04-17 11:26:50 +08:00
usleep ( 1000 ) ;
2012-11-19 08:30:30 +08:00
}
while ( time ( NULL ) = = start ) ;
2012-04-17 11:26:50 +08:00
}
2012-12-03 15:38:38 +08:00
static wcstring_list_t generate_history_lines ( int pid )
{
wcstring_list_t result ;
long max = 256 ;
result . reserve ( max ) ;
for ( long i = 0 ; i < max ; i + + )
{
result . push_back ( format_string ( L " %ld %ld " , ( long ) pid , i ) ) ;
}
return result ;
}
void history_tests_t : : test_history_races_pound_on_history ( )
{
/* Called in child process to modify history */
history_t * hist = new history_t ( L " race_test " ) ;
hist - > chaos_mode = true ;
const wcstring_list_t lines = generate_history_lines ( getpid ( ) ) ;
for ( size_t idx = 0 ; idx < lines . size ( ) ; idx + + )
{
const wcstring & line = lines . at ( idx ) ;
hist - > add ( line ) ;
hist - > save ( ) ;
}
delete hist ;
}
void history_tests_t : : test_history_races ( void )
{
say ( L " Testing history race conditions " ) ;
2012-12-03 18:25:08 +08:00
2012-12-03 15:38:38 +08:00
// Ensure history is clear
history_t * hist = new history_t ( L " race_test " ) ;
hist - > clear ( ) ;
delete hist ;
2012-12-03 18:25:08 +08:00
2012-12-03 15:38:38 +08:00
// Test concurrent history writing
2012-12-03 17:53:52 +08:00
# define RACE_COUNT 10
2012-12-03 15:38:38 +08:00
pid_t children [ RACE_COUNT ] ;
2012-12-03 18:25:08 +08:00
2012-12-03 15:38:38 +08:00
for ( size_t i = 0 ; i < RACE_COUNT ; i + + )
{
pid_t pid = fork ( ) ;
if ( ! pid )
{
// Child process
setup_fork_guards ( ) ;
test_history_races_pound_on_history ( ) ;
exit_without_destructors ( 0 ) ;
}
else
{
// Parent process
children [ i ] = pid ;
}
}
2012-12-03 18:25:08 +08:00
2012-12-03 15:38:38 +08:00
// Wait for all children
for ( size_t i = 0 ; i < RACE_COUNT ; i + + )
{
int stat ;
waitpid ( children [ i ] , & stat , WUNTRACED ) ;
}
2012-12-03 18:25:08 +08:00
2012-12-03 15:38:38 +08:00
// Compute the expected lines
wcstring_list_t lines [ RACE_COUNT ] ;
for ( size_t i = 0 ; i < RACE_COUNT ; i + + )
{
lines [ i ] = generate_history_lines ( children [ i ] ) ;
}
2012-12-03 18:25:08 +08:00
2012-12-03 15:38:38 +08:00
// Count total lines
size_t line_count = 0 ;
for ( size_t i = 0 ; i < RACE_COUNT ; i + + )
{
line_count + = lines [ i ] . size ( ) ;
}
// Ensure we consider the lines that have been outputted as part of our history
time_barrier ( ) ;
2012-12-03 18:25:08 +08:00
2012-12-03 15:38:38 +08:00
/* Ensure that we got sane, sorted results */
hist = new history_t ( L " race_test " ) ;
hist - > chaos_mode = true ;
size_t hist_idx ;
for ( hist_idx = 1 ; ; hist_idx + + )
{
history_item_t item = hist - > item_at_index ( hist_idx ) ;
if ( item . empty ( ) )
break ;
2012-12-03 18:25:08 +08:00
2012-12-03 15:38:38 +08:00
// The item must be present in one of our 'lines' arrays
// If it is present, then every item after it is assumed to be missed
size_t i ;
for ( i = 0 ; i < RACE_COUNT ; i + + )
{
wcstring_list_t : : iterator where = std : : find ( lines [ i ] . begin ( ) , lines [ i ] . end ( ) , item . str ( ) ) ;
if ( where ! = lines [ i ] . end ( ) )
{
// Delete everything from the found location onwards
lines [ i ] . resize ( where - lines [ i ] . begin ( ) ) ;
2012-12-03 18:25:08 +08:00
2012-12-03 15:38:38 +08:00
// Break because we found it
break ;
}
}
2012-12-03 17:53:52 +08:00
if ( i > = RACE_COUNT )
{
err ( L " Line '%ls' found in history not found in some array " , item . str ( ) . c_str ( ) ) ;
}
2012-12-03 15:38:38 +08:00
}
// every write should add at least one item
assert ( hist_idx > = RACE_COUNT ) ;
2012-12-03 18:25:08 +08:00
2012-12-03 15:38:38 +08:00
//hist->clear();
delete hist ;
}
2012-11-19 08:30:30 +08:00
void history_tests_t : : test_history_merge ( void )
{
2012-04-17 11:26:50 +08:00
// In a single fish process, only one history is allowed to exist with the given name
// But it's common to have multiple history instances with the same name active in different processes,
// e.g. when you have multiple shells open.
// We try to get that right and merge all their history together. Test that case.
2012-11-19 08:30:30 +08:00
say ( L " Testing history merge " ) ;
2012-04-17 11:26:50 +08:00
const size_t count = 3 ;
const wcstring name = L " merge_test " ;
history_t * hists [ count ] = { new history_t ( name ) , new history_t ( name ) , new history_t ( name ) } ;
wcstring texts [ count ] = { L " History 1 " , L " History 2 " , L " History 3 " } ;
2012-11-18 18:23:22 +08:00
2012-04-17 11:26:50 +08:00
/* Make sure history is clear */
2012-11-19 08:30:30 +08:00
for ( size_t i = 0 ; i < count ; i + + )
{
2012-04-17 11:26:50 +08:00
hists [ i ] - > clear ( ) ;
}
2012-11-18 18:23:22 +08:00
2012-04-17 11:26:50 +08:00
/* Make sure we don't add an item in the same second as we created the history */
time_barrier ( ) ;
2012-11-18 18:23:22 +08:00
2012-04-17 11:26:50 +08:00
/* Add a different item to each */
2012-11-19 08:30:30 +08:00
for ( size_t i = 0 ; i < count ; i + + )
{
2012-04-17 11:26:50 +08:00
hists [ i ] - > add ( texts [ i ] ) ;
}
2012-11-18 18:23:22 +08:00
2012-04-17 11:26:50 +08:00
/* Save them */
2012-11-19 08:30:30 +08:00
for ( size_t i = 0 ; i < count ; i + + )
{
2012-04-17 11:26:50 +08:00
hists [ i ] - > save ( ) ;
}
2012-11-18 18:23:22 +08:00
2012-04-17 11:26:50 +08:00
/* Make sure each history contains what it ought to, but they have not leaked into each other */
2012-11-19 08:30:30 +08:00
for ( size_t i = 0 ; i < count ; i + + )
{
for ( size_t j = 0 ; j < count ; j + + )
{
2012-04-17 11:26:50 +08:00
bool does_contain = history_contains ( hists [ i ] , texts [ j ] ) ;
bool should_contain = ( i = = j ) ;
assert ( should_contain = = does_contain ) ;
}
}
2012-11-18 18:23:22 +08:00
2012-04-17 11:26:50 +08:00
/* Make a new history. It should contain everything. The time_barrier() is so that the timestamp is newer, since we only pick up items whose timestamp is before the birth stamp. */
time_barrier ( ) ;
history_t * everything = new history_t ( name ) ;
2012-11-19 08:30:30 +08:00
for ( size_t i = 0 ; i < count ; i + + )
{
2012-04-17 11:26:50 +08:00
assert ( history_contains ( everything , texts [ i ] ) ) ;
}
/* Clean up */
2012-11-19 08:30:30 +08:00
for ( size_t i = 0 ; i < 3 ; i + + )
{
2012-04-17 11:26:50 +08:00
delete hists [ i ] ;
}
everything - > clear ( ) ;
delete everything ; //not as scary as it looks
2012-02-06 08:42:24 +08:00
}
2012-11-19 08:30:30 +08:00
static bool install_sample_history ( const wchar_t * name )
{
2012-06-16 07:22:37 +08:00
char command [ 512 ] ;
2012-11-18 18:23:22 +08:00
snprintf ( command , sizeof command , " cp tests/%ls ~/.config/fish/%ls_history " , name , name ) ;
2012-11-19 08:30:30 +08:00
if ( system ( command ) )
{
2012-06-16 07:22:37 +08:00
err ( L " Failed to copy sample history " ) ;
return false ;
}
return true ;
}
/* Indicates whether the history is equal to the given null-terminated array of strings. */
2012-11-19 08:30:30 +08:00
static bool history_equals ( history_t & hist , const wchar_t * const * strings )
{
2012-06-16 07:22:37 +08:00
/* Count our expected items */
size_t expected_count = 0 ;
2012-11-19 08:30:30 +08:00
while ( strings [ expected_count ] )
{
2012-06-16 07:22:37 +08:00
expected_count + + ;
}
2012-11-18 18:23:22 +08:00
2012-06-16 07:22:37 +08:00
/* Ensure the contents are the same */
size_t history_idx = 1 ;
size_t array_idx = 0 ;
2012-11-19 08:30:30 +08:00
for ( ; ; )
{
2012-06-16 07:22:37 +08:00
const wchar_t * expected = strings [ array_idx ] ;
history_item_t item = hist . item_at_index ( history_idx ) ;
2012-11-19 08:30:30 +08:00
if ( expected = = NULL )
{
if ( ! item . empty ( ) )
{
2012-06-16 07:22:37 +08:00
err ( L " Expected empty item at history index %lu " , history_idx ) ;
}
break ;
2012-11-19 08:30:30 +08:00
}
else
{
if ( item . str ( ) ! = expected )
{
2012-06-16 07:22:37 +08:00
err ( L " Expected '%ls', found '%ls' at index %lu " , expected , item . str ( ) . c_str ( ) , history_idx ) ;
}
}
history_idx + + ;
array_idx + + ;
}
2012-11-18 18:23:22 +08:00
2012-06-16 07:22:37 +08:00
return true ;
}
2012-11-19 08:30:30 +08:00
void history_tests_t : : test_history_formats ( void )
{
2012-06-16 07:22:37 +08:00
const wchar_t * name ;
2012-11-18 18:23:22 +08:00
2012-06-16 07:22:37 +08:00
// Test inferring and reading legacy and bash history formats
name = L " history_sample_fish_1_x " ;
say ( L " Testing %ls " , name ) ;
2012-11-19 08:30:30 +08:00
if ( ! install_sample_history ( name ) )
{
2012-06-16 07:22:37 +08:00
err ( L " Couldn't open file tests/%ls " , name ) ;
2012-11-19 08:30:30 +08:00
}
else
{
2012-06-16 07:22:37 +08:00
/* Note: This is backwards from what appears in the file */
2012-11-19 08:30:30 +08:00
const wchar_t * const expected [ ] =
{
2012-06-16 07:22:37 +08:00
L " #def " ,
L " echo #abc " ,
2012-11-18 18:23:22 +08:00
2012-06-16 07:22:37 +08:00
L " function yay \n "
" echo hi \n "
" end " ,
2012-11-18 18:23:22 +08:00
2012-06-16 07:22:37 +08:00
L " cd foobar " ,
2012-11-18 18:23:22 +08:00
2012-06-16 07:22:37 +08:00
L " ls / " ,
2012-11-18 18:23:22 +08:00
2012-06-16 07:22:37 +08:00
NULL
} ;
2012-11-18 18:23:22 +08:00
2012-06-16 07:22:37 +08:00
history_t & test_history = history_t : : history_with_name ( name ) ;
2012-11-19 08:30:30 +08:00
if ( ! history_equals ( test_history , expected ) )
{
2012-06-16 07:22:37 +08:00
err ( L " test_history_formats failed for %ls \n " , name ) ;
}
test_history . clear ( ) ;
}
2012-11-18 18:23:22 +08:00
2012-06-16 07:22:37 +08:00
name = L " history_sample_fish_2_0 " ;
say ( L " Testing %ls " , name ) ;
2012-11-19 08:30:30 +08:00
if ( ! install_sample_history ( name ) )
{
2012-06-16 07:22:37 +08:00
err ( L " Couldn't open file tests/%ls " , name ) ;
2012-11-19 08:30:30 +08:00
}
else
{
const wchar_t * const expected [ ] =
{
2012-06-16 07:22:37 +08:00
L " echo this has \\ \n backslashes " ,
2012-11-18 18:23:22 +08:00
2012-06-16 07:22:37 +08:00
L " function foo \n "
" echo bar \n "
" end " ,
2012-11-18 18:23:22 +08:00
2012-06-16 07:22:37 +08:00
L " echo alpha " ,
2012-11-18 18:23:22 +08:00
2012-06-16 07:22:37 +08:00
NULL
} ;
2012-11-18 18:23:22 +08:00
2012-06-16 07:22:37 +08:00
history_t & test_history = history_t : : history_with_name ( name ) ;
2012-11-19 08:30:30 +08:00
if ( ! history_equals ( test_history , expected ) )
{
2012-06-16 07:22:37 +08:00
err ( L " test_history_formats failed for %ls \n " , name ) ;
}
test_history . clear ( ) ;
}
2012-11-18 18:23:22 +08:00
2012-06-16 07:22:37 +08:00
say ( L " Testing bash import " ) ;
FILE * f = fopen ( " tests/history_sample_bash " , " r " ) ;
2012-11-19 08:30:30 +08:00
if ( ! f )
{
2012-06-16 07:22:37 +08:00
err ( L " Couldn't open file tests/history_sample_bash " ) ;
2012-11-19 08:30:30 +08:00
}
else
{
2012-06-16 07:22:37 +08:00
// It should skip over the export command since that's a bash-ism
2012-11-19 08:30:30 +08:00
const wchar_t * expected [ ] =
{
2012-06-16 07:22:37 +08:00
L " echo supsup " ,
2012-11-18 18:23:22 +08:00
2012-06-16 07:22:37 +08:00
L " history --help " ,
2012-11-18 18:23:22 +08:00
2012-06-16 07:22:37 +08:00
L " echo foo " ,
2012-11-18 18:23:22 +08:00
2012-06-16 07:22:37 +08:00
NULL
} ;
history_t & test_history = history_t : : history_with_name ( L " bash_import " ) ;
test_history . populate_from_bash ( f ) ;
2012-11-19 08:30:30 +08:00
if ( ! history_equals ( test_history , expected ) )
{
2012-06-16 07:22:37 +08:00
err ( L " test_history_formats failed for bash import \n " ) ;
}
test_history . clear ( ) ;
fclose ( f ) ;
}
}
2012-12-03 18:25:08 +08:00
void history_tests_t : : test_history_speed ( void )
{
2013-04-28 06:21:14 +08:00
say ( L " Testing history speed (pid is %d) " , getpid ( ) ) ;
2012-12-03 18:25:08 +08:00
history_t * hist = new history_t ( L " speed_test " ) ;
wcstring item = L " History Speed Test - X " ;
/* Test for 10 seconds */
double start = timef ( ) ;
2013-04-28 06:21:14 +08:00
double end = start + 10 ;
2012-12-03 18:25:08 +08:00
double stop = 0 ;
size_t count = 0 ;
for ( ; ; )
{
item [ item . size ( ) - 1 ] = L ' 0 ' + ( count % 10 ) ;
hist - > add ( item ) ;
count + + ;
stop = timef ( ) ;
if ( stop > = end )
break ;
}
printf ( " %lu items - %.2f msec per item \n " , ( unsigned long ) count , ( stop - start ) * 1E6 / count ) ;
hist - > clear ( ) ;
delete hist ;
}
2013-06-09 10:20:26 +08:00
static void test_new_parser ( void )
{
say ( L " Testing new parser! " ) ;
2013-06-16 05:32:38 +08:00
const wcstring src = L " echo hello world " ;
parse_node_tree_t parse_tree ;
2013-06-09 10:20:26 +08:00
parse_t parser ;
2013-06-16 05:32:38 +08:00
parser . parse ( src , & parse_tree ) ;
parse_execution_context_t ctx ( parse_tree , src ) ;
say ( L " Simulating execution: " ) ;
wcstring simulation = ctx . simulate ( ) ;
printf ( " %ls \n " , simulation . c_str ( ) ) ;
2013-06-09 10:20:26 +08:00
}
2007-05-11 03:11:28 +08:00
2005-10-24 23:26:25 +08:00
/**
2012-11-18 18:23:22 +08:00
Main test
2005-10-24 23:26:25 +08:00
*/
2012-11-19 08:30:30 +08:00
int main ( int argc , char * * argv )
2005-09-20 21:26:39 +08:00
{
2012-11-19 08:30:30 +08:00
setlocale ( LC_ALL , " " ) ;
srand ( time ( 0 ) ) ;
2012-05-14 11:19:02 +08:00
configure_thread_assertions_for_testing ( ) ;
2013-05-05 17:33:17 +08:00
2012-11-19 08:30:30 +08:00
program_name = L " (ignore) " ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
say ( L " Testing low-level functionality " ) ;
say ( L " Lines beginning with '(ignore):' are not errors, they are warning messages \n generated by the fish parser library when given broken input, and can be \n ignored. All actual errors begin with 'Error:'. " ) ;
set_main_thread ( ) ;
2012-03-07 16:54:01 +08:00
setup_fork_guards ( ) ;
2013-06-09 10:20:26 +08:00
//proc_init();
2012-11-19 08:30:30 +08:00
event_init ( ) ;
function_init ( ) ;
builtin_init ( ) ;
reader_init ( ) ;
env_init ( ) ;
2013-06-09 10:20:26 +08:00
test_new_parser ( ) ;
return 0 ;
2013-05-05 17:33:17 +08:00
2012-03-01 03:27:14 +08:00
test_format ( ) ;
2012-11-19 08:30:30 +08:00
test_escape ( ) ;
test_convert ( ) ;
2012-12-20 05:31:06 +08:00
test_convert_nulls ( ) ;
2012-11-19 08:30:30 +08:00
test_tok ( ) ;
2012-03-01 09:55:50 +08:00
test_fork ( ) ;
2012-11-19 08:30:30 +08:00
test_parser ( ) ;
test_lru ( ) ;
test_expand ( ) ;
2013-05-26 06:41:18 +08:00
test_fuzzy_match ( ) ;
2012-03-07 16:54:01 +08:00
test_test ( ) ;
2012-11-19 08:30:30 +08:00
test_path ( ) ;
2012-12-21 09:37:09 +08:00
test_word_motion ( ) ;
2012-05-14 11:19:02 +08:00
test_is_potential_path ( ) ;
2012-02-13 10:05:59 +08:00
test_colors ( ) ;
2013-03-06 12:54:16 +08:00
test_complete ( ) ;
test_completion_insertions ( ) ;
2013-01-06 07:21:42 +08:00
test_autosuggestion_combining ( ) ;
2012-07-07 05:34:53 +08:00
test_autosuggest_suggest_special ( ) ;
2012-02-16 16:24:27 +08:00
history_tests_t : : test_history ( ) ;
2012-04-17 11:26:50 +08:00
history_tests_t : : test_history_merge ( ) ;
2012-12-03 15:38:38 +08:00
history_tests_t : : test_history_races ( ) ;
2012-06-16 07:22:37 +08:00
history_tests_t : : test_history_formats ( ) ;
2012-12-03 18:25:08 +08:00
//history_tests_t::test_history_speed();
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
say ( L " Encountered %d errors in low-level tests " , err_count ) ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
/*
Skip performance tests for now , since they seem to hang when running from inside make ( ? )
*/
2012-11-18 18:23:22 +08:00
// say( L"Testing performance" );
// perf_complete();
2012-11-19 08:30:30 +08:00
env_destroy ( ) ;
reader_destroy ( ) ;
builtin_destroy ( ) ;
wutil_destroy ( ) ;
event_destroy ( ) ;
proc_destroy ( ) ;
2012-11-18 18:23:22 +08:00
2005-09-20 21:26:39 +08:00
}