6 #include <directories.h>
14 * entry used to define the parameter of a tesh option.
16 typedef struct s_optentry
18 int c; /* the character of the option */
21 * the type of the argument of the option
25 flag, /* it's a flag option, by default the flag is set to zero */
26 string, /* the option has strings as argument */
27 number /* the option has an integral positive number as argument */
30 byte* value; /* the value of the option */
31 byte* optional_value; /* the optional value of the option if not specified */
32 const char * long_name; /* the long name of the command */
33 }s_optentry_t,* optentry_t;
37 XBT_LOG_NEW_DEFAULT_CATEGORY(tesh,"TEst SHell utility");
40 /* Windows specific : the previous process error mode */
46 root_directory = NULL;
51 /* the current version of tesh */
55 /* ------------------------------------------------------------ */
57 /* ------------------------------------------------------------ */
59 /* ------------------------------------------------------------ */
61 /* ------------------------------------------------------------ */
64 /* --jobs is specified with arg */
68 /* --jobs option is not specified (use the default job count) */
70 default_number_of_jobs = 1;
72 /* --jobs is specified but has no arg (one job per unit) */
74 optional_number_of_jobs = -1;
76 /* the global timeout */
80 /* ------------------------------------------------------------ */
82 /* ------------------------------------------------------------ */
84 /* --C change the directory before running the units */
88 /* the include directories : see the !i metacommand */
92 /* the list of tesh files to run */
103 /* the ddlist of tesh file suffixes */
107 /* ------------------------------------------------------------ */
109 /* ------------------------------------------------------------ */
111 /* if 1, keep going when some commands can't be found
112 * default value 0 : not keep going
117 /* if 1, ignore failures from commands
118 * default value : do not ignore failures
121 want_keep_going_unit = 0;
123 /* if 1, display tesh usage */
125 want_display_usage = 0;
127 /* if 1, display the tesh version */
129 want_display_version = 0;
131 /* if 1, the syntax of all tesh files is checked
132 * before running them
135 want_check_syntax = 0;
137 /* if 1, all the tesh file of the current directory
141 want_load_directory = 0;
143 /* if 1, the status of all the units is display at
149 /* if 1, the directories are displayed */
151 dont_want_display_directory = 0;
153 /* if 1, just check the syntax of all the tesh files
159 /* if 1, display the tesh files syntax and exit */
161 want_display_semantic = 0;
167 want_just_display = 0;
173 display_data_base = 0;
178 /* the semaphore used to synchronize the jobs */
182 /* the semaphore used by the runner to wait the end of all the units */
193 /* the table of the entries of the options */
194 static const struct s_optentry opt_entries[] =
196 { 'C', string, (byte*)&directories, 0, "directory" },
197 { 'x', string, (byte*)&suffixes, 0, "suffix" },
198 { 'e', flag, (byte*)&env_overrides, 0, "environment-overrides", },
199 { 'f', string, (byte*)&fstreams, 0, "file" },
200 { 'h', flag, (byte*)&want_display_usage, 0, "help" },
201 { 'a', flag, (byte*)&want_display_semantic, 0, "semantic" },
202 { 'i', flag, (byte*)&want_keep_going_unit, 0, "keep-going-unit" },
203 { 'I', string, (byte*)&includes, 0, "include-dir" },
204 { 'j', number, (byte*)&number_of_jobs, (byte*) &optional_number_of_jobs, "jobs" },
205 { 'k', flag, (byte*)&want_keep_going, 0, "keep-going" },
206 { 'c', flag, (byte*)&want_just_display, 0, "just-display" },
207 { 'd', flag, (byte*)&display_data_base, 0,"display-data-base" },
208 { 'q', flag, (byte*)&question, 0, "question" },
209 { 's', flag, (byte*)&want_silent, 0, "silent" },
210 { 'V', flag, (byte*)&want_display_version, 0, "version" },
211 { 'w', flag, (byte*)&dont_want_display_directory, 0,"dont-display-directory" },
212 { 'n', flag, (byte*)&want_dry_run, 0, "dry-run"},
213 { 't', number, (byte*)&timeout, 0, "timeout" },
214 { 'S', flag, (byte*)&want_check_syntax, 0, "check-syntax"},
215 { 'r', flag, (byte*)&want_load_directory, 0, "load-directory"},
216 { 'v', flag, (byte*)&want_verbose, 0, "verbose"},
217 { 'F', string,(byte*)&excludes, 0, "exclude"},
218 { 'l', string,(byte*)&logs,0,"log"},
224 static const char* usage[] =
227 " -C DIRECTORY, --directory=DIRECTORY Change to DIRECTORY before running any commands.\n",
228 " -e, --environment-overrides Environment variables override files.\n",
229 " -f FILE, --file=FILE Read FILE as a teshfile.\n",
231 " all argument of the command line without\n",
232 " option is dealed as a tesh file.\n",
233 " -h, --help Display this message and exit.\n",
234 " -i, --keep-going-unit Ignore failures from commands.\n",
235 " The possible failures are :\n",
236 " - the exit code differ from the expected\n",
237 " - the signal throw differ from the expected\n",
238 " - the output differ from the expected\n",
239 " - the read pipe is broken\n",
240 " - the write pipe is broken\n",
241 " - the command assigned delay is outdated\n",
242 " -I DIRECTORY, --include-dir=DIRECTORY Search DIRECTORY for included files.\n",
243 " -j [N], --jobs[=N] Allow N commands at once; infinite commands with\n"
245 " -k, --keep-going Keep going when some commands can't be made or\n",
247 " -c, --just-display Don't actually run any commands; just display them.\n",
248 " -p, --display-data-base Display tesh's internal database.\n",
249 " -q, --question Run no commands; exit status says if up to date.\n",
250 " -s, --silent, Don't echo commands.\n",
251 " -V, --version Display the version number of tesh and exit.\n",
252 " -d, --dont-display-directory Don't display the current directory.\n",
253 " -n, --dry-run Check the syntax of the specified tesh files, display the result and exit.\n",
254 " -t, --timeout Wait the end of the commands at most timeout seconds.\n",
255 " -S, --check-syntax Check the syntax of the tesh files before run them. \n",
256 " -x, --suffix Consider the new suffix for the tesh files.\n"
258 " the default suffix for the tesh files is \".tesh\".\n",
259 " -a, --semantic Display the tesh file metacommands syntax and exit.\n",
260 " -b, --build-file Build a tesh file.\n",
261 " -r, --load-directory Run all the tesh files located in the directories specified by the option --directory.\n",
262 " -v, --verbose Display the status of the commands.\n",
263 " -F file , --exclude=FILE Ignore the tesh file FILE.\n",
264 " -l format, --log Format of the xbt logs.\n",
268 /* the string of options of tesh */
270 optstring[1 + sizeof (opt_entries) / sizeof (opt_entries[0]) * 3];
272 /* the option table of tesh */
274 option longopts[(sizeof (opt_entries) / sizeof (s_optentry_t))];
280 process_command_line(int argc, char** argv);
286 display_usage(int exit_code);
289 display_version(void);
295 display_semantic(void);
303 main(int argc, char* argv[])
307 /* process the command line */
308 if((exit_code = process_command_line(argc, argv)))
311 /* initialize the xbt library
312 * for thread portability layer
315 if(!lstrings_is_empty(logs))
317 int size = lstrings_get_size(logs);
318 char** cstr = lstrings_to_cstr(logs);
320 xbt_init(&size, cstr);
326 xbt_init(&argc, argv);
328 /* the user wants to display the usage of tesh */
329 if(want_display_usage)
332 /* the user wants to display the version of tesh */
333 if(want_display_version)
339 /* the user wants to display the semantic of the tesh file metacommands */
340 if(want_display_semantic)
346 if(!directories_has_directories_to_load(directories) && want_load_directory)
347 WARN0("--load-directory specified but no directory specified");
349 excludes_check(excludes, fstreams);
352 if((exit_code = load()))
357 if(-2 == number_of_jobs)
358 {/* --jobs is not specified (use the default value) */
359 number_of_jobs = default_number_of_jobs;
361 else if(optional_number_of_jobs == number_of_jobs)
362 {/* --jobs option is specified with no args (use one job per unit) */
363 number_of_jobs = fstreams_get_size(fstreams);
366 if(number_of_jobs > fstreams_get_size(fstreams))
367 {/* --jobs = N is specified and N is more than the number of tesh files */
369 WARN0("number of requested jobs exceed the number of files");
371 /* assume one job per file */
372 number_of_jobs = fstreams_get_size(fstreams);
375 /* initialize the semaphore used to synchronize the jobs */
376 jobs_sem = xbt_os_sem_init(number_of_jobs);
378 /* initialize the semaphore used by the runner to wait for the end of all units */
379 units_sem = xbt_os_sem_init(0);
381 /* initialize the runner */
382 if((0 != (exit_code = runner_init(
390 if(want_just_display && want_silent)
393 if(want_just_display && want_dry_run)
394 WARN0("mismatch in the syntax : --just-check-syntax and --just-display options at same time");
396 /* run all the units */
399 /* show the result of the units */
400 if(want_verbose || want_dry_run)
401 runner_display_status();
404 /* all the test are runned, destroy the runner */
407 /* then, finalize tesh */
419 char* buffer = getcwd(NULL, 0);
422 /* Windows specific : don't display the general-protection-fault message box and
423 * the the critical-error-handler message box (instead the system send the error
424 * to the calling process : tesh)
426 prev_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
429 /* used to store the file streams to run */
430 fstreams = fstreams_new(DEFAULT_FSTREAMS_CAPACITY, fstream_free);
432 root_directory = directory_new(buffer,want_load_directory);
434 /* used to store the directories to loads */
435 directories = directories_new();
437 /* register the current directory */
438 directories_add(directories, root_directory);
440 /* used to store the includes directories */
441 includes = vector_new(DEFAULT_INCLUDES_CAPACITY, directory_free);
444 logs = lstrings_new();
446 /* used to to store all the excluded file streams */
447 excludes = excludes_new();
449 /* list of file streams suffixes */
450 suffixes = lstrings_new();
452 lstrings_push_back(suffixes,".tesh");
460 chdir(directory_get_name(root_directory));
462 if(want_load_directory)
463 directories_load(directories, fstreams, suffixes);
465 /* on a aucun fichier specifie dans la ligne de commande
466 * l'option --run-current-directory n'a pas ete specifie ou aucun fichier ne se trouve dans le repertoire a charger
468 if(fstreams_is_empty(fstreams))
470 struct stat buffer = {0};
472 /* add the default tesh file if it exists */
473 if(!stat("teshfile", &buffer) && S_ISREG(buffer.st_mode))
474 fstreams_add(fstreams, fstream_new(getcwd(NULL, 0), "teshfile"));
477 if(!excludes_is_empty(excludes) && !fstreams_is_empty(fstreams))
478 fstreams_exclude(fstreams, excludes);
480 if(fstreams_is_empty(fstreams))
481 fstreams_add(fstreams, fstream_new(NULL, "stdin"));
483 fstreams_load(fstreams);
491 if((!exit_code && want_display_usage) || (!exit_code && !prepared))
492 display_usage(exit_code);
495 fstreams_free((void**)&fstreams);
498 excludes_free((void**)&excludes);
501 directories_free((void**)&directories);
504 vector_free(&includes);
507 lstrings_free(&suffixes);
510 lstrings_free(&logs);
512 /* destroy the semaphore used to synchronize the jobs */
514 xbt_os_sem_destroy(jobs_sem);
517 xbt_os_sem_destroy(units_sem);
519 /* exit from the xbt framework */
523 SetErrorMode(prev_error_mode);
526 if(!want_verbose && !want_dry_run && !want_silent && !want_just_display)
527 INFO2("tesh terminated with exit code %d : %s",exit_code, (!exit_code ? "success" : error_to_string(exit_code)));
538 if(optstring[0] != '\0')
544 /* Return switch and non-switch args in order, regardless of
545 POSIXLY_CORRECT. Non-switch args are returned as option 1. */
547 /* le premier caractère de la chaîne d'options vaut -.
548 * les arguments ne correspondant pas à une option sont
549 * manipulés comme s'ils étaient des arguments d'une option
550 * dont le caractère est le caractère de code 1
554 for (i = 0; opt_entries[i].c != '\0'; ++i)
556 /* initialize le nom de l'option longue*/
557 longopts[i].name = (opt_entries[i].long_name == 0 ? "" : opt_entries[i].long_name);
559 /* getopt_long() retourne la valeur de val */
560 longopts[i].flag = 0;
562 /* la valeur de l'option courte est le caractère spécifié dans opt_entries[i].c */
563 longopts[i].val = opt_entries[i].c;
565 /* on l'ajoute à la chaine des optstring */
566 *p++ = opt_entries[i].c;
568 switch (opt_entries[i].type)
570 /* si c'est une option qui sert a positionner un flag ou que l'on doit ignorée, elle n'a pas d'argument */
572 longopts[i].has_arg = no_argument;
575 /* c'est une option qui attent un argument :
576 * une chaine de caractères, un nombre flottant,
577 * ou un entier positif
584 if(opt_entries[i].optional_value != 0)
588 longopts[i].has_arg = optional_argument;
591 longopts[i].has_arg = required_argument;
598 longopts[i].name = 0;
602 process_command_line(int argc, char** argv)
604 register const struct s_optentry* entry;
606 directory_t directory;
609 /* initialize the options table of tesh */
612 /* display the errors of the function getopt_long() */
617 while (optind < argc)
619 c = getopt_long (argc, argv, optstring, longopts, (int *) 0);
623 /* end of the command line or "--". */
628 /* the argument of the command line is not an option (no "-"), assume it's a tesh file */
629 /*struct stat buffer = {0};
630 char* prev = getcwd(NULL, 0);
632 directory = directories_get_back(directories);
634 chdir(directory->name);
636 if(stat(optarg, &buffer) || !S_ISREG(buffer.st_mode))
640 ERROR1("file %s not found", optarg);
641 return EFILENOTFOUND;
647 directory = directories_search_fstream_directory(directories, optarg);
651 if(1 == directories_get_size(directories))
653 ERROR1("file %s not found in the current directory",optarg);
654 return EFILENOTINCURDIR;
658 ERROR1("file %s not found in the specified directories",optarg);
659 return EFILENOTINSPECDIR;
663 if(!(fstream = fstream_new(directory_get_name(directory), optarg)))
665 ERROR1("command line processing failed with the error code %d", errno);
666 return EPROCESSCMDLINE;
670 if(fstreams_contains(fstreams, fstream))
672 fstream_free((void**)&fstream);
673 WARN1("file %s already specified", optarg);
677 if((errno = fstreams_add(fstreams, fstream)))
679 fstream_free((void**)&fstream);
680 ERROR1("command line processing failed with the error code %d", errno);
681 return EPROCESSCMDLINE;
688 /* unknown option, getopt_long() displays the error */
693 for (entry = opt_entries; entry->c != '\0'; ++entry)
702 ERROR0("command line processing failed : internal error");
703 return EPROCESSCMDLINE;
708 /* set the flag to one */
709 *(int*) entry->value = 1;
718 /* an option with a optional arg is specified use the entry->optional_value */
719 optarg = (char*)entry->optional_value;
721 else if (*optarg == '\0')
723 /* a non optional argument is not specified */
724 ERROR2("the option %c \"%s\"requires an argument",entry->c,entry->long_name);
728 /* --directory option */
729 if(!strcmp(entry->long_name,"directory"))
731 if(!(directory = directory_new(optarg, want_load_directory)))
735 ERROR1("directory %s not found",optarg);
740 ERROR1("command line processing failed with the error code %d", errno);
741 return EPROCESSCMDLINE;
746 if(directories_contains(directories, directory))
748 directory_free((void**)&directory);
749 WARN1("directory %s already specified",optarg);
753 if((errno = directories_add(directories, directory)))
755 directory_free((void**)&directory);
756 ERROR1("command line processing failed with the error code %d", errno);
757 return EPROCESSCMDLINE;
762 /* --suffix option */
763 else if(!strcmp(entry->long_name,"suffix"))
765 if(strlen(optarg) > MAX_SUFFIX)
767 ERROR1("suffix %s too long",optarg);
768 return ESUFFIXTOOLONG;
773 char suffix[MAX_SUFFIX + 2] = {0};
774 sprintf(suffix,".%s",optarg);
776 if(lstrings_contains(suffixes, suffix))
777 WARN1("suffix %s already specified", optarg);
779 lstrings_push_back(suffixes, suffix);
783 if(lstrings_contains(suffixes, optarg))
784 WARN1("suffix %s already specified", optarg);
786 lstrings_push_back(suffixes, optarg);
790 else if(!strcmp(entry->long_name,"file"))
793 /* the argument of the command line is not an option (no "-"), assume it's a tesh file */
794 /*struct stat buffer = {0};
795 char* prev = getcwd(NULL, 0);
797 directory = directories_get_back(directories);
799 chdir(directory->name);
801 if(stat(optarg, &buffer) || !S_ISREG(buffer.st_mode))
805 ERROR1("file %s not found", optarg);
806 return EFILENOTFOUND;
812 directory = directories_search_fstream_directory(directories, optarg);
816 if(1 == directories_get_size(directories))
818 ERROR1("file %s not found in the current directory",optarg);
819 return EFILENOTINCURDIR;
823 ERROR1("file %s not found in the specified directories",optarg);
824 return EFILENOTINSPECDIR;
828 if(!(fstream = fstream_new(directory_get_name(directory),optarg)))
830 ERROR1("command line processing failed with the error code %d", errno);
831 return EPROCESSCMDLINE;
835 if(fstreams_contains(fstreams, fstream))
837 fstream_free((void**)&fstream);
838 WARN1("file %s already specified", optarg);
842 if((errno = fstreams_add(fstreams, fstream)))
844 fstream_free((void**)&fstream);
845 ERROR1("command line processing failed with the error code %d", errno);
846 return EPROCESSCMDLINE;
851 /* --include-dir option */
852 else if(!strcmp(entry->long_name,"include-dir"))
854 if(!(directory = directory_new(optarg, want_load_directory)))
858 ERROR1("%s is not a directory",optarg);
863 ERROR1("command line processing failed with the error code %d", errno);
864 return EPROCESSCMDLINE;
869 if(vector_contains(includes, directory))
871 directory_free((void**)&directory);
872 WARN1("include directory %s already specified",optarg);
877 if((errno = vector_push_back(includes, directory)))
879 directory_free((void**)&directory);
880 ERROR1("command line processing failed with the error code %d", errno);
881 return EPROCESSCMDLINE;
886 /* --exclude option */
887 else if(!strcmp(entry->long_name,"exclude"))
889 directory = directories_get_back(directories);
891 if(!(fstream = fstream_new(directory_get_name(directory), optarg)))
895 ERROR1("file to exclude %s not found", optarg);
896 return EFILENOTFOUND;
900 ERROR1("command line processing failed with the error code %d", errno);
901 return EPROCESSCMDLINE;
906 if(excludes_contains(excludes, fstream))
908 fstream_free((void**)&fstream);
909 WARN1("file to exclude %s already specified", optarg);
913 if((errno = excludes_add(excludes, fstream)))
915 fstream_free((void**)&fstream);
916 ERROR1("command line processing failed with the error code %d", errno);
917 return EPROCESSCMDLINE;
923 else if(!strcmp(entry->long_name,"log"))
925 lstrings_push_back(logs, optarg);
935 /* strictly positve number options */
938 if ((NULL == optarg) && (argc > optind))
942 for (cp = argv[optind]; isdigit(cp[0]); ++cp)
944 optarg = argv[optind++];
947 /* the number option is specified */
950 int i = atoi(optarg);
953 for (cp = optarg; isdigit(cp[0]); ++cp);
955 if (i < 1 || cp[0] != '\0')
957 ERROR2("option %c \"%s\" requires an strictly positive integer as argument",entry->c, entry->long_name);
958 return ENOTPOSITIVENUM;
961 *(int*)entry->value = i;
963 /* the number option is specified but has no arg, use the optional value*/
965 *(int*)entry->value = *(int*) entry->optional_value;
979 display_usage(int exit_code)
984 if (want_display_version)
987 stream = exit_code ? stderr : stdout;
989 fprintf (stream, "Usage: tesh [options] [file] ...\n");
991 for (cpp = usage; *cpp; ++cpp)
992 fputs (*cpp, stream);
994 fprintf(stream, "\nReport bugs to <martin.quinson@loria.fr | malek.cherier@loria.fr>\n");
998 display_version(void)
1000 /* TODO : display the version of tesh */
1001 printf("Version :\n");
1002 printf(" tesh version %s : Mini shell specialized in running test units by Martin Quinson \n", version);
1003 printf(" and Malek Cherier\n");
1004 printf(" Copyright (c) 2007, 2008 Martin Quinson, Malek Cherier\n");
1005 printf(" All rights reserved\n");
1006 printf(" This program is free software; you can redistribute it and/or modify it\n");
1007 printf(" under the terms of the license (GNU LGPL) which comes with this package.\n\n");
1009 if(!want_display_usage)
1010 printf("Report bugs to <martin.quinson@loria.fr | malek.cherier@loria.fr>");
1014 display_semantic(void)
1019 FILE* stream = fopen("README.txt", "r");
1023 ERROR0("Unable to locate the README.txt file");
1024 exit_code = EREADMENOTFOUND;
1028 while(getline(&line, &len, stream) != -1)