-
-#include <runner.h>
-#include <fstream.h>
-#include <fstreams.h>
-#include <directory.h>
-#include <directories.h>
-#include <excludes.h>
-#include <error.h>
-
-#include <getopt.h>
-
-/*
- * entry used to define the parameter of a tesh option.
- */
-typedef struct s_optentry
-{
- int c; /* the character of the option */
-
- /*
- * the type of the argument of the option
- */
- enum
- {
- flag, /* it's a flag option, by default the flag is set to zero */
- string, /* the option has strings as argument */
- number /* the option has an integral positive number as argument */
- }type;
-
- byte* value; /* the value of the option */
- byte* optional_value; /* the optional value of the option if not specified */
- const char * long_name; /* the long name of the command */
-}s_optentry_t,* optentry_t;
-
-
-
-XBT_LOG_NEW_DEFAULT_CATEGORY(tesh,"TEst SHell utility");
-
-#ifdef WIN32
-/* Windows specific : the previous process error mode */
-static UINT
-prev_error_mode = 0;
-#endif
-
-directory_t
-root_directory = NULL;
-
-int
-exit_code = 0;
-
-/* the current version of tesh */
-static const char*
-version = "1.0";
-
-/* ------------------------------------------------------------ */
-/* options */
-/* ------------------------------------------------------------ */
-
-/* ------------------------------------------------------------ */
-/* numbers */
-/* ------------------------------------------------------------ */
-
-
-/* --jobs is specified with arg */
-static int
-number_of_jobs = -2;
-
-/* --jobs option is not specified (use the default job count) */
-static int
-default_number_of_jobs = 1;
-
-/* --jobs is specified but has no arg (one job per unit) */
-static int
-optional_number_of_jobs = -1;
-
-/* the global timeout */
-static int
-timeout = INDEFINITE;
-
-/* ------------------------------------------------------------ */
-/* strings dlists */
-/* ------------------------------------------------------------ */
-
-/* --C change the directory before running the units */
-static directories_t
-directories = NULL;
-
-/* the include directories : see the !i metacommand */
-vector_t
-includes = NULL;
-
-/* the list of tesh files to run */
-static fstreams_t
-fstreams = NULL;
-
-/* xbt logs */
-static lstrings_t
-logs = NULL;
-
-static excludes_t
-excludes = NULL;
-
-/* the ddlist of tesh file suffixes */
-static lstrings_t
-suffixes = NULL;
-
-/* ------------------------------------------------------------ */
-/* flags */
-/* ------------------------------------------------------------ */
-
-/* if 1, keep going when some commands can't be found
- * default value 0 : not keep going
- */
-int
-want_keep_going = 0;
-
-/* if 1, ignore failures from commands
- * default value : do not ignore failures
- */
-int
-want_keep_going_unit = 0;
-
-/* if 1, display tesh usage */
-static int
-want_display_usage = 0;
-
-/* if 1, display the tesh version */
-static int
-want_display_version = 0;
-
-/* if 1, the syntax of all tesh files is checked
- * before running them
- */
-static int
-want_check_syntax = 0;
-
-/* if 1, all the tesh file of the current directory
- * are runned
- */
-static int
-want_load_directory = 0;
-
-/* if 1, the status of all the units is display at
- * the end.
- */
-static int
-want_verbose = 0;
-
-/* if 1, the directories are displayed */
-int
-dont_want_display_directory = 0;
-
-/* if 1, just check the syntax of all the tesh files
- * do not run them.
- */
-int
-want_dry_run = 0;
-
-/* if 1, display the tesh files syntax and exit */
-static int
-want_display_semantic = 0;
-
-int
-want_silent = 0;
-
-int
-want_just_display = 0;
-
-static int
-env_overrides = 0;
-
-static int
-display_data_base = 0;
-
-static int
-question = 0;
-
-/* the semaphore used to synchronize the jobs */
-xbt_os_sem_t
-jobs_sem = NULL;
-
-/* the semaphore used by the runner to wait the end of all the units */
-xbt_os_sem_t
-units_sem = NULL;
-
-static int
-prepared = 0;
-
-
-int
-interrupted = 0;
-
-/* the table of the entries of the options */
-static const struct s_optentry opt_entries[] =
-{
- { 'C', string, (byte*)&directories, 0, "directory" },
- { 'x', string, (byte*)&suffixes, 0, "suffix" },
- { 'e', flag, (byte*)&env_overrides, 0, "environment-overrides", },
- { 'f', string, (byte*)&fstreams, 0, "file" },
- { 'h', flag, (byte*)&want_display_usage, 0, "help" },
- { 'a', flag, (byte*)&want_display_semantic, 0, "semantic" },
- { 'i', flag, (byte*)&want_keep_going_unit, 0, "keep-going-unit" },
- { 'I', string, (byte*)&includes, 0, "include-dir" },
- { 'j', number, (byte*)&number_of_jobs, (byte*) &optional_number_of_jobs, "jobs" },
- { 'k', flag, (byte*)&want_keep_going, 0, "keep-going" },
- { 'c', flag, (byte*)&want_just_display, 0, "just-display" },
- { 'd', flag, (byte*)&display_data_base, 0,"display-data-base" },
- { 'q', flag, (byte*)&question, 0, "question" },
- { 's', flag, (byte*)&want_silent, 0, "silent" },
- { 'V', flag, (byte*)&want_display_version, 0, "version" },
- { 'w', flag, (byte*)&dont_want_display_directory, 0,"dont-display-directory" },
- { 'n', flag, (byte*)&want_dry_run, 0, "dry-run"},
- { 't', number, (byte*)&timeout, 0, "timeout" },
- { 'S', flag, (byte*)&want_check_syntax, 0, "check-syntax"},
- { 'r', flag, (byte*)&want_load_directory, 0, "load-directory"},
- { 'v', flag, (byte*)&want_verbose, 0, "verbose"},
- { 'F', string,(byte*)&excludes, 0, "exclude"},
- { 'l', string,(byte*)&logs,0,"log"},
- { 0, 0, 0, 0, 0}
-
-};
-
-/* the tesh usage */
-static const char* usage[] =
-{
- "Options:\n",
- " -C DIRECTORY, --directory=DIRECTORY Change to DIRECTORY before running any commands.\n",
- " -e, --environment-overrides Environment variables override files.\n",
- " -f FILE, --file=FILE Read FILE as a teshfile.\n",
- " remark :\n",
- " all argument of the command line without\n",
- " option is dealed as a tesh file.\n",
- " -h, --help Display this message and exit.\n",
- " -i, --keep-going-unit Ignore failures from commands.\n",
- " The possible failures are :\n",
- " - the exit code differ from the expected\n",
- " - the signal throw differ from the expected\n",
- " - the output differ from the expected\n",
- " - the read pipe is broken\n",
- " - the write pipe is broken\n",
- " - the command assigned delay is outdated\n",
- " -I DIRECTORY, --include-dir=DIRECTORY Search DIRECTORY for included files.\n",
- " -j [N], --jobs[=N] Allow N commands at once; infinite commands with\n"
- " no arg.\n",
- " -k, --keep-going Keep going when some commands can't be made or\n",
- " failed.\n",
- " -c, --just-display Don't actually run any commands; just display them.\n",
- " -p, --display-data-base Display tesh's internal database.\n",
- " -q, --question Run no commands; exit status says if up to date.\n",
- " -s, --silent, Don't echo commands.\n",
- " -V, --version Display the version number of tesh and exit.\n",
- " -d, --dont-display-directory Don't display the current directory.\n",
- " -n, --dry-run Check the syntax of the specified tesh files, display the result and exit.\n",
- " -t, --timeout Wait the end of the commands at most timeout seconds.\n",
- " -S, --check-syntax Check the syntax of the tesh files before run them. \n",
- " -x, --suffix Consider the new suffix for the tesh files.\n"
- " remark :\n",
- " the default suffix for the tesh files is \".tesh\".\n",
- " -a, --semantic Display the tesh file metacommands syntax and exit.\n",
- " -b, --build-file Build a tesh file.\n",
- " -r, --load-directory Run all the tesh files located in the directories specified by the option --directory.\n",
- " -v, --verbose Display the status of the commands.\n",
- " -F file , --exclude=FILE Ignore the tesh file FILE.\n",
- " -l format, --log Format of the xbt logs.\n",
- NULL
-};
-
-/* the string of options of tesh */
-static char
-optstring[1 + sizeof (opt_entries) / sizeof (opt_entries[0]) * 3];
-
-/* the option table of tesh */
-static struct
-option longopts[(sizeof (opt_entries) / sizeof (s_optentry_t))];
-
-static void
-init_options(void);
-
-static int
-process_command_line(int argc, char** argv);
-
-static int
-load(void);
-
-static void
-display_usage(int exit_code);
-
-static void
-display_version(void);
-
-static void
-finalize(void);
-
-static void
-display_semantic(void);
-
-static int
-init(void);
-
-
-
-int
-main(int argc, char* argv[])
-{
- init();
-
- /* process the command line */
- if((exit_code = process_command_line(argc, argv)))
- finalize();
-
- /* initialize the xbt library
- * for thread portability layer
- */
-
- if(!lstrings_is_empty(logs))
- {
- int size = lstrings_get_size(logs);
- char** cstr = lstrings_to_cstr(logs);
-
- xbt_init(&size, cstr);
-
- free(cstr);
-
- }
- else
- xbt_init(&argc, argv);
-
- /* the user wants to display the usage of tesh */
- if(want_display_usage)
- finalize();
-
- /* the user wants to display the version of tesh */
- if(want_display_version)
- {
- display_version();
- finalize();
- }
-
- /* the user wants to display the semantic of the tesh file metacommands */
- if(want_display_semantic)
- {
- display_semantic();
- finalize();
- }
-
- if(!directories_has_directories_to_load(directories) && want_load_directory)
- WARN0("--load-directory specified but no directory specified");
-
- excludes_check(excludes, fstreams);
-
- /* load tesh */
- if((exit_code = load()))
- finalize();
-
- prepared = 1;
-
- if(-2 == number_of_jobs)
- {/* --jobs is not specified (use the default value) */
- number_of_jobs = default_number_of_jobs;
- }
- else if(optional_number_of_jobs == number_of_jobs)
- {/* --jobs option is specified with no args (use one job per unit) */
- number_of_jobs = fstreams_get_size(fstreams);
- }
-
- if(number_of_jobs > fstreams_get_size(fstreams))
- {/* --jobs = N is specified and N is more than the number of tesh files */
-
- WARN0("number of requested jobs exceed the number of files");
-
- /* assume one job per file */
- number_of_jobs = fstreams_get_size(fstreams);
- }
-
- /* initialize the semaphore used to synchronize the jobs */
- jobs_sem = xbt_os_sem_init(number_of_jobs);
-
- /* initialize the semaphore used by the runner to wait for the end of all units */
- units_sem = xbt_os_sem_init(0);
-
- /* initialize the runner */
- if((0 != (exit_code = runner_init(
- want_check_syntax,
- timeout,
- fstreams))))
- {
- finalize();
- }
-
- if(want_just_display && want_silent)
- want_silent = 0;
-
- if(want_just_display && want_dry_run)
- WARN0("mismatch in the syntax : --just-check-syntax and --just-display options at same time");
-
- /* run all the units */
- runner_run();
-
- /* show the result of the units */
- if(want_verbose || want_dry_run)
- runner_display_status();
-
-
- /* all the test are runned, destroy the runner */
- runner_destroy();
-
- /* then, finalize tesh */
- finalize();
-
- #ifndef WIN32
- return exit_code;
- #endif
-
-}
-
-static int
-init(void)
-{
- char* buffer = getcwd(NULL, 0);
-
- #ifdef WIN32
- /* Windows specific : don't display the general-protection-fault message box and
- * the the critical-error-handler message box (instead the system send the error
- * to the calling process : tesh)
- */
- prev_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
- #endif
-
- /* used to store the file streams to run */
- fstreams = fstreams_new(DEFAULT_FSTREAMS_CAPACITY, fstream_free);
-
- root_directory = directory_new(buffer,want_load_directory);
- free(buffer);
- /* used to store the directories to loads */
- directories = directories_new();
-
- /* register the current directory */
- directories_add(directories, root_directory);
-
- /* used to store the includes directories */
- includes = vector_new(DEFAULT_INCLUDES_CAPACITY, directory_free);
-
- /* xbt logs */
- logs = lstrings_new();
-
- /* used to to store all the excluded file streams */
- excludes = excludes_new();
-
- /* list of file streams suffixes */
- suffixes = lstrings_new();
-
- lstrings_push_back(suffixes,".tesh");
-
- return 0;
-}
-
-static int
-load(void)
-{
- chdir(directory_get_name(root_directory));
-
- if(want_load_directory)
- directories_load(directories, fstreams, suffixes);
-
- /* on a aucun fichier specifie dans la ligne de commande
- * l'option --run-current-directory n'a pas ete specifie ou aucun fichier ne se trouve dans le repertoire a charger
- */
- if(fstreams_is_empty(fstreams))
- {
- struct stat buffer = {0};
-
- /* add the default tesh file if it exists */
- if(!stat("teshfile", &buffer) && S_ISREG(buffer.st_mode))
- fstreams_add(fstreams, fstream_new(getcwd(NULL, 0), "teshfile"));
- }
-
- if(!excludes_is_empty(excludes) && !fstreams_is_empty(fstreams))
- fstreams_exclude(fstreams, excludes);
-
- if(fstreams_is_empty(fstreams))
- fstreams_add(fstreams, fstream_new(NULL, "stdin"));
-
- fstreams_load(fstreams);
-
- return 0;
-}
-
-static void
-finalize(void)
-{
- if((!exit_code && want_display_usage) || (!exit_code && !prepared))
- display_usage(exit_code);
-
- if(fstreams)
- fstreams_free((void**)&fstreams);
-
- if(excludes)
- excludes_free((void**)&excludes);
-
- if(directories)
- directories_free((void**)&directories);
-
- if(includes)
- vector_free(&includes);
-
- if(suffixes)
- lstrings_free(&suffixes);
-
- if(logs)
- lstrings_free(&logs);
-
- /* destroy the semaphore used to synchronize the jobs */
- if(jobs_sem)
- xbt_os_sem_destroy(jobs_sem);
-
- if(units_sem)
- xbt_os_sem_destroy(units_sem);
-
- /* exit from the xbt framework */
- xbt_exit();
-
- #ifdef WIN32
- SetErrorMode(prev_error_mode);
- #endif
-
- if(!want_verbose && !want_dry_run && !want_silent && !want_just_display)
- INFO2("tesh terminated with exit code %d : %s",exit_code, (!exit_code ? "success" : error_to_string(exit_code)));
-
- exit(exit_code);
-}
-
-static void
-init_options (void)
-{
- char *p;
- unsigned int i;
-
- if(optstring[0] != '\0')
- /* déjà traité. */
- return;
-
- p = optstring;
-
- /* Return switch and non-switch args in order, regardless of
- POSIXLY_CORRECT. Non-switch args are returned as option 1. */
-
- /* le premier caractère de la chaîne d'options vaut -.
- * les arguments ne correspondant pas à une option sont
- * manipulés comme s'ils étaient des arguments d'une option
- * dont le caractère est le caractère de code 1
- */
- *p++ = '-';
-
- for (i = 0; opt_entries[i].c != '\0'; ++i)
- {
- /* initialize le nom de l'option longue*/
- longopts[i].name = (opt_entries[i].long_name == 0 ? "" : opt_entries[i].long_name);
-
- /* getopt_long() retourne la valeur de val */
- longopts[i].flag = 0;
-
- /* la valeur de l'option courte est le caractère spécifié dans opt_entries[i].c */
- longopts[i].val = opt_entries[i].c;
-
- /* on l'ajoute à la chaine des optstring */
- *p++ = opt_entries[i].c;
-
- switch (opt_entries[i].type)
- {
- /* si c'est une option qui sert a positionner un flag ou que l'on doit ignorée, elle n'a pas d'argument */
- case flag:
- longopts[i].has_arg = no_argument;
- break;
-
- /* c'est une option qui attent un argument :
- * une chaine de caractères, un nombre flottant,
- * ou un entier positif
- */
- case string:
- case number:
-
- *p++ = ':';
-
- if(opt_entries[i].optional_value != 0)
- {
- *p++ = ':';
-
- longopts[i].has_arg = optional_argument;
- }
- else
- longopts[i].has_arg = required_argument;
-
- break;
- }
- }
-
- *p = '\0';
- longopts[i].name = 0;
-}
-
-static int
-process_command_line(int argc, char** argv)
-{
- register const struct s_optentry* entry;
- register int c;
- directory_t directory;
- fstream_t fstream;
-
- /* initialize the options table of tesh */
- init_options();
-
- /* display the errors of the function getopt_long() */
- opterr = 1;
-
- optind = 0;
-
- while (optind < argc)
- {
- c = getopt_long (argc, argv, optstring, longopts, (int *) 0);
-
- if(c == EOF)
- {
- /* end of the command line or "--". */
- break;
- }
- else if (c == 1)
- {
- /* the argument of the command line is not an option (no "-"), assume it's a tesh file */
- /*struct stat buffer = {0};
- char* prev = getcwd(NULL, 0);
-
- directory = directories_get_back(directories);
-
- chdir(directory->name);
-
- if(stat(optarg, &buffer) || !S_ISREG(buffer.st_mode))
- {
- chdir(prev);
- free(prev);
- ERROR1("file %s not found", optarg);
- return EFILENOTFOUND;
- }
-
- chdir(prev);
- free(prev);*/
-
- directory = directories_search_fstream_directory(directories, optarg);
-
- if(!directory)
- {
- if(1 == directories_get_size(directories))
- {
- ERROR1("file %s not found in the current directory",optarg);
- return EFILENOTINCURDIR;
- }
- else
- {
- ERROR1("file %s not found in the specified directories",optarg);
- return EFILENOTINSPECDIR;
- }
- }
-
- if(!(fstream = fstream_new(directory_get_name(directory), optarg)))
- {
- ERROR1("command line processing failed with the error code %d", errno);
- return EPROCESSCMDLINE;
- }
- else
- {
- if(fstreams_contains(fstreams, fstream))
- {
- fstream_free((void**)&fstream);
- WARN1("file %s already specified", optarg);
- }
- else
- {
- if((errno = fstreams_add(fstreams, fstream)))
- {
- fstream_free((void**)&fstream);
- ERROR1("command line processing failed with the error code %d", errno);
- return EPROCESSCMDLINE;
- }
- }
- }
- }
- else if (c == '?')
- {
- /* unknown option, getopt_long() displays the error */
- return 1;
- }
- else
- {
- for (entry = opt_entries; entry->c != '\0'; ++entry)
-
- if(c == entry->c)
- {
-
- switch (entry->type)
- {
- /* impossible */
- default:
- ERROR0("command line processing failed : internal error");
- return EPROCESSCMDLINE;
-
-
- /* flag options */
- case flag:
- /* set the flag to one */
- *(int*) entry->value = 1;
-
- break;
-
- /* string options */
- case string:
-
- if(!optarg)
- {
- /* an option with a optional arg is specified use the entry->optional_value */
- optarg = (char*)entry->optional_value;
- }
- else if (*optarg == '\0')
- {
- /* a non optional argument is not specified */
- ERROR2("the option %c \"%s\"requires an argument",entry->c,entry->long_name);
- return EARGNOTSPEC;
- }
-
- /* --directory option */
- if(!strcmp(entry->long_name,"directory"))
- {
- if(!(directory = directory_new(optarg, want_load_directory)))
- {
- if(ENOTDIR == errno)
- {
- ERROR1("directory %s not found",optarg);
- return EDIRNOTFOUND;
- }
- else
- {
- ERROR1("command line processing failed with the error code %d", errno);
- return EPROCESSCMDLINE;
- }
- }
- else
- {
- if(directories_contains(directories, directory))
- {
- directory_free((void**)&directory);
- WARN1("directory %s already specified",optarg);
- }
- else
- {
- if((errno = directories_add(directories, directory)))
- {
- directory_free((void**)&directory);
- ERROR1("command line processing failed with the error code %d", errno);
- return EPROCESSCMDLINE;
- }
- }
- }
- }
- /* --suffix option */
- else if(!strcmp(entry->long_name,"suffix"))
- {
- if(strlen(optarg) > MAX_SUFFIX)
- {
- ERROR1("suffix %s too long",optarg);
- return ESUFFIXTOOLONG;
- }
-
- if(optarg[0] == '.')
- {
- char suffix[MAX_SUFFIX + 2] = {0};
- sprintf(suffix,".%s",optarg);
-
- if(lstrings_contains(suffixes, suffix))
- WARN1("suffix %s already specified", optarg);
- else
- lstrings_push_back(suffixes, suffix);
- }
- else
- {
- if(lstrings_contains(suffixes, optarg))
- WARN1("suffix %s already specified", optarg);
- else
- lstrings_push_back(suffixes, optarg);
- }
- }
- /* --file option */
- else if(!strcmp(entry->long_name,"file"))
- {
-
- /* the argument of the command line is not an option (no "-"), assume it's a tesh file */
- /*struct stat buffer = {0};
- char* prev = getcwd(NULL, 0);
-
- directory = directories_get_back(directories);
-
- chdir(directory->name);
-
- if(stat(optarg, &buffer) || !S_ISREG(buffer.st_mode))
- {
- chdir(prev);
- free(prev);
- ERROR1("file %s not found", optarg);
- return EFILENOTFOUND;
- }
-
- chdir(prev);
- free(prev);*/
-
- directory = directories_search_fstream_directory(directories, optarg);
-
- if(!directory)
- {
- if(1 == directories_get_size(directories))
- {
- ERROR1("file %s not found in the current directory",optarg);
- return EFILENOTINCURDIR;
- }
- else
- {
- ERROR1("file %s not found in the specified directories",optarg);
- return EFILENOTINSPECDIR;
- }
- }
-
- if(!(fstream = fstream_new(directory_get_name(directory),optarg)))
- {
- ERROR1("command line processing failed with the error code %d", errno);
- return EPROCESSCMDLINE;
- }
- else
- {
- if(fstreams_contains(fstreams, fstream))
- {
- fstream_free((void**)&fstream);
- WARN1("file %s already specified", optarg);
- }
- else
- {
- if((errno = fstreams_add(fstreams, fstream)))
- {
- fstream_free((void**)&fstream);
- ERROR1("command line processing failed with the error code %d", errno);
- return EPROCESSCMDLINE;
- }
- }
- }
- }
- /* --include-dir option */
- else if(!strcmp(entry->long_name,"include-dir"))
- {
- if(!(directory = directory_new(optarg, want_load_directory)))
- {
- if(ENOTDIR == errno)
- {
- ERROR1("%s is not a directory",optarg);
- return EDIRNOTFOUND;
- }
- else
- {
- ERROR1("command line processing failed with the error code %d", errno);
- return EPROCESSCMDLINE;
- }
- }
- else
- {
- if(vector_contains(includes, directory))
- {
- directory_free((void**)&directory);
- WARN1("include directory %s already specified",optarg);
-
- }
- else
- {
- if((errno = vector_push_back(includes, directory)))
- {
- directory_free((void**)&directory);
- ERROR1("command line processing failed with the error code %d", errno);
- return EPROCESSCMDLINE;
- }
- }
- }
- }
- /* --exclude option */
- else if(!strcmp(entry->long_name,"exclude"))
- {
- directory = directories_get_back(directories);
-
- if(!(fstream = fstream_new(directory_get_name(directory), optarg)))
- {
- if(ENOENT == errno)
- {
- ERROR1("file to exclude %s not found", optarg);
- return EFILENOTFOUND;
- }
- else
- {
- ERROR1("command line processing failed with the error code %d", errno);
- return EPROCESSCMDLINE;
- }
- }
- else
- {
- if(excludes_contains(excludes, fstream))
- {
- fstream_free((void**)&fstream);
- WARN1("file to exclude %s already specified", optarg);
- }
- else
- {
- if((errno = excludes_add(excludes, fstream)))
- {
- fstream_free((void**)&fstream);
- ERROR1("command line processing failed with the error code %d", errno);
- return EPROCESSCMDLINE;
- }
- }
- }
- }
- /* --log option */
- else if(!strcmp(entry->long_name,"log"))
- {
- lstrings_push_back(logs, optarg);
- }
- else
- {
- /* TODO */
- }
-
-
- break;
-
- /* strictly positve number options */
- case number:
-
- if ((NULL == optarg) && (argc > optind))
- {
- const char* cp;
-
- for (cp = argv[optind]; isdigit(cp[0]); ++cp)
- if(cp[0] == '\0')
- optarg = argv[optind++];
- }
-
- /* the number option is specified */
- if(NULL != optarg)
- {
- int i = atoi(optarg);
- const char *cp;
-
- for (cp = optarg; isdigit(cp[0]); ++cp);
-
- if (i < 1 || cp[0] != '\0')
- {
- ERROR2("option %c \"%s\" requires an strictly positive integer as argument",entry->c, entry->long_name);
- return ENOTPOSITIVENUM;
- }
- else
- *(int*)entry->value = i;
- }
- /* the number option is specified but has no arg, use the optional value*/
- else
- *(int*)entry->value = *(int*) entry->optional_value;
-
- break;
-
- }
- break;
- }
- }
- }
-
- return 0;
-}
-
-static void
-display_usage(int exit_code)
-{
- const char **cpp;
- FILE* stream;
-
- if (want_display_version)
- display_version();
-
- stream = exit_code ? stderr : stdout;
-
- fprintf (stream, "Usage: tesh [options] [file] ...\n");
-
- for (cpp = usage; *cpp; ++cpp)
- fputs (*cpp, stream);
-
- fprintf(stream, "\nReport bugs to <martin.quinson@loria.fr | malek.cherier@loria.fr>\n");
-}
-
-static void
-display_version(void)
-{
- /* TODO : display the version of tesh */
- printf("Version :\n");
- printf(" tesh version %s : Mini shell specialized in running test units by Martin Quinson \n", version);
- printf(" and Malek Cherier\n");
- printf(" Copyright (c) 2007, 2008 Martin Quinson, Malek Cherier\n");
- printf(" All rights reserved\n");
- printf(" This program is free software; you can redistribute it and/or modify it\n");
- printf(" under the terms of the license (GNU LGPL) which comes with this package.\n\n");
-
- if(!want_display_usage)
- printf("Report bugs to <martin.quinson@loria.fr | malek.cherier@loria.fr>");
-}
-
-static void
-display_semantic(void)
-{
- size_t len;
- char * line = NULL;
-
- FILE* stream = fopen("README.txt", "r");
-
- if(!stream)
- {
- ERROR0("Unable to locate the README.txt file");
- exit_code = EREADMENOTFOUND;
- return;
- }
-
- while(getline(&line, &len, stream) != -1)
- printf("%s",line);
-
- fclose(stream);
-}
-
-
+/*\r
+ * src/main.c - this file contains the main function of tesh.\r
+ *\r
+ * Copyright 2008,2009 Martin Quinson, Malek Cherier All right reserved. \r
+ *\r
+ * This program is free software; you can redistribute it and/or modify it \r
+ * under the terms of the license (GNU LGPL) which comes with this package.\r
+ *\r
+ *\r
+ */\r
+#include <runner.h>\r
+#include <fstream.h>\r
+#include <fstreams.h>\r
+#include <directory.h>\r
+#include <directories.h>\r
+#include <excludes.h>\r
+#include <getopt.h>\r
+#include <getpath.h>\r
+\r
+#include <locale.h>\r
+\r
+/*\r
+ * entry used to define the parameter of a tesh option.\r
+ */\r
+typedef struct s_optentry\r
+{\r
+ int c; /* the character of the option */\r
+ \r
+ /* \r
+ * the type of the argument of the option \r
+ */\r
+ enum \r
+ {\r
+ flag, /* it's a flag option, by default the flag is set to zero */ \r
+ string, /* the option has strings as argument */ \r
+ number /* the option has an integral positive number as argument */ \r
+ }type;\r
+ \r
+ byte* value; /* the value of the option */ \r
+ byte* optional_value; /* the optional value of the option if not specified */\r
+ const char * long_name; /* the long name of the command */\r
+}s_optentry_t,* optentry_t;\r
+\r
+\r
+/* logs */\r
+XBT_LOG_NEW_DEFAULT_CATEGORY(tesh,"TEst SHell utility");\r
+\r
+#ifdef WIN32\r
+/* Windows specific : the previous process error mode */\r
+static UINT \r
+prev_error_mode = 0;\r
+#endif\r
+\r
+/* this object represents the root directory */\r
+directory_t\r
+root_directory = NULL;\r
+\r
+/* the current version of tesh */\r
+static const char* \r
+version = "1.0";\r
+\r
+/* ------------------------------------------------------------ */\r
+/* options */\r
+/* ------------------------------------------------------------ */\r
+\r
+/* ------------------------------------------------------------ */\r
+/* numbers */\r
+/* ------------------------------------------------------------ */\r
+\r
+\r
+/* --jobs is specified with arg */\r
+static int \r
+jobs_nb = -2;\r
+\r
+/* --jobs option is not specified (use the default job count) */\r
+static int \r
+default_jobs_nb = 1;\r
+\r
+/* --jobs is specified but has no arg (one job per unit) */\r
+static int \r
+optional_jobs_nb = -1;\r
+\r
+/* the global timeout */\r
+static int\r
+timeout = INDEFINITE;\r
+\r
+/* ------------------------------------------------------------ */\r
+/* strings */\r
+/* ------------------------------------------------------------ */\r
+\r
+/* --C change the directory before running the units */\r
+static directories_t \r
+directories = NULL;\r
+\r
+/* the include directories : see the !i metacommand */\r
+/*vector_t \r
+include_dirs = NULL;*/\r
+xbt_dynar_t\r
+include_dirs = NULL;\r
+\r
+/* the list of tesh files to run */\r
+static fstreams_t \r
+fstreams = NULL;\r
+\r
+/* xbt logs */\r
+static xbt_dynar_t\r
+logs = NULL;\r
+\r
+/* the list of tesh file suffixes */\r
+static xbt_dynar_t\r
+suffixes = NULL;\r
+\r
+\r
+static excludes_t\r
+excludes = NULL;\r
+\r
+\r
+/* ------------------------------------------------------------ */\r
+/* flags */\r
+/* ------------------------------------------------------------ */\r
+\r
+/* if 1, keep going when some commands can't be found\r
+ * default value 0 : not keep going\r
+ */\r
+int \r
+keep_going_flag = 0;\r
+\r
+/* if 1, ignore failures from commands\r
+ * default value : do not ignore failures\r
+ */\r
+int \r
+keep_going_unit_flag = 0;\r
+\r
+/* if 1, display tesh usage */\r
+static int \r
+print_usage_flag = 0;\r
+\r
+/* if 1, display the tesh version */\r
+static int \r
+print_version_flag = 0;\r
+\r
+/* if 1, the syntax of all tesh files is checked \r
+ * before running them\r
+ */\r
+static int\r
+check_syntax_flag = 0;\r
+\r
+/* if 1, the status of all the units is display at\r
+ * the end.\r
+ */\r
+static int\r
+summary_flag = 0;\r
+\r
+/* if 1 and the flag want_summay is set to 1 tesh display the detailed summary of the run */\r
+int \r
+detail_summary_flag = 0;\r
+\r
+/* if 1, the directories are displayed */\r
+int \r
+print_directory_flag = 0;\r
+\r
+/* if 1, just check the syntax of all the tesh files\r
+ * do not run them.\r
+ */\r
+int\r
+dry_run_flag = 0;\r
+\r
+/* if 1, display the tesh files syntax and exit */\r
+static int\r
+print_readme_flag = 0;\r
+\r
+int \r
+silent_flag = 0;\r
+\r
+int \r
+just_print_flag = 0;\r
+\r
+static int \r
+env_overrides_flag = 0;\r
+\r
+static int \r
+print_database_flag = 0;\r
+\r
+static int \r
+question_flag = 0;\r
+\r
+/* the semaphore used to synchronize the jobs */\r
+xbt_os_sem_t\r
+jobs_sem = NULL;\r
+\r
+/* the semaphore used by the runner to wait the end of all the units */\r
+xbt_os_sem_t\r
+units_sem = NULL;\r
+\r
+static int\r
+loaded = 0;\r
+\r
+int \r
+interrupted = 0;\r
+\r
+int\r
+exit_code = 0; \r
+\r
+pid_t\r
+pid =0;\r
+\r
+int \r
+is_tesh_root = 1;\r
+\r
+xbt_dynar_t\r
+errors = NULL;\r
+\r
+xbt_os_mutex_t\r
+err_mutex = NULL;\r
+\r
+/* the table of the entries of the options */ \r
+static const struct s_optentry opt_entries[] =\r
+{\r
+ { 'C', string, (byte*)NULL, 0, "directory" },\r
+ { 'x', string, (byte*)&suffixes, 0, "suffix" },\r
+ { 'e', flag, (byte*)&env_overrides_flag, 0, "environment-overrides", },\r
+ { 'f', string, (byte*)&fstreams, 0, "file" },\r
+ { 'h', flag, (byte*)&print_usage_flag, 0, "help" },\r
+ { 'a', flag, (byte*)&print_readme_flag, 0, "read-me" },\r
+ { 'i', flag, (byte*)&keep_going_unit_flag, 0, "keep-going-unit" },\r
+ { 'I', string, (byte*)&include_dirs, 0, "include-dir" },\r
+ { 'j', number, (byte*)&jobs_nb, (byte*) &optional_jobs_nb, "jobs" },\r
+ { 'k', flag, (byte*)&keep_going_flag, 0, "keep-going" },\r
+ { 'm', flag, (byte*)&detail_summary_flag, 0, "detail-summary" },\r
+ { 'c', flag, (byte*)&just_print_flag, 0, "just-print" },\r
+ { 'd', flag, (byte*)&print_database_flag, 0,"display-data-base" },\r
+ { 'q', flag, (byte*)&question_flag, 0, "question_flag" },\r
+ { 's', flag, (byte*)&silent_flag, 0, "silent" },\r
+ { 'V', flag, (byte*)&print_version_flag, 0, "version" },\r
+ { 'w', flag, (byte*)&print_directory_flag, 0,"dont-display-directory" },\r
+ { 'n', flag, (byte*)&dry_run_flag, 0, "dry-run"},\r
+ { 't', number, (byte*)&timeout, 0, "timeout" },\r
+ { 'S', flag, (byte*)&check_syntax_flag, 0, "check-syntax"},\r
+ { 'r', string, (byte*)&directories, 0, "load-directory"},\r
+ { 'v', flag, (byte*)&summary_flag, 0, "summary"},\r
+ { 'F', string,(byte*)&excludes, 0, "exclude"},\r
+ { 'l', string,(byte*)&logs,0,"log"},\r
+ { 0, 0, 0, 0, 0}\r
+};\r
+\r
+/* the tesh usage */\r
+static const char* usage[] =\r
+{\r
+ "Options:\n",\r
+ " -C DIRECTORY, --directory=DIRECTORY Change to DIRECTORY before running any commands.\n",\r
+ " -e, --environment-overrides Environment variables override files.\n",\r
+ " -f FILE, --file=FILE Read FILE as a teshfile.\n",\r
+ " remark :\n",\r
+ " all argument of the command line without\n",\r
+ " option is dealed as a tesh file.\n",\r
+ " -h, --help Print this message and exit.\n",\r
+ " -i, --keep-going-unit Ignore failures from commands.\n",\r
+ " The possible failures are :\n",\r
+ " - the exit code differ from the expected\n",\r
+ " - the signal throw differ from the expected\n",\r
+ " - the output differ from the expected\n",\r
+ " - the read pipe is broken\n",\r
+ " - the write pipe is broken\n",\r
+ " - the command assigned delay is outdated\n",\r
+ " -I DIRECTORY, --include-dir=DIRECTORY Search DIRECTORY for included files.\n",\r
+ " -j [N], --jobs[=N] Allow N commands at once; infinite commands with\n"\r
+ " no arg.\n",\r
+ " -k, --keep-going Keep going when some commands can't be made or\n",\r
+ " failed.\n",\r
+ " -c, --just-print Don't actually run any commands; just print them.\n",\r
+ " -p, --print-data-base Display tesh's internal database.\n",\r
+ " -q, --question Run no commands; exit status says if up to date.\n",\r
+ " -s, --silent, Don't echo commands.\n",\r
+ " -V, --version Print the version number of tesh and exit.\n",\r
+ " -d, --dont-print-directory Don't display the current directory.\n",\r
+ " -n, --dry-run Check the syntax of the specified tesh files, display the result and exit.\n",\r
+ " -t, --timeout Wait the end of the commands at most timeout seconds.\n",\r
+ " -S, --check-syntax Check the syntax of the tesh files before run them. \n",\r
+ " -x, --suffix Consider the new suffix for the tesh files.\n"\r
+ " remark :\n",\r
+ " the default suffix for the tesh files is \".tesh\".\n",\r
+ " -a, --read-me Print the read me file and exit.\n",\r
+ " -b, --build-file Build a tesh file.\n",\r
+ " -r, --load-directory Run all the tesh files located in the directories specified by the option --directory.\n",\r
+ " -v, --summary Display the status of the commands.\n",\r
+ " -m, --detail-summary Detail the summary of the run.\n",\r
+ " -F file , --exclude=FILE Ignore the tesh file FILE.\n",\r
+ " -l format, --log Format of the xbt logs.\n",\r
+ NULL\r
+};\r
+\r
+/* the string of options of tesh */ \r
+static char \r
+optstring[1 + sizeof (opt_entries) / sizeof (opt_entries[0]) * 3];\r
+\r
+/* the option table of tesh */\r
+static struct \r
+option longopts[(sizeof (opt_entries) / sizeof (s_optentry_t))];\r
+\r
+static void\r
+init_options(void);\r
+\r
+static int\r
+process_command_line(int argc, char** argv);\r
+\r
+static void\r
+load(void);\r
+\r
+static void\r
+print_usage(void);\r
+\r
+static void\r
+print_version(void);\r
+\r
+static void\r
+finalize(void);\r
+\r
+static void\r
+print_readme(void);\r
+\r
+static int\r
+init(void);\r
+\r
+\r
+static void \r
+sig_abort_handler(int signum)\r
+{\r
+ /* TODO : implement this function */\r
+ INFO0("sig_abort_handler() called");\r
+}\r
+\r
+static void \r
+sig_int_handler(int signum)\r
+{\r
+ /* TODO : implement this function */\r
+ INFO0("sig_int_handler() called");\r
+}\r
+\r
+static void \r
+free_string(void* str)\r
+{\r
+ free(*(void**)str);\r
+}\r
+\r
+int\r
+main(int argc, char* argv[])\r
+{\r
+ int _argc;\r
+\r
+ /* set the locale to the default*/\r
+ setlocale(LC_ALL,"");\r
+ \r
+ /* initialize tesh */\r
+ if(init() < 0)\r
+ finalize();\r
+ \r
+ /* process the command line */\r
+ if(process_command_line(argc, argv) < 0)\r
+ finalize();\r
+ \r
+ /* move to the root directory (the directory may change during the command line processing) */\r
+ chdir(root_directory->name);\r
+ \r
+ /* initialize the xbt library \r
+ * for thread portability layer\r
+ */\r
+ \r
+ /* xbt initialization */\r
+\r
+ if((_argc = xbt_dynar_length(logs)))\r
+ {\r
+ int i;\r
+\r
+ char** _argv = (char**)calloc(_argc, sizeof(char*));\r
+ \r
+ for(i = 0; i < _argc; i++)\r
+ xbt_dynar_pop(logs, &(_argv[i]));\r
+\r
+ xbt_init(&_argc, _argv);\r
+\r
+ while(--i)\r
+ free(_argv[i]);\r
+\r
+ free(_argv);\r
+ }\r
+ else\r
+ xbt_init(&argc, argv);\r
+ \r
+ /* the user wants to display the usage of tesh */\r
+ if(print_version_flag)\r
+ {\r
+ print_version();\r
+\r
+ if(!print_usage_flag)\r
+ finalize();\r
+ }\r
+ \r
+ /* the user wants to display the usage of tesh */\r
+ if(print_usage_flag)\r
+ {\r
+ print_usage();\r
+ finalize();\r
+ }\r
+ \r
+ /* the user wants to display the semantic of the tesh file metacommands */\r
+ if(print_readme_flag)\r
+ {\r
+ print_readme();\r
+ finalize();\r
+ }\r
+ \r
+ /* load tesh files */\r
+ load();\r
+ \r
+ \r
+ /* use by the finalize function to known if it must display the tesh usage */ \r
+ loaded = 1;\r
+\r
+ if(-2 == jobs_nb)\r
+ {/* --jobs is not specified (use the default value) */\r
+ jobs_nb = default_jobs_nb;\r
+ }\r
+ else if(optional_jobs_nb == jobs_nb)\r
+ {/* --jobs option is specified with no args (use one job per unit) */\r
+ jobs_nb = fstreams_get_size(fstreams);\r
+ }\r
+\r
+ if(jobs_nb > fstreams_get_size(fstreams))\r
+ {/* --jobs = N is specified and N is more than the number of tesh files */\r
+ \r
+ WARN0("Number of requested jobs exceed the number of files to run");\r
+ \r
+ /* assume one job per file */\r
+ jobs_nb = fstreams_get_size(fstreams);\r
+ }\r
+\r
+ /* initialize the semaphore used to synchronize all the units */\r
+ jobs_sem = xbt_os_sem_init(jobs_nb);\r
+\r
+ /* initialize the semaphore used by the runner to wait for the end of all units */\r
+ units_sem = xbt_os_sem_init(0);\r
+ \r
+ /* initialize the runner */\r
+ if(runner_init(check_syntax_flag, timeout, fstreams) < 0)\r
+ finalize();\r
+ \r
+ if(just_print_flag && silent_flag)\r
+ silent_flag = 0;\r
+ \r
+ if(just_print_flag && dry_run_flag)\r
+ WARN0("mismatch in the syntax : --just-check-syntax and --just-display options at same time");\r
+ \r
+ /* run all the units */\r
+ runner_run();\r
+ \r
+ \r
+ /* show the result of the units */\r
+ if(summary_flag || dry_run_flag)\r
+ runner_summarize();\r
+ \r
+ /* all the test are runned, destroy the runner */\r
+ runner_destroy();\r
+ \r
+ /* then, finalize tesh (release all the allocated memory and exits) */\r
+ finalize();\r
+ \r
+ #ifndef WIN32\r
+ return exit_code;\r
+ #endif\r
+ \r
+}\r
+\r
+/* init -- initialize tesh : allocated all the objects needed by tesh to run\r
+ * the tesh files specified in the command line.\r
+ *\r
+ * return If successful the function returns zero. Otherwise the function returns\r
+ * -1 and sets the global variable errno to the appropriate error code.\r
+ */\r
+\r
+\r
+\r
+static int\r
+init(void)\r
+{\r
+ char* buffer;\r
+ char* suffix = strdup(".tesh");\r
+ \r
+ #ifdef WIN32\r
+ /* Windows specific : don't display the general-protection-fault message box and\r
+ * the the critical-error-handler message box (instead the system send the error\r
+ * to the calling process : tesh)\r
+ */\r
+ prev_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);\r
+ #else\r
+ struct sigaction act;\r
+ /* Ignore pipe issues.\r
+ * They will show up when we try to send data to dead buddies, \r
+ * but we will stop doing so when we're done with provided input \r
+ */\r
+ memset(&act,0, sizeof(struct sigaction));\r
+ act.sa_handler = SIG_IGN;\r
+ sigaction(SIGPIPE, &act, NULL);\r
+ \r
+ \r
+ memset(&act,0, sizeof(struct sigaction));\r
+ act.sa_handler = sig_abort_handler;\r
+ sigaction(SIGABRT, &act, NULL);\r
+ \r
+ memset(&act,0, sizeof(struct sigaction));\r
+ act.sa_handler = sig_int_handler;\r
+ sigaction(SIGINT, &act, NULL);\r
+ \r
+ #endif\r
+ \r
+ err_mutex = xbt_os_mutex_init();\r
+ \r
+ /* handle the abort signal */\r
+ /*signal(SIGABRT, sig_abort_handler);*/\r
+ \r
+ /* handle the interrupt signal */\r
+ /*signal(SIGINT, sig_int_handler);*/\r
+ \r
+ /* used to store the files to run */\r
+ if(!(fstreams = fstreams_new(DEFAULT_FSTREAMS_CAPACITY, (void_f_pvoid_t)fstream_free)))\r
+ {\r
+ ERROR1("(system error) %s", strerror(errno));\r
+ return -1;\r
+ }\r
+ \r
+ /* register the current directory */\r
+ if(!(buffer = getcwd(NULL, 0)))\r
+ {\r
+ ERROR1("(system error) %s", strerror(errno));\r
+ return -1;\r
+ }\r
+ \r
+ /* save the root directory */\r
+ if(!(root_directory = directory_new(buffer)))\r
+ {\r
+ ERROR1("(system error) %s", strerror(errno));\r
+ return -1;\r
+ }\r
+ \r
+ free(buffer);\r
+ \r
+ /* this vector contains all the errors of the run */\r
+ errors = xbt_dynar_new(sizeof(xerror_t), free);\r
+ \r
+ /* the directories to loads */\r
+ if(!(directories = directories_new()))\r
+ {\r
+ ERROR1("(system error) %s", strerror(errno));\r
+ return -1;\r
+ }\r
+ \r
+ /* the include directories */\r
+ include_dirs = xbt_dynar_new(sizeof(directory_t), (void_f_pvoid_t)directory_free);\r
+ \r
+ /* xbt logs option */\r
+ if(!(logs = xbt_dynar_new(sizeof(char*), free_string)))\r
+ {\r
+ ERROR1("(system error) %s", strerror(errno));\r
+ return -1;\r
+ }\r
+ \r
+ /* the excluded files */\r
+ if(!(excludes = excludes_new()))\r
+ {\r
+ ERROR1("(system error) %s", strerror(errno));\r
+ return -1;\r
+ }\r
+ \r
+ /* the suffixes */\r
+ suffixes = xbt_dynar_new(sizeof(char*),free_string);\r
+ \r
+ /* register the default suffix ".tesh" */\r
+ xbt_dynar_push(suffixes, &suffix);\r
+ \r
+ return 0;\r
+}\r
+\r
+/* load -- load the tesh files to run */\r
+static void\r
+load(void)\r
+{\r
+ \r
+ /* if the directories object is not empty load all the tesh files contained in\r
+ * the directories specified in the command line (this tesh files must have the\r
+ * a suffix specified in the suffixes object.\r
+ */\r
+ if(!directories_is_empty(directories))\r
+ directories_load(directories, fstreams, suffixes);\r
+ \r
+ /* if no tesh file has been specified in the command line try to load the default tesh file\r
+ * teshfile from the current directory\r
+ */\r
+ if(fstreams_is_empty(fstreams))\r
+ {\r
+ struct stat buffer = {0};\r
+ \r
+ /* add the default tesh file if it exists in the current directory */\r
+ if(!stat("teshfile", &buffer) && S_ISREG(buffer.st_mode))\r
+ fstreams_add(fstreams, fstream_new(getcwd(NULL, 0), "teshfile"));\r
+ }\r
+ \r
+ /* excludes the files specified in the command line and stored in the excludes object */\r
+ if(!excludes_is_empty(excludes) && !fstreams_is_empty(fstreams))\r
+ {\r
+ /* check the files to excludes before */ \r
+ excludes_check(excludes, fstreams);\r
+ \r
+ /* exclude the specified tesh files */\r
+ fstreams_exclude(fstreams, excludes);\r
+ }\r
+ \r
+ /* if the fstreams object is empty use the stdin */\r
+ if(fstreams_is_empty(fstreams))\r
+ fstreams_add(fstreams, fstream_new(NULL, "stdin"));\r
+ \r
+ /* load the tesh files (open them) */\r
+ fstreams_load(fstreams);\r
+ \r
+}\r
+\r
+/* finalize -- cleanup all the allocated objects and display the tesh usage if needed */\r
+static void\r
+finalize(void)\r
+{\r
+ \r
+ if(is_tesh_root && !summary_flag)\r
+ {\r
+ xerror_t error;\r
+ unsigned int i;\r
+\r
+ xbt_dynar_foreach(errors, i, error)\r
+ {\r
+ if(error->command)\r
+ fprintf(stderr, "[tesh/ERROR] %s : <Command `%s'> <Unit `%s'> <C%d (%s)>\n", error->reason, error->command, error->unit, error->errcode, error_to_string(error->errcode));\r
+ else if(!error->command && error->unit)\r
+ fprintf(stderr, "[tesh/ERROR] %s : Unit `%s' - C%d (%s)\n", error->reason, error->unit, error->errcode, error_to_string(error->errcode));\r
+ else if(!error->command && !error->unit && error->reason)\r
+ fprintf(stderr, "[tesh/ERROR] %s : C%d (%s)\n", error->reason, error->errcode, error_to_string(error->errcode)); \r
+ else if(!error->command && !error->unit && !error->reason)\r
+ fprintf(stderr, "[tesh/ERROR] C%d (%s)\n", error->errcode, error_to_string(error->errcode)); \r
+ \r
+ }\r
+ }\r
+ \r
+ /* delete vector of errors */\r
+ if(errors)\r
+ xbt_dynar_free(&errors);\r
+ \r
+ xbt_os_mutex_destroy(err_mutex);\r
+ \r
+ /* delete the fstreams object */\r
+ if(fstreams)\r
+ fstreams_free((void**)&fstreams);\r
+ \r
+ /* delete the excludes object */\r
+ if(excludes) \r
+ excludes_free((void**)&excludes);\r
+ \r
+ /* delete the directories object */\r
+ if(directories)\r
+ directories_free((void**)&directories);\r
+ \r
+ /* delete the root directory object */\r
+ if(root_directory)\r
+ directory_free((void**)&root_directory);\r
+ \r
+ /* delete the include directories object */\r
+ if(include_dirs)\r
+ xbt_dynar_free(&include_dirs);\r
+ \r
+ /* delete the list of tesh files suffixes */ \r
+ if(suffixes)\r
+ xbt_dynar_free(&suffixes);\r
+ \r
+ /* delete the xbt log options list */\r
+ if(logs)\r
+ xbt_dynar_free(&logs);\r
+ \r
+ /* destroy the semaphore used to synchronize the units */\r
+ if(jobs_sem)\r
+ xbt_os_sem_destroy(jobs_sem);\r
+ \r
+ /* destroy the semaphore used by the runner used to wait for the end of the units */\r
+ if(units_sem)\r
+ xbt_os_sem_destroy(units_sem);\r
+ \r
+ /* exit from the xbt framework */\r
+ xbt_exit();\r
+ \r
+ /* Windows specific (restore the previouse error mode */\r
+ #ifdef WIN32\r
+ SetErrorMode(prev_error_mode);\r
+ #endif\r
+ \r
+ if(!summary_flag && !dry_run_flag && !silent_flag && !just_print_flag && !print_version_flag && !print_usage_flag && is_tesh_root)\r
+ {\r
+ if(!exit_code)\r
+ INFO2("tesh terminated with exit code %d : %s",exit_code, "success");\r
+ else\r
+ ERROR2("tesh terminated with exit code %d : %s",exit_code, error_to_string(exit_code));\r
+ }\r
+ \r
+ \r
+ /* exit with the last error code */\r
+ exit(exit_code);\r
+}\r
+\r
+/* init_options -- initialize the options string */\r
+static void\r
+init_options (void)\r
+{\r
+ char *p;\r
+ unsigned int i;\r
+ \r
+ /* the function has been already called */\r
+ if(optstring[0] != '\0')\r
+ return;\r
+ \r
+ p = optstring;\r
+ \r
+ *p++ = '-';\r
+ \r
+ for (i = 0; opt_entries[i].c != '\0'; ++i)\r
+ {\r
+ /* initialize the long name of the option*/\r
+ longopts[i].name = (opt_entries[i].long_name == 0 ? "" : opt_entries[i].long_name);\r
+ \r
+ /* getopt_long returns the value of val */\r
+ longopts[i].flag = 0;\r
+ \r
+ /* the short option */\r
+ longopts[i].val = opt_entries[i].c;\r
+ \r
+ /* add the short option in the options string */\r
+ *p++ = opt_entries[i].c;\r
+ \r
+ switch (opt_entries[i].type)\r
+ {\r
+ /* if this option is used to set a flag or if the argument must be ignored\r
+ * the option has no argument\r
+ */\r
+ case flag:\r
+ longopts[i].has_arg = no_argument;\r
+ break;\r
+ \r
+ /* the option has an argument */\r
+ case string:\r
+ case number:\r
+ \r
+ *p++ = ':';\r
+ \r
+ if(opt_entries[i].optional_value != 0)\r
+ {\r
+ *p++ = ':';\r
+ \r
+ longopts[i].has_arg = optional_argument;\r
+ }\r
+ else\r
+ longopts[i].has_arg = required_argument;\r
+ \r
+ break;\r
+ }\r
+ }\r
+ \r
+ *p = '\0';\r
+ longopts[i].name = 0;\r
+}\r
+\r
+/* process_command_line -- process the command line\r
+ *\r
+ * param argc the number of the arguments contained by the command line.\r
+ * param The array of C strings containing all the arguments of the command \r
+ * line.\r
+ *\r
+ * return If successful, the function returns 0. Otherwise -1 is returned\r
+ * and sets the global variable errno to indicate the error.\r
+ *\r
+ * errors [ENOENT] A file name specified in the command line does not exist\r
+ */ \r
+\r
+static int\r
+process_command_line(int argc, char** argv)\r
+{\r
+ register const struct s_optentry* entry;\r
+ register int c;\r
+ directory_t directory;\r
+ fstream_t fstream;\r
+ \r
+ /* initialize the options string of tesh */\r
+ init_options();\r
+ \r
+ /* let the function getopt_long display the errors if any */\r
+ opterr = 1;\r
+ \r
+ /* set option index to zero */\r
+ optind = 0;\r
+ \r
+ while (optind < argc)\r
+ {\r
+ c = getopt_long(argc, argv, optstring, longopts, (int *) 0);\r
+ \r
+ if(c == EOF)\r
+ {\r
+ /* end of the command line or "--". */\r
+ break;\r
+ }\r
+ else if (c == 1)\r
+ {\r
+ /* no option specified, assume it's a tesh file to run */\r
+ char* path;\r
+ char* delimiter;\r
+ \r
+ /* getpath returns -1 when the file to get the path doesn't exist */\r
+ if(getpath(optarg, &path) < 0)\r
+ {\r
+ exit_code = errno;\r
+ \r
+ if(ENOENT == errno)\r
+ ERROR1("File %s does not exist", optarg);\r
+ else\r
+ ERROR0("Insufficient memory is available to parse the command line : system error");\r
+ \r
+ return -1;\r
+ }\r
+ \r
+ /* get to the last / (if any) to get the short name of the file */\r
+ delimiter = strrchr(optarg,'/');\r
+ \r
+ /* create a new file stream which represents the tesh file to run */\r
+ fstream = fstream_new(path, delimiter ? delimiter + 1 : optarg);\r
+ \r
+ free(path);\r
+ \r
+ /* if the list of all tesh files to run already contains this file\r
+ * destroy it and display a warning, otherwise add it in the list.\r
+ */\r
+ if(fstreams_contains(fstreams, fstream))\r
+ {\r
+ fstream_free(&fstream);\r
+ WARN1("File %s already specified to be run", optarg);\r
+ }\r
+ else\r
+ fstreams_add(fstreams, fstream);\r
+ \r
+ \r
+ \r
+ \r
+ }\r
+ else if (c == '?')\r
+ {\r
+ /* unknown option, let getopt_long() displays the error */\r
+ ERROR0("Command line processing failed : invalid command line");\r
+ exit_code = EINVCMDLINE;\r
+ return -1;\r
+ }\r
+ else\r
+ {\r
+ for(entry = opt_entries; entry->c != '\0'; ++entry)\r
+ \r
+ if(c == entry->c)\r
+ {\r
+ \r
+ switch (entry->type)\r
+ {\r
+ /* impossible */\r
+ default:\r
+ ERROR0("Command line processing failed : internal error");\r
+ exit_code = EPROCCMDLINE;\r
+ return -1;\r
+ \r
+ \r
+ /* flag options */\r
+ case flag:\r
+ /* set the flag to one */\r
+ *(int*) entry->value = 1;\r
+ \r
+ break;\r
+ \r
+ /* string options */\r
+ case string:\r
+ \r
+ if(!optarg)\r
+ {\r
+ /* an option with a optional arg is specified use the entry->optional_value */\r
+ optarg = (char*)entry->optional_value;\r
+ }\r
+ else if (*optarg == '\0')\r
+ {\r
+ /* a non optional argument is not specified */\r
+ ERROR2("Option %c \"%s\"requires an argument",entry->c,entry->long_name);\r
+ exit_code = ENOARG;\r
+ return -1;\r
+ }\r
+ \r
+ /* --load-directory option */\r
+ if(!strcmp(entry->long_name,"load-directory"))\r
+ {\r
+ #ifdef WIN32\r
+ struct stat info = {0};\r
+ if(stat(optarg, &info) || !S_ISDIR(info.st_mode))\r
+ {\r
+ ERROR1("%s is not a directory",optarg);\r
+ exit_code = ENOTDIR;\r
+ return -1;\r
+ } \r
+\r
+ #else\r
+ char* path;\r
+ \r
+ if(translatepath(optarg, &path) < 0)\r
+ {\r
+ exit_code = errno;\r
+ \r
+ if(ENOTDIR == errno)\r
+ ERROR1("%s is not a directory",optarg);\r
+ else\r
+ ERROR0("Insufficient memory is available to process the command line - system error");\r
+ \r
+ return -1;\r
+ \r
+ }\r
+ #endif\r
+ else\r
+ {\r
+ #ifdef WIN32\r
+ directory = directory_new(optarg);\r
+ #else\r
+ directory = directory_new(path);\r
+ free(path);\r
+ #endif\r
+ \r
+ if(directories_contains(directories, directory))\r
+ {\r
+ directory_free((void**)&directory);\r
+ WARN1("Directory %s already specified to be load",optarg);\r
+ }\r
+ else\r
+ directories_add(directories, directory);\r
+ \r
+ \r
+ } \r
+ }\r
+ else if(!strcmp(entry->long_name,"directory"))\r
+ {\r
+ #ifdef WIN32\r
+ struct stat info = {0};\r
+ if(stat(optarg, &info) || !S_ISDIR(info.st_mode))\r
+ {\r
+ ERROR1("%s is not a directory",optarg);\r
+ exit_code = ENOTDIR;\r
+ return -1;\r
+ } \r
+\r
+ #else\r
+ char* path ;\r
+ \r
+ if(translatepath(optarg, &path) < 0)\r
+ {\r
+ exit_code = errno;\r
+ \r
+ if(ENOTDIR == errno)\r
+ ERROR1("%s is not a directory",optarg);\r
+ else\r
+ ERROR0("Insufficient memory is available to process the command line - system error");\r
+ \r
+ return -1;\r
+ }\r
+ #endif\r
+ else\r
+ {\r
+ char* buffer = getcwd(NULL, 0);\r
+\r
+ #ifdef WIN32\r
+ \r
+ if(!strcmp(buffer, optarg))\r
+ WARN1("Already in the directory %s", optarg);\r
+ else if(!print_directory_flag)\r
+ INFO1("Entering directory \"%s\"",optarg);\r
+ \r
+ chdir(optarg);\r
+ #else\r
+ \r
+ if(!strcmp(buffer, path))\r
+ WARN1("Already in the directory %s", optarg);\r
+ else if(!print_directory_flag)\r
+ INFO1("Entering directory \"%s\"",path);\r
+\r
+ chdir(path);\r
+ free(path);\r
+ #endif\r
+\r
+ free(buffer);\r
+ \r
+ \r
+ } \r
+ }\r
+ \r
+ /* --suffix option */\r
+ else if(!strcmp(entry->long_name,"suffix"))\r
+ {\r
+ if(strlen(optarg) > MAX_SUFFIX)\r
+ {\r
+ ERROR1("Suffix %s too long",optarg);\r
+ exit_code = ESUFFIXTOOLONG; \r
+ return -1;\r
+ }\r
+ \r
+ if(optarg[0] == '.')\r
+ {\r
+ char* cur;\r
+ unsigned int i;\r
+ int exists = 0;\r
+\r
+ char suffix[MAX_SUFFIX + 2] = {0};\r
+ sprintf(suffix,".%s",optarg);\r
+ \r
+ xbt_dynar_foreach(suffixes, i, cur)\r
+ {\r
+ if(!strcmp(suffix, cur))\r
+ {\r
+ exists = 1;\r
+ break;\r
+ }\r
+ }\r
+\r
+ if(exists)\r
+ WARN1("Suffix %s already specified to be used", optarg);\r
+ else\r
+ xbt_dynar_push(suffixes, &suffix);\r
+ }\r
+ else\r
+ {\r
+ char* cur;\r
+ unsigned int i;\r
+ int exists = 0;\r
+\r
+ xbt_dynar_foreach(suffixes, i, cur)\r
+ {\r
+ if(!strcmp(optarg, cur))\r
+ {\r
+ exists = 1;\r
+ break;\r
+ }\r
+ }\r
+\r
+ if(exists)\r
+ WARN1("Suffix %s already specified to be used", optarg);\r
+ else\r
+ xbt_dynar_push(suffixes, &optarg); \r
+ }\r
+ }\r
+ /* --file option */\r
+ else if(!strcmp(entry->long_name,"file"))\r
+ {\r
+ char* path;\r
+ char* delimiter;\r
+ \r
+ if(getpath(optarg, &path) < 0)\r
+ {\r
+ exit_code = errno;\r
+ \r
+ if(ENOENT == errno)\r
+ ERROR1("File %s does not exist", optarg);\r
+ else\r
+ ERROR0("Insufficient memory is available to process the command line - system error");\r
+ \r
+ return -1;\r
+ }\r
+ \r
+ delimiter = strrchr(optarg,'/');\r
+ \r
+ fstream = fstream_new(path, delimiter ? delimiter + 1 : optarg);\r
+ \r
+ free(path);\r
+ \r
+ if(fstreams_contains(fstreams, fstream))\r
+ {\r
+ fstream_free(&fstream);\r
+ WARN1("File %s already specified to run", optarg);\r
+ }\r
+ else\r
+ fstreams_add(fstreams, fstream);\r
+ }\r
+ /* --include-dir option */\r
+ else if(!strcmp(entry->long_name,"include-dir"))\r
+ {\r
+ #ifdef WIN32\r
+ struct stat info = {0};\r
+ if(stat(optarg, &info) || !S_ISDIR(info.st_mode))\r
+ {\r
+ ERROR1("%s is not a directory",optarg);\r
+ exit_code = ENOTDIR;\r
+ return -1;\r
+ } \r
+\r
+ #else\r
+ char* path ;\r
+ \r
+ if(translatepath(optarg, &path) < 0)\r
+ {\r
+ exit_code = errno;\r
+ \r
+ if(ENOTDIR == errno)\r
+ ERROR1("%s is not a directory",optarg);\r
+ else\r
+ ERROR0("Insufficient memory is available to process the command line - system error");\r
+ \r
+ return -1;\r
+ }\r
+ #endif\r
+ else\r
+ {\r
+ int exists = 0;\r
+ unsigned int i;\r
+ directory_t cur;\r
+ #ifdef WIN32\r
+ directory = directory_new(optarg);\r
+ #else\r
+ directory = directory_new(path);\r
+ free(path);\r
+ #endif\r
+\r
+ xbt_dynar_foreach(include_dirs, i , cur)\r
+ {\r
+ if(!strcmp(cur->name, optarg))\r
+ {\r
+ exists = 1;\r
+ break;\r
+ }\r
+ }\r
+\r
+ if(exists)\r
+ {\r
+ directory_free((void**)&directory);\r
+ WARN1("Include directory %s already specified to be used",optarg);\r
+ \r
+ }\r
+ else\r
+ xbt_dynar_push(include_dirs, &directory);\r
+\r
+ }\r
+ }\r
+ /* --exclude option */ \r
+ else if(!strcmp(entry->long_name,"exclude"))\r
+ {\r
+ char* path;\r
+ char* delimiter;\r
+ \r
+ if(getpath(optarg, &path) < 0)\r
+ {\r
+ exit_code = errno;\r
+ \r
+ if(ENOENT == errno)\r
+ ERROR1("file %s does not exist", optarg);\r
+ else\r
+ ERROR0("Insufficient memory is available to process the command line - system error");\r
+ \r
+ return -1;\r
+ }\r
+ \r
+ delimiter = strrchr(optarg,'/');\r
+ \r
+ fstream = fstream_new(path, delimiter ? delimiter + 1 : optarg);\r
+ free(path);\r
+ \r
+ if(excludes_contains(excludes, fstream))\r
+ {\r
+ fstream_free(&fstream);\r
+ WARN1("File %s already specified to be exclude", optarg);\r
+ }\r
+ else\r
+ excludes_add(excludes, fstream);\r
+ \r
+ }\r
+ /* --log option */\r
+ else if(!strcmp(entry->long_name,"log"))\r
+ {\r
+ xbt_dynar_push(logs, &optarg);\r
+ }\r
+ else\r
+ {\r
+ INFO1("Unexpected option %s", optarg);\r
+ return -1;\r
+ }\r
+ \r
+ \r
+ break;\r
+ \r
+ /* strictly positve number options */\r
+ case number:\r
+ \r
+ if ((NULL == optarg) && (argc > optind))\r
+ {\r
+ const char* cp;\r
+ \r
+ for (cp = argv[optind]; isdigit(cp[0]); ++cp)\r
+ if(cp[0] == '\0')\r
+ optarg = argv[optind++];\r
+ }\r
+ \r
+ /* the number option is specified */\r
+ if(NULL != optarg)\r
+ {\r
+ int i = atoi(optarg);\r
+ const char *cp;\r
+\r
+ for (cp = optarg; isdigit(cp[0]); ++cp);\r
+ \r
+ if (i < 1 || cp[0] != '\0')\r
+ {\r
+ ERROR2("Option %c \"%s\" requires an strictly positive integer as argument",entry->c, entry->long_name);\r
+ exit_code = ENOTPOSITIVENUM;\r
+ return -1;\r
+ }\r
+ else\r
+ *(int*)entry->value = i;\r
+ }\r
+ /* the number option is specified but has no arg, use the optional value*/\r
+ else\r
+ *(int*)entry->value = *(int*) entry->optional_value;\r
+ \r
+ break;\r
+ \r
+ }\r
+ break;\r
+ }\r
+ }\r
+ }\r
+\r
+ return 0;\r
+}\r
+\r
+static void\r
+print_usage(void)\r
+{\r
+ const char **cpp;\r
+ FILE* stream;\r
+ \r
+ stream = exit_code ? stderr : stdout;\r
+ \r
+ fprintf (stream, "Usage: tesh [options] [file] ...\n");\r
+ \r
+ for (cpp = usage; *cpp; ++cpp)\r
+ fputs (*cpp, stream);\r
+ \r
+ fprintf(stream, "\nReport bugs to <martin.quinson@loria.fr | malek.cherier@loria.fr>\n");\r
+}\r
+\r
+static void\r
+print_version(void)\r
+{\r
+ /* TODO : display the version of tesh */\r
+ printf("Version :\n");\r
+ printf(" tesh version %s : Mini shell specialized in running test units by Martin Quinson \n", version);\r
+ printf(" and Malek Cherier\n");\r
+ printf(" Copyright (c) 2007, 2008 Martin Quinson, Malek Cherier\n");\r
+ printf(" All rights reserved\n");\r
+ printf(" This program is free software; you can redistribute it and/or modify it\n");\r
+ printf(" under the terms of the license (GNU LGPL) which comes with this package.\n\n");\r
+ \r
+ if(!print_usage_flag)\r
+ printf("Report bugs to <martin.quinson@loria.fr | malek.cherier@loria.fr>");\r
+}\r
+\r
+static void\r
+print_readme(void)\r
+{\r
+ size_t len;\r
+ char * line = NULL;\r
+ \r
+ FILE* stream = fopen("README.txt", "r");\r
+ \r
+ if(!stream)\r
+ {\r
+ ERROR0("Unable to locate the README.txt file");\r
+ exit_code = EREADMENOTFOUND;\r
+ return;\r
+ }\r
+ \r
+ while(getline(&line, &len, stream) != -1)\r
+ printf("%s",line);\r
+ \r
+ fclose(stream);\r
+}\r
+\r
+\r