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;
53 want_detail_summary = 0;
55 /* the current version of tesh */
59 /* ------------------------------------------------------------ */
61 /* ------------------------------------------------------------ */
63 /* ------------------------------------------------------------ */
65 /* ------------------------------------------------------------ */
68 /* --jobs is specified with arg */
72 /* --jobs option is not specified (use the default job count) */
74 default_number_of_jobs = 1;
76 /* --jobs is specified but has no arg (one job per unit) */
78 optional_number_of_jobs = -1;
80 /* the global timeout */
84 /* ------------------------------------------------------------ */
86 /* ------------------------------------------------------------ */
88 /* --C change the directory before running the units */
92 /* the include directories : see the !i metacommand */
96 /* the list of tesh files to run */
107 /* the ddlist of tesh file suffixes */
111 /* ------------------------------------------------------------ */
113 /* ------------------------------------------------------------ */
115 /* if 1, keep going when some commands can't be found
116 * default value 0 : not keep going
121 /* if 1, ignore failures from commands
122 * default value : do not ignore failures
125 want_keep_going_unit = 0;
127 /* if 1, display tesh usage */
129 want_display_usage = 0;
131 /* if 1, display the tesh version */
133 want_display_version = 0;
135 /* if 1, the syntax of all tesh files is checked
136 * before running them
139 want_check_syntax = 0;
141 /* if 1, the status of all the units is display at
147 /* if 1, the directories are displayed */
149 dont_want_display_directory = 0;
151 /* if 1, just check the syntax of all the tesh files
157 /* if 1, display the tesh files syntax and exit */
159 want_display_semantic = 0;
165 want_just_display = 0;
171 display_data_base = 0;
176 /* the semaphore used to synchronize the jobs */
180 /* the semaphore used by the runner to wait the end of all the units */
196 /* the table of the entries of the options */
197 static const struct s_optentry opt_entries[] =
199 { 'C', string, (byte*)NULL, 0, "directory" },
200 { 'x', string, (byte*)&suffixes, 0, "suffix" },
201 { 'e', flag, (byte*)&env_overrides, 0, "environment-overrides", },
202 { 'f', string, (byte*)&fstreams, 0, "file" },
203 { 'h', flag, (byte*)&want_display_usage, 0, "help" },
204 { 'a', flag, (byte*)&want_display_semantic, 0, "semantic" },
205 { 'i', flag, (byte*)&want_keep_going_unit, 0, "keep-going-unit" },
206 { 'I', string, (byte*)&include_dirs, 0, "include-dir" },
207 { 'j', number, (byte*)&number_of_jobs, (byte*) &optional_number_of_jobs, "jobs" },
208 { 'k', flag, (byte*)&want_keep_going, 0, "keep-going" },
209 { 'm', flag, (byte*)&want_detail_summary, 0, "detail-summary" },
210 { 'c', flag, (byte*)&want_just_display, 0, "just-display" },
211 { 'd', flag, (byte*)&display_data_base, 0,"display-data-base" },
212 { 'q', flag, (byte*)&question, 0, "question" },
213 { 's', flag, (byte*)&want_silent, 0, "silent" },
214 { 'V', flag, (byte*)&want_display_version, 0, "version" },
215 { 'w', flag, (byte*)&dont_want_display_directory, 0,"dont-display-directory" },
216 { 'n', flag, (byte*)&want_dry_run, 0, "dry-run"},
217 { 't', number, (byte*)&timeout, 0, "timeout" },
218 { 'S', flag, (byte*)&want_check_syntax, 0, "check-syntax"},
219 { 'r', string, (byte*)&directories, 0, "load-directory"},
220 { 'v', flag, (byte*)&want_verbose, 0, "verbose"},
221 { 'F', string,(byte*)&excludes, 0, "exclude"},
222 { 'l', string,(byte*)&logs,0,"log"},
228 static const char* usage[] =
231 " -C DIRECTORY, --directory=DIRECTORY Change to DIRECTORY before running any commands.\n",
232 " -e, --environment-overrides Environment variables override files.\n",
233 " -f FILE, --file=FILE Read FILE as a teshfile.\n",
235 " all argument of the command line without\n",
236 " option is dealed as a tesh file.\n",
237 " -h, --help Display this message and exit.\n",
238 " -i, --keep-going-unit Ignore failures from commands.\n",
239 " The possible failures are :\n",
240 " - the exit code differ from the expected\n",
241 " - the signal throw differ from the expected\n",
242 " - the output differ from the expected\n",
243 " - the read pipe is broken\n",
244 " - the write pipe is broken\n",
245 " - the command assigned delay is outdated\n",
246 " -I DIRECTORY, --include-dir=DIRECTORY Search DIRECTORY for included files.\n",
247 " -j [N], --jobs[=N] Allow N commands at once; infinite commands with\n"
249 " -k, --keep-going Keep going when some commands can't be made or\n",
251 " -c, --just-display Don't actually run any commands; just display them.\n",
252 " -p, --display-data-base Display tesh's internal database.\n",
253 " -q, --question Run no commands; exit status says if up to date.\n",
254 " -s, --silent, Don't echo commands.\n",
255 " -V, --version Display the version number of tesh and exit.\n",
256 " -d, --dont-display-directory Don't display the current directory.\n",
257 " -n, --dry-run Check the syntax of the specified tesh files, display the result and exit.\n",
258 " -t, --timeout Wait the end of the commands at most timeout seconds.\n",
259 " -S, --check-syntax Check the syntax of the tesh files before run them. \n",
260 " -x, --suffix Consider the new suffix for the tesh files.\n"
262 " the default suffix for the tesh files is \".tesh\".\n",
263 " -a, --semantic Display the tesh file metacommands syntax and exit.\n",
264 " -b, --build-file Build a tesh file.\n",
265 " -r, --load-directory Run all the tesh files located in the directories specified by the option --directory.\n",
266 " -v, --verbose Display the status of the commands.\n",
267 " -m, --detail-summary Detail the summary of the run.\n",
268 " -F file , --exclude=FILE Ignore the tesh file FILE.\n",
269 " -l format, --log Format of the xbt logs.\n",
273 /* the string of options of tesh */
275 optstring[1 + sizeof (opt_entries) / sizeof (opt_entries[0]) * 3];
277 /* the option table of tesh */
279 option longopts[(sizeof (opt_entries) / sizeof (s_optentry_t))];
285 process_command_line(int argc, char** argv);
294 display_version(void);
300 display_semantic(void);
308 main(int argc, char* argv[])
313 /* process the command line */
314 if(process_command_line(argc, argv) < 0)
317 /* move to the root directory (the directory may change during the command line processing) */
318 chdir(root_directory->name);
320 /* initialize the xbt library
321 * for thread portability layer
324 /* xbt initialization */
325 if(!lstrings_is_empty(logs))
327 int size = lstrings_get_size(logs);
328 char** cstr = lstrings_to_cstr(logs);
330 xbt_init(&size, cstr);
336 xbt_init(&argc, argv);
338 /* the user wants to display the usage of tesh */
339 if(want_display_usage)
342 /* the user wants to display the version of tesh */
343 if(want_display_version)
349 /* the user wants to display the semantic of the tesh file metacommands */
350 if(want_display_semantic)
356 /* load tesh files */
360 /* use by the finalize function to known if it must display the tesh usage */
363 if(-2 == number_of_jobs)
364 {/* --jobs is not specified (use the default value) */
365 number_of_jobs = default_number_of_jobs;
367 else if(optional_number_of_jobs == number_of_jobs)
368 {/* --jobs option is specified with no args (use one job per unit) */
369 number_of_jobs = fstreams_get_size(fstreams);
372 if(number_of_jobs > fstreams_get_size(fstreams))
373 {/* --jobs = N is specified and N is more than the number of tesh files */
375 WARN0("Number of requested jobs exceed the number of files to run");
377 /* assume one job per file */
378 number_of_jobs = fstreams_get_size(fstreams);
381 /* initialize the semaphore used to synchronize all the units */
382 jobs_sem = xbt_os_sem_init(number_of_jobs);
384 /* initialize the semaphore used by the runner to wait for the end of all units */
385 units_sem = xbt_os_sem_init(0);
387 /* initialize the runner */
388 if(runner_init(want_check_syntax, timeout, fstreams))
391 if(want_just_display && want_silent)
394 if(want_just_display && want_dry_run)
395 WARN0("mismatch in the syntax : --just-check-syntax and --just-display options at same time");
397 /* run all the units */
401 /* show the result of the units */
402 if(want_verbose || want_dry_run)
403 runner_display_status();
405 /* all the test are runned, destroy the runner */
408 /* then, finalize tesh (release all the allocated memory and exits) */
417 /* init -- initialize tesh : allocated all the objects needed by tesh to run
418 * the tesh files specified in the command line.
420 * return If successful the function returns zero. Otherwise the function returns
421 * -1 and sets the global variable errno to the appropriate error code.
434 /* Windows specific : don't display the general-protection-fault message box and
435 * the the critical-error-handler message box (instead the system send the error
436 * to the calling process : tesh)
438 prev_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
441 /* used to store the files to run */
442 if(!(fstreams = fstreams_new(DEFAULT_FSTREAMS_CAPACITY, fstream_free)))
444 ERROR0("Insufficient memory is available to initialize tesh : system error");
448 /* register the current directory */
449 if(!(buffer = getcwd(NULL, 0)))
454 ERROR0("tesh initialization failed - Insufficient permission to read the current directory");
456 ERROR0("Insufficient memory is available to initialize tesh : system error");
461 /* save the root directory */
462 if(!(root_directory = directory_new(buffer)))
464 ERROR0("Insufficient memory is available to initialize tesh : system error");
470 /* the directories to loads */
471 if(!(directories = directories_new()))
473 ERROR0("Insufficient memory is available to initialize tesh : system error");
477 /* the include directories */
478 if(!(include_dirs = vector_new(DEFAULT_INCLUDE_DIRS_CAPACITY, directory_free)))
480 ERROR0("Insufficient memory is available to initialize tesh : system error");
484 /* xbt logs option */
485 if(!(logs = lstrings_new()))
487 ERROR0("Insufficient memory is available to initialize tesh : system error");
491 /* the excluded files */
492 if(!(excludes = excludes_new()))
494 ERROR0("Insufficient memory is available to initialize tesh : system error");
499 if(!(suffixes = lstrings_new()))
501 ERROR0("Insufficient memory is available to initialize tesh : system error");
505 /* register the default suffix ".tesh" */
506 if(lstrings_push_back(suffixes,".tesh"))
508 ERROR0("Insufficient memory is available to initialize tesh : system error");
517 /* load -- load the tesh files to run */
522 /* if the directories object is not empty load all the tesh files contained in
523 * the directories specified in the command line (this tesh files must have the
524 * a suffix specified in the suffixes object.
526 if(!directories_is_empty(directories))
527 directories_load(directories, fstreams, suffixes);
529 /* if no tesh file has been specified in the command line try to load the default tesh file
530 * teshfile from the current directory
532 if(fstreams_is_empty(fstreams))
534 struct stat buffer = {0};
536 /* add the default tesh file if it exists in the current directory */
537 if(!stat("teshfile", &buffer) && S_ISREG(buffer.st_mode))
538 fstreams_add(fstreams, fstream_new(getcwd(NULL, 0), "teshfile"));
541 /* excludes the files specified in the command line and stored in the excludes object */
542 if(!excludes_is_empty(excludes) && !fstreams_is_empty(fstreams))
544 /* check the files to excludes before */
545 excludes_check(excludes, fstreams);
547 /* exclude the specified tesh files */
548 fstreams_exclude(fstreams, excludes);
551 /* if the fstreams object is empty use the stdin */
552 if(fstreams_is_empty(fstreams))
553 fstreams_add(fstreams, fstream_new(NULL, "stdin"));
555 /* load the tesh files (open them) */
556 fstreams_load(fstreams);
560 /* finalize -- cleanup all the allocated objects and display the tesh usage if needed */
564 /* if there is not an error and the user wants display the usage or
565 * if there is an error and all the files to load are loaded, display the usage
567 if((!exit_code && want_display_usage) || (!exit_code && !loaded))
570 /* delete the fstreams object */
572 fstreams_free((void**)&fstreams);
574 /* delete the excludes object */
576 excludes_free((void**)&excludes);
578 /* delete the directories object */
580 directories_free((void**)&directories);
582 /* delete the root directory object */
584 directory_free((void**)&root_directory);
586 /* delete the include directories object */
588 vector_free(&include_dirs);
590 /* delete the list of tesh files suffixes */
592 lstrings_free(&suffixes);
594 /* delete the xbt log options list */
596 lstrings_free(&logs);
599 /* destroy the semaphore used to synchronize the units */
601 xbt_os_sem_destroy(jobs_sem);
603 /* destroy the semaphore used by the runner used to wait for the end of the units */
605 xbt_os_sem_destroy(units_sem);
607 /* exit from the xbt framework */
610 /* Windows specific (restore the previouse error mode */
612 SetErrorMode(prev_error_mode);
615 if(!want_verbose && !want_dry_run && !want_silent && !want_just_display)
616 INFO2("tesh terminated with exit code %d : %s",exit_code, (!exit_code ? "success" : error_to_string(exit_code)));
618 /* exit with the last error code */
622 /* init_options -- initialize the options string */
629 /* the function has been already called */
630 if(optstring[0] != '\0')
638 for (i = 0; opt_entries[i].c != '\0'; ++i)
640 /* initialize the long name of the option*/
641 longopts[i].name = (opt_entries[i].long_name == 0 ? "" : opt_entries[i].long_name);
643 /* getopt_long returns the value of val */
644 longopts[i].flag = 0;
646 /* the short option */
647 longopts[i].val = opt_entries[i].c;
649 /* add the short option in the options string */
650 *p++ = opt_entries[i].c;
652 switch (opt_entries[i].type)
654 /* if this option is used to set a flag or if the argument must be ignored
655 * the option has no argument
658 longopts[i].has_arg = no_argument;
661 /* the option has an argument */
667 if(opt_entries[i].optional_value != 0)
671 longopts[i].has_arg = optional_argument;
674 longopts[i].has_arg = required_argument;
681 longopts[i].name = 0;
684 /* process_command_line -- process the command line
686 * param argc the number of the arguments contained by the command line.
687 * param The array of C strings containing all the arguments of the command
690 * return If successful, the function returns 0. Otherwise -1 is returned
691 * and sets the global variable errno to indicate the error.
693 * errors [ENOENT] A file name specified in the command line does not exist
697 process_command_line(int argc, char** argv)
699 register const struct s_optentry* entry;
701 directory_t directory;
704 /* initialize the options string of tesh */
707 /* let the function getopt_long display the errors if any */
710 /* set option index to zero */
713 while (optind < argc)
715 c = getopt_long(argc, argv, optstring, longopts, (int *) 0);
719 /* end of the command line or "--". */
724 /* no option specified, assume it's a tesh file to run */
728 /* getpath returns -1 when the file to get the path doesn't exist */
729 if(getpath(optarg, &path) < 0)
734 ERROR1("File %s does not exist", optarg);
736 ERROR0("Insufficient memory is available to parse the command line : system error");
741 /* get to the last / (if any) to get the short name of the file */
742 delimiter = strrchr(optarg,'/');
744 /* create a new file stream which represents the tesh file to run */
745 fstream = fstream_new(path, delimiter ? delimiter + 1 : optarg);
749 /* if the list of all tesh files to run already contains this file
750 * destroy it and display a warning, otherwise add it in the list.
752 if(fstreams_contains(fstreams, fstream))
754 fstream_free((void**)&fstream);
755 WARN1("File %s already specified to be run", optarg);
758 fstreams_add(fstreams, fstream);
766 /* unknown option, let getopt_long() displays the error */
771 for(entry = opt_entries; entry->c != '\0'; ++entry)
780 ERROR0("Command line processing failed : internal error");
781 exit_code = EPROCCMDLINE;
787 /* set the flag to one */
788 *(int*) entry->value = 1;
797 /* an option with a optional arg is specified use the entry->optional_value */
798 optarg = (char*)entry->optional_value;
800 else if (*optarg == '\0')
802 /* a non optional argument is not specified */
803 ERROR2("Option %c \"%s\"requires an argument",entry->c,entry->long_name);
808 /* --load-directory option */
809 if(!strcmp(entry->long_name,"load-directory"))
813 if(translatepath(optarg, &path) < 0)
818 ERROR1("%s is not a directory",optarg);
820 ERROR0("Insufficient memory is available to process the command line - system error");
827 directory = directory_new(path);
830 if(directories_contains(directories, directory))
832 directory_free((void**)&directory);
833 WARN1("Directory %s already specified to be load",optarg);
836 directories_add(directories, directory);
841 else if(!strcmp(entry->long_name,"directory"))
845 if(translatepath(optarg, &path) < 0)
850 ERROR1("%s is not a directory",optarg);
852 ERROR0("Insufficient memory is available to process the command line - system error");
858 if(!dont_want_display_directory)
859 INFO1("Entering directory \"%s\"",path);
868 /* --suffix option */
869 else if(!strcmp(entry->long_name,"suffix"))
871 if(strlen(optarg) > MAX_SUFFIX)
873 ERROR1("Suffix %s too long",optarg);
874 exit_code = ESUFFIXTOOLONG;
880 char suffix[MAX_SUFFIX + 2] = {0};
881 sprintf(suffix,".%s",optarg);
883 if(lstrings_contains(suffixes, suffix))
884 WARN1("Suffix %s already specified to be used", optarg);
886 lstrings_push_back(suffixes, suffix);
890 if(lstrings_contains(suffixes, optarg))
891 WARN1("Suffix %s already specified to be used", optarg);
893 lstrings_push_back(suffixes, optarg);
897 else if(!strcmp(entry->long_name,"file"))
902 if(getpath(optarg, &path) < 0)
907 ERROR1("File %s does not exist", optarg);
909 ERROR0("Insufficient memory is available to process the command line - system error");
914 delimiter = strrchr(optarg,'/');
916 fstream = fstream_new(path, delimiter ? delimiter + 1 : optarg);
920 if(fstreams_contains(fstreams, fstream))
922 fstream_free((void**)&fstream);
923 WARN1("File %s already specified to run", optarg);
926 fstreams_add(fstreams, fstream);
928 /* --include-dir option */
929 else if(!strcmp(entry->long_name,"include-dir"))
934 if(translatepath(optarg, &path) < 0)
939 ERROR1("%s is not a directory",optarg);
941 ERROR0("Insufficient memory is available to process the command line - system error");
948 directory = directory_new(path);
951 if(vector_contains(include_dirs, directory))
953 directory_free((void**)&directory);
954 WARN1("Include directory %s already specified to be used",optarg);
958 vector_push_back(include_dirs, directory);
961 /* --exclude option */
962 else if(!strcmp(entry->long_name,"exclude"))
968 if(getpath(optarg, &path) < 0)
973 ERROR1("file %s does not exist", optarg);
975 ERROR0("Insufficient memory is available to process the command line - system error");
980 delimiter = strrchr(optarg,'/');
982 fstream = fstream_new(path, delimiter ? delimiter + 1 : optarg);
985 if(excludes_contains(excludes, fstream))
987 fstream_free((void**)&fstream);
988 WARN1("File %s already specified to be exclude", optarg);
991 excludes_add(excludes, fstream);
995 else if(!strcmp(entry->long_name,"log"))
997 lstrings_push_back(logs, optarg);
1001 INFO1("Unexpected option %s", optarg);
1008 /* strictly positve number options */
1011 if ((NULL == optarg) && (argc > optind))
1015 for (cp = argv[optind]; isdigit(cp[0]); ++cp)
1017 optarg = argv[optind++];
1020 /* the number option is specified */
1023 int i = atoi(optarg);
1026 for (cp = optarg; isdigit(cp[0]); ++cp);
1028 if (i < 1 || cp[0] != '\0')
1030 ERROR2("Option %c \"%s\" requires an strictly positive integer as argument",entry->c, entry->long_name);
1031 exit_code = ENOTPOSITIVENUM;
1035 *(int*)entry->value = i;
1037 /* the number option is specified but has no arg, use the optional value*/
1039 *(int*)entry->value = *(int*) entry->optional_value;
1058 if (want_display_version)
1061 stream = exit_code ? stderr : stdout;
1063 fprintf (stream, "Usage: tesh [options] [file] ...\n");
1065 for (cpp = usage; *cpp; ++cpp)
1066 fputs (*cpp, stream);
1068 fprintf(stream, "\nReport bugs to <martin.quinson@loria.fr | malek.cherier@loria.fr>\n");
1072 display_version(void)
1074 /* TODO : display the version of tesh */
1075 printf("Version :\n");
1076 printf(" tesh version %s : Mini shell specialized in running test units by Martin Quinson \n", version);
1077 printf(" and Malek Cherier\n");
1078 printf(" Copyright (c) 2007, 2008 Martin Quinson, Malek Cherier\n");
1079 printf(" All rights reserved\n");
1080 printf(" This program is free software; you can redistribute it and/or modify it\n");
1081 printf(" under the terms of the license (GNU LGPL) which comes with this package.\n\n");
1083 if(!want_display_usage)
1084 printf("Report bugs to <martin.quinson@loria.fr | malek.cherier@loria.fr>");
1088 display_semantic(void)
1093 FILE* stream = fopen("README.txt", "r");
1097 ERROR0("Unable to locate the README.txt file");
1098 exit_code = EREADMENOTFOUND;
1102 while(getline(&line, &len, stream) != -1)