Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Better modularization of the tesh source code
[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 "tesh.h"
17
18 #include <sys/types.h>
19 #include <sys/wait.h>
20
21 /**
22  ** Options
23  **/
24 int timeout_value = 5; /* child timeout value */
25 char* expected_signal=NULL; /* !=NULL if the following command should raise a signal */
26 int expected_return=0; /* the exepeted return code of following command */
27 int verbose=0; /* wheather we should wine on problems */
28
29 /**
30  ** Dealing with timeouts
31  **/
32 int timeouted;
33 static void timeout_handler(int sig) {
34   timeouted = 1;
35 }
36 /**
37  ** Dealing with timeouts
38  **/
39 int brokenpipe;
40 static void pipe_handler(int sig) {
41   brokenpipe = 1;
42 }
43
44 /**
45  ** Launching a child
46  **/
47 buff_t *input;
48 buff_t *output_wanted;
49 buff_t *output_got;
50
51 static void check_output() {
52   if (output_wanted->used==0 
53       && output_got->used==0)
54     return;
55   buff_chomp(output_got);
56   buff_chomp(output_wanted);
57   buff_trim(output_got);
58   buff_trim(output_wanted);
59
60   if (   output_got->used != output_wanted->used
61       || strcmp(output_got->data, output_wanted->data)) {
62     fprintf(stderr,"Output don't match expectations\n");
63     fprintf(stderr,">>>>> Expected %d chars:\n%s\n<<<<< Expected\n",
64             output_wanted->used,output_wanted->data);
65     fprintf(stderr,">>>>> Got %d chars:\n%s\n<<<<< Got\n",
66             output_got->used,output_got->data);
67     exit(2);
68   }
69   buff_empty(output_wanted);
70   buff_empty(output_got);
71   
72 }
73
74 static void exec_cmd(char *cmd) {
75   int child_stdin[2];
76   int child_stdout[2];
77
78   if (pipe(child_stdin) || pipe(child_stdout)) {
79     perror("Cannot open the pipes");
80     exit(4);
81   }
82
83   int pid=fork();
84   if (pid<0) {
85     perror("Cannot fork the command");
86     exit(4);
87   }
88
89   if (pid) { /* father */
90     char buffout[4096];
91     int posw,posr;
92     int status;
93     close(child_stdin[0]);
94     fcntl(child_stdin[1], F_SETFL, O_NONBLOCK);
95     close(child_stdout[1]);
96     fcntl(child_stdout[0], F_SETFL, O_NONBLOCK);
97
98     brokenpipe = 0;
99     for (posw=0; posw<input->used && !brokenpipe; ) {
100       int got;
101       //      fprintf(stderr,"Still %d chars to write\n",input->used-posw);
102       got=write(child_stdin[1],input->data+posw,input->used-posw);
103       if (got>0)
104         posw+=got;
105       if (got<0 && errno!=EINTR && errno!=EAGAIN && errno!=EPIPE) {
106         perror("Error while writing input to child");
107         exit(4);
108       }
109       //      fprintf(stderr,"written %d chars so far\n",posw);
110
111       posr=read(child_stdout[0],&buffout,4096);
112       //      fprintf(stderr,"got %d chars\n",posr);
113       if (posr<0 && errno!=EINTR && errno!=EAGAIN) {
114         perror("Error while reading output of child");
115         exit(4);
116       }
117       if (posr>0) {
118         buffout[posr]='\0';      
119         buff_append(output_got,buffout);
120       }
121        
122       if (got <= 0 && posr <= 0)
123          usleep(100);
124     }
125     input->data[0]='\0';
126     input->used=0;
127     close(child_stdin[1]);
128
129     timeouted = 0;
130     alarm(timeout_value);
131     do {
132       posr=read(child_stdout[0],&buffout,4096);
133       if (posr<0 && errno!=EINTR && errno!=EAGAIN) {
134         perror("Error while reading output of child");
135         exit(4);
136       }
137       if (posr>0) {
138         buffout[posr]='\0';
139         buff_append(output_got,buffout);
140       } else {
141         usleep(100);
142       }
143     } while (!timeouted && posr!=0);
144
145     /* Check for broken pipe */
146     if (brokenpipe && verbose) {
147       fprintf(stderr,"Warning: Child did not consume all its input (I got broken pipe)\n");
148     }
149
150     /* Check for timeouts */
151     if (timeouted) {
152       fprintf(stderr,"Child timeouted (waited %d sec)\n",timeout_value);
153       exit(3);
154     }
155     alarm(0);
156       
157
158     /* Wait for child, and check why it terminated */
159     wait(&status);
160
161     if (WIFSIGNALED(status) && 
162         strcmp(signal_name(WTERMSIG(status),expected_signal),
163                expected_signal)) {
164                 fprintf(stderr,"Child got signal %s instead of signal %s\n",
165                         signal_name(WTERMSIG(status),expected_signal),
166                         expected_signal);
167                 exit(WTERMSIG(status)+4);       
168         }
169         
170     if (!WIFSIGNALED(status) && expected_signal) {
171       fprintf(stderr,"Child didn't got expected signal %s\n",
172               expected_signal);
173       exit(5);
174     }
175
176     if (WIFEXITED(status) && WEXITSTATUS(status) != expected_return ) {
177       if (expected_return) 
178         fprintf(stderr,"Child returned code %d instead of %d\n",
179                 WEXITSTATUS(status), expected_return);
180       else
181         fprintf(stderr,"Child returned code %d\n", WEXITSTATUS(status));
182       exit(40+WEXITSTATUS(status));
183     }
184     expected_return = 0;
185     
186     if(expected_signal){
187         free(expected_signal);
188         expected_signal = NULL;
189     }
190
191   } else { /* child */
192
193     close(child_stdin[1]);
194     close(child_stdout[0]);
195     dup2(child_stdin[0],0);
196     close(child_stdin[0]);
197     dup2(child_stdout[1],1);
198     dup2(child_stdout[1],2);
199     close(child_stdout[1]);
200
201     execlp ("/bin/sh", "sh", "-c", cmd, NULL);
202   }
203 }
204
205 static void run_cmd(char *cmd) {
206   if (cmd[0] == 'c' && cmd[1] == 'd' && cmd[2] == ' ') {
207     int pos = 2;
208     /* Search end */
209     pos = strlen(cmd)-1;
210     while (cmd[pos] == '\n' || cmd[pos] == ' ' || cmd[pos] == '\t')
211       cmd[pos--] = '\0';
212     /* search begining */
213     pos = 2;
214     while (cmd[pos++] == ' ');
215     pos--;
216     //    fprintf(stderr,"Saw cd '%s'\n",cmd+pos);
217     if (chdir(cmd+pos)) {
218       perror("Chdir failed");
219       exit(4);
220     }
221     
222   } else {
223     exec_cmd(cmd);
224   }
225 }
226
227 static void handle_line(int nl, char *line) {
228   
229   // printf("%d: %s",nl,line);  fflush(stdout);
230   switch (line[0]) {
231   case '#': break;
232   case '$': 
233     check_output(); /* Check that last command ran well */
234      
235     printf("[%d] %s",nl,line);
236     fflush(stdout);
237     run_cmd(line+2);
238     break;
239     
240   case '<':
241     buff_append(input,line+2);
242     break;
243
244   case '>':
245     buff_append(output_wanted,line+2);
246     break;
247
248   case '!':
249     if (!strncmp(line+2,"set timeout ",strlen("set timeout "))) {
250       timeout_value=atoi(line+2+strlen("set timeout"));
251       printf("[%d] (new timeout value: %d)\n",
252              nl,timeout_value);
253
254     } else if (!strncmp(line+2,"expect signal ",strlen("expect signal "))) {
255       expected_signal = strdup(line+2 + strlen("expect signal "));
256       xbt_str_trim(expected_signal," \n");
257            printf("[%d] (next command must raise signal %s)\n", nl, expected_signal);
258
259     } else if (!strncmp(line+2,"expect return ",strlen("expect return "))) {
260       expected_return = atoi(line+2+strlen("expect return "));
261       printf("[%d] (next command must return code %d)\n",
262              nl, expected_return);
263        
264     } else if (!strncmp(line+2,"verbose on",strlen("verbose on"))) {
265       verbose = 1;
266       printf("[%d] (increase verbosity)\n", nl);
267        
268     } else if (!strncmp(line+2,"verbose off",strlen("verbose off"))) {
269       verbose = 1;
270       printf("[%d] (decrease verbosity)\n", nl);
271
272     } else {
273       fprintf(stderr,"%d: Malformed metacommand: %s",nl,line);
274       exit(1);
275     }
276     break;
277
278   case 'p':
279     printf("[%d] %s",nl,line+2);
280     break;
281
282   default:
283     fprintf(stderr,"Syntax error line %d: %s",nl, line);
284     exit(1);
285     break;
286   }
287 }
288
289 static int handle_suite(FILE* IN) {
290   int len;
291   char * line = NULL;
292   int line_num=0;
293
294   buff_t *buff=buff_new();
295   int buffbegin = 0;   
296
297   while (getline(&line,(size_t*) &len, IN) != -1) {
298     line_num++;
299
300     /* Count the line length while checking wheather it's blank */
301     int blankline=1;
302     int linelen = 0;    
303     while (line[linelen] != '\0') {
304       if (line[linelen] != ' ' && line[linelen] != '\t' && line[linelen]!='\n')
305         blankline = 0;
306       linelen++;
307     }
308     
309     if (blankline)
310       continue;
311
312     /* Deal with \ at the end of the line, and call handle_line on result */
313     int to_be_continued = 0;
314     if (linelen>1 && line[linelen-2]=='\\') {
315       if (linelen>2 && line[linelen-3] == '\\') {
316         /* Damn. Escaped \ */
317         line[linelen-2] = '\n';
318         line[linelen-1] = '\0';
319       } else {
320         to_be_continued = 1;
321         line[linelen-2] = '\0';
322         linelen -= 2;  
323         if (!buff->used)
324           buffbegin = line_num;
325       }
326     }
327
328     if (buff->used || to_be_continued) { 
329       buff_append(buff,line);
330
331       if (!to_be_continued) {
332         handle_line(buffbegin, buff->data);    
333         buff_empty(buff);
334       }
335         
336     } else {
337       handle_line(line_num,line);    
338     }
339   }
340   check_output(); /* Check that last command ran well */
341
342   /* Clear buffers */
343   if (line)
344     free(line);
345   buff_free(buff);
346   return 1;
347 }
348
349 int main(int argc,char *argv[]) {
350
351   /* Setup the signal handlers */
352   struct sigaction newact,oldact;
353   memset(&newact,0,sizeof(newact));
354   newact.sa_handler=timeout_handler;
355   sigaction(SIGALRM,&newact,&oldact);
356
357   newact.sa_handler=pipe_handler;
358   sigaction(SIGPIPE,&newact,&oldact);
359    
360   /* Setup the input/output buffers */
361   input=buff_new();
362   output_wanted=buff_new();
363   output_got=buff_new();
364
365   /* Find the description file */
366   FILE *IN;
367
368   if (argc == 1) {
369     printf("Test suite from stdin\n");fflush(stdout);
370     handle_suite(stdin);
371     fprintf(stderr,"Test suite from stdin OK\n");
372      
373   } else {
374     int i;
375      
376     for (i=1; i<argc; i++) {
377        printf("Test suite `%s'\n",argv[i]);fflush(stdout);
378        IN=fopen(argv[i], "r");
379        if (!IN) {
380           perror(bprintf("Impossible to open the suite file `%s'",argv[i]));
381           exit(1);
382        }
383        handle_suite(IN);
384 //       fclose(IN); ->leads to segfault on amd64...
385        fprintf(stderr,"Test suite `%s' OK\n",argv[i]);
386     }
387   }
388    
389   buff_free(input);
390   buff_free(output_wanted);
391   buff_free(output_got);
392   return 0;  
393 }
394