4 XBT_LOG_NEW_DEFAULT_CATEGORY(tesh,"TEst SHell utility");
7 /* Windows specific : the previous process error mode */
15 /* the current version of tesh */
19 /* the root directory */
21 root_directory = NULL;
23 /* ------------------------------------------------------------ */
25 /* ------------------------------------------------------------ */
27 /* ------------------------------------------------------------ */
29 /* ------------------------------------------------------------ */
31 /* the number of tesh files to run */
35 /* --jobs is specified with arg */
39 /* --jobs option is not specified (use the default job count) */
41 default_number_of_jobs = 1;
43 /* --jobs is specified but has no arg (one job per unit) */
45 optional_number_of_jobs = -1;
47 /* the global timeout */
49 timeout = INDEFINITE_TIMEOUT;
51 /* ------------------------------------------------------------ */
53 /* ------------------------------------------------------------ */
55 /* --C change the directory before running the units */
59 /* the include directories : see the !i metacommand */
61 include_directories = NULL;
63 /* the ddlist of tesh files to run */
74 /* the ddlist of tesh file suffixes */
78 /* ------------------------------------------------------------ */
80 /* ------------------------------------------------------------ */
82 /* if 1, keep going when some commands can't be found
83 * default value 0 : not keep going
88 /* if 1, ignore failures from commands
89 * default value : do not ignore failures
92 want_keep_going_unit = 0;
94 /* if 1, display tesh usage */
96 want_display_usage = 0;
98 /* if 1, display the tesh version */
100 want_display_version = 0;
102 /* if 1, the syntax of all tesh files is checked
103 * before running them
106 want_check_syntax = 0;
108 /* if 1, all the tesh file of the current directory
112 want_run_current_directory = 0;
114 /* if 1, the status of all the units is display at
120 /* if 1, the directories are displayed */
122 dont_want_display_directory = 0;
124 /* if 1, just check the syntax of all the tesh files :
130 /* if 1, display the tesh files syntax and exit */
132 want_display_semantic = 0;
134 /* vector of the stream of the tesh files to process */
142 want_just_display = 0;
148 display_data_base = 0;
153 /* the semaphore used to synchronize the jobs */
157 /* the semaphore used by the runner to wait the end of all the units */
168 /* the table of the entries of the options */
169 static const struct s_optentry opt_entries[] =
171 { 'C', string, (byte*)&directories, 0, "directory" },
172 { 'x', string, (byte*)&suffixes, 0, "suffix" },
173 { 'e', flag, (byte*)&env_overrides, 0, "environment-overrides", },
174 { 'f', string, (byte*)&files, 0, "file" },
175 { 'h', flag, (byte*)&want_display_usage, 0, "help" },
176 { 'a', flag, (byte*)&want_display_semantic, 0, "semantic" },
177 { 'i', flag, (byte*)&want_keep_going_unit, 0, "keep-going-unit" },
178 { 'I', string, (byte*)&include_directories, 0, "include-dir" },
179 { 'j', number, (byte*)&number_of_jobs, (byte*) &optional_number_of_jobs, "jobs" },
180 { 'k', flag, (byte*)&want_keep_going, 0, "keep-going" },
181 { 'c', flag, (byte*)&want_just_display, 0, "just-display" },
182 { 'd', flag, (byte*)&display_data_base, 0,"display-data-base" },
183 { 'q', flag, (byte*)&question, 0, "question" },
184 { 's', flag, (byte*)&want_silent, 0, "silent" },
185 { 'V', flag, (byte*)&want_display_version, 0, "version" },
186 { 'w', flag, (byte*)&dont_want_display_directory, 0,"dont-display-directory" },
187 { 'n', flag, (byte*)&want_dry_run, 0, "dry-run"},
188 { 't', number, (byte*)&timeout, 0, "timeout" },
189 { 'S', flag, (byte*)&want_check_syntax, 0, "check-syntax"},
190 { 'r', flag, (byte*)&want_run_current_directory, 0, "run-current-directory"},
191 { 'v', flag, (byte*)&want_verbose, 0, "verbose"},
192 { 'F', string,(byte*)&ignored_files, 0, "ignore-file"},
193 { 'l', string,(byte*)&log,0,"log"},
199 static const char* usage[] =
202 " -C DIRECTORY, --directory=DIRECTORY Change to DIRECTORY before running any commands.\n",
203 " -e, --environment-overrides Environment variables override files.\n",
204 " -f FILE, --file=FILE Read FILE as a teshfile.\n",
206 " all argument of the command line without\n",
207 " option is dealed as a tesh file.\n",
208 " -h, --help Display this message and exit.\n",
209 " -i, --keep-going-unit Ignore failures from commands.\n",
210 " The possible failures are :\n",
211 " - the exit code differ from the expected\n",
212 " - the signal throw differ from the expected\n",
213 " - the output differ from the expected\n",
214 " - the read pipe is broken\n",
215 " - the write pipe is broken\n",
216 " - the command assigned delay is outdated\n",
217 " -I DIRECTORY, --include-dir=DIRECTORY Search DIRECTORY for included files.\n",
218 " -j [N], --jobs[=N] Allow N commands at once; infinite commands with\n"
220 " -k, --keep-going Keep going when some commands can't be made or\n",
222 " -c, --just-display Don't actually run any commands; just display them.\n",
223 " -p, --display-data-base Display tesh's internal database.\n",
224 " -q, --question Run no commands; exit status says if up to date.\n",
225 " -s, --silent, Don't echo commands.\n",
226 " -V, --version Display the version number of tesh and exit.\n",
227 " -d, --dont-display-directory Don't display the current directory.\n",
228 " -n, --dry-run Check the syntax of the specified tesh files, display the result and exit.\n",
229 " -t, --timeout Wait the end of the commands at most timeout seconds.\n",
230 " -S, --check-syntax Check the syntax of the tesh files before run them. \n",
231 " -x, --suffix Consider the new suffix for the tesh files.\n"
233 " the default suffix for the tesh files is \".tesh\".\n",
234 " -a, --semantic Display the tesh file metacommands syntax and exit.\n",
235 " -b, --build-file Build a tesh file.\n",
236 " -r, --run-current-directory Run all the tesh files located in the current directory.\n",
237 " -v, --verbose Display the status of the commands.\n",
238 " -F file , --ignore-file=FILE Ignore the tesh file FILE.\n",
239 " -l format, --log Format of the xbt log.\n",
243 /* the string of options of tesh */
245 optstring[1 + sizeof (opt_entries) / sizeof (opt_entries[0]) * 3];
247 /* the option table of tesh */
249 option longopts[(sizeof (opt_entries) / sizeof (s_optentry_t))];
255 process_command_line(int argc, char** argv);
261 display_usage(int exit_code);
264 display_version(void);
270 is_valid_file(const char* file_name);
273 display_semantic(void);
276 main(int argc, char* argv[])
279 /* Windows specific : don't display the general-protection-fault message box and
280 * the the critical-error-handler message box (instead the system send the error
281 * to the calling process : tesh)
283 prev_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
288 /* process the command line */
289 if(0 != (exit_code = process_command_line(argc, argv)))
292 /* initialize the xbt library
293 * for thread portability layer
297 xbt_init(&(log->number), log->items);
299 xbt_init(&argc, argv);
301 /* the user wants to display the usage of tesh */
302 if(want_display_usage)
305 /* the user wants to display the version of tesh */
306 if(want_display_version)
312 /* the user wants to display the semantic of the tesh file metacommands */
313 if(want_display_semantic)
320 if(0 != (exit_code = prepare()))
325 if(-2 == number_of_jobs)
326 {/* --jobs is not specified (use the default value) */
327 number_of_jobs = default_number_of_jobs;
329 else if(optional_number_of_jobs == number_of_jobs)
330 {/* --jobs option is specified with no args (use one job per unit) */
331 number_of_jobs = lstrings_get_size(files);
334 if(number_of_jobs > lstrings_get_size(files))
335 {/* --jobs = N is specified and N is more than the number of tesh files */
337 WARN0("number of requested jobs exceed the number of files");
339 /* assume one job per file */
340 number_of_jobs = lstrings_get_size(files);
343 /* initialize the semaphore used to synchronize the jobs */
344 jobs_sem = xbt_os_sem_init(number_of_jobs);
346 /* initialize the semaphore used by the runner to wait for the end of all units */
347 units_sem = xbt_os_sem_init(0);
350 /* initialize the runner */
351 if((0 != (exit_code = runner_init(
361 if(want_just_display && want_silent)
364 if(want_just_display && want_dry_run)
365 WARN0("mismatch in the syntax : --just-check-syntax and --just-display options at same time");
367 /* run all the units */
370 /* show the result of the units */
371 if(want_verbose || want_dry_run)
372 runner_display_status();
375 /* all the test are runned, destroy the runner */
378 /* then, finalize tesh */
390 struct dirent* entry ={0};
393 /* get the current directory and save it */
394 if(NULL == (root_directory = getcwd(NULL, 0)))
396 ERROR1("system error - %d : getcwd() function failed",errno);
397 return E_GETCWD_FAILED;
399 else if(!dont_want_display_directory)
400 INFO1("entering directory \"%s\"",root_directory);
402 /* first, if the option --directory is specified change
409 /* assume that the current directory and the new directory are not different */
412 /* if length the current directory and the directory specified in the command line
413 * are not equal the to directories are different.
415 if(strlen(root_directory) != strlen(directories->items[0]))
418 /* if the two directories have the same length
423 for(i = 0; i < strlen(root_directory); i++)
425 if(toupper(root_directory[i]) != toupper((directories->items[0])[i]))
433 /* the directory specified in the command line is the current directory
434 * if the want_warn_on_mismatch_syntax flag is set to 1, warn the user and
439 WARN1("already in the directory %s",root_directory);
443 /* the directory specified in the command line is not the current directory
444 * change the current directory
446 if(-1 == chdir(directories->items[0]))
450 ERROR1("the directory %s does not exist", directories->items[0]);
451 return E_DIR_DOES_NOT_EXIST;
455 ERROR1("system error - errno : %d : chdir() failed", errno);
456 return E_CHDIR_FAILED;
460 /* if the want_display_directory flag is set to 1, display the change */
461 if(!dont_want_display_directory)
462 INFO1("entering directory \"%s\"",directories->items[0]);
466 /* if the --run-current-directory flag is specified laod all the tesh file contained in the current directory
467 * the tesh files having the default suffix ".tesh"
468 * the tesh files having a suffix specified by an option --suffix if any
469 * remark if the tesh file of the current directory is specified in the command line, it is not laoded a second
470 * time and if the flag --warn-mismatch-syntax is specified a warning is displayed.
472 if(want_run_current_directory)
475 /* try to find the default tesh file teshfile or the file having the default suffix ".tesh" or a suffix
476 * specified in the command line with the option -x in the current directory
478 dir = opendir(getcwd(NULL, 0));
482 while(NULL != (entry = readdir(dir)))
484 /* there is a teshfile in the current directory add it in the ddlist of tesh files to process */
485 if(is_valid_file(entry->d_name))
486 lstrings_push_back(files, entry->d_name);
492 /* if no tesh files are specified on the command line and if the option --run-current-directory
493 * is specified and that the current directory does not contain any file, try to load the default
494 * tesh file named teshfile.
496 if(lstrings_is_empty(files))
500 stream = fopen("teshfile","r");
504 streams = xbt_new0(FILE*, 1);
507 lstrings_push_back(files, "teshfile");
511 /* no tesh file and can't locate the default tesh file teshfile */
513 /*ERROR0("no tesh file specified and no teshfile found");
514 return E_NO_TESH_FILE;*/
518 streams = xbt_new0(FILE*, 1);
520 lstrings_push_back(files, "stdin");
525 /* the user has specified some tesh files or the option --run-current-directory is specified */
528 streams = xbt_new0(FILE*, files->number);
530 for(i = 0; i < files->number; i++)
532 streams[i] = fopen(files->items[i], "r");
534 /* cannot open the tesh files, display the error and exit */
537 perror(bprintf("tesh file `%s' not found",files->items[i]));
538 ERROR1("tesh file `%s': not found",files->items[i]);
539 return E_TESH_FILE_NOT_FOUND;
544 number_of_files = lstrings_get_size(files);
546 /* deal with the ignored tesh files now */
549 int i, j, ignored, exists;
551 for(j = 0; j < ignored_files->number; j++)
555 for(i = 0; i < files->number; i++)
557 if(!strcmp(files->items[i], ignored_files->items[j]))
565 WARN1("the file %s cannot be ignored",ignored_files->items[j]);
568 for(i = 0; i < files->number; i++)
572 for(j = 0; j < ignored_files->number; j++)
574 if(!strcmp(files->items[i],ignored_files->items[j]))
592 /*ERROR0("no tesh file to run");
593 return E_NO_TESH_FILE;*/
597 streams = xbt_new0(FILE*, 1);
599 files = (strings_t)malloc (sizeof (s_strings_t));
601 files->items = (char **) malloc (1 * sizeof (char *));
604 files->items[0] = strdup("stdin");
609 chdir(root_directory);
615 is_valid_file(const char* file_name)
619 /* test if the file is specified in the command line */
622 for(j = 0; j < files->number; j++)
624 if(!strcmp(files->items[j], file_name))
626 WARN1("the file %s specified in the command line is in the current directory",file_name);
632 if(!strncmp(".tesh", file_name + (strlen(file_name) - 5), 5))
639 for(i = 0; i < suffixes->number; i++)
641 if(!strncmp(suffixes->items[i], file_name + (strlen(file_name) - strlen(suffixes->items[i])), strlen(suffixes->items[i])))
653 /* close all file streams and free the stream allocated table */
658 for(i = 0; i < files->number; i++)
659 if(NULL != streams[i])
665 /* release the files allocated memory */
670 for(i = 0; i < files->number; i++)
671 free(files->items[i]);
681 for(i = 0; i < ignored_files->number; i++)
682 free(ignored_files->items[i]);
684 free(ignored_files->items);
688 /* free the root directory */
690 free(root_directory);
697 if(((0 == exit_code) && want_display_usage) || ((0 != exit_code) && !prepared))
698 display_usage(exit_code);
700 /* close the openned tesh files */
703 /* clenup the directory string list */
708 for(i = 0; i < directories->number; i++)
709 free(directories->items[i]);
711 free(directories->items);
715 /* clenup the include directory string list */
716 if(include_directories)
720 for(i = 0; i < include_directories->number; i++)
721 free(include_directories->items[i]);
723 free(include_directories->items);
724 free(include_directories);
727 /* clenup the suffix string ddlist */
732 for(i = 0; i < suffixes->number; i++)
733 free(suffixes->items[i]);
735 free(suffixes->items);
739 /* clenup the log string ddlist */
744 for(i = 0; i < log->number; i++)
751 /* destroy the semaphore used to synchronize the jobs */
753 xbt_os_sem_destroy(jobs_sem);
755 /* destroy the semaphore used by the runner to wait for
756 * the end of all the units
758 if(NULL != units_sem)
759 xbt_os_sem_destroy(units_sem);
763 /* exit from the xbt framework */
767 SetErrorMode(prev_error_mode);
770 if(!want_verbose && !want_dry_run && !want_silent && !want_just_display)
771 INFO1("tesh terminated with exit code %d",exit_code);
782 if(optstring[0] != '\0')
788 /* Return switch and non-switch args in order, regardless of
789 POSIXLY_CORRECT. Non-switch args are returned as option 1. */
791 /* le premier caractère de la chaîne d'options vaut -.
792 * les arguments ne correspondant pas à une option sont
793 * manipulés comme s'ils étaient des arguments d'une option
794 * dont le caractère est le caractère de code 1
798 for (i = 0; opt_entries[i].c != '\0'; ++i)
800 /* initialize le nom de l'option longue*/
801 longopts[i].name = (opt_entries[i].long_name == 0 ? "" : opt_entries[i].long_name);
803 /* getopt_long() retourne la valeur de val */
804 longopts[i].flag = 0;
806 /* la valeur de l'option courte est le caractère spécifié dans opt_entries[i].c */
807 longopts[i].val = opt_entries[i].c;
809 /* on l'ajoute à la chaine des optstring */
810 *p++ = opt_entries[i].c;
812 switch (opt_entries[i].type)
814 /* si c'est une option qui sert a positionner un flag ou que l'on doit ignorée, elle n'a pas d'argument */
816 longopts[i].has_arg = no_argument;
819 /* c'est une option qui attent un argument :
820 * une chaine de caractères, un nombre flottant,
821 * ou un entier positif
828 if(opt_entries[i].optional_value != 0)
832 longopts[i].has_arg = optional_argument;
835 longopts[i].has_arg = required_argument;
842 longopts[i].name = 0;
846 process_command_line(int argc, char** argv)
848 register const struct s_optentry* entry;
849 register strings_t v;
852 /* initialize the options table of tesh */
855 /* display the errors of the function getopt_long() */
860 while (optind < argc)
862 c = getopt_long (argc, argv, optstring, longopts, (int *) 0);
866 /* end of the command line or "--". */
871 /* the argument of the command line is not an option (no "-"), assume it's a tesh file */
875 files = (strings_t)malloc (sizeof (s_strings_t));
876 files->capacity = DEFAULT_CAPACITY;
878 files->items = (char **) malloc (DEFAULT_CAPACITY * sizeof (char *));
880 else if (files->number == files->capacity - 1)
882 files->capacity += DEFAULT_CAPACITY;
883 files->items = (char **)realloc ((char *) files->items,files->capacity * sizeof (char *));
886 /* special case of suffix : add a point at the beginning */
887 files->items[files->number++] = strdup(optarg);
888 files->items[files->number] = NULL;
893 /* unknown option, getopt_long() displays the error */
898 for (entry = opt_entries; entry->c != '\0'; ++entry)
907 ERROR0("\ninternal error - process_command_line() function failed");
908 return E_PROCESS_COMMAND_LINE_FAILED;
912 *(int *) entry->value = 1;
920 /* an option with a optional arg is specified use the entry->optional_value */
921 optarg = (char*)entry->optional_value;
923 else if (*optarg == '\0')
925 /* a non optional argument is not specified */
926 ERROR2("the option %c \"%s\"requires an argument",entry->c,entry->long_name);
927 return E_ARG_NOT_SPECIFIED;
930 v = *(strings_t*) entry->value;
934 v = (strings_t)malloc (sizeof (s_strings_t));
935 v->capacity = DEFAULT_CAPACITY;
937 v->items = (char **) malloc (DEFAULT_CAPACITY * sizeof (char *));
938 *(struct s_strings **) entry->value = v;
940 else if (v->number == v->capacity - 1)
942 v->capacity += DEFAULT_CAPACITY;
943 v->items = (char **)realloc ((char *) v->items,v->capacity * sizeof (char *));
946 /* special case of suffix : add a point at the beginning */
947 if(c == 'x' && optarg[0])
949 char buffer[MAX_PATH + 1] = {0};
950 sprintf(buffer,".%s",optarg);
951 v->items[v->number++] = strdup(buffer);
954 v->items[v->number++] = strdup(optarg);
956 v->items[v->number] = NULL;
962 if ((NULL == optarg) && (argc > optind))
966 for (cp = argv[optind]; isdigit(cp[0]); ++cp)
968 optarg = argv[optind++];
971 /* the number option is specified and has no arg */
974 int i = atoi(optarg);
977 for (cp = optarg; isdigit(cp[0]); ++cp);
979 if (i < 1 || cp[0] != '\0')
981 ERROR2("\nthe option %c \"%s\" requires an strictly positive integer as argument",entry->c, entry->long_name);
982 return E_NOT_POSITIVE_NUMBER;
985 *(int*)entry->value = i;
987 /* the number option is specified but has no arg, use the optional value*/
989 *(int*)entry->value = *(int*) entry->optional_value;
1003 display_usage(int exit_code)
1008 if (want_display_version)
1011 stream = exit_code ? stderr : stdout;
1013 fprintf (stream, "Usage: tesh [options] [file] ...\n");
1015 for (cpp = usage; *cpp; ++cpp)
1016 fputs (*cpp, stream);
1018 fprintf(stream, "\nReport bugs to <martin.quinson@loria.fr | malek.cherier@loria.fr>\n");
1022 display_version(void)
1024 /* TODO : display the version of tesh */
1025 printf("Version :\n");
1026 printf(" tesh version %s : Mini shell specialized in running test units by Martin Quinson \n", version);
1027 printf(" and Malek Cherier\n");
1028 printf(" Copyright (c) 2007, 2008 Martin Quinson, Malek Cherier\n");
1029 printf(" All rights reserved\n");
1030 printf(" This program is free software; you can redistribute it and/or modify it\n");
1031 printf(" under the terms of the license (GNU LGPL) which comes with this package.\n\n");
1033 if(!want_display_usage)
1034 printf("Report bugs to <martin.quinson@loria.fr | malek.cherier@loria.fr>");
1038 display_semantic(void)
1043 FILE* stream = fopen("README.txt", "r");
1047 ERROR0("Unable to locate the README.txt file");
1048 exit_code = E_README_NOT_FOUND;
1052 while(getline(&line, &len, stream) != -1)