3 /* run_context -- stuff in which TESH runs a command */
5 /* Copyright (c) 2007 Martin Quinson. */
6 /* All rights reserved. */
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. */
13 #include <sys/types.h>
17 XBT_LOG_EXTERNAL_DEFAULT_CATEGORY(tesh);
19 xbt_dynar_t bg_jobs = NULL;
25 static void join_it(void*t) {
26 xbt_thread_t th = *(xbt_thread_t*)t;
27 VERB1("Join thread %p which were running a background cmd",th);
28 xbt_thread_join(th,NULL);
31 void rctx_init(void) {
32 bg_jobs = xbt_dynar_new(sizeof(xbt_thread_t),join_it);
35 void rctx_exit(void) {
36 xbt_dynar_free(&bg_jobs);
39 void rctx_wait_bg(void) {
40 xbt_dynar_free(&bg_jobs);
41 bg_jobs = xbt_dynar_new(sizeof(xbt_thread_t),join_it);
48 void rctx_empty(rctx_t rc) {
53 rc->is_background = 0;
55 rc->output = e_output_check;
58 buff_empty(rc->input);
59 buff_empty(rc->output_wanted);
60 buff_empty(rc->output_got);
64 rctx_t res = xbt_new0(s_rctx_t,1);
66 res->input=buff_new();
67 res->output_wanted=buff_new();
68 res->output_got=buff_new();
73 void rctx_free(rctx_t rctx) {
74 DEBUG1("RCTX: Free %p", rctx);
75 rctx_dump(rctx,"free");
81 buff_free(rctx->input);
82 buff_free(rctx->output_got);
83 buff_free(rctx->output_wanted);
87 void rctx_dump(rctx_t rctx, const char *str) {
88 DEBUG9("%s RCTX %p={in%p={%d,%10s}, want={%d,%10s}, out={%d,%10s}}",
90 rctx->input, rctx->input->used, rctx->input->data,
91 rctx->output_wanted->used,rctx->output_wanted->data,
92 rctx->output_got->used, rctx->output_got->data);
93 DEBUG5("%s RCTX %p=[cmd%p=%10s, pid=%d]",
94 str,rctx,rctx->cmd,rctx->cmd,rctx->pid);
99 * Getting instructions from the file
102 void rctx_pushline(const char* filepos, char kind, char *line) {
108 if (!rctx->is_empty) {
109 ERROR2("[%s] More than one command in this chunk of lines (previous: %s).\n"
110 " Dunno which input/output belongs to which command.",
112 ERROR1("Test suite `%s': NOK (syntax error)",testsuite_name);
116 VERB1("[%s] More than one command in this chunk of lines",filepos);
119 rctx->is_background = 1;
121 rctx->is_background = 0;
123 rctx->cmd = xbt_strdup(line);
124 INFO3("[%s] %s%s",filepos,line,
125 ((rctx->is_background)?" (background command)":""));
131 buff_append(rctx->input,line);
132 buff_append(rctx->input,"\n");
137 buff_append(rctx->output_wanted,line);
138 buff_append(rctx->output_wanted,"\n");
145 if (!strncmp(line,"set timeout ",strlen("set timeout "))) {
146 timeout_value=atoi(line+strlen("set timeout"));
147 VERB2("[%s] (new timeout value: %d)",
148 filepos,timeout_value);
150 } else if (!strncmp(line,"expect signal ",strlen("expect signal "))) {
151 rctx->expected_signal = strdup(line + strlen("expect signal "));
152 xbt_str_trim(rctx->expected_signal," \n");
153 VERB2("[%s] (next command must raise signal %s)",
154 filepos, rctx->expected_signal);
156 } else if (!strncmp(line,"expect return ",strlen("expect return "))) {
157 rctx->expected_return = atoi(line+strlen("expect return "));
158 VERB2("[%s] (next command must return code %d)",
159 filepos, rctx->expected_return);
161 } else if (!strncmp(line,"output ignore",strlen("output ignore"))) {
162 rctx->output = e_output_ignore;
163 VERB1("[%s] (ignore output of next command)", filepos);
165 } else if (!strncmp(line,"output display",strlen("output display"))) {
166 rctx->output = e_output_display;
167 VERB1("[%s] (ignore output of next command)", filepos);
170 ERROR2("%s: Malformed metacommand: %s",filepos,line);
171 ERROR1("Test suite `%s': NOK (syntax error)",testsuite_name);
179 * Actually doing the job
182 /* The IO of the childs are handled by the two following threads
183 (one pair per child) */
185 static void* thread_writer(void *r) {
187 rctx_t rctx = (rctx_t)r;
188 for (posw=0; posw<rctx->input->used && !rctx->brokenpipe; ) {
190 DEBUG1("Still %d chars to write",rctx->input->used-posw);
191 got=write(rctx->child_to,rctx->input->data+posw,rctx->input->used-posw);
195 if (errno == EPIPE) {
196 rctx->brokenpipe = 1;
197 } else if (errno!=EINTR && errno!=EAGAIN && errno!=EPIPE) {
198 perror("Error while writing input to child");
199 ERROR1("Test suite `%s': NOK (system error)",testsuite_name);
203 DEBUG1("written %d chars so far",posw);
208 rctx->input->data[0]='\0';
210 close(rctx->child_to);
214 static void *thread_reader(void *r) {
215 rctx_t rctx = (rctx_t)r;
216 char *buffout=malloc(4096);
220 posr=read(rctx->child_from,buffout,4095);
221 if (posr<0 && errno!=EINTR && errno!=EAGAIN) {
222 perror("Error while reading output of child");
223 ERROR1("Test suite `%s': NOK (system error)", testsuite_name);
228 buff_append(rctx->output_got,buffout);
232 } while (!rctx->timeout && posr!=0);
235 /* let this thread wait for the child so that the main thread can detect the timeout without blocking on the wait */
236 got_pid = waitpid(rctx->pid,&rctx->status,0);
237 if (got_pid != rctx->pid) {
238 perror(bprintf("Cannot wait for the child %s",rctx->cmd));
239 ERROR1("Test suite `%s': NOK (system error)", testsuite_name);
243 rctx->reader_done = 1;
247 /* Start a new child, plug the pipes as expected and fire up the
248 helping threads. Is also waits for the child to end if this is a
249 foreground job, or fire up a thread to wait otherwise. */
251 void rctx_start(void) {
255 VERB2("Start %s %s",rctx->cmd,(rctx->is_background?"(background job)":""));
256 if (pipe(child_in) || pipe(child_out)) {
257 perror("Cannot open the pipes");
258 ERROR1("Test suite `%s': NOK (system error)", testsuite_name);
264 perror("Cannot fork the command");
265 ERROR1("Test suite `%s': NOK (system error)", testsuite_name);
269 if (rctx->pid) { /* father */
271 rctx->child_to = child_in[1];
274 rctx->child_from = child_out[0];
276 rctx->end_time = time(NULL) + timeout_value;
278 rctx->reader_done = 0;
279 rctx->reader = xbt_thread_create(thread_reader,(void*)rctx);
280 rctx->writer = xbt_thread_create(thread_writer,(void*)rctx);
289 dup2(child_out[1],1);
290 dup2(child_out[1],2);
293 execlp ("/bin/sh", "sh", "-c", rctx->cmd, NULL);
296 rctx->is_stoppable = 1;
298 if (!rctx->is_background) {
301 /* Damn. Copy the rctx and launch a thread to handle it */
306 DEBUG2("RCTX: new bg=%p, new fg=%p",old,rctx);
308 DEBUG2("Launch a thread to wait for %s %d",old->cmd,old->pid);
309 runner = xbt_thread_create(rctx_wait,(void*)old);
310 VERB3("Launched thread %p to wait for %s %d",
311 runner,old->cmd, old->pid);
312 xbt_dynar_push(bg_jobs,&runner);
316 /* Waits for the child to end (or to timeout), and check its
317 ending conditions. This is launched from rctx_start but either in main
318 thread (for foreground jobs) or in a separate one for background jobs.
319 That explains the prototype, forced by xbt_thread_create. */
321 void *rctx_wait(void* r) {
322 rctx_t rctx = (rctx_t)r;
324 int now = time(NULL);
326 rctx_dump(rctx,"wait");
328 if (!rctx->is_stoppable)
329 THROW1(unknown_error,0,"Cmd '%s' not started yet. Cannot wait it",
333 /* Wait for the child to die or the timeout to happen */
334 while (!rctx->reader_done && rctx->end_time >= now) {
339 if (rctx->end_time < now) {
340 INFO1("Child '%s' timeouted. Kill it",rctx->cmd);
342 kill(rctx->pid,SIGTERM);
344 kill(rctx->pid,SIGKILL);
347 /* Make sure helper threads die.
348 Cannot block since they wait for the child we just killed
349 if not already dead. */
350 xbt_thread_join(rctx->writer,NULL);
351 xbt_thread_join(rctx->reader,NULL);
353 /* Check for broken pipe */
354 if (rctx->brokenpipe)
355 VERB0("Warning: Child did not consume all its input (I got broken pipe)");
357 /* Check for timeouts */
359 ERROR2("Test suite `%s': NOK (timeout after %d sec)", testsuite_name,timeout_value);
363 DEBUG2("RCTX=%p (pid=%d)",rctx,rctx->pid);
364 DEBUG3("Status(%s|%d)=%d",rctx->cmd,rctx->pid,rctx->status);
366 if (WIFSIGNALED(rctx->status) && !rctx->expected_signal) {
367 ERROR2("Test suite `%s': NOK (child got signal %s)",
369 signal_name(WTERMSIG(rctx->status),NULL));
370 errcode = WTERMSIG(rctx->status)+4;
373 if (WIFSIGNALED(rctx->status) && rctx->expected_signal &&
374 strcmp(signal_name(WTERMSIG(rctx->status),rctx->expected_signal),
375 rctx->expected_signal)) {
376 ERROR3("Test suite `%s': NOK (child got signal %s instead of %s)", testsuite_name,
377 signal_name(WTERMSIG(rctx->status),rctx->expected_signal),
378 rctx->expected_signal);
379 errcode = WTERMSIG(rctx->status)+4;
382 if (!WIFSIGNALED(rctx->status) && rctx->expected_signal) {
383 ERROR2("Test suite `%s': NOK (child expected signal %s)", testsuite_name,
384 rctx->expected_signal);
388 if (WIFEXITED(rctx->status) && WEXITSTATUS(rctx->status) != rctx->expected_return ) {
389 if (rctx->expected_return)
390 ERROR3("Test suite `%s': NOK (child returned code %d instead of %d)", testsuite_name,
391 WEXITSTATUS(rctx->status), rctx->expected_return);
393 ERROR2("Test suite `%s': NOK (child returned code %d)", testsuite_name, WEXITSTATUS(rctx->status));
394 errcode = 40+WEXITSTATUS(rctx->status);
397 rctx->expected_return = 0;
399 if(rctx->expected_signal){
400 free(rctx->expected_signal);
401 rctx->expected_signal = NULL;
404 buff_chomp(rctx->output_got);
405 buff_chomp(rctx->output_wanted);
406 buff_trim(rctx->output_got);
407 buff_trim(rctx->output_wanted);
409 if ( rctx->output == e_output_check
410 && ( rctx->output_got->used != rctx->output_wanted->used
411 || strcmp(rctx->output_got->data, rctx->output_wanted->data))) {
412 if (XBT_LOG_ISENABLED(tesh,xbt_log_priority_info)) {
413 char *diff= xbt_str_diff(rctx->output_wanted->data,rctx->output_got->data);
414 ERROR1("Output mismatch:\n%s",
418 ERROR1("Test suite `%s': NOK (output mismatch)", testsuite_name);
421 } else if (rctx->output == e_output_ignore) {
422 INFO0("(ignoring the output as requested)");
423 } else if (rctx->output == e_output_display) {
424 xbt_dynar_t a = xbt_str_split(rctx->output_got->data, "\n");
425 char *out = xbt_str_join(a,"\n||");
427 INFO1("Here is the (ignored) command output: \n||%s",out);
431 if (rctx->is_background)
436 if (rctx->output == e_output_check)
437 INFO1("Here is the child's output:\n%s",rctx->output_got->data);