From 86051ed1b66342bae0e21fd6643e66331ac2c07d Mon Sep 17 00:00:00 2001 From: mquinson Date: Mon, 7 May 2007 11:18:56 +0000 Subject: [PATCH] Huge code cleanup + implementation of the background commands. Damn thing, that was harder than expected git-svn-id: svn+ssh://scm.gforge.inria.fr/svn/simgrid/simgrid/trunk@3483 48e7efb5-ca39-0410-a469-dd3cf9ba447f --- .../{non-blocking-IO.tesh => IO-bigsize.tesh} | 2 - .../{broken-pipe.tesh => IO-broken-pipe.tesh} | 0 tools/tesh/IO-orders.tesh | 57 +++ tools/tesh/Makefile.am | 10 +- tools/tesh/Makefile.in | 32 +- tools/tesh/README.tesh | 79 ++++ tools/tesh/background.tesh | 50 +++ tools/tesh/bg-basic.tesh | 20 + tools/tesh/bg-set-signal.tesh | 21 + tools/tesh/buff.c | 26 +- tools/tesh/buff.h | 14 +- tools/tesh/catch-return.tesh | 25 ++ tools/tesh/catch-signal.tesh | 26 ++ tools/tesh/catch-timeout.tesh | 13 + tools/tesh/catch-wrong-output.tesh | 16 + tools/tesh/run_context.c | 410 ++++++++++++++++++ tools/tesh/run_context.h | 72 +++ tools/tesh/set-ignore-output.tesh | 13 + tools/tesh/set-return.tesh | 20 + tools/tesh/{segfault.tesh => set-signal.tesh} | 0 tools/tesh/{timeout.tesh => set-timeout.tesh} | 0 tools/tesh/signal.c | 2 +- tools/tesh/tesh.c | 383 +++++----------- tools/tesh/tesh.h | 6 + 24 files changed, 982 insertions(+), 315 deletions(-) rename tools/tesh/{non-blocking-IO.tesh => IO-bigsize.tesh} (99%) rename tools/tesh/{broken-pipe.tesh => IO-broken-pipe.tesh} (100%) create mode 100644 tools/tesh/IO-orders.tesh create mode 100644 tools/tesh/README.tesh create mode 100644 tools/tesh/background.tesh create mode 100644 tools/tesh/bg-basic.tesh create mode 100644 tools/tesh/bg-set-signal.tesh create mode 100644 tools/tesh/catch-return.tesh create mode 100644 tools/tesh/catch-signal.tesh create mode 100644 tools/tesh/catch-timeout.tesh create mode 100644 tools/tesh/catch-wrong-output.tesh create mode 100644 tools/tesh/run_context.c create mode 100644 tools/tesh/run_context.h create mode 100644 tools/tesh/set-ignore-output.tesh create mode 100644 tools/tesh/set-return.tesh rename tools/tesh/{segfault.tesh => set-signal.tesh} (100%) rename tools/tesh/{timeout.tesh => set-timeout.tesh} (100%) diff --git a/tools/tesh/non-blocking-IO.tesh b/tools/tesh/IO-bigsize.tesh similarity index 99% rename from tools/tesh/non-blocking-IO.tesh rename to tools/tesh/IO-bigsize.tesh index 216152bf96..1cbc1e836c 100644 --- a/tools/tesh/non-blocking-IO.tesh +++ b/tools/tesh/IO-bigsize.tesh @@ -2013,9 +2013,7 @@ p And now, a read/write test < 997 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA < 998 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA < 999 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - $ cat - > 000 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA > 001 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA > 002 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA diff --git a/tools/tesh/broken-pipe.tesh b/tools/tesh/IO-broken-pipe.tesh similarity index 100% rename from tools/tesh/broken-pipe.tesh rename to tools/tesh/IO-broken-pipe.tesh diff --git a/tools/tesh/IO-orders.tesh b/tools/tesh/IO-orders.tesh new file mode 100644 index 0000000000..4862eb1b67 --- /dev/null +++ b/tools/tesh/IO-orders.tesh @@ -0,0 +1,57 @@ + +p This tests that TESH accepts any order for the input/output + +p Order: in, out, cmd +< < TOTO +< > TOTO +< $ cat +> [0.000000] [tesh/INFO] Test suite from stdin +> [0.000000] [tesh/INFO] [stdin:3] cat +> [0.000000] [tesh/INFO] Test suite from stdin OK +$ ./tesh + +p Order: out, in, cmd +< > TOTO +< < TOTO +< $ cat +> [0.000000] [tesh/INFO] Test suite from stdin +> [0.000000] [tesh/INFO] [stdin:3] cat +> [0.000000] [tesh/INFO] Test suite from stdin OK +$ ./tesh + +p Order: out, cmd, in +< > TOTO +< $ cat +< < TOTO +> [0.000000] [tesh/INFO] Test suite from stdin +> [0.000000] [tesh/INFO] [stdin:2] cat +> [0.000000] [tesh/INFO] Test suite from stdin OK +$ ./tesh + +p Order: in, cmd, out +< < TOTO +< $ cat +< > TOTO +> [0.000000] [tesh/INFO] Test suite from stdin +> [0.000000] [tesh/INFO] [stdin:2] cat +> [0.000000] [tesh/INFO] Test suite from stdin OK +$ ./tesh + +p Order: cmd, out, in +< $ cat +< > TOTO +< < TOTO +> [0.000000] [tesh/INFO] Test suite from stdin +> [0.000000] [tesh/INFO] [stdin:1] cat +> [0.000000] [tesh/INFO] Test suite from stdin OK +$ ./tesh + +p Order: cmd, in, out +< $ cat +< < TOTO +< > TOTO +> [0.000000] [tesh/INFO] Test suite from stdin +> [0.000000] [tesh/INFO] [stdin:1] cat +> [0.000000] [tesh/INFO] Test suite from stdin OK +$ ./tesh + diff --git a/tools/tesh/Makefile.am b/tools/tesh/Makefile.am index c72f5e8df2..5e9c7095fa 100644 --- a/tools/tesh/Makefile.am +++ b/tools/tesh/Makefile.am @@ -4,14 +4,16 @@ INCLUDES = -I$(top_srcdir)/include -I$(top_srcdir)/src/include bin_PROGRAMS = tesh -tesh_SOURCES = tesh.c tesh.h buff.h buff.c signal.c +tesh_SOURCES = run_context.c run_context.h tesh.c tesh.h buff.h buff.c signal.c tesh_LDADD = $(top_builddir)/src/libsimgrid.la TESTS_ENVIRONMENT=./tesh TESTS=basic.tesh cd.tesh \ - broken-pipe.tesh segfault.tesh timeout.tesh - -# non-blocking-IO.tesh -> blocks on AMD64 + IO-broken-pipe.tesh IO-orders.tesh IO-bigsize.tesh \ + set-return.tesh set-signal.tesh set-timeout.tesh set-ignore-output.tesh\ + catch-return.tesh catch-signal.tesh catch-timeout.tesh \ + catch-wrong-output.tesh \ + bg-basic.tesh bg-set-signal.tesh background.tesh $(top_builddir)/src/libsimgrid.la: make -C $(top_builddir)/src libsimgrid.la diff --git a/tools/tesh/Makefile.in b/tools/tesh/Makefile.in index 3ea7e9a7d7..c95fe7fd20 100644 --- a/tools/tesh/Makefile.in +++ b/tools/tesh/Makefile.in @@ -63,7 +63,8 @@ CONFIG_CLEAN_FILES = am__installdirs = "$(DESTDIR)$(bindir)" binPROGRAMS_INSTALL = $(INSTALL_PROGRAM) PROGRAMS = $(bin_PROGRAMS) -am_tesh_OBJECTS = tesh.$(OBJEXT) buff.$(OBJEXT) signal.$(OBJEXT) +am_tesh_OBJECTS = run_context.$(OBJEXT) tesh.$(OBJEXT) buff.$(OBJEXT) \ + signal.$(OBJEXT) tesh_OBJECTS = $(am_tesh_OBJECTS) tesh_DEPENDENCIES = $(top_builddir)/src/libsimgrid.la DEFAULT_INCLUDES = -I. -I$(srcdir) -I$(top_builddir)/src @@ -121,6 +122,9 @@ FLEXML = @FLEXML@ GRAMINE_MODE_FALSE = @GRAMINE_MODE_FALSE@ GRAMINE_MODE_TRUE = @GRAMINE_MODE_TRUE@ GRAS_DEP = @GRAS_DEP@ +GREP = @GREP@ +GTNETS_CFLAGS = @GTNETS_CFLAGS@ +GTNETS_LDFLAGS = @GTNETS_LDFLAGS@ HAVE_FLEXML_FALSE = @HAVE_FLEXML_FALSE@ HAVE_FLEXML_TRUE = @HAVE_FLEXML_TRUE@ HAVE_SDP_FALSE = @HAVE_SDP_FALSE@ @@ -159,6 +163,8 @@ SET_MAKE = @SET_MAKE@ SHELL = @SHELL@ SIMGRID_DEP = @SIMGRID_DEP@ STRIP = @STRIP@ +USE_GTNETS_FALSE = @USE_GTNETS_FALSE@ +USE_GTNETS_TRUE = @USE_GTNETS_TRUE@ USE_SIMIX_FALSE = @USE_SIMIX_FALSE@ USE_SIMIX_TRUE = @USE_SIMIX_TRUE@ VERSION = @VERSION@ @@ -168,15 +174,9 @@ abs_srcdir = @abs_srcdir@ abs_top_builddir = @abs_top_builddir@ abs_top_srcdir = @abs_top_srcdir@ ac_configure_args = @ac_configure_args@ -ac_ct_AR = @ac_ct_AR@ -ac_ct_AS = @ac_ct_AS@ ac_ct_CC = @ac_ct_CC@ ac_ct_CXX = @ac_ct_CXX@ -ac_ct_DLLTOOL = @ac_ct_DLLTOOL@ ac_ct_F77 = @ac_ct_F77@ -ac_ct_OBJDUMP = @ac_ct_OBJDUMP@ -ac_ct_RANLIB = @ac_ct_RANLIB@ -ac_ct_STRIP = @ac_ct_STRIP@ am__fastdepCC_FALSE = @am__fastdepCC_FALSE@ am__fastdepCC_TRUE = @am__fastdepCC_TRUE@ am__fastdepCXX_FALSE = @am__fastdepCXX_FALSE@ @@ -194,23 +194,30 @@ build_id = @build_id@ build_os = @build_os@ build_vendor = @build_vendor@ datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ exec_prefix = @exec_prefix@ host = @host@ host_alias = @host_alias@ host_cpu = @host_cpu@ host_os = @host_os@ host_vendor = @host_vendor@ +htmldir = @htmldir@ includedir = @includedir@ infodir = @infodir@ install_sh = @install_sh@ libdir = @libdir@ libexecdir = @libexecdir@ +localedir = @localedir@ localstatedir = @localstatedir@ mandir = @mandir@ mkdir_p = @mkdir_p@ oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ prefix = @prefix@ program_transform_name = @program_transform_name@ +psdir = @psdir@ pth_skaddr_makecontext = @pth_skaddr_makecontext@ pth_sksize_makecontext = @pth_sksize_makecontext@ sbindir = @sbindir@ @@ -222,11 +229,15 @@ target_cpu = @target_cpu@ target_os = @target_os@ target_vendor = @target_vendor@ INCLUDES = -I$(top_srcdir)/include -I$(top_srcdir)/src/include -tesh_SOURCES = tesh.c tesh.h buff.h buff.c signal.c +tesh_SOURCES = run_context.c run_context.h tesh.c tesh.h buff.h buff.c signal.c tesh_LDADD = $(top_builddir)/src/libsimgrid.la TESTS_ENVIRONMENT = ./tesh TESTS = basic.tesh cd.tesh \ - broken-pipe.tesh segfault.tesh timeout.tesh + IO-broken-pipe.tesh IO-orders.tesh IO-bigsize.tesh \ + set-return.tesh set-signal.tesh set-timeout.tesh set-ignore-output.tesh\ + catch-return.tesh catch-signal.tesh catch-timeout.tesh \ + catch-wrong-output.tesh \ + bg-basic.tesh bg-set-signal.tesh background.tesh all: all-am @@ -300,6 +311,7 @@ distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/buff.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/run_context.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/signal.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tesh.Po@am__quote@ @@ -579,8 +591,6 @@ uninstall-am: uninstall-binPROGRAMS uninstall-info-am uninstall-info-am -# non-blocking-IO.tesh -> blocks on AMD64 - $(top_builddir)/src/libsimgrid.la: make -C $(top_builddir)/src libsimgrid.la diff --git a/tools/tesh/README.tesh b/tools/tesh/README.tesh new file mode 100644 index 0000000000..0b391c2bc8 --- /dev/null +++ b/tools/tesh/README.tesh @@ -0,0 +1,79 @@ +This is the TESH tool. It constitutes a testing shell, ie a sort of shell +specialized to run tests. The list of actions to take is parsed from files +files called testsuite. + +Testsuites syntax +----------------- +Here is the syntax of these files: + +The kind of each line is given by the first char (the second char should be +blank and is ignored): + + `$' command to run in forground + `&' command to run in background + `<' input to pass to the command + `>' output expected from the command + `!' metacommand, which can be one of: + `set timeout' + `expect signal' + `expect return' + +If the expected output do not match what the command spits, TESH will produce +an error showing the diff. + +IO orders +--------- + +The < and > lines add IO to the command defined in the current block (blocks +are separated by blank lines). It is possible to place these lines either after +the command or before. The difference between the two following chunks is +mainly cosmetic in your testsuites, TESH don't care. (cf IO-orders.tesh) + + $ cat + < TOTO + > TOTO + + > TOTO + $ cat + < TOTO + +Nevertheless, it is possible to have several commands in the same block, but +none of them can have any output. It may seem a bit restrictive, as one could +say that a command gets all the IO until the next command, but I'm afraid of +errors such as the following: + + $ cd toto + > TOTO + $ cat > file + +TOTO will be passed to the cd command, where the user clearly want to pass it +to cat. + +RETURN CODE +----------- + +TESH spits an appropriate error message when the child do not return 0 as +return code (cf. catch-return.tesh). + +It is also possible to specify that a given command must return another +value. For this, use the "expect return" metacommand, which takes an integer as +argument. The change only apply to the next command (cf. set-return.tesh). + +SIGNALS +------- + +TESH detects when the child is killed by a signal (like on segfaults), and +spits an appropriate error message (cf. catch-signal.tesh). + +It is also possible to specify that a given command must raise a given +signal. For this, use the "expect signal" metacommand. It takes the signal name +as argument. The change only apply to the next command (cf. set-signal.tesh). + +TIMEOUTS +-------- + +By default, all commands are given 5 seconds to execute +(cf. catch-timeout.tesh). You can change this with the "set timeout", which +takes an integer as argument. The change only apply to the next command +(cf. set-timeout.tesh). + diff --git a/tools/tesh/background.tesh b/tools/tesh/background.tesh new file mode 100644 index 0000000000..a416496aef --- /dev/null +++ b/tools/tesh/background.tesh @@ -0,0 +1,50 @@ + +$ rm -rf temp_testdir +$ mkdir temp_testdir +$ cd temp_testdir + +< #include +< #include +< #include +< #include +< #include +< #include +< +< int main() { +< char buff[2048]; +< int got; +< int in; +< +< sleep(1); +< in = open("tmp_fich",O_RDONLY|O_CREAT); +< if (in == -1) { +< perror("Cannot open tmp_fich: "); +< exit(1); +< } +< while ((got = read(in,&buff,2048))>0) { +< int w = write(1,&buff,got); +< if (w<0) { +< perror("Error while writing:"); +< exit(1); +< } +< } +< if (got < 0) { +< perror("Error while reading:"); +< exit(1); +< } +< return 0; +< } +$ cat > delayed_cat.c + +$ gcc -Wall -o delayed_cat delayed_cat.c + +& ./delayed_cat +> TOTO + +< TOTO +$ cat > tmp_fich + +$ sleep 2 +$ cd .. +$ rm -rf temp_testdir + diff --git a/tools/tesh/bg-basic.tesh b/tools/tesh/bg-basic.tesh new file mode 100644 index 0000000000..9889856766 --- /dev/null +++ b/tools/tesh/bg-basic.tesh @@ -0,0 +1,20 @@ +#! ./tesh + +p This is a basic test + +< TOTO \ +TUTU +& cat +> TOTO TUTU + +p And now, some multilines examples + +< a +< b +< c +< d +& cat +> a +> b +> c +> d \ No newline at end of file diff --git a/tools/tesh/bg-set-signal.tesh b/tools/tesh/bg-set-signal.tesh new file mode 100644 index 0000000000..681e844ea2 --- /dev/null +++ b/tools/tesh/bg-set-signal.tesh @@ -0,0 +1,21 @@ +#! ./tesh +# This suite builds and uses a program raising a segfault, ie a program dying +# of SIGSEV. tesh must detect this condition and report the issue. + +$ rm -rf temp_testdir +$ mkdir temp_testdir + +$ cd temp_testdir +< #include +< int main(void) { +< char *A=NULL; +< *A = 1; +< } +$ cat > segfault.c + +$ gcc -o segfault segfault.c +! expect signal SIGSEGV +& ./segfault +$ sleep 1 +$ cd .. +$ rm -rf temp_testdir diff --git a/tools/tesh/buff.c b/tools/tesh/buff.c index 4c8cbd69e1..691d53df08 100644 --- a/tools/tesh/buff.c +++ b/tools/tesh/buff.c @@ -15,32 +15,40 @@ #include "buff.h" +XBT_LOG_EXTERNAL_DEFAULT_CATEGORY(tesh); + /** ** Buffer code **/ -void buff_empty(buff_t *b) { +void buff_empty(buff_t b) { b->used=0; b->data[0]='\n'; b->data[1]='\0'; } -buff_t *buff_new(void) { - buff_t *res=malloc(sizeof(buff_t)); +buff_t buff_new(void) { + buff_t res=malloc(sizeof(s_buff_t)); res->data=malloc(512); res->size=512; buff_empty(res); return res; } -void buff_free(buff_t *b) { +void buff_free(buff_t b) { if (b) { if (b->data) free(b->data); free(b); } } -void buff_append(buff_t *b, char *toadd) { - int addlen=strlen(toadd); - int needed_space=b->used+addlen+1; +void buff_append(buff_t b, const char *toadd) { + int addlen; + int needed_space; + + if (!b) + THROW0(arg_error,0,"Asked to append stuff to NULL buffer"); + + addlen = strlen(toadd); + needed_space=b->used+addlen+1; if (needed_space > b->size) { b->data = realloc(b->data, needed_space); @@ -49,7 +57,7 @@ void buff_append(buff_t *b, char *toadd) { strcpy(b->data+b->used, toadd); b->used += addlen; } -void buff_chomp(buff_t *b) { +void buff_chomp(buff_t b) { while (b->data[b->used] == '\n') { b->data[b->used] = '\0'; if (b->used) @@ -57,7 +65,7 @@ void buff_chomp(buff_t *b) { } } -void buff_trim(buff_t* b) { +void buff_trim(buff_t b) { xbt_str_trim(b->data," "); b->used = strlen(b->data); } diff --git a/tools/tesh/buff.h b/tools/tesh/buff.h index a0a16336bd..55a2e9f610 100644 --- a/tools/tesh/buff.h +++ b/tools/tesh/buff.h @@ -23,14 +23,14 @@ typedef struct { char *data; int used,size; -} buff_t; +} s_buff_t, *buff_t; -void buff_empty(buff_t *b); -buff_t *buff_new(void); -void buff_free(buff_t *b); -void buff_append(buff_t *b, char *toadd); -void buff_chomp(buff_t *b); -void buff_trim(buff_t* b); +void buff_empty(buff_t b); +buff_t buff_new(void); +void buff_free(buff_t b); +void buff_append(buff_t b, const char *toadd); +void buff_chomp(buff_t b); +void buff_trim(buff_t b); #endif diff --git a/tools/tesh/catch-return.tesh b/tools/tesh/catch-return.tesh new file mode 100644 index 0000000000..b69cc1e174 --- /dev/null +++ b/tools/tesh/catch-return.tesh @@ -0,0 +1,25 @@ +#! ./tesh +# This suite builds and uses a program returning 1. +# tesh must detect this condition and report the issue. + +$ rm -rf temp_testdir +$ mkdir temp_testdir + +$ cd temp_testdir +< #include +< int main(void) { +< exit(1); +< } +$ cat > return1.c + +$ gcc -o return1 return1.c + +! expect return 41 +< $ ./return1 +$ ../tesh +> [0.000000] [tesh/INFO] Test suite from stdin +> [0.000000] [tesh/INFO] [stdin:1] ./return1 +> [0.000000] run_context.c:374: [tesh/ERROR] Child "./return1" returned code 1 + +$ cd .. +$ rm -rf temp_testdir diff --git a/tools/tesh/catch-signal.tesh b/tools/tesh/catch-signal.tesh new file mode 100644 index 0000000000..4fc075402e --- /dev/null +++ b/tools/tesh/catch-signal.tesh @@ -0,0 +1,26 @@ +#! ./tesh +# This suite builds and uses a program raising a segfault, ie a program dying +# of SIGSEV. tesh must detect this condition and report the issue. + +$ rm -rf temp_testdir +$ mkdir temp_testdir + +$ cd temp_testdir +< #include +< int main(void) { +< char *A=NULL; +< *A = 1; +< } +$ cat > segfault.c + +$ gcc -o segfault segfault.c + +! expect return 15 +< $ ./segfault +$ ../tesh +> [0.000000] [tesh/INFO] Test suite from stdin +> [0.000000] [tesh/INFO] [stdin:1] ./segfault +> [0.000000] run_context.c:350: [tesh/ERROR] Child "./segfault" got signal SIGSEGV. + +$ cd .. +$ rm -rf temp_testdir diff --git a/tools/tesh/catch-timeout.tesh b/tools/tesh/catch-timeout.tesh new file mode 100644 index 0000000000..a5000a00d7 --- /dev/null +++ b/tools/tesh/catch-timeout.tesh @@ -0,0 +1,13 @@ +#! ./tesh + +# This suite must be functional because we changed the timeout value to 10 +# before sleeping 6 secs. + +! expect return 3 +< ! set timeout 1 +< $ sleep 6 +> [0.000000] [tesh/INFO] Test suite from stdin +> [0.000000] [tesh/INFO] [stdin:2] sleep 6 +> [0.000000] [tesh/INFO] Child 'sleep 6' timeouted. Kill it +> [0.000000] run_context.c:335: [tesh/ERROR] Child timeouted (waited 1 sec) +$ ./tesh diff --git a/tools/tesh/catch-wrong-output.tesh b/tools/tesh/catch-wrong-output.tesh new file mode 100644 index 0000000000..2532086e65 --- /dev/null +++ b/tools/tesh/catch-wrong-output.tesh @@ -0,0 +1,16 @@ +#! ./tesh + +p This tests whether TESH detects wrong outputs + +! expect return 2 +< > TOTO +< < TUTU +< $ cat +$ ./tesh +> [0.000000] [tesh/INFO] Test suite from stdin +> [0.000000] [tesh/INFO] [stdin:3] cat +> [0.000000] run_context.c:394: [tesh/ERROR] Output of child "cat" don't match expectations. Here is a diff between expected and got output: +> + TUTU +> - TOTO +> +> diff --git a/tools/tesh/run_context.c b/tools/tesh/run_context.c new file mode 100644 index 0000000000..3f65821eb4 --- /dev/null +++ b/tools/tesh/run_context.c @@ -0,0 +1,410 @@ +/* $Id$ */ + +/* run_context -- stuff in which TESH runs a command */ + +/* Copyright (c) 2007 Martin Quinson. */ +/* All rights reserved. */ + +/* This program is free software; you can redistribute it and/or modify it + * under the terms of the license (GNU LGPL) which comes with this package. */ + +#include "tesh.h" + +#include +#include + + +XBT_LOG_EXTERNAL_DEFAULT_CATEGORY(tesh); + +xbt_dynar_t bg_jobs = NULL; + +/* + * Module management + */ + +static void join_it(void*t) { + xbt_thread_t th = *(xbt_thread_t*)t; + VERB1("Join thread %p which were running a background cmd",th); + xbt_thread_join(th,NULL); +} + +void rctx_init(void) { + bg_jobs = xbt_dynar_new(sizeof(xbt_thread_t),join_it); +} + +void rctx_exit(void) { + xbt_dynar_free(&bg_jobs); +} + +void rctx_wait_bg(void) { + xbt_dynar_free(&bg_jobs); + bg_jobs = xbt_dynar_new(sizeof(xbt_thread_t),join_it); +} + +/* + * Memory management + */ + +void rctx_empty(rctx_t rc) { + if (rc->cmd) + free(rc->cmd); + rc->cmd = NULL; + rc->is_empty = 1; + rc->is_background = 0; + rc->is_stoppable = 0; + rc->check_output = 1; + rc->brokenpipe = 0; + rc->timeout = 0; + buff_empty(rc->input); + buff_empty(rc->output_wanted); + buff_empty(rc->output_got); +} + +rctx_t rctx_new() { + rctx_t res = xbt_new0(s_rctx_t,1); + + res->input=buff_new(); + res->output_wanted=buff_new(); + res->output_got=buff_new(); + rctx_empty(res); + return res; +} + +void rctx_free(rctx_t rctx) { + DEBUG1("RCTX: Free %p", rctx); + rctx_dump(rctx,"free"); + if (!rctx) + return; + + if (rctx->cmd) + free(rctx->cmd); + buff_free(rctx->input); + buff_free(rctx->output_got); + buff_free(rctx->output_wanted); + free(rctx); +} + +void rctx_dump(rctx_t rctx, const char *str) { + DEBUG9("%s RCTX %p={in%p={%d,%10s}, want={%d,%10s}, out={%d,%10s}}", + str, rctx, + rctx->input, rctx->input->used, rctx->input->data, + rctx->output_wanted->used,rctx->output_wanted->data, + rctx->output_got->used, rctx->output_got->data); + DEBUG5("%s RCTX %p=[cmd%p=%10s, pid=%d]", + str,rctx,rctx->cmd,rctx->cmd,rctx->pid); + +} + +/* + * Getting instructions from the file + */ + +void rctx_pushline(const char* filepos, char kind, char *line) { + + switch (kind) { + case '$': + case '&': + if (rctx->cmd) { + if (!rctx->is_empty) { + ERROR2("[%s] More than one command in this chunk of lines (previous: %s).\n" + " Dunno which input/output belongs to which command.", + filepos,rctx->cmd); + exit(1); + } + rctx_start(); + VERB1("[%s] More than one command in this chunk of lines",filepos); + } + if (kind == '&') + rctx->is_background = 1; + else + rctx->is_background = 0; + + rctx->cmd = xbt_strdup(line); + INFO3("[%s] %s%s",filepos,line, + ((rctx->is_background)?" (background command)":"")); + + break; + + case '<': + rctx->is_empty = 0; + buff_append(rctx->input,line); + buff_append(rctx->input,"\n"); + break; + + case '>': + rctx->is_empty = 0; + buff_append(rctx->output_wanted,line); + buff_append(rctx->output_wanted,"\n"); + break; + + case '!': + if (rctx->cmd) + rctx_start(); + + if (!strncmp(line,"set timeout ",strlen("set timeout "))) { + timeout_value=atoi(line+strlen("set timeout")); + VERB2("[%s] (new timeout value: %d)", + filepos,timeout_value); + + } else if (!strncmp(line,"expect signal ",strlen("expect signal "))) { + rctx->expected_signal = strdup(line + strlen("expect signal ")); + xbt_str_trim(rctx->expected_signal," \n"); + VERB2("[%s] (next command must raise signal %s)", + filepos, rctx->expected_signal); + + } else if (!strncmp(line,"expect return ",strlen("expect return "))) { + rctx->expected_return = atoi(line+strlen("expect return ")); + VERB2("[%s] (next command must return code %d)", + filepos, rctx->expected_return); + + } else if (!strncmp(line,"ignore output",strlen("ignore output"))) { + rctx->check_output = 0; + VERB1("[%s] (ignore output of next command)", filepos); + + } else { + ERROR2("%s: Malformed metacommand: %s",filepos,line); + exit(1); + } + break; + } +} + +/* + * Actually doing the job + */ + +/* The IO of the childs are handled by the two following threads + (one pair per child) */ + +static void* thread_writer(void *r) { + int posw; + rctx_t rctx = (rctx_t)r; + for (posw=0; poswinput->used && !rctx->brokenpipe; ) { + int got; + DEBUG1("Still %d chars to write",rctx->input->used-posw); + got=write(rctx->child_to,rctx->input->data+posw,rctx->input->used-posw); + if (got>0) + posw+=got; + if (got<0) { + if (errno == EPIPE) { + rctx->brokenpipe = 1; + } else if (errno!=EINTR && errno!=EAGAIN && errno!=EPIPE) { + perror("Error while writing input to child"); + exit(4); + } + } + DEBUG1("written %d chars so far",posw); + + if (got <= 0) + usleep(100); + } + rctx->input->data[0]='\0'; + rctx->input->used=0; + close(rctx->child_to); + + return NULL; +} +static void *thread_reader(void *r) { + rctx_t rctx = (rctx_t)r; + char *buffout=malloc(4096); + int posr; + + do { + posr=read(rctx->child_from,buffout,4095); + if (posr<0 && errno!=EINTR && errno!=EAGAIN) { + perror("Error while reading output of child"); + exit(4); + } + if (posr>0) { + buffout[posr]='\0'; + buff_append(rctx->output_got,buffout); + } else { + usleep(100); + } + } while (!rctx->timeout && posr!=0); + free(buffout); + rctx->reader_done = 1; + return NULL; +} + +/* Start a new child, plug the pipes as expected and fire up the + helping threads. Is also waits for the child to end if this is a + foreground job, or fire up a thread to wait otherwise. */ + +void rctx_start(void) { + int child_in[2]; + int child_out[2]; + + VERB2("Start %s %s",rctx->cmd,(rctx->is_background?"(background job)":"")); + if (pipe(child_in) || pipe(child_out)) { + perror("Cannot open the pipes"); + exit(4); + } + + rctx->pid=fork(); + if (rctx->pid<0) { + perror("Cannot fork the command"); + exit(4); + } + + if (rctx->pid) { /* father */ + close(child_in[0]); + rctx->child_to = child_in[1]; + + close(child_out[1]); + rctx->child_from = child_out[0]; + + rctx->end_time = time(NULL) + timeout_value; + + rctx->reader = xbt_thread_create(thread_reader,(void*)rctx); + rctx->writer = xbt_thread_create(thread_writer,(void*)rctx); + + } else { /* child */ + + close(child_in[1]); + dup2(child_in[0],0); + close(child_in[0]); + + close(child_out[0]); + dup2(child_out[1],1); + dup2(child_out[1],2); + close(child_out[1]); + + execlp ("/bin/sh", "sh", "-c", rctx->cmd, NULL); + } + + rctx->is_stoppable = 1; + + if (!rctx->is_background) { + rctx_wait(rctx); + } else { + /* Damn. Copy the rctx and launch a thread to handle it */ + rctx_t old = rctx; + xbt_thread_t runner; + + rctx = rctx_new(); + DEBUG2("RCTX: new bg=%p, new fg=%p",old,rctx); + + DEBUG2("Launch a thread to wait for %s %d",old->cmd,old->pid); + runner = xbt_thread_create(rctx_wait,(void*)old); + VERB3("Launched thread %p to wait for %s %d", + runner,old->cmd, old->pid); + xbt_dynar_push(bg_jobs,&runner); + } +} + +/* Waits for the child to end (or to timeout), and check its + ending conditions. This is launched from rctx_start but either in main + thread (for foreground jobs) or in a separate one for background jobs. + That explains the prototype, forced by xbt_thread_create. */ + +void *rctx_wait(void* r) { + rctx_t rctx = (rctx_t)r; + int errcode = 0; + int res; + int status; + + rctx_dump(rctx,"wait"); + + if (!rctx->is_stoppable) + THROW1(unknown_error,0,"Cmd '%s' not started yet. Cannot wait it", + rctx->cmd); + + /* Wait for the child to die or the timeout to happen */ + while (!rctx->reader_done && rctx->end_time > time(NULL)) { + usleep(100); + } + + if (!rctx->reader_done) { + INFO1("Child '%s' timeouted. Kill it",rctx->cmd); + rctx->timeout = 1; + kill(rctx->pid,SIGKILL); + } + /* Make sure helper threads die. + Cannot block since they wait for the child we just killed + if not already dead. */ + xbt_thread_join(rctx->writer,NULL); + xbt_thread_join(rctx->reader,NULL); + + /* Check for broken pipe */ + if (rctx->brokenpipe) + VERB0("Warning: Child did not consume all its input (I got broken pipe)"); + + /* Check for timeouts */ + if (rctx->timeout) { + ERROR1("Child timeouted (waited %d sec)",timeout_value); + exit(3); + } + + DEBUG2("Wait for %s (%d)",rctx->cmd,rctx->pid); + res = waitpid(rctx->pid,&status,0); + if (res != rctx->pid) { + perror(bprintf("Cannot wait for the child %s",rctx->cmd)); + exit(1); + } + DEBUG2("RCTX=%p (pid=%d)",rctx,rctx->pid); + DEBUG3("Status(%s|%d)=%d",rctx->cmd,rctx->pid,status); + + if (WIFSIGNALED(status) && !rctx->expected_signal) { + ERROR2("Child \"%s\" got signal %s.", rctx->cmd, + signal_name(WTERMSIG(status),NULL)); + errcode = WTERMSIG(status)+4; + } + + if (WIFSIGNALED(status) && rctx->expected_signal && + strcmp(signal_name(WTERMSIG(status),rctx->expected_signal), + rctx->expected_signal)) { + ERROR3("Child \"%s\" got signal %s instead of signal %s", rctx->cmd, + signal_name(WTERMSIG(status),rctx->expected_signal), + rctx->expected_signal); + errcode = WTERMSIG(status)+4; + } + + if (!WIFSIGNALED(status) && rctx->expected_signal) { + ERROR2("Child \"%s\" didn't got expected signal %s", + rctx->cmd, rctx->expected_signal); + errcode = 5; + } + + if (WIFEXITED(status) && WEXITSTATUS(status) != rctx->expected_return ) { + if (rctx->expected_return) + ERROR3("Child \"%s\" returned code %d instead of %d", rctx->cmd, + WEXITSTATUS(status), rctx->expected_return); + else + ERROR2("Child \"%s\" returned code %d", rctx->cmd, WEXITSTATUS(status)); + errcode = 40+WEXITSTATUS(status); + } + rctx->expected_return = 0; + + if(rctx->expected_signal){ + free(rctx->expected_signal); + rctx->expected_signal = NULL; + } + + buff_chomp(rctx->output_got); + buff_chomp(rctx->output_wanted); + buff_trim(rctx->output_got); + buff_trim(rctx->output_wanted); + + if ( rctx->check_output + && ( rctx->output_got->used != rctx->output_wanted->used + || strcmp(rctx->output_got->data, rctx->output_wanted->data))) { + char *diff= xbt_str_diff(rctx->output_wanted->data,rctx->output_got->data); + ERROR2("Output of child \"%s\" don't match expectations. Here is a diff between expected and got output:\n%s", + rctx->cmd,diff); + free(diff); + errcode=2; + } else if (!rctx->check_output) { + INFO0("(ignoring the output as requested)"); + } + + if (rctx->is_background) + rctx_free(rctx); + else + rctx_empty(rctx); + if (errcode) + exit (errcode); + + return NULL; +} + diff --git a/tools/tesh/run_context.h b/tools/tesh/run_context.h new file mode 100644 index 0000000000..1f496d74a6 --- /dev/null +++ b/tools/tesh/run_context.h @@ -0,0 +1,72 @@ +/* $Id$ */ + +/* run_context -- stuff in which TESH runs a command */ + +/* Copyright (c) 2007 Martin Quinson. */ +/* All rights reserved. */ + +/* This program is free software; you can redistribute it and/or modify it + * under the terms of the license (GNU LGPL) which comes with this package. */ + +#ifndef TESH_RUN_CONTEXT_H +#define TESH_RUN_CONTEXT_H + +#include "tesh.h" + +typedef struct { + /* kind of job */ + char *cmd; + int pid; + int is_background:1; + int is_empty:1; + int is_stoppable:1; + int check_output:1; + + int brokenpipe:1; + int timeout:1; + + int reader_done:1; /* reader set this to true when he's done because + the child is dead. The main thread use it to detect + that the child is not dead before the end of timeout */ + + /* expected results */ + int end_time; /* begin_time + timeout, as epoch */ + char* expected_signal; /* name of signal to raise (or NULL if none) */ + int expected_return; /* the exepeted return code of following command */ + + /* buffers */ + buff_t input; + buff_t output_wanted; + buff_t output_got; + + /* Threads */ + xbt_thread_t writer, reader; /* IO handlers */ + + /* Pipes from/to the child */ + int child_to, child_from; + +} s_rctx_t, *rctx_t; + +/* module mgmt */ +void rctx_init(void); +void rctx_exit(void); + +/* wait for all currently running background jobs */ +void rctx_wait_bg(void); + +/* create a new empty running context */ +rctx_t rctx_new(void); +void rctx_free(rctx_t rctx); +void rctx_empty(rctx_t rc); /*reset to empty*/ +void rctx_dump(rctx_t rctx,const char *str); + + +/* Launch the current command */ +void rctx_start(void); +/* Wait till the end of this command */ +void *rctx_wait(void* rctx); + +/* Parse a line comming from the suite file, and add this into the rctx */ +void rctx_pushline(const char* filepos, char kind ,char *line); + +#endif /* TESH_RUN_CONTEXT_H */ diff --git a/tools/tesh/set-ignore-output.tesh b/tools/tesh/set-ignore-output.tesh new file mode 100644 index 0000000000..7e447bcfc4 --- /dev/null +++ b/tools/tesh/set-ignore-output.tesh @@ -0,0 +1,13 @@ +#! ./tesh + +p This tests whether TESH accepts to ignore command output + +< ! ignore output +< > TOTO +< < TUTU +< $ cat +$ ./tesh +> [0.000000] [tesh/INFO] Test suite from stdin +> [0.000000] [tesh/INFO] [stdin:4] cat +> [0.000000] [tesh/INFO] (ignoring the output as requested) +> [0.000000] [tesh/INFO] Test suite from stdin OK diff --git a/tools/tesh/set-return.tesh b/tools/tesh/set-return.tesh new file mode 100644 index 0000000000..d3b16005b7 --- /dev/null +++ b/tools/tesh/set-return.tesh @@ -0,0 +1,20 @@ +#! ./tesh +# This suite builds and uses a program returning 1. +# tesh is instructed of this return code and must not whine. + +$ rm -rf temp_testdir +$ mkdir temp_testdir + +$ cd temp_testdir +< #include +< int main(void) { +< exit(1); +< } +$ cat > return1.c + +$ gcc -o return1 return1.c + +! expect return 1 +$ ./return1 +$ cd .. +$ rm -rf temp_testdir diff --git a/tools/tesh/segfault.tesh b/tools/tesh/set-signal.tesh similarity index 100% rename from tools/tesh/segfault.tesh rename to tools/tesh/set-signal.tesh diff --git a/tools/tesh/timeout.tesh b/tools/tesh/set-timeout.tesh similarity index 100% rename from tools/tesh/timeout.tesh rename to tools/tesh/set-timeout.tesh diff --git a/tools/tesh/signal.c b/tools/tesh/signal.c index b70ce5b615..a6d26b8559 100644 --- a/tools/tesh/signal.c +++ b/tools/tesh/signal.c @@ -62,5 +62,5 @@ const char* signal_name(unsigned int got, char *expected) { if (signals[i].number == got) return (signals[i].name); - return "SIG UNKNOWN"; + return bprintf("SIG UNKNOWN (%d)", got); } diff --git a/tools/tesh/tesh.c b/tools/tesh/tesh.c index 7b3db461eb..98a6d14da5 100644 --- a/tools/tesh/tesh.c +++ b/tools/tesh/tesh.c @@ -14,286 +14,85 @@ #endif #include "tesh.h" +#include "xbt.h" -#include -#include +XBT_LOG_NEW_DEFAULT_CATEGORY(tesh,"TEst SHell utility"); -/** - ** Options - **/ +/*** Options ***/ int timeout_value = 5; /* child timeout value */ -char* expected_signal=NULL; /* !=NULL if the following command should raise a signal */ -int expected_return=0; /* the exepeted return code of following command */ -int verbose=0; /* wheather we should wine on problems */ - -/** - ** Dealing with timeouts - **/ -int timeouted; -static void timeout_handler(int sig) { - timeouted = 1; -} -/** - ** Dealing with timeouts - **/ -int brokenpipe; -static void pipe_handler(int sig) { - brokenpipe = 1; -} -/** - ** Launching a child - **/ -buff_t *input; -buff_t *output_wanted; -buff_t *output_got; - -static void check_output() { - if (output_wanted->used==0 - && output_got->used==0) - return; - buff_chomp(output_got); - buff_chomp(output_wanted); - buff_trim(output_got); - buff_trim(output_wanted); - - if ( output_got->used != output_wanted->used - || strcmp(output_got->data, output_wanted->data)) { - fprintf(stderr,"Output don't match expectations\n"); - fprintf(stderr,">>>>> Expected %d chars:\n%s\n<<<<< Expected\n", - output_wanted->used,output_wanted->data); - fprintf(stderr,">>>>> Got %d chars:\n%s\n<<<<< Got\n", - output_got->used,output_got->data); - exit(2); - } - buff_empty(output_wanted); - buff_empty(output_got); - -} -static void exec_cmd(char *cmd) { - int child_stdin[2]; - int child_stdout[2]; +static void handle_line(const char * filepos, char *line) { + int pos; - if (pipe(child_stdin) || pipe(child_stdout)) { - perror("Cannot open the pipes"); - exit(4); - } + /* Search end */ + xbt_str_rtrim(line+2,"\n"); - int pid=fork(); - if (pid<0) { - perror("Cannot fork the command"); - exit(4); - } + /* + DEBUG7("rctx={%s,in={%d,>>%10s<<},exp={%d,>>%10s<<},got={%d,>>%10s<<}}", + rctx->cmd, + rctx->input->used, rctx->input->data, + rctx->output_wanted->used,rctx->output_wanted->data, + rctx->output_got->used, rctx->output_got->data); + */ + DEBUG2("[%s] %s",filepos,line); - if (pid) { /* father */ - char buffout[4096]; - int posw,posr; - int status; - close(child_stdin[0]); - fcntl(child_stdin[1], F_SETFL, O_NONBLOCK); - close(child_stdout[1]); - fcntl(child_stdout[0], F_SETFL, O_NONBLOCK); - - brokenpipe = 0; - for (posw=0; poswused && !brokenpipe; ) { - int got; - // fprintf(stderr,"Still %d chars to write\n",input->used-posw); - got=write(child_stdin[1],input->data+posw,input->used-posw); - if (got>0) - posw+=got; - if (got<0 && errno!=EINTR && errno!=EAGAIN && errno!=EPIPE) { - perror("Error while writing input to child"); - exit(4); - } - // fprintf(stderr,"written %d chars so far\n",posw); + switch (line[0]) { + case '#': break; - posr=read(child_stdout[0],&buffout,4096); - // fprintf(stderr,"got %d chars\n",posr); - if (posr<0 && errno!=EINTR && errno!=EAGAIN) { - perror("Error while reading output of child"); - exit(4); - } - if (posr>0) { - buffout[posr]='\0'; - buff_append(output_got,buffout); - } - - if (got <= 0 && posr <= 0) - usleep(100); - } - input->data[0]='\0'; - input->used=0; - close(child_stdin[1]); - - timeouted = 0; - alarm(timeout_value); - do { - posr=read(child_stdout[0],&buffout,4096); - if (posr<0 && errno!=EINTR && errno!=EAGAIN) { - perror("Error while reading output of child"); - exit(4); - } - if (posr>0) { - buffout[posr]='\0'; - buff_append(output_got,buffout); - } else { - usleep(100); - } - } while (!timeouted && posr!=0); + case '$': + /* further trim useless chars which are significant for in/output */ + xbt_str_rtrim(line+2," \t"); - /* Check for broken pipe */ - if (brokenpipe && verbose) { - fprintf(stderr,"Warning: Child did not consume all its input (I got broken pipe)\n"); - } + /* Deal with CD commands here, not in rctx */ + if (!strncmp("cd ",line+2,3)) { + char *dir=line+4; - /* Check for timeouts */ - if (timeouted) { - fprintf(stderr,"Child timeouted (waited %d sec)\n",timeout_value); - exit(3); - } - alarm(0); + if (rctx->cmd) + rctx_start(); - - /* Wait for child, and check why it terminated */ - wait(&status); - - if (WIFSIGNALED(status) && - strcmp(signal_name(WTERMSIG(status),expected_signal), - expected_signal)) { - fprintf(stderr,"Child got signal %s instead of signal %s\n", - signal_name(WTERMSIG(status),expected_signal), - expected_signal); - exit(WTERMSIG(status)+4); - } - - if (!WIFSIGNALED(status) && expected_signal) { - fprintf(stderr,"Child didn't got expected signal %s\n", - expected_signal); - exit(5); - } - - if (WIFEXITED(status) && WEXITSTATUS(status) != expected_return ) { - if (expected_return) - fprintf(stderr,"Child returned code %d instead of %d\n", - WEXITSTATUS(status), expected_return); - else - fprintf(stderr,"Child returned code %d\n", WEXITSTATUS(status)); - exit(40+WEXITSTATUS(status)); - } - expected_return = 0; - - if(expected_signal){ - free(expected_signal); - expected_signal = NULL; - } - - } else { /* child */ - - close(child_stdin[1]); - close(child_stdout[0]); - dup2(child_stdin[0],0); - close(child_stdin[0]); - dup2(child_stdout[1],1); - dup2(child_stdout[1],2); - close(child_stdout[1]); - - execlp ("/bin/sh", "sh", "-c", cmd, NULL); - } -} - -static void run_cmd(char *cmd) { - if (cmd[0] == 'c' && cmd[1] == 'd' && cmd[2] == ' ') { - int pos = 2; - /* Search end */ - pos = strlen(cmd)-1; - while (cmd[pos] == '\n' || cmd[pos] == ' ' || cmd[pos] == '\t') - cmd[pos--] = '\0'; - /* search begining */ - pos = 2; - while (cmd[pos++] == ' '); - pos--; - // fprintf(stderr,"Saw cd '%s'\n",cmd+pos); - if (chdir(cmd+pos)) { - perror("Chdir failed"); - exit(4); - } - - } else { - exec_cmd(cmd); - } -} - -static void handle_line(int nl, char *line) { - - // printf("%d: %s",nl,line); fflush(stdout); - switch (line[0]) { - case '#': break; - case '$': - check_output(); /* Check that last command ran well */ - - printf("[%d] %s",nl,line); - fflush(stdout); - run_cmd(line+2); - break; - + /* search begining */ + while (*(dir++) == ' '); + dir--; + VERB1("Saw cd '%s'",dir); + if (chdir(dir)) { + char buff[256]; + strerror_r(errno, buff, 256); + + ERROR2("Chdir to %s failed: %s",dir+pos+2,buff); + exit(4); + } + break; + } /* else, pushline */ + case '&': case '<': - buff_append(input,line+2); - break; - case '>': - buff_append(output_wanted,line+2); - break; - case '!': - if (!strncmp(line+2,"set timeout ",strlen("set timeout "))) { - timeout_value=atoi(line+2+strlen("set timeout")); - printf("[%d] (new timeout value: %d)\n", - nl,timeout_value); - - } else if (!strncmp(line+2,"expect signal ",strlen("expect signal "))) { - expected_signal = strdup(line+2 + strlen("expect signal ")); - xbt_str_trim(expected_signal," \n"); - printf("[%d] (next command must raise signal %s)\n", nl, expected_signal); - - } else if (!strncmp(line+2,"expect return ",strlen("expect return "))) { - expected_return = atoi(line+2+strlen("expect return ")); - printf("[%d] (next command must return code %d)\n", - nl, expected_return); - - } else if (!strncmp(line+2,"verbose on",strlen("verbose on"))) { - verbose = 1; - printf("[%d] (increase verbosity)\n", nl); - - } else if (!strncmp(line+2,"verbose off",strlen("verbose off"))) { - verbose = 1; - printf("[%d] (decrease verbosity)\n", nl); - - } else { - fprintf(stderr,"%d: Malformed metacommand: %s",nl,line); - exit(1); - } + rctx_pushline(filepos, line[0], line+2 /* pass '$ ' stuff*/); break; case 'p': - printf("[%d] %s",nl,line+2); + INFO2("[%s] %s",filepos,line+2); break; default: - fprintf(stderr,"Syntax error line %d: %s",nl, line); + ERROR2("[%s] Syntax error: %s",filepos, line); exit(1); break; } } -static int handle_suite(FILE* IN) { +static void handle_suite(const char* filename, FILE* IN) { int len; char * line = NULL; int line_num=0; + char file_pos[256]; - buff_t *buff=buff_new(); + buff_t buff=buff_new(); int buffbegin = 0; + rctx = rctx_new(); + while (getline(&line,(size_t*) &len, IN) != -1) { line_num++; @@ -306,8 +105,17 @@ static int handle_suite(FILE* IN) { linelen++; } - if (blankline) + if (blankline) { + if (!rctx->cmd && !rctx->is_empty) { + ERROR1("[%d] Error: no command found in this chunk of lines.", + buffbegin); + exit(1); + } + if (rctx->cmd) + rctx_start(); + continue; + } /* Deal with \ at the end of the line, and call handle_line on result */ int to_be_continued = 0; @@ -329,66 +137,79 @@ static int handle_suite(FILE* IN) { buff_append(buff,line); if (!to_be_continued) { - handle_line(buffbegin, buff->data); + snprintf(file_pos,256,"%s:%d",filename,buffbegin); + handle_line(file_pos, buff->data); buff_empty(buff); } } else { - handle_line(line_num,line); + snprintf(file_pos,256,"%s:%d",filename,line_num); + handle_line(file_pos, line); } } - check_output(); /* Check that last command ran well */ + /* Check that last command of the file ran well */ + if (rctx->cmd) + rctx_start(); + + /* Wait all background commands */ + + rctx_free(rctx); /* Clear buffers */ if (line) free(line); buff_free(buff); - return 1; + } int main(int argc,char *argv[]) { - /* Setup the signal handlers */ - struct sigaction newact,oldact; - memset(&newact,0,sizeof(newact)); - newact.sa_handler=timeout_handler; - sigaction(SIGALRM,&newact,&oldact); + FILE *IN; - newact.sa_handler=pipe_handler; - sigaction(SIGPIPE,&newact,&oldact); + /* Ignore pipe issues. + They will show up when we try to send data to dead buddies, + but we will stop doing so when we're done with provided input */ + struct sigaction newact; + memset(&newact,0, sizeof(newact)); + newact.sa_handler=SIG_IGN; + sigaction(SIGPIPE,&newact,NULL); - /* Setup the input/output buffers */ - input=buff_new(); - output_wanted=buff_new(); - output_got=buff_new(); + xbt_init(&argc,argv); + rctx_init(); /* Find the description file */ - FILE *IN; - if (argc == 1) { - printf("Test suite from stdin\n");fflush(stdout); - handle_suite(stdin); - fprintf(stderr,"Test suite from stdin OK\n"); + INFO0("Test suite from stdin"); + handle_suite("stdin",stdin); + INFO0("Test suite from stdin OK"); } else { int i; for (i=1; ileads to segfault on amd64... - fprintf(stderr,"Test suite `%s' OK\n",argv[i]); + handle_suite(suitename,IN); + rctx_wait_bg(); + fclose(IN); //->leads to segfault on amd64... + INFO1("Test suite `%s' OK",suitename); + free(suitename); } } - - buff_free(input); - buff_free(output_wanted); - buff_free(output_got); + + rctx_exit(); + xbt_exit(); return 0; } diff --git a/tools/tesh/tesh.h b/tools/tesh/tesh.h index 2659fb7a92..badb810c69 100644 --- a/tools/tesh/tesh.h +++ b/tools/tesh/tesh.h @@ -11,6 +11,7 @@ #ifndef TESH_H #define TESH_H +#include "xbt/xbt_thread.h" /*** Buffers ***/ /***************/ #include "buff.h" @@ -21,6 +22,11 @@ segfault leads to any of them depending on the system */ const char* signal_name(unsigned int got, char *expected); +#include "run_context.h" + +/*** Options ***/ +int timeout_value; /* child timeout value */ +rctx_t rctx; #endif /* TESH_H */ -- 2.20.1