Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
3703b4b2b53ddd39a86cdd577b01ef59f0853d5f
[simgrid.git] / tools / tesh / tesh.c
1 /* $Id$ */
2
3 /* TESH (Test Shell) -- mini shell specialized in running test units        */
4
5 /* Copyright (c) 2007 Martin Quinson.                                       */
6 /* All rights reserved.                                                     */
7
8 /* This program is free software; you can redistribute it and/or modify it
9  * under the terms of the license (GNU LGPL) which comes with this package. */
10
11 /* specific to Borland Compiler */
12 #ifdef __BORLANDDC__
13 #pragma hdrstop
14 #endif
15
16 #include "portable.h"
17 #include "xbt/sysdep.h"
18 #include "xbt/function_types.h"
19 #include "xbt/log.h"
20 #include "xbt/trim.h"
21
22 #include <sys/types.h>
23 #include <sys/wait.h>
24
25 /**
26  ** Buffer code
27  **/
28 typedef struct {
29   char *data;
30   int used,size;
31 } buff_t;
32
33 static void buff_empty(buff_t *b) {
34   b->used=0;
35   b->data[0]='\n';
36   b->data[1]='\0';
37 }
38 static buff_t *buff_new(void) {
39   buff_t *res=malloc(sizeof(buff_t));
40   res->data=malloc(512);
41   res->size=512;
42   buff_empty(res);
43   return res;
44 }
45 static void buff_free(buff_t *b) {
46   if (b) {
47     if (b->data)
48       free(b->data);
49     free(b);
50   }
51 }
52 static void buff_append(buff_t *b, char *toadd) {
53   int addlen=strlen(toadd);
54   int needed_space=b->used+addlen+1;
55
56   if (needed_space > b->size) {
57     b->data = realloc(b->data, needed_space);
58     b->size = needed_space;
59   }
60   strcpy(b->data+b->used, toadd);
61   b->used += addlen;  
62 }
63 static void buff_chomp(buff_t *b) {
64   while (b->data[b->used] == '\n') {
65     b->data[b->used] = '\0';
66     if (b->used)
67       b->used--;
68   }
69 }
70
71 static void buff_trim(buff_t* b)
72 {
73         trim(b->data," ");
74         b->used = strlen(b->data);
75 }
76
77 typedef struct s_signal_entry
78 {
79         const char* name;
80         int number;
81 }s_signal_entry_t,* signal_entry_t;
82
83 static const s_signal_entry_t signals[] = 
84 {
85         {"SIGHUP"       ,1},
86         {"SIGINT"       ,2},
87         {"SIGQUIT"      ,3},
88         {"SIGILL"       ,4},
89         {"SIGTRAP"      ,5},
90         {"SIGABRT"      ,6},
91         {"SIGEMT"       ,7},
92         {"SIGFPE"       ,8},
93         {"SIGKILL"      ,9},
94         {"SIGBUS"       ,10},
95         {"SIGSEGV"      ,11},
96         {"SIGSYS"       ,12},
97         {"SIGPIPE"      ,13},
98         {"SIGALRM"      ,14},
99         {"SIGTERM"      ,15},
100         {"SIGURG"       ,16},
101         {"SIGSTOP"      ,17},
102         {"SIGTSTP"      ,18},
103         {"SIGCONT"      ,19},
104         {"SIGCHLD"      ,20},
105         {"SIGTTIN"      ,21},
106         {"SIGTTOU"      ,22},
107         {"SIGIO"        ,23},
108         {"SIGXCPU"      ,24},
109         {"SIGXFSZ"      ,25},
110         {"SIGVTALRM",26},
111         {"SIGPROF"      ,27},
112         {"SIGWINCH"     ,28},
113         {"SIGINFO"      ,29},
114         {"SIGUSR1"      ,30},
115         {"SIGUSR2"      ,31}
116 };
117
118 #define SIGMAX          31
119 #define SIGUNKNW        SIGMAX + 1
120
121 /* returns the name of the signal from it number */
122 const char* 
123 signal_name(unsigned int number);
124
125 /**
126  ** Options
127  **/
128 int timeout_value = 5; /* child timeout value */
129 char* expected_signal=NULL; /* !=NULL if the following command should raise a signal */
130 int expected_return=0; /* the exepeted return code of following command */
131 int verbose=0; /* wheather we should wine on problems */
132
133 /**
134  ** Dealing with timeouts
135  **/
136 int timeouted;
137 static void timeout_handler(int sig) {
138   timeouted = 1;
139 }
140 /**
141  ** Dealing with timeouts
142  **/
143 int brokenpipe;
144 static void pipe_handler(int sig) {
145   brokenpipe = 1;
146 }
147
148 /**
149  ** Launching a child
150  **/
151 buff_t *input;
152 buff_t *output_wanted;
153 buff_t *output_got;
154
155 static void check_output() {
156   if (output_wanted->used==0 
157       && output_got->used==0)
158     return;
159   buff_chomp(output_got);
160   buff_chomp(output_wanted);
161    buff_trim(output_got);
162 buff_trim(output_wanted);
163
164   if (   output_got->used != output_wanted->used
165       || strcmp(output_got->data, output_wanted->data)) {
166     fprintf(stderr,"Output don't match expectations\n");
167     fprintf(stderr,">>>>> Expected %d chars:\n%s\n<<<<< Expected\n",
168             output_wanted->used,output_wanted->data);
169     fprintf(stderr,">>>>> Got %d chars:\n%s\n<<<<< Got\n",
170             output_got->used,output_got->data);
171     exit(2);
172   }
173   buff_empty(output_wanted);
174   buff_empty(output_got);
175   
176 }
177
178 static void exec_cmd(char *cmd) {
179   int child_stdin[2];
180   int child_stdout[2];
181
182   if (pipe(child_stdin) || pipe(child_stdout)) {
183     perror("Cannot open the pipes");
184     exit(4);
185   }
186
187   int pid=fork();
188   if (pid<0) {
189     perror("Cannot fork the command");
190     exit(4);
191   }
192
193   if (pid) { /* father */
194     char buffout[4096];
195     int posw,posr;
196     int status;
197     close(child_stdin[0]);
198     fcntl(child_stdin[1], F_SETFL, O_NONBLOCK);
199     close(child_stdout[1]);
200     fcntl(child_stdout[0], F_SETFL, O_NONBLOCK);
201
202     brokenpipe = 0;
203     for (posw=0; posw<input->used && !brokenpipe; ) {
204       int got;
205       //      fprintf(stderr,"Still %d chars to write\n",input->used-posw);
206       got=write(child_stdin[1],input->data+posw,input->used-posw);
207       if (got>0)
208         posw+=got;
209       if (got<0 && errno!=EINTR && errno!=EAGAIN && errno!=EPIPE) {
210         perror("Error while writing input to child");
211         exit(4);
212       }
213       //      fprintf(stderr,"written %d chars so far\n",posw);
214
215       posr=read(child_stdout[0],&buffout,4096);
216       //      fprintf(stderr,"got %d chars\n",posr);
217       if (posr<0 && errno!=EINTR && errno!=EAGAIN) {
218         perror("Error while reading output of child");
219         exit(4);
220       }
221       if (posr>0) {
222         buffout[posr]='\0';      
223         buff_append(output_got,buffout);
224       }
225        
226       if (got <= 0 && posr <= 0)
227          usleep(100);
228     }
229     input->data[0]='\0';
230     input->used=0;
231     close(child_stdin[1]);
232
233     timeouted = 0;
234     alarm(timeout_value);
235     do {
236       posr=read(child_stdout[0],&buffout,4096);
237       if (posr<0 && errno!=EINTR && errno!=EAGAIN) {
238         perror("Error while reading output of child");
239         exit(4);
240       }
241       if (posr>0) {
242         buffout[posr]='\0';
243         buff_append(output_got,buffout);
244       } else {
245         usleep(100);
246       }
247     } while (!timeouted && posr!=0);
248
249     /* Check for broken pipe */
250     if (brokenpipe && verbose) {
251       fprintf(stderr,"Warning: Child did not consume all its input (I got broken pipe)\n");
252     }
253
254     /* Check for timeouts */
255     if (timeouted) {
256       fprintf(stderr,"Child timeouted (waited %d sec)\n",timeout_value);
257       exit(3);
258     }
259     alarm(0);
260       
261
262     /* Wait for child, and check why it terminated */
263     wait(&status);
264
265     if (WIFSIGNALED(status) && strcmp(signal_name(WTERMSIG(status)),expected_signal)) {
266                 fprintf(stderr,"Child got signal %s instead of signal %s\n",signal_name(WTERMSIG(status)), expected_signal);
267                 exit(WTERMSIG(status)+4);       
268         }
269         
270     if (!WIFSIGNALED(status) && expected_signal) {
271       fprintf(stderr,"Child didn't got expected signal %s\n",
272               expected_signal);
273       exit(5);
274     }
275
276     if (WIFEXITED(status) && WEXITSTATUS(status) != expected_return ) {
277       if (expected_return) 
278         fprintf(stderr,"Child returned code %d instead of %d\n",
279                 WEXITSTATUS(status), expected_return);
280       else
281         fprintf(stderr,"Child returned code %d\n", WEXITSTATUS(status));
282       exit(40+WEXITSTATUS(status));
283     }
284     expected_return = 0;
285     
286     if(expected_signal){
287         free(expected_signal);
288         expected_signal = NULL;
289     }
290
291   } else { /* child */
292
293     close(child_stdin[1]);
294     close(child_stdout[0]);
295     dup2(child_stdin[0],0);
296     close(child_stdin[0]);
297     dup2(child_stdout[1],1);
298     dup2(child_stdout[1],2);
299     close(child_stdout[1]);
300
301     execlp ("/bin/sh", "sh", "-c", cmd, NULL);
302   }
303 }
304
305 static void run_cmd(char *cmd) {
306   if (cmd[0] == 'c' && cmd[1] == 'd' && cmd[2] == ' ') {
307     int pos = 2;
308     /* Search end */
309     pos = strlen(cmd)-1;
310     while (cmd[pos] == '\n' || cmd[pos] == ' ' || cmd[pos] == '\t')
311       cmd[pos--] = '\0';
312     /* search begining */
313     pos = 2;
314     while (cmd[pos++] == ' ');
315     pos--;
316     //    fprintf(stderr,"Saw cd '%s'\n",cmd+pos);
317     if (chdir(cmd+pos)) {
318       perror("Chdir failed");
319       exit(4);
320     }
321     
322   } else {
323     exec_cmd(cmd);
324   }
325 }
326
327 static void handle_line(int nl, char *line) {
328   
329   // printf("%d: %s",nl,line);  fflush(stdout);
330   switch (line[0]) {
331   case '#': break;
332   case '$': 
333     check_output(); /* Check that last command ran well */
334      
335     printf("[%d] %s",nl,line);
336     fflush(stdout);
337     run_cmd(line+2);
338     break;
339     
340   case '<':
341     buff_append(input,line+2);
342     break;
343
344   case '>':
345     buff_append(output_wanted,line+2);
346     break;
347
348   case '!':
349     if (!strncmp(line+2,"set timeout ",strlen("set timeout "))) {
350       timeout_value=atoi(line+2+strlen("set timeout"));
351       printf("[%d] (new timeout value: %d)\n",
352              nl,timeout_value);
353
354     } else if (!strncmp(line+2,"expect signal ",strlen("expect signal "))) {
355       expected_signal = strdup(line+2 + strlen("expect signal "));
356       trim(expected_signal," \n");
357            printf("[%d] (next command must raise signal %s)\n", nl, expected_signal);
358
359     } else if (!strncmp(line+2,"expect return ",strlen("expect return "))) {
360       expected_return = atoi(line+2+strlen("expect return "));
361       printf("[%d] (next command must return code %d)\n",
362              nl, expected_return);
363        
364     } else if (!strncmp(line+2,"verbose on",strlen("verbose on"))) {
365       verbose = 1;
366       printf("[%d] (increase verbosity)\n", nl);
367        
368     } else if (!strncmp(line+2,"verbose off",strlen("verbose off"))) {
369       verbose = 1;
370       printf("[%d] (decrease verbosity)\n", nl);
371
372     } else {
373       fprintf(stderr,"%d: Malformed metacommand: %s",nl,line);
374       exit(1);
375     }
376     break;
377
378   case 'p':
379     printf("[%d] %s",nl,line+2);
380     break;
381
382   default:
383     fprintf(stderr,"Syntax error line %d: %s",nl, line);
384     exit(1);
385     break;
386   }
387 }
388
389 static int handle_suite(FILE* IN) {
390   int len;
391   char * line = NULL;
392   int line_num=0;
393
394   buff_t *buff=buff_new();
395   int buffbegin = 0;   
396
397   while (getline(&line,(size_t*) &len, IN) != -1) {
398     line_num++;
399
400     /* Count the line length while checking wheather it's blank */
401     int blankline=1;
402     int linelen = 0;    
403     while (line[linelen] != '\0') {
404       if (line[linelen] != ' ' && line[linelen] != '\t' && line[linelen]!='\n')
405         blankline = 0;
406       linelen++;
407     }
408     
409     if (blankline)
410       continue;
411
412     /* Deal with \ at the end of the line, and call handle_line on result */
413     int to_be_continued = 0;
414     if (linelen>1 && line[linelen-2]=='\\') {
415       if (linelen>2 && line[linelen-3] == '\\') {
416         /* Damn. Escaped \ */
417         line[linelen-2] = '\n';
418         line[linelen-1] = '\0';
419       } else {
420         to_be_continued = 1;
421         line[linelen-2] = '\0';
422         linelen -= 2;  
423         if (!buff->used)
424           buffbegin = line_num;
425       }
426     }
427
428     if (buff->used || to_be_continued) { 
429       buff_append(buff,line);
430
431       if (!to_be_continued) {
432         handle_line(buffbegin, buff->data);    
433         buff_empty(buff);
434       }
435         
436     } else {
437       handle_line(line_num,line);    
438     }
439   }
440   check_output(); /* Check that last command ran well */
441
442   /* Clear buffers */
443   if (line)
444     free(line);
445   buff_free(buff);
446   return 1;
447 }
448
449 int main(int argc,char *argv[]) {
450
451   /* Setup the signal handlers */
452   struct sigaction newact,oldact;
453   memset(&newact,0,sizeof(newact));
454   newact.sa_handler=timeout_handler;
455   sigaction(SIGALRM,&newact,&oldact);
456
457   newact.sa_handler=pipe_handler;
458   sigaction(SIGPIPE,&newact,&oldact);
459    
460   /* Setup the input/output buffers */
461   input=buff_new();
462   output_wanted=buff_new();
463   output_got=buff_new();
464
465   /* Find the description file */
466   FILE *IN;
467
468   if (argc == 1) {
469     printf("Test suite from stdin\n");fflush(stdout);
470     handle_suite(stdin);
471     fprintf(stderr,"Test suite from stdin OK\n");
472      
473   } else {
474     int i;
475      
476     for (i=1; i<argc; i++) {
477        printf("Test suite `%s'\n",argv[i]);fflush(stdout);
478        IN=fopen(argv[i], "r");
479        if (!IN) {
480           perror(bprintf("Impossible to open the suite file `%s'",argv[i]));
481           exit(1);
482        }
483        handle_suite(IN);
484 //       fclose(IN); ->leads to segfault on amd64...
485        fprintf(stderr,"Test suite `%s' OK\n",argv[i]);
486     }
487   }
488    
489   buff_free(input);
490   buff_free(output_wanted);
491   buff_free(output_got);
492   return 0;  
493 }
494
495 const char* 
496 signal_name(unsigned int number)
497 {
498         if(number > SIGMAX)
499                 return "SIGUNKNW";
500                 
501         /* special case of SIGSEGV and SIGBUS (be conditional of the implementation)*/
502         if((number == SIGBUS) && strcmp("SIGBUS",expected_signal))
503                 number = SIGSEGV;
504                 
505         return (signals[number - 1].name);
506 }