Logo AND Algorithmique Numérique Distribuée

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