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"
2013-07-17 16:35:30 +08:00
# include "parse_util.h"
2012-07-07 05:34:53 +08:00
2013-10-13 02:32:34 +08:00
static const char * const * s_arguments ;
2013-12-09 05:41:12 +08:00
static int s_test_run_count = 0 ;
2013-10-13 02:32:34 +08:00
/* Indicate if we should test the given function. Either we test everything (all arguments) or we run only tests that have a prefix in s_arguments */
static bool should_test_function ( const char * func_name )
{
/* No args, test everything */
2013-12-09 05:41:12 +08:00
bool result = false ;
2013-10-13 02:32:34 +08:00
if ( ! s_arguments | | ! s_arguments [ 0 ] )
{
2013-12-09 05:41:12 +08:00
result = true ;
}
else
{
for ( size_t i = 0 ; s_arguments [ i ] ! = NULL ; i + + )
2013-10-13 02:32:34 +08:00
{
2013-12-09 05:41:12 +08:00
if ( ! strncmp ( func_name , s_arguments [ i ] , strlen ( s_arguments [ i ] ) ) )
{
/* Prefix match */
result = true ;
break ;
}
2013-10-13 02:32:34 +08:00
}
}
2013-12-09 05:41:12 +08:00
if ( result )
s_test_run_count + + ;
return result ;
2013-10-13 02:32:34 +08:00
}
2007-09-24 04:59:18 +08:00
/**
The number of tests to run
*/
2013-11-25 14:57:49 +08:00
# define ESCAPE_TEST_COUNT 100000
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
}
2013-11-25 14:57:49 +08:00
/* Test sane escapes */
static void test_unescape_sane ( )
{
const struct test_t { const wchar_t * input ; const wchar_t * expected ; } tests [ ] =
{
{ L " abcd " , L " abcd " } ,
{ L " 'abcd' " , L " abcd " } ,
{ L " 'abcd \\ n' " , L " abcd \\ n " } ,
{ L " \" abcd \\ n \" " , L " abcd \\ n " } ,
{ L " \" abcd \\ n \" " , L " abcd \\ n " } ,
{ L " \\ 143 " , L " c " } ,
{ L " ' \\ 143' " , L " \\ 143 " } ,
{ L " \\ n " , L " \n " } // \n normally becomes newline
} ;
wcstring output ;
for ( size_t i = 0 ; i < sizeof tests / sizeof * tests ; i + + )
{
bool ret = unescape_string ( tests [ i ] . input , & output , UNESCAPE_DEFAULT ) ;
if ( ! ret )
{
err ( L " Failed to unescape '%ls' \n " , tests [ i ] . input ) ;
}
else if ( output ! = tests [ i ] . expected )
{
err ( L " In unescaping '%ls', expected '%ls' but got '%ls' \n " , tests [ i ] . input , tests [ i ] . expected , output . c_str ( ) ) ;
}
}
2013-11-27 02:52:03 +08:00
// test for overflow
if ( unescape_string ( L " echo \\ UFFFFFF " , & output , UNESCAPE_DEFAULT ) )
{
err ( L " Should not have been able to unescape \\ UFFFFFF \n " ) ;
}
if ( unescape_string ( L " echo \\ U110000 " , & output , UNESCAPE_DEFAULT ) )
{
err ( L " Should not have been able to unescape \\ U110000 \n " ) ;
}
if ( ! unescape_string ( L " echo \\ U10FFFF " , & output , UNESCAPE_DEFAULT ) )
{
err ( L " Should have been able to unescape \\ U10FFFF \n " ) ;
}
2013-11-25 14:57:49 +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 .
*/
2012-11-18 18:23:22 +08:00
2013-11-25 14:57:49 +08:00
static void test_escape_crazy ( )
{
2012-11-19 08:30:30 +08:00
say ( L " Testing escaping and unescaping " ) ;
2013-11-25 14:57:49 +08:00
wcstring random_string ;
wcstring escaped_string ;
wcstring unescaped_string ;
for ( size_t i = 0 ; i < ESCAPE_TEST_COUNT ; i + + )
2012-11-18 18:23:22 +08:00
{
2013-11-25 14:57:49 +08:00
random_string . clear ( ) ;
2012-11-19 08:30:30 +08:00
while ( rand ( ) % ESCAPE_TEST_LENGTH )
{
2013-11-25 14:57:49 +08:00
random_string . push_back ( ( rand ( ) % ESCAPE_TEST_CHAR ) + 1 ) ;
2012-11-19 08:30:30 +08:00
}
2012-11-18 18:23:22 +08:00
2013-11-25 14:57:49 +08:00
escaped_string = escape_string ( random_string , ESCAPE_ALL ) ;
bool unescaped_success = unescape_string ( escaped_string , & unescaped_string , UNESCAPE_DEFAULT ) ;
2012-11-18 18:23:22 +08:00
2013-11-25 14:57:49 +08:00
if ( ! unescaped_success )
2012-11-19 08:30:30 +08:00
{
2013-11-25 14:57:49 +08:00
err ( L " Failed to unescape string <%ls> " , escaped_string . c_str ( ) ) ;
}
else if ( unescaped_string ! = random_string )
{
err ( L " Escaped and then unescaped string '%ls', but got back a different string '%ls' " , random_string . c_str ( ) , unescaped_string . c_str ( ) ) ;
2012-11-19 08:30:30 +08:00
}
}
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
}
2013-10-14 07:58:40 +08:00
/* Test redirection_type_for_string */
if ( redirection_type_for_string ( L " < " ) ! = TOK_REDIRECT_IN ) err ( L " redirection_type_for_string failed on line %ld " , ( long ) __LINE__ ) ;
if ( redirection_type_for_string ( L " ^ " ) ! = TOK_REDIRECT_OUT ) err ( L " redirection_type_for_string failed on line %ld " , ( long ) __LINE__ ) ;
if ( redirection_type_for_string ( L " > " ) ! = TOK_REDIRECT_OUT ) err ( L " redirection_type_for_string failed on line %ld " , ( long ) __LINE__ ) ;
if ( redirection_type_for_string ( L " 2> " ) ! = TOK_REDIRECT_OUT ) err ( L " redirection_type_for_string failed on line %ld " , ( long ) __LINE__ ) ;
if ( redirection_type_for_string ( L " >> " ) ! = TOK_REDIRECT_APPEND ) err ( L " redirection_type_for_string failed on line %ld " , ( long ) __LINE__ ) ;
if ( redirection_type_for_string ( L " 2>> " ) ! = TOK_REDIRECT_APPEND ) err ( L " redirection_type_for_string failed on line %ld " , ( long ) __LINE__ ) ;
if ( redirection_type_for_string ( L " 2>? " ) ! = TOK_REDIRECT_NOCLOB ) err ( L " redirection_type_for_string failed on line %ld " , ( long ) __LINE__ ) ;
if ( redirection_type_for_string ( L " 9999999999999999>? " ) ! = TOK_NONE ) err ( L " redirection_type_for_string failed on line %ld " , ( long ) __LINE__ ) ;
if ( redirection_type_for_string ( L " 2>&3 " ) ! = TOK_REDIRECT_FD ) err ( L " redirection_type_for_string failed on line %ld " , ( long ) __LINE__ ) ;
if ( redirection_type_for_string ( L " 2>| " ) ! = TOK_NONE ) err ( L " redirection_type_for_string failed on line %ld " , ( long ) __LINE__ ) ;
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 + + )
{
2013-11-25 23:15:36 +08:00
printf ( " %lu / %lu \n " , ( unsigned long ) ( i + 1 ) , ( unsigned long ) max ) ;
2012-03-01 09:55:50 +08:00
/* 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
}
2013-11-28 08:04:12 +08:00
// Little function that runs in the main thread
static int test_iothread_main_call ( int * addr )
{
* addr + = 1 ;
return * addr ;
}
// Little function that runs in a background thread, bouncing to the main
static int test_iothread_thread_call ( int * addr )
{
int before = * addr ;
iothread_perform_on_main ( test_iothread_main_call , addr ) ;
int after = * addr ;
// Must have incremented it at least once
if ( before > = after )
{
err ( L " Failed to increment from background thread " ) ;
}
return after ;
}
static void test_iothread ( void )
{
say ( L " Testing iothreads " ) ;
int * int_ptr = new int ( 0 ) ;
int iterations = 1000 ;
for ( int i = 0 ; i < iterations ; i + + )
{
iothread_perform ( test_iothread_thread_call , ( void ( * ) ( int * , int ) ) NULL , int_ptr ) ;
}
// Now wait until we're done
iothread_drain_all ( ) ;
// Should have incremented it once per thread
if ( * int_ptr ! = iterations )
{
say ( L " Expected int to be %d, but instead it was %d " , iterations , * int_ptr ) ;
}
delete int_ptr ;
}
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 block nesting " ) ;
2013-12-17 09:18:32 +08:00
if ( ! parse_util_detect_errors ( L " if; end " ) )
2012-11-19 08:30:30 +08:00
{
err ( L " Incomplete if statement undetected " ) ;
}
2013-12-17 09:18:32 +08:00
if ( ! parse_util_detect_errors ( L " if test; echo " ) )
2012-11-19 08:30:30 +08:00
{
err ( L " Missing end undetected " ) ;
}
2013-12-17 09:18:32 +08:00
if ( ! parse_util_detect_errors ( 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-12-17 09:18:32 +08:00
if ( ! parse_util_detect_errors ( L " case foo " ) )
2012-11-19 08:30:30 +08:00
{
err ( L " 'case' command outside of block context undetected " ) ;
}
2013-12-17 09:18:32 +08:00
if ( ! parse_util_detect_errors ( 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-12-17 09:18:32 +08:00
if ( ! parse_util_detect_errors ( L " else " ) )
2012-11-19 08:30:30 +08:00
{
err ( L " 'else' command outside of conditional block context undetected " ) ;
}
2013-12-17 09:18:32 +08:00
if ( ! parse_util_detect_errors ( L " else if " ) )
2012-11-19 08:30:30 +08:00
{
err ( L " 'else if' command outside of conditional block context undetected " ) ;
}
2013-12-17 09:18:32 +08:00
if ( ! parse_util_detect_errors ( L " if false; else if; end " ) )
2012-11-19 08:30:30 +08:00
{
err ( L " 'else if' missing command undetected " ) ;
}
2013-12-17 09:18:32 +08:00
if ( ! parse_util_detect_errors ( L " break " ) )
2012-11-19 08:30:30 +08:00
{
err ( L " 'break' command outside of loop block context undetected " ) ;
}
2013-12-13 10:18:07 +08:00
2013-12-17 09:18:32 +08:00
if ( parse_util_detect_errors ( L " break --help " ) )
2013-12-13 10:18:07 +08:00
{
err ( L " 'break --help' incorrectly marked as error " ) ;
}
2013-12-17 09:18:32 +08:00
if ( ! parse_util_detect_errors ( L " while false ; function foo ; break ; end ; end " ) )
2013-12-13 10:18:07 +08:00
{
err ( L " 'break' command inside function allowed to break from loop outside it " ) ;
}
2013-12-17 09:18:32 +08:00
if ( ! parse_util_detect_errors ( L " exec ls|less " ) | | ! parse_util_detect_errors ( L " echo|return " ) )
2012-11-19 08:30:30 +08:00
{
err ( L " Invalid pipe command undetected " ) ;
}
2013-12-13 10:18:07 +08:00
2013-12-17 09:18:32 +08:00
if ( parse_util_detect_errors ( L " for i in foo ; switch $i ; case blah ; break; end; end " ) )
2013-12-13 10:18:07 +08:00
{
err ( L " 'break' command inside switch falsely reported as error " ) ;
}
2012-11-19 08:30:30 +08:00
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 " ) ;
}
2014-01-02 07:29:56 +08:00
/* These are disabled since they produce a long backtrace. We should find a way to either visually compress the backtrace, or disable error spewing */
# if 1
/* Ensure that we don't crash on infinite self recursion and mutual recursion. These must use the principal parser because we cannot yet execute jobs on other parsers (!) */
say ( L " Testing recursion detection " ) ;
parser_t : : principal_parser ( ) . eval ( L " function recursive ; recursive ; end ; recursive; " , io_chain_t ( ) , TOP ) ;
parser_t : : principal_parser ( ) . eval ( L " function recursive1 ; recursive2 ; end ; function recursive2 ; recursive1 ; end ; recursive1; " , io_chain_t ( ) , TOP ) ;
# endif
2012-01-28 03:43:45 +08:00
}
2013-12-09 05:41:12 +08:00
static void test_indents ( )
{
say ( L " Testing indents " ) ;
// Here are the components of our source and the indents we expect those to be
struct indent_component_t {
const wchar_t * txt ;
int indent ;
} ;
const indent_component_t components1 [ ] =
{
{ L " if foo " , 0 } ,
{ L " end " , 0 } ,
{ NULL , - 1 }
} ;
const indent_component_t components2 [ ] =
{
{ L " if foo " , 0 } ,
{ L " " , 1 } , //trailing newline!
{ NULL , - 1 }
} ;
const indent_component_t components3 [ ] =
{
{ L " if foo " , 0 } ,
{ L " foo " , 1 } ,
{ L " end " , 0 } , //trailing newline!
{ NULL , - 1 }
} ;
const indent_component_t components4 [ ] =
{
{ L " if foo " , 0 } ,
{ L " if bar " , 1 } ,
{ L " end " , 1 } ,
{ L " end " , 0 } ,
{ L " " , 0 } ,
{ NULL , - 1 }
} ;
const indent_component_t components5 [ ] =
{
{ L " if foo " , 0 } ,
{ L " if bar " , 1 } ,
{ L " " , 2 } ,
{ NULL , - 1 }
} ;
const indent_component_t components6 [ ] =
{
{ L " begin " , 0 } ,
{ L " foo " , 1 } ,
{ L " " , 1 } ,
{ NULL , - 1 }
} ;
const indent_component_t components7 [ ] =
{
{ L " begin; end " , 0 } ,
{ L " foo " , 0 } ,
{ L " " , 0 } ,
{ NULL , - 1 }
} ;
const indent_component_t components8 [ ] =
{
{ L " if foo " , 0 } ,
{ L " if bar " , 1 } ,
{ L " baz " , 2 } ,
{ L " end " , 1 } ,
{ L " " , 1 } ,
{ NULL , - 1 }
} ;
const indent_component_t components9 [ ] =
{
{ L " switch foo " , 0 } ,
{ L " " , 1 } ,
{ NULL , - 1 }
} ;
const indent_component_t components10 [ ] =
{
{ L " switch foo " , 0 } ,
{ L " case bar " , 1 } ,
{ L " case baz " , 1 } ,
{ L " quux " , 2 } ,
{ L " " , 2 } ,
{ NULL , - 1 }
} ;
2013-12-09 06:13:23 +08:00
const indent_component_t components11 [ ] =
{
{ L " switch foo " , 0 } ,
{ L " cas " , 1 } , //parse error indentation handling
{ NULL , - 1 }
} ;
2013-12-09 05:41:12 +08:00
2013-12-09 06:13:23 +08:00
const indent_component_t * tests [ ] = { components1 , components2 , components3 , components4 , components5 , components6 , components7 , components8 , components9 , components10 , components11 } ;
2013-12-09 05:41:12 +08:00
for ( size_t which = 0 ; which < sizeof tests / sizeof * tests ; which + + )
{
const indent_component_t * components = tests [ which ] ;
// Count how many we have
size_t component_count = 0 ;
while ( components [ component_count ] . txt ! = NULL )
{
component_count + + ;
}
// Generate the expected indents
wcstring text ;
std : : vector < int > expected_indents ;
for ( size_t i = 0 ; i < component_count ; i + + )
{
if ( i > 0 )
{
text . push_back ( L ' \n ' ) ;
expected_indents . push_back ( components [ i ] . indent ) ;
}
text . append ( components [ i ] . txt ) ;
expected_indents . resize ( text . size ( ) , components [ i ] . indent ) ;
}
assert ( expected_indents . size ( ) = = text . size ( ) ) ;
// Compute the indents
std : : vector < int > indents = parse_util_compute_indents ( text ) ;
if ( expected_indents . size ( ) ! = indents . size ( ) )
{
err ( L " Indent vector has wrong size! Expected %lu, actual %lu " , expected_indents . size ( ) , indents . size ( ) ) ;
}
assert ( expected_indents . size ( ) = = indents . size ( ) ) ;
for ( size_t i = 0 ; i < text . size ( ) ; i + + )
{
if ( expected_indents . at ( i ) ! = indents . at ( i ) )
{
err ( L " Wrong indent at index %lu in test #%lu (expected %d, actual %d): \n %ls \n " , i , which + 1 , expected_indents . at ( i ) , indents . at ( i ) , text . c_str ( ) ) ;
break ; //don't keep showing errors for the rest of the line
}
}
}
}
2013-07-17 16:35:30 +08:00
static void test_utils ( )
{
say ( L " Testing utils " ) ;
const wchar_t * a = L " echo (echo (echo hi " ;
2013-07-23 09:26:15 +08:00
2013-07-17 16:35:30 +08:00
const wchar_t * begin = NULL , * end = NULL ;
parse_util_cmdsubst_extent ( a , 0 , & begin , & end ) ;
2013-07-17 17:55:15 +08:00
if ( begin ! = a | | end ! = begin + wcslen ( begin ) ) err ( L " parse_util_cmdsubst_extent failed on line %ld " , ( long ) __LINE__ ) ;
2013-07-17 16:35:30 +08:00
parse_util_cmdsubst_extent ( a , 1 , & begin , & end ) ;
2013-07-17 17:55:15 +08:00
if ( begin ! = a | | end ! = begin + wcslen ( begin ) ) err ( L " parse_util_cmdsubst_extent failed on line %ld " , ( long ) __LINE__ ) ;
2013-07-17 16:35:30 +08:00
parse_util_cmdsubst_extent ( a , 2 , & begin , & end ) ;
2013-07-17 17:55:15 +08:00
if ( begin ! = a | | end ! = begin + wcslen ( begin ) ) err ( L " parse_util_cmdsubst_extent failed on line %ld " , ( long ) __LINE__ ) ;
2013-07-17 16:35:30 +08:00
parse_util_cmdsubst_extent ( a , 3 , & begin , & end ) ;
2013-07-17 17:55:15 +08:00
if ( begin ! = a | | end ! = begin + wcslen ( begin ) ) err ( L " parse_util_cmdsubst_extent failed on line %ld " , ( long ) __LINE__ ) ;
2013-07-23 09:26:15 +08:00
2013-07-17 16:35:30 +08:00
parse_util_cmdsubst_extent ( a , 8 , & begin , & end ) ;
if ( begin ! = a + wcslen ( L " echo ( " ) ) err ( L " parse_util_cmdsubst_extent failed on line %ld " , ( long ) __LINE__ ) ;
parse_util_cmdsubst_extent ( a , 17 , & begin , & end ) ;
if ( begin ! = a + wcslen ( L " echo (echo ( " ) ) err ( L " parse_util_cmdsubst_extent failed on line %ld " , ( long ) __LINE__ ) ;
}
2013-09-29 17:48:35 +08:00
static void test_escape_sequences ( void )
{
say ( L " Testing escape codes " ) ;
if ( escape_code_length ( L " " ) ! = 0 ) err ( L " test_escape_sequences failed on line %d \n " , __LINE__ ) ;
if ( escape_code_length ( L " abcd " ) ! = 0 ) err ( L " test_escape_sequences failed on line %d \n " , __LINE__ ) ;
if ( escape_code_length ( L " \x1b [2J " ) ! = 4 ) err ( L " test_escape_sequences failed on line %d \n " , __LINE__ ) ;
if ( escape_code_length ( L " \x1b [38;5;123mABC " ) ! = strlen ( " \x1b [38;5;123m " ) ) err ( L " test_escape_sequences failed on line %d \n " , __LINE__ ) ;
if ( escape_code_length ( L " \x1b @ " ) ! = 2 ) err ( L " test_escape_sequences failed on line %d \n " , __LINE__ ) ;
}
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
}
2013-07-17 15:38:04 +08:00
static void test_abbreviations ( void )
{
say ( L " Testing abbreviations " ) ;
const wchar_t * abbreviations =
L " gc=git checkout " ARRAY_SEP_STR
L " foo= " ARRAY_SEP_STR
L " gc=something else " ARRAY_SEP_STR
L " = " ARRAY_SEP_STR
L " =foo " ARRAY_SEP_STR
L " foo " ARRAY_SEP_STR
L " foo=bar " ;
env_push ( true ) ;
int ret = env_set ( USER_ABBREVIATIONS_VARIABLE_NAME , abbreviations , ENV_LOCAL ) ;
if ( ret ! = 0 ) err ( L " Unable to set abbreviation variable " ) ;
wcstring result ;
if ( expand_abbreviation ( L " " , & result ) ) err ( L " Unexpected success with empty abbreviation " ) ;
if ( expand_abbreviation ( L " nothing " , & result ) ) err ( L " Unexpected success with missing abbreviation " ) ;
if ( ! expand_abbreviation ( L " gc " , & result ) ) err ( L " Unexpected failure with gc abbreviation " ) ;
if ( result ! = L " git checkout " ) err ( L " Wrong abbreviation result for gc " ) ;
result . clear ( ) ;
if ( ! expand_abbreviation ( L " foo " , & result ) ) err ( L " Unexpected failure with foo abbreviation " ) ;
if ( result ! = L " bar " ) err ( L " Wrong abbreviation result for foo " ) ;
bool expanded ;
2013-07-17 17:55:15 +08:00
expanded = reader_expand_abbreviation_in_command ( L " just a command " , 3 , & result ) ;
2013-07-17 15:38:04 +08:00
if ( expanded ) err ( L " Command wrongly expanded on line %ld " , ( long ) __LINE__ ) ;
expanded = reader_expand_abbreviation_in_command ( L " gc somebranch " , 0 , & result ) ;
2013-07-17 17:55:15 +08:00
if ( ! expanded ) err ( L " Command not expanded on line %ld " , ( long ) __LINE__ ) ;
2013-07-17 15:38:04 +08:00
2013-07-17 17:55:15 +08:00
expanded = reader_expand_abbreviation_in_command ( L " gc somebranch " , wcslen ( L " gc " ) , & result ) ;
2013-07-17 15:38:04 +08:00
if ( ! expanded ) err ( L " gc not expanded " ) ;
if ( result ! = L " git checkout somebranch " ) err ( L " gc incorrectly expanded on line %ld to '%ls' " , ( long ) __LINE__ , result . c_str ( ) ) ;
2013-07-17 17:55:15 +08:00
expanded = reader_expand_abbreviation_in_command ( L " echo hi ; gc somebranch " , wcslen ( L " echo hi ; g " ) , & result ) ;
2013-07-17 15:38:04 +08:00
if ( ! expanded ) err ( L " gc not expanded on line %ld " , ( long ) __LINE__ ) ;
if ( result ! = L " echo hi ; git checkout somebranch " ) err ( L " gc incorrectly expanded on line %ld " , ( long ) __LINE__ ) ;
2013-07-17 17:55:15 +08:00
expanded = reader_expand_abbreviation_in_command ( L " echo (echo (echo (echo (gc " , wcslen ( L " echo (echo (echo (echo (gc " ) , & result ) ;
if ( ! expanded ) err ( L " gc not expanded on line %ld " , ( long ) __LINE__ ) ;
if ( result ! = L " echo (echo (echo (echo (git checkout " ) err ( L " gc incorrectly expanded on line %ld to '%ls' " , ( long ) __LINE__ , result . c_str ( ) ) ;
/* if commands should be expanded */
expanded = reader_expand_abbreviation_in_command ( L " if gc " , wcslen ( L " if gc " ) , & result ) ;
2013-07-17 15:38:04 +08:00
if ( ! expanded ) err ( L " gc not expanded on line %ld " , ( long ) __LINE__ ) ;
2013-07-17 17:55:15 +08:00
if ( result ! = L " if git checkout " ) err ( L " gc incorrectly expanded on line %ld to '%ls' " , ( long ) __LINE__ , result . c_str ( ) ) ;
/* others should not be */
expanded = reader_expand_abbreviation_in_command ( L " of gc " , wcslen ( L " of gc " ) , & result ) ;
if ( expanded ) err ( L " gc incorrectly expanded on line %ld " , ( long ) __LINE__ ) ;
2013-07-17 15:38:04 +08:00
2013-10-09 17:03:50 +08:00
/* others should not be */
expanded = reader_expand_abbreviation_in_command ( L " command gc " , wcslen ( L " command gc " ) , & result ) ;
if ( expanded ) err ( L " gc incorrectly expanded on line %ld " , ( long ) __LINE__ ) ;
2013-07-17 15:38:04 +08:00
env_pop ( ) ;
}
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/ " ;
2013-08-28 09:26:22 +08:00
path_make_canonical ( path ) ;
if ( path ! = L " /foo/bar " )
2012-11-19 08:30:30 +08:00
{
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
}
2013-10-27 06:27:39 +08:00
2013-08-28 09:26:22 +08:00
if ( paths_are_equivalent ( L " /foo/bar/baz " , L " foo/bar/baz " ) ) err ( L " Bug in canonical PATH code on line %ld " , ( long ) __LINE__ ) ;
if ( ! paths_are_equivalent ( L " ///foo///bar/baz " , L " /foo/bar////baz// " ) ) err ( L " Bug in canonical PATH code on line %ld " , ( long ) __LINE__ ) ;
if ( ! paths_are_equivalent ( L " /foo/bar/baz " , L " /foo/bar/baz " ) ) err ( L " Bug in canonical PATH code on line %ld " , ( long ) __LINE__ ) ;
if ( ! paths_are_equivalent ( L " / " , L " / " ) ) err ( L " Bug in canonical PATH code on line %ld " , ( long ) __LINE__ ) ;
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-10-13 02:32:34 +08:00
completions . clear ( ) ;
2013-10-13 03:04:31 +08:00
complete ( L " echo (/bin/mkdi " , completions , COMPLETION_REQUEST_DEFAULT ) ;
2013-10-13 02:32:34 +08:00
assert ( completions . size ( ) = = 1 ) ;
2013-10-13 03:04:31 +08:00
assert ( completions . at ( 0 ) . completion = = L " r " ) ;
2013-10-13 02:32:34 +08:00
2013-10-13 03:04:31 +08:00
completions . clear ( ) ;
complete ( L " echo (ls /bin/mkdi " , completions , COMPLETION_REQUEST_DEFAULT ) ;
assert ( completions . size ( ) = = 1 ) ;
assert ( completions . at ( 0 ) . completion = = L " r " ) ;
completions . clear ( ) ;
complete ( L " echo (command ls /bin/mkdi " , completions , COMPLETION_REQUEST_DEFAULT ) ;
assert ( completions . size ( ) = = 1 ) ;
assert ( completions . at ( 0 ) . completion = = L " r " ) ;
/* Add a function and test completing it in various ways */
struct function_data_t func_data ;
func_data . name = L " scuttlebutt " ;
func_data . definition = L " echo gongoozle " ;
function_add ( func_data , parser_t : : principal_parser ( ) ) ;
2013-05-26 06:41:18 +08:00
2013-10-13 03:04:31 +08:00
/* Complete a function name */
completions . clear ( ) ;
complete ( L " echo (scuttlebut " , completions , COMPLETION_REQUEST_DEFAULT ) ;
assert ( completions . size ( ) = = 1 ) ;
assert ( completions . at ( 0 ) . completion = = L " t " ) ;
/* But not with the command prefix */
completions . clear ( ) ;
complete ( L " echo (command scuttlebut " , completions , COMPLETION_REQUEST_DEFAULT ) ;
assert ( completions . size ( ) = = 0 ) ;
2013-06-02 16:14:26 +08:00
2013-10-13 03:04:31 +08:00
/* Not with the builtin prefix */
completions . clear ( ) ;
complete ( L " echo (builtin scuttlebut " , completions , COMPLETION_REQUEST_DEFAULT ) ;
assert ( completions . size ( ) = = 0 ) ;
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-11-30 15:44:26 +08:00
complete ( str , out , COMPLETION_REQUEST_DEFAULT ) ;
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-11-30 15:44:26 +08:00
complete ( str , out , COMPLETION_REQUEST_DEFAULT ) ;
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-08-09 06:06:46 +08:00
static void test_new_parser_correctness ( void )
{
say ( L " Testing new parser! " ) ;
const struct parser_test_t
{
const wchar_t * src ;
bool ok ;
}
parser_tests [ ] =
{
{ L " ; ; ; " , true } ,
{ L " if ; end " , false } ,
{ L " if true ; end " , true } ,
{ L " if true; end ; end " , false } ,
{ L " if end; end ; end " , false } ,
2013-08-11 15:35:00 +08:00
{ L " if end " , false } ,
{ L " end " , false } ,
{ L " for i i " , false } ,
{ L " for i in a b c ; end " , true }
2013-08-09 06:06:46 +08:00
} ;
2013-08-11 15:35:00 +08:00
2013-08-09 06:06:46 +08:00
for ( size_t i = 0 ; i < sizeof parser_tests / sizeof * parser_tests ; i + + )
{
const parser_test_t * test = & parser_tests [ i ] ;
2013-08-11 15:35:00 +08:00
2013-08-09 06:06:46 +08:00
parse_node_tree_t parse_tree ;
2013-10-12 16:17:55 +08:00
bool success = parse_t : : parse ( test - > src , parse_flag_none , & parse_tree , NULL ) ;
2013-08-09 06:06:46 +08:00
say ( L " %lu / %lu: Parse \" %ls \" : %s " , i + 1 , sizeof parser_tests / sizeof * parser_tests , test - > src , success ? " yes " : " no " ) ;
if ( success & & ! test - > ok )
{
err ( L " \" %ls \" should NOT have parsed, but did " , test - > src ) ;
}
else if ( ! success & & test - > ok )
{
err ( L " \" %ls \" should have parsed, but failed " , test - > src ) ;
}
}
say ( L " Parse tests complete " ) ;
2013-08-11 15:35:00 +08:00
}
struct parser_fuzz_token_t
{
parse_token_type_t token_type ;
parse_keyword_t keyword ;
parser_fuzz_token_t ( ) : token_type ( FIRST_TERMINAL_TYPE ) , keyword ( parse_keyword_none )
{
}
} ;
static bool increment ( std : : vector < parser_fuzz_token_t > & tokens )
{
size_t i , end = tokens . size ( ) ;
for ( i = 0 ; i < end ; i + + )
{
bool wrapped = false ;
struct parser_fuzz_token_t & token = tokens [ i ] ;
bool incremented_in_keyword = false ;
if ( token . token_type = = parse_token_type_string )
{
// try incrementing the keyword
token . keyword + + ;
if ( token . keyword < = LAST_KEYWORD )
{
incremented_in_keyword = true ;
}
else
{
token . keyword = parse_keyword_none ;
incremented_in_keyword = false ;
}
}
if ( ! incremented_in_keyword )
{
token . token_type + + ;
2013-12-27 06:52:15 +08:00
// Skip the very special parse_token_type_terminate, since that's always the last thing delivered
if ( token . token_type = = parse_token_type_terminate )
{
token . token_type + + ;
}
2013-08-11 15:35:00 +08:00
if ( token . token_type > LAST_TERMINAL_TYPE )
{
token . token_type = FIRST_TERMINAL_TYPE ;
wrapped = true ;
}
}
2013-08-09 06:06:46 +08:00
2013-08-11 15:35:00 +08:00
if ( ! wrapped )
{
break ;
}
}
return i = = end ;
}
static void test_new_parser_fuzzing ( void )
{
say ( L " Fuzzing parser (node size: %lu) " , sizeof ( parse_node_t ) ) ;
double start = timef ( ) ;
2013-11-26 16:01:23 +08:00
bool log_it = false ;
2013-08-11 15:35:00 +08:00
// ensure nothing crashes
2013-11-26 16:01:23 +08:00
size_t max = 4 ;
2013-08-11 15:35:00 +08:00
for ( size_t len = 1 ; len < = max ; len + + )
{
2013-11-26 16:01:23 +08:00
if ( log_it )
fprintf ( stderr , " %lu / %lu... " , len , max ) ;
2013-08-11 15:35:00 +08:00
std : : vector < parser_fuzz_token_t > tokens ( len ) ;
2013-10-09 17:03:50 +08:00
size_t count = 0 ;
parse_t parser ;
parse_node_tree_t parse_tree ;
2013-08-11 15:35:00 +08:00
do
{
2013-10-09 17:03:50 +08:00
parser . clear ( ) ;
parse_tree . clear ( ) ;
count + + ;
2013-08-11 15:35:00 +08:00
for ( size_t i = 0 ; i < len ; i + + )
{
const parser_fuzz_token_t & token = tokens [ i ] ;
2013-10-07 07:23:45 +08:00
parser . parse_1_token ( token . token_type , token . keyword , & parse_tree , NULL ) ;
2013-08-11 15:35:00 +08:00
}
// keep going until we wrap
}
while ( ! increment ( tokens ) ) ;
2013-11-26 16:01:23 +08:00
if ( log_it )
fprintf ( stderr , " done (%lu) \n " , count ) ;
2013-08-11 15:35:00 +08:00
}
double end = timef ( ) ;
2013-11-26 16:01:23 +08:00
if ( log_it )
say ( L " All fuzzed in %f seconds! " , end - start ) ;
2013-08-09 06:06:46 +08:00
}
2013-10-10 06:57:10 +08:00
// Parse a statement, returning the command, args (joined by spaces), and the decoration. Returns true if successful.
static bool test_1_parse_ll2 ( const wcstring & src , wcstring * out_cmd , wcstring * out_joined_args , enum parse_statement_decoration_t * out_deco )
{
out_cmd - > clear ( ) ;
out_joined_args - > clear ( ) ;
* out_deco = parse_statement_decoration_none ;
bool result = false ;
parse_node_tree_t tree ;
2013-10-12 16:17:55 +08:00
if ( parse_t : : parse ( src , parse_flag_none , & tree , NULL ) )
2013-10-10 06:57:10 +08:00
{
/* Get the statement. Should only have one */
const parse_node_tree_t : : parse_node_list_t stmt_nodes = tree . find_nodes ( tree . at ( 0 ) , symbol_plain_statement ) ;
if ( stmt_nodes . size ( ) ! = 1 )
{
say ( L " Unexpected number of statements (%lu) found in '%ls' " , stmt_nodes . size ( ) , src . c_str ( ) ) ;
return false ;
}
const parse_node_t & stmt = * stmt_nodes . at ( 0 ) ;
/* Return its decoration */
* out_deco = tree . decoration_for_plain_statement ( stmt ) ;
/* Return its command */
tree . command_for_plain_statement ( stmt , src , out_cmd ) ;
/* Return arguments separated by spaces */
const parse_node_tree_t : : parse_node_list_t arg_nodes = tree . find_nodes ( stmt , symbol_argument ) ;
for ( size_t i = 0 ; i < arg_nodes . size ( ) ; i + + )
{
if ( i > 0 ) out_joined_args - > push_back ( L ' ' ) ;
out_joined_args - > append ( arg_nodes . at ( i ) - > get_source ( src ) ) ;
}
result = true ;
}
return result ;
}
/* Test the LL2 (two token lookahead) nature of the parser by exercising the special builtin and command handling. In particular, 'command foo' should be a decorated statement 'foo' but 'command --help' should be an undecorated statement 'command' with argument '--help', and NOT attempt to run a command called '--help' */
static void test_new_parser_ll2 ( void )
{
say ( L " Testing parser two-token lookahead " ) ;
const struct
{
wcstring src ;
wcstring cmd ;
wcstring args ;
enum parse_statement_decoration_t deco ;
} tests [ ] =
{
{ L " echo hello " , L " echo " , L " hello " , parse_statement_decoration_none } ,
{ L " command echo hello " , L " echo " , L " hello " , parse_statement_decoration_command } ,
{ L " command command hello " , L " command " , L " hello " , parse_statement_decoration_command } ,
{ L " builtin command hello " , L " command " , L " hello " , parse_statement_decoration_builtin } ,
{ L " command --help " , L " command " , L " --help " , parse_statement_decoration_none } ,
{ L " command -h " , L " command " , L " -h " , parse_statement_decoration_none } ,
{ L " command " , L " command " , L " " , parse_statement_decoration_none } ,
2013-10-12 16:46:22 +08:00
{ L " command - " , L " command " , L " - " , parse_statement_decoration_none } ,
{ L " command -- " , L " command " , L " -- " , parse_statement_decoration_none } ,
2013-10-12 17:46:49 +08:00
{ L " builtin --names " , L " builtin " , L " --names " , parse_statement_decoration_none } ,
2013-10-10 06:57:10 +08:00
{ L " function " , L " function " , L " " , parse_statement_decoration_none } ,
{ L " function --help " , L " function " , L " --help " , parse_statement_decoration_none }
} ;
for ( size_t i = 0 ; i < sizeof tests / sizeof * tests ; i + + )
{
wcstring cmd , args ;
enum parse_statement_decoration_t deco = parse_statement_decoration_none ;
bool success = test_1_parse_ll2 ( tests [ i ] . src , & cmd , & args , & deco ) ;
if ( ! success )
err ( L " Parse of '%ls' failed on line %ld " , tests [ i ] . cmd . c_str ( ) , ( long ) __LINE__ ) ;
if ( cmd ! = tests [ i ] . cmd )
err ( L " When parsing '%ls', expected command '%ls' but got '%ls' on line %ld " , tests [ i ] . src . c_str ( ) , tests [ i ] . cmd . c_str ( ) , cmd . c_str ( ) , ( long ) __LINE__ ) ;
if ( args ! = tests [ i ] . args )
err ( L " When parsing '%ls', expected args '%ls' but got '%ls' on line %ld " , tests [ i ] . src . c_str ( ) , tests [ i ] . args . c_str ( ) , args . c_str ( ) , ( long ) __LINE__ ) ;
if ( deco ! = tests [ i ] . deco )
err ( L " When parsing '%ls', expected decoration %d but got %d on line %ld " , tests [ i ] . src . c_str ( ) , ( int ) tests [ i ] . deco , ( int ) deco , ( long ) __LINE__ ) ;
}
}
2013-12-09 13:54:06 +08:00
static void test_new_parser_ad_hoc ( )
2013-06-09 10:20:26 +08:00
{
2013-12-09 05:41:12 +08:00
/* Very ad-hoc tests for issues encountered */
say ( L " Testing new parser ad hoc tests " ) ;
/* Ensure that 'case' terminates a job list */
const wcstring src = L " switch foo ; case bar; case baz; end " ;
2013-06-16 05:32:38 +08:00
parse_node_tree_t parse_tree ;
2013-10-12 16:17:55 +08:00
bool success = parse_t : : parse ( src , parse_flag_none , & parse_tree , NULL ) ;
2013-06-16 06:21:35 +08:00
if ( ! success )
{
2013-12-09 05:41:12 +08:00
err ( L " Parsing failed " ) ;
2013-06-16 06:21:35 +08:00
}
2013-12-09 05:41:12 +08:00
/* Expect three case_item_lists: one for each case, and a terminal one. The bug was that we'd try to run a command 'case' */
const parse_node_t & root = parse_tree . at ( 0 ) ;
const parse_node_tree_t : : parse_node_list_t node_list = parse_tree . find_nodes ( root , symbol_case_item_list ) ;
if ( node_list . size ( ) ! = 3 )
2013-06-16 06:21:35 +08:00
{
2013-12-09 05:41:12 +08:00
err ( L " Expected 3 case item nodes, found %lu " , node_list . size ( ) ) ;
2013-06-16 06:21:35 +08:00
}
2013-06-09 10:20:26 +08:00
}
2007-05-11 03:11:28 +08:00
2013-12-09 13:54:06 +08:00
static void test_new_parser_errors ( void )
{
say ( L " Testing new parser error reporting " ) ;
const struct
{
const wchar_t * src ;
parse_error_code_t code ;
}
tests [ ] =
{
{ L " echo (abc " , parse_error_tokenizer } ,
{ L " end " , parse_error_unbalancing_end } ,
{ L " echo hi ; end " , parse_error_unbalancing_end } ,
{ L " else " , parse_error_unbalancing_else } ,
{ L " if true ; end ; else " , parse_error_unbalancing_else } ,
{ L " case " , parse_error_unbalancing_case } ,
{ L " if true ; case ; end " , parse_error_unbalancing_case }
} ;
for ( size_t i = 0 ; i < sizeof tests / sizeof * tests ; i + + )
{
const wcstring src = tests [ i ] . src ;
parse_error_code_t expected_code = tests [ i ] . code ;
parse_error_list_t errors ;
parse_node_tree_t parse_tree ;
bool success = parse_t : : parse ( src , parse_flag_none , & parse_tree , & errors ) ;
if ( success )
{
err ( L " Source '%ls' was expected to fail to parse, but succeeded " , src . c_str ( ) ) ;
}
if ( errors . size ( ) ! = 1 )
{
err ( L " Source '%ls' was expected to produce 1 error, but instead produced %lu errors " , src . c_str ( ) , errors . size ( ) ) ;
}
else if ( errors . at ( 0 ) . code ! = expected_code )
{
err ( L " Source '%ls' was expected to produce error code %lu, but instead produced error code %lu " , src . c_str ( ) , expected_code , ( unsigned long ) errors . at ( 0 ) . code ) ;
for ( size_t i = 0 ; i < errors . size ( ) ; i + + )
{
err ( L " \t \t %ls " , errors . at ( i ) . describe ( src ) . c_str ( ) ) ;
}
}
}
}
2013-08-11 15:35:00 +08:00
static void test_highlighting ( void )
{
say ( L " Testing syntax highlighting " ) ;
if ( system ( " mkdir -p /tmp/fish_highlight_test/ " ) ) err ( L " mkdir failed " ) ;
if ( system ( " touch /tmp/fish_highlight_test/foo " ) ) err ( L " touch failed " ) ;
if ( system ( " touch /tmp/fish_highlight_test/bar " ) ) err ( L " touch failed " ) ;
// Here are the components of our source and the colors we expect those to be
struct highlight_component_t {
const wchar_t * txt ;
int color ;
} ;
const highlight_component_t components1 [ ] =
{
{ L " echo " , HIGHLIGHT_COMMAND } ,
{ L " /tmp/fish_highlight_test/foo " , HIGHLIGHT_PARAM | HIGHLIGHT_VALID_PATH } ,
{ L " & " , HIGHLIGHT_END } ,
{ NULL , - 1 }
} ;
const highlight_component_t components2 [ ] =
{
{ L " command " , HIGHLIGHT_COMMAND } ,
{ L " echo " , HIGHLIGHT_COMMAND } ,
{ L " abc " , HIGHLIGHT_PARAM } ,
{ L " /tmp/fish_highlight_test/foo " , HIGHLIGHT_PARAM | HIGHLIGHT_VALID_PATH } ,
{ L " & " , HIGHLIGHT_END } ,
{ NULL , - 1 }
} ;
const highlight_component_t components3 [ ] =
{
{ L " if command ls " , HIGHLIGHT_COMMAND } ,
{ L " ; " , HIGHLIGHT_END } ,
{ L " echo " , HIGHLIGHT_COMMAND } ,
{ L " abc " , HIGHLIGHT_PARAM } ,
{ L " ; " , HIGHLIGHT_END } ,
{ L " /bin/definitely_not_a_command " , HIGHLIGHT_ERROR } ,
{ L " ; " , HIGHLIGHT_END } ,
{ L " end " , HIGHLIGHT_COMMAND } ,
{ NULL , - 1 }
} ;
2013-10-07 18:56:09 +08:00
/* Verify that cd shows errors for non-directories */
const highlight_component_t components4 [ ] =
{
{ L " cd " , HIGHLIGHT_COMMAND } ,
{ L " /tmp/fish_highlight_test " , HIGHLIGHT_PARAM | HIGHLIGHT_VALID_PATH } ,
{ NULL , - 1 }
} ;
const highlight_component_t components5 [ ] =
{
{ L " cd " , HIGHLIGHT_COMMAND } ,
{ L " /tmp/fish_highlight_test/foo " , HIGHLIGHT_ERROR } ,
{ NULL , - 1 }
} ;
const highlight_component_t components6 [ ] =
{
{ L " cd " , HIGHLIGHT_COMMAND } ,
{ L " --help " , HIGHLIGHT_PARAM } ,
{ L " -h " , HIGHLIGHT_PARAM } ,
{ L " definitely_not_a_directory " , HIGHLIGHT_ERROR } ,
{ NULL , - 1 }
} ;
2013-10-09 09:41:35 +08:00
// Command substitutions
const highlight_component_t components7 [ ] =
{
{ L " echo " , HIGHLIGHT_COMMAND } ,
{ L " param1 " , HIGHLIGHT_PARAM } ,
{ L " ( " , HIGHLIGHT_OPERATOR } ,
{ L " ls " , HIGHLIGHT_COMMAND } ,
{ L " param2 " , HIGHLIGHT_PARAM } ,
{ L " ) " , HIGHLIGHT_OPERATOR } ,
{ NULL , - 1 }
} ;
2013-10-14 07:58:40 +08:00
// Redirections substitutions
const highlight_component_t components8 [ ] =
{
{ L " echo " , HIGHLIGHT_COMMAND } ,
{ L " param1 " , HIGHLIGHT_PARAM } ,
/* Input redirection */
{ L " < " , HIGHLIGHT_REDIRECTION } ,
{ L " /bin/echo " , HIGHLIGHT_REDIRECTION } ,
/* Output redirection to a valid fd */
{ L " 1>&2 " , HIGHLIGHT_REDIRECTION } ,
/* Output redirection to an invalid fd */
{ L " 2>& " , HIGHLIGHT_REDIRECTION } ,
{ L " LOL " , HIGHLIGHT_ERROR } ,
/* Just a param, not a redirection */
{ L " /tmp/blah " , HIGHLIGHT_PARAM } ,
/* Input redirection from directory */
{ L " < " , HIGHLIGHT_REDIRECTION } ,
{ L " /tmp/ " , HIGHLIGHT_ERROR } ,
/* Output redirection to an invalid path */
{ L " 3> " , HIGHLIGHT_REDIRECTION } ,
{ L " /not/a/valid/path/nope " , HIGHLIGHT_ERROR } ,
/* Output redirection to directory */
{ L " 3> " , HIGHLIGHT_REDIRECTION } ,
{ L " /tmp/nope/ " , HIGHLIGHT_ERROR } ,
/* Redirections to overflow fd */
{ L " 99999999999999999999>&2 " , HIGHLIGHT_ERROR } ,
{ L " 2>& " , HIGHLIGHT_REDIRECTION } ,
{ L " 99999999999999999999 " , HIGHLIGHT_ERROR } ,
/* Output redirection containing a command substitution */
{ L " 4> " , HIGHLIGHT_REDIRECTION } ,
{ L " ( " , HIGHLIGHT_OPERATOR } ,
{ L " echo " , HIGHLIGHT_COMMAND } ,
{ L " /tmp/somewhere " , HIGHLIGHT_PARAM } ,
{ L " ) " , HIGHLIGHT_OPERATOR } ,
/* Just another param */
{ L " param2 " , HIGHLIGHT_PARAM } ,
{ NULL , - 1 }
} ;
2013-11-25 16:48:01 +08:00
const highlight_component_t components9 [ ] =
{
2013-11-26 16:01:23 +08:00
{ L " end " , HIGHLIGHT_ERROR } ,
{ L " ; " , HIGHLIGHT_END } ,
{ L " if " , HIGHLIGHT_COMMAND } ,
2013-11-25 16:48:01 +08:00
{ L " end " , HIGHLIGHT_ERROR } ,
{ NULL , - 1 }
} ;
2013-10-07 18:56:09 +08:00
2013-11-25 16:48:01 +08:00
const highlight_component_t * tests [ ] = { components1 , components2 , components3 , components4 , components5 , components6 , components7 , components8 , components9 } ;
2013-08-11 15:35:00 +08:00
for ( size_t which = 0 ; which < sizeof tests / sizeof * tests ; which + + )
{
const highlight_component_t * components = tests [ which ] ;
// Count how many we have
size_t component_count = 0 ;
while ( components [ component_count ] . txt ! = NULL )
{
component_count + + ;
}
// Generate the text
wcstring text ;
std : : vector < int > expected_colors ;
for ( size_t i = 0 ; i < component_count ; i + + )
{
if ( i > 0 )
{
text . push_back ( L ' ' ) ;
expected_colors . push_back ( 0 ) ;
}
text . append ( components [ i ] . txt ) ;
2013-10-14 07:58:40 +08:00
expected_colors . resize ( text . size ( ) , components [ i ] . color ) ;
2013-08-11 15:35:00 +08:00
}
assert ( expected_colors . size ( ) = = text . size ( ) ) ;
std : : vector < int > colors ( text . size ( ) ) ;
highlight_shell ( text , colors , 20 , NULL , env_vars_snapshot_t ( ) ) ;
if ( expected_colors . size ( ) ! = colors . size ( ) )
{
err ( L " Color vector has wrong size! Expected %lu, actual %lu " , expected_colors . size ( ) , colors . size ( ) ) ;
}
assert ( expected_colors . size ( ) = = colors . size ( ) ) ;
for ( size_t i = 0 ; i < text . size ( ) ; i + + )
{
2013-10-14 07:58:40 +08:00
// Hackish space handling. We don't care about the colors in spaces.
if ( text . at ( i ) = = L ' ' )
continue ;
2013-08-11 15:35:00 +08:00
if ( expected_colors . at ( i ) ! = colors . at ( i ) )
{
const wcstring spaces ( i , L ' ' ) ;
2013-10-07 07:23:45 +08:00
err ( L " Wrong color at index %lu in text (expected %#x, actual %#x): \n %ls \n %ls^ " , i , expected_colors . at ( i ) , colors . at ( i ) , text . c_str ( ) , spaces . c_str ( ) ) ;
2013-08-11 15:35:00 +08:00
}
}
}
system ( " rm -Rf /tmp/fish_highlight_test " ) ;
}
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 , " " ) ;
2013-08-09 06:06:46 +08:00
//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) " ;
2013-10-14 07:58:40 +08:00
s_arguments = argv + 1 ;
2012-11-18 18:23:22 +08:00
2012-11-19 08:30:30 +08:00
say ( L " Testing low-level functionality " ) ;
set_main_thread ( ) ;
2012-03-07 16:54:01 +08:00
setup_fork_guards ( ) ;
2013-10-09 17:03:50 +08:00
//proc_init(); //disabling this prevents catching SIGINT
2012-11-19 08:30:30 +08:00
event_init ( ) ;
function_init ( ) ;
builtin_init ( ) ;
reader_init ( ) ;
env_init ( ) ;
2013-07-23 09:26:15 +08:00
2013-10-13 02:32:34 +08:00
if ( should_test_function ( " highlighting " ) ) test_highlighting ( ) ;
if ( should_test_function ( " new_parser_ll2 " ) ) test_new_parser_ll2 ( ) ;
2013-11-26 16:01:23 +08:00
if ( should_test_function ( " new_parser_fuzzing " ) ) test_new_parser_fuzzing ( ) ; //fuzzing is expensive
2013-10-13 02:32:34 +08:00
if ( should_test_function ( " new_parser_correctness " ) ) test_new_parser_correctness ( ) ;
2013-12-09 05:41:12 +08:00
if ( should_test_function ( " new_parser_ad_hoc " ) ) test_new_parser_ad_hoc ( ) ;
2013-12-09 13:54:06 +08:00
if ( should_test_function ( " new_parser_errors " ) ) test_new_parser_errors ( ) ;
2013-11-25 15:21:00 +08:00
if ( should_test_function ( " escape " ) ) test_unescape_sane ( ) ;
if ( should_test_function ( " escape " ) ) test_escape_crazy ( ) ;
2013-10-13 02:32:34 +08:00
if ( should_test_function ( " format " ) ) test_format ( ) ;
if ( should_test_function ( " convert " ) ) test_convert ( ) ;
if ( should_test_function ( " convert_nulls " ) ) test_convert_nulls ( ) ;
if ( should_test_function ( " tok " ) ) test_tok ( ) ;
if ( should_test_function ( " fork " ) ) test_fork ( ) ;
2013-12-08 04:54:43 +08:00
if ( should_test_function ( " iothread " ) ) test_iothread ( ) ;
2013-10-13 02:32:34 +08:00
if ( should_test_function ( " parser " ) ) test_parser ( ) ;
2013-12-09 05:41:12 +08:00
if ( should_test_function ( " indents " ) ) test_indents ( ) ;
2013-10-13 02:32:34 +08:00
if ( should_test_function ( " utils " ) ) test_utils ( ) ;
if ( should_test_function ( " escape_sequences " ) ) test_escape_sequences ( ) ;
if ( should_test_function ( " lru " ) ) test_lru ( ) ;
if ( should_test_function ( " expand " ) ) test_expand ( ) ;
if ( should_test_function ( " fuzzy_match " ) ) test_fuzzy_match ( ) ;
if ( should_test_function ( " abbreviations " ) ) test_abbreviations ( ) ;
if ( should_test_function ( " test " ) ) test_test ( ) ;
if ( should_test_function ( " path " ) ) test_path ( ) ;
if ( should_test_function ( " word_motion " ) ) test_word_motion ( ) ;
if ( should_test_function ( " is_potential_path " ) ) test_is_potential_path ( ) ;
if ( should_test_function ( " colors " ) ) test_colors ( ) ;
if ( should_test_function ( " complete " ) ) test_complete ( ) ;
if ( should_test_function ( " completion_insertions " ) ) test_completion_insertions ( ) ;
if ( should_test_function ( " autosuggestion_combining " ) ) test_autosuggestion_combining ( ) ;
if ( should_test_function ( " autosuggest_suggest_special " ) ) test_autosuggest_suggest_special ( ) ;
if ( should_test_function ( " history " ) ) history_tests_t : : test_history ( ) ;
if ( should_test_function ( " history_merge " ) ) history_tests_t : : test_history_merge ( ) ;
if ( should_test_function ( " history_races " ) ) history_tests_t : : test_history_races ( ) ;
if ( should_test_function ( " history_formats " ) ) 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 ) ;
2013-12-09 05:41:12 +08:00
if ( s_test_run_count = = 0 )
say ( L " *** No Tests Were Actually Run! *** " ) ;
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
}