2 * src/main.c - this file contains the main function of tesh.
4 * Copyright 2008,2009 Martin Quinson, Malek Cherier All right reserved.
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the license (GNU LGPL) which comes with this package.
14 #include <directory.h>
15 #include <directories.h>
23 * entry used to define the parameter of a tesh option.
25 typedef struct s_optentry
27 int c; /* the character of the option */
30 * the type of the argument of the option
34 flag, /* it's a flag option, by default the flag is set to zero */
35 string, /* the option has strings as argument */
36 number /* the option has an integral positive number as argument */
39 byte* value; /* the value of the option */
40 byte* optional_value; /* the optional value of the option if not specified */
41 const char * long_name; /* the long name of the command */
42 }s_optentry_t,* optentry_t;
46 XBT_LOG_NEW_DEFAULT_CATEGORY(tesh,"TEst SHell utility");
49 /* Windows specific : the previous process error mode */
54 /* this object represents the root directory */
56 root_directory = NULL;
58 /* the current version of tesh */
62 /* ------------------------------------------------------------ */
64 /* ------------------------------------------------------------ */
66 /* ------------------------------------------------------------ */
68 /* ------------------------------------------------------------ */
71 /* --jobs is specified with arg */
75 /* --jobs option is not specified (use the default job count) */
79 /* --jobs is specified but has no arg (one job per unit) */
81 optional_jobs_nb = -1;
83 /* the global timeout */
87 /* ------------------------------------------------------------ */
89 /* ------------------------------------------------------------ */
91 /* --C change the directory before running the units */
95 /* the include directories : see the !i metacommand */
99 /* the list of tesh files to run */
103 /* the list 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 keep_going_unit_flag = 0;
127 /* if 1, display tesh usage */
129 print_usage_flag = 0;
131 /* if 1, display the tesh version */
133 print_version_flag = 0;
135 /* if 1, the status of all the units is display at
141 /* if 1 and the flag want_summay is set to 1 tesh display the detailed summary of the run */
143 detail_summary_flag = 0;
145 /* if 1, the directories are displayed */
147 print_directory_flag = 0;
149 /* if 1, just check the syntax of all the tesh files
155 /* if 1, display the tesh files syntax and exit */
157 print_readme_flag = 0;
165 /* the semaphore used to synchronize the jobs */
169 /* the semaphore used by the runner to wait the end of all the units */
192 /* the table of the entries of the options */
193 static const struct s_optentry opt_entries[] =
195 { 'C', string, (byte*)NULL, 0, "directory" },
196 { 'x', string, (byte*)&suffixes, 0, "suffix" },
197 { 'f', string, (byte*)&fstreams, 0, "file" },
198 { 'h', flag, (byte*)&print_usage_flag, 0, "help" },
199 { 'a', flag, (byte*)&print_readme_flag, 0, "README" },
200 { 'i', flag, (byte*)&keep_going_unit_flag, 0, "keep-going-unit" },
201 { 'I', string, (byte*)&include_dirs, 0, "include-dir" },
202 { 'j', number, (byte*)&jobs_nb, (byte*) &optional_jobs_nb, "jobs" },
203 { 'k', flag, (byte*)&keep_going_flag, 0, "keep-going" },
204 { 'm', flag, (byte*)&detail_summary_flag, 0, "detail-summary" },
205 { 'c', flag, (byte*)&just_print_flag, 0, "just-print" },
206 { 's', flag, (byte*)&silent_flag, 0, "silent" },
207 { 'V', flag, (byte*)&print_version_flag, 0, "version" },
208 { 'w', flag, (byte*)&print_directory_flag, 0,"dont-print-directory" },
209 { 'n', flag, (byte*)&dry_run_flag, 0, "dry-run"},
210 { 't', number, (byte*)&timeout, 0, "timeout" },
211 { 'r', string, (byte*)&directories, 0, "load-directory"},
212 { 'v', flag, (byte*)&summary_flag, 0, "summary"},
213 { 'F', string,(byte*)&excludes, 0, "exclude"},
214 { 'l', string, (byte*)NULL, 0, "log" },
219 static const char* usage[] =
222 " -C DIRECTORY, --directory=DIRECTORY Change to DIRECTORY before running any commands.\n",
223 " -e, --environment-overrides Environment variables override files.\n",
224 " -f FILE, --file=FILE Read FILE as a teshfile.\n",
226 " all argument of the command line without\n",
227 " option is dealed as a tesh file.\n",
228 " -h, --help Print this message and exit.\n",
229 " -i, --keep-going-unit Ignore failures from commands.\n",
230 " The possible failures are :\n",
231 " - the exit code differ from the expected\n",
232 " - the signal throw differ from the expected\n",
233 " - the output differ from the expected\n",
234 " - the read pipe is broken\n",
235 " - the write pipe is broken\n",
236 " - the command assigned delay is outdated\n",
237 " -I DIRECTORY, --include-dir=DIRECTORY Search DIRECTORY for included files.\n",
238 " -j [N], --jobs[=N] Allow N units at once; infinite units with\n"
240 " -k, --keep-going Keep going when some commands can't be made or\n",
242 " -c, --just-print Don't actually run any commands; just print them.\n",
243 " -s, --silent, Don't echo commands.\n",
244 " -V, --version Print the version number of tesh and exit.\n",
245 " -d, --dont-print-directory Don't display the current directory.\n",
246 " -n, --dry-run Check the syntax of the specified tesh files, display the result and exit.\n",
247 " -t, --timeout Wait the end of the commands at most timeout seconds.\n",
248 " -x, --suffix Consider the new suffix for the tesh files.\n"
250 " the default suffix for the tesh files is \".tesh\".\n",
251 " -a, --README Print the read me file and exit.\n",
252 " -r, --load-directory Run all the tesh files located in the directories specified by the option --directory.\n",
253 " -v, --summary Display the status of the commands.\n",
254 " -m, --detail-summary Detail the summary of the run.\n",
255 " -F file , --exclude=FILE Ignore the tesh file FILE.\n",
256 " -l format, --log Format of the xbt logs.\n",
260 /* the string of options of tesh */
262 optstring[1 + sizeof (opt_entries) / sizeof (opt_entries[0]) * 3];
264 /* the option table of tesh */
266 option longopts[(sizeof (opt_entries) / sizeof (s_optentry_t))];
272 process_command_line(int argc, char** argv);
294 sig_abort_handler(int signum)
296 /* TODO : implement this function */
297 INFO0("sig_abort_handler() called");
301 sig_int_handler(int signum)
303 /* TODO : implement this function */
304 INFO0("sig_int_handler() called");
308 free_string(void* str)
314 main(int argc, char* argv[])
316 /* set the locale to the default*/
317 setlocale(LC_ALL,"");
319 /* xbt initialization */
320 xbt_init(&argc, argv);
322 /* initialize tesh */
326 /* process the command line */
327 if(process_command_line(argc, argv) < 0)
330 /* move to the root directory (the directory may change during the command line processing) */
331 chdir(root_directory->name);
333 /* the user wants to display the usage of tesh */
334 if(print_version_flag)
338 if(!print_usage_flag)
342 /* the user wants to display the usage of tesh */
349 /* the user wants to display the semantic of the tesh file metacommands */
350 if(print_readme_flag)
356 /* load tesh files */
360 /* use by the finalize function to known if it must display the tesh usage */
364 {/* --jobs is not specified (use the default value) */
365 jobs_nb = default_jobs_nb;
367 else if(optional_jobs_nb == jobs_nb)
368 {/* --jobs option is specified with no args (use one job per unit) */
369 jobs_nb = fstreams_get_size(fstreams);
372 if(jobs_nb > 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 jobs_nb = fstreams_get_size(fstreams);
381 if(jobs_nb != 1 && dry_run_flag)
384 INFO0("Dry run specified : force jobs count to 1");
387 /* initialize the semaphore used to synchronize all the units */
388 jobs_sem = xbt_os_sem_init(jobs_nb);
390 /* initialize the semaphore used by the runner to wait for the end of all units */
391 units_sem = xbt_os_sem_init(0);
393 /* initialize the runner */
394 if(runner_init(/*check_syntax_flag,*/ timeout, fstreams) < 0)
397 if(just_print_flag && silent_flag)
400 if(just_print_flag && dry_run_flag)
401 WARN0("mismatch in the syntax : --just-check-syntax and --just-display options at same time");
403 /* run all the units */
406 if(runner_is_timedout())
407 ERROR1("Tesh timed out after `(%d)' seconds", timeout);
409 /* show the result of the units */
410 if(detail_summary_flag ||summary_flag || dry_run_flag)
413 /* all the test are runned, destroy the runner */
416 /* then, finalize tesh (release all the allocated memory and exits) */
425 /* init -- initialize tesh : allocated all the objects needed by tesh to run
426 * the tesh files specified in the command line.
428 * return If successful the function returns zero. Otherwise the function returns
429 * -1 and sets the global variable errno to the appropriate error code.
438 char* suffix = strdup(".tesh");
441 /* Windows specific : don't display the general-protection-fault message box and
442 * the the critical-error-handler message box (instead the system send the error
443 * to the calling process : tesh)
445 prev_error_mode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
447 struct sigaction act;
448 /* Ignore pipe issues.
449 * They will show up when we try to send data to dead buddies,
450 * but we will stop doing so when we're done with provided input
452 memset(&act,0, sizeof(struct sigaction));
453 act.sa_handler = SIG_IGN;
454 sigaction(SIGPIPE, &act, NULL);
457 memset(&act,0, sizeof(struct sigaction));
458 act.sa_handler = sig_abort_handler;
459 sigaction(SIGABRT, &act, NULL);
461 memset(&act,0, sizeof(struct sigaction));
462 act.sa_handler = sig_int_handler;
463 sigaction(SIGINT, &act, NULL);
468 /* handle the abort signal */
469 /*signal(SIGABRT, sig_abort_handler);*/
471 /* handle the interrupt signal */
472 /*signal(SIGINT, sig_int_handler);*/
474 /* used to store the files to run */
475 if(!(fstreams = fstreams_new((void_f_pvoid_t)fstream_free)))
477 ERROR1("(system error) %s", strerror(errno));
481 /* register the current directory */
482 if(!(buffer = getcwd(NULL, 0)))
484 ERROR1("(system error) %s", strerror(errno));
488 /* save the root directory */
489 if(!(root_directory = directory_new(buffer)))
491 ERROR1("(system error) %s", strerror(errno));
497 /* the directories to loads */
498 if(!(directories = directories_new()))
500 ERROR1("(system error) %s", strerror(errno));
504 /* the include directories */
505 include_dirs = xbt_dynar_new(sizeof(directory_t), (void_f_pvoid_t)directory_free);
507 /* the excluded files */
508 if(!(excludes = excludes_new()))
510 ERROR1("(system error) %s", strerror(errno));
515 suffixes = xbt_dynar_new(sizeof(char*),free_string);
517 /* register the default suffix ".tesh" */
518 xbt_dynar_push(suffixes, &suffix);
523 /* load -- load the tesh files to run */
528 /* if the directories object is not empty load all the tesh files contained in
529 * the directories specified in the command line (this tesh files must have the
530 * a suffix specified in the suffixes object.
532 if(!directories_is_empty(directories))
533 directories_load(directories, fstreams, suffixes);
535 /* if no tesh file has been specified in the command line try to load the default tesh file
536 * teshfile from the current directory
538 if(fstreams_is_empty(fstreams))
540 struct stat buffer = {0};
542 /* add the default tesh file if it exists in the current directory */
543 if(!stat("teshfile", &buffer) && S_ISREG(buffer.st_mode))
544 fstreams_add(fstreams, fstream_new(getcwd(NULL, 0), "teshfile"));
547 /* excludes the files specified in the command line and stored in the excludes object */
548 if(!excludes_is_empty(excludes) && !fstreams_is_empty(fstreams))
550 /* check the files to excludes before */
551 excludes_check(excludes, fstreams);
553 /* exclude the specified tesh files */
554 fstreams_exclude(fstreams, excludes);
557 /* if the fstreams object is empty use the stdin */
558 if(fstreams_is_empty(fstreams))
559 fstreams_add(fstreams, fstream_new(NULL, "stdin"));
561 /* load the tesh files (open them) */
562 fstreams_load(fstreams);
566 /* finalize -- cleanup all the allocated objects and display the tesh usage if needed */
571 /* delete the fstreams object */
573 fstreams_free((void**)&fstreams);
575 /* delete the excludes object */
577 excludes_free((void**)&excludes);
579 /* delete the directories object */
581 directories_free((void**)&directories);
583 /* delete the root directory object */
585 directory_free((void**)&root_directory);
587 /* delete the include directories object */
589 xbt_dynar_free(&include_dirs);
591 /* delete the list of tesh files suffixes */
593 xbt_dynar_free(&suffixes);
595 /* destroy the semaphore used to synchronize the units */
597 xbt_os_sem_destroy(jobs_sem);
599 /* destroy the semaphore used by the runner used to wait for the end of the units */
601 xbt_os_sem_destroy(units_sem);
603 /* Windows specific (restore the previouse error mode */
605 SetErrorMode(prev_error_mode);
608 if(!summary_flag && !dry_run_flag && !silent_flag && !just_print_flag && !print_version_flag && !print_usage_flag && is_tesh_root)
611 INFO2("Tesh terminated with exit code %d : %s",exit_code, "success");
613 ERROR2("Tesh terminated with exit code `(%s)' (%d)",error_to_string(exit_code, err_kind), exit_code);
616 /* exit from the xbt framework */
619 /* exit with the last error code */
623 /* init_options -- initialize the options string */
630 /* the function has been already called */
631 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)
735 ERROR1("File %s does not exist", optarg);
737 ERROR0("Insufficient memory is available to parse the command line : system error");
742 /* get to the last / (if any) to get the short name of the file */
743 delimiter = strrchr(optarg,'/');
747 delimiter = strrchr(optarg,'\\');
750 /* create a new file stream which represents the tesh file to run */
751 fstream = fstream_new(path, delimiter ? delimiter + 1 : optarg);
755 /* if the list of all tesh files to run already contains this file
756 * destroy it and display a warning, otherwise add it in the list.
758 if(fstreams_contains(fstreams, fstream))
760 fstream_free(&fstream);
761 WARN1("File %s already specified to be run", optarg);
764 fstreams_add(fstreams, fstream);
769 /* unknown option, let getopt_long() displays the error */
770 ERROR0("Command line processing failed : invalid command line");
771 exit_code = EINVCMDLINE;
777 for(entry = opt_entries; entry->c != '\0'; ++entry)
786 ERROR0("Command line processing failed : internal error");
787 exit_code = EPROCCMDLINE;
794 /* set the flag to one */
795 *(int*) entry->value = 1;
804 /* an option with a optional arg is specified use the entry->optional_value */
805 optarg = (char*)entry->optional_value;
807 else if (*optarg == '\0')
809 /* a non optional argument is not specified */
810 ERROR2("Option %c \"%s\"requires an argument",entry->c,entry->long_name);
816 /* --load-directory option */
817 if(!strcmp(entry->long_name,"load-directory"))
820 struct stat info = {0};
821 if(stat(optarg, &info) || !S_ISDIR(info.st_mode))
823 ERROR1("%s is not a directory",optarg);
832 if(translatepath(optarg, &path) < 0)
838 ERROR1("%s is not a directory",optarg);
840 ERROR0("Insufficient memory is available to process the command line - system error");
849 directory = directory_new(optarg);
851 directory = directory_new(path);
855 if(directories_contains(directories, directory))
857 directory_free((void**)&directory);
858 WARN1("Directory %s already specified to be load",optarg);
861 directories_add(directories, directory);
866 else if(!strcmp(entry->long_name,"directory"))
869 struct stat info = {0};
870 if(stat(optarg, &info) || !S_ISDIR(info.st_mode))
872 ERROR1("%s is not a directory",optarg);
881 if(translatepath(optarg, &path) < 0)
887 ERROR1("%s is not a directory",optarg);
889 ERROR0("Insufficient memory is available to process the command line - system error");
896 char* buffer = getcwd(NULL, 0);
900 if(!strcmp(buffer, optarg))
901 WARN1("Already in the directory %s", optarg);
902 else if(!print_directory_flag)
903 INFO1("Entering directory \"%s\"",optarg);
908 if(!strcmp(buffer, path))
909 WARN1("Already in the directory %s", optarg);
910 else if(!print_directory_flag)
911 INFO1("Entering directory \"%s\"",path);
923 /* --suffix option */
924 else if(!strcmp(entry->long_name,"suffix"))
926 if(strlen(optarg) > MAX_SUFFIX)
928 ERROR1("Suffix %s too long",optarg);
929 exit_code = ESUFFIXTOOLONG;
940 char* suffix = xbt_new0(char, MAX_SUFFIX + 2);
941 sprintf(suffix,".%s",optarg);
943 xbt_dynar_foreach(suffixes, i, cur)
945 if(!strcmp(suffix, cur))
953 WARN1("Suffix %s already specified to be used", optarg);
955 xbt_dynar_push(suffixes, &suffix);
963 xbt_dynar_foreach(suffixes, i, cur)
965 if(!strcmp(optarg, cur))
973 WARN1("Suffix %s already specified to be used", optarg);
976 char* suffix = strdup(optarg);
977 xbt_dynar_push(suffixes, &suffix);
982 else if(!strcmp(entry->long_name,"file"))
987 if(getpath(optarg, &path) < 0)
993 ERROR1("File %s does not exist", optarg);
995 ERROR0("Insufficient memory is available to process the command line - system error");
1000 delimiter = strrchr(optarg,'/');
1004 delimiter = strrchr(optarg,'\\');
1007 fstream = fstream_new(path, delimiter ? delimiter + 1 : optarg);
1011 if(fstreams_contains(fstreams, fstream))
1013 fstream_free(&fstream);
1014 WARN1("File %s already specified to run", optarg);
1017 fstreams_add(fstreams, fstream);
1019 /* --include-dir option */
1020 else if(!strcmp(entry->long_name,"include-dir"))
1023 struct stat info = {0};
1024 if(stat(optarg, &info) || !S_ISDIR(info.st_mode))
1026 ERROR1("%s is not a directory",optarg);
1027 exit_code = ENOTDIR;
1035 if(translatepath(optarg, &path) < 0)
1040 if(ENOTDIR == errno)
1041 ERROR1("%s is not a directory",optarg);
1043 ERROR0("Insufficient memory is available to process the command line - system error");
1054 directory = directory_new(optarg);
1056 directory = directory_new(path);
1060 xbt_dynar_foreach(include_dirs, i , cur)
1062 if(!strcmp(cur->name, optarg))
1071 directory_free((void**)&directory);
1072 WARN1("Include directory %s already specified to be used",optarg);
1076 xbt_dynar_push(include_dirs, &directory);
1080 /* --exclude option */
1081 else if(!strcmp(entry->long_name,"exclude"))
1086 if(getpath(optarg, &path) < 0)
1092 ERROR1("file %s does not exist", optarg);
1094 ERROR0("Insufficient memory is available to process the command line - system error");
1099 delimiter = strrchr(optarg,'/');
1103 delimiter = strrchr(optarg,'\\');
1106 fstream = fstream_new(path, delimiter ? delimiter + 1 : optarg);
1109 if(excludes_contains(excludes, fstream))
1111 fstream_free(&fstream);
1112 WARN1("File %s already specified to be exclude", optarg);
1115 excludes_add(excludes, fstream);
1118 else if(!strcmp(entry->long_name,"log"))
1120 /* NOTHING TODO : log for xbt */
1124 INFO1("Unexpected option %s", optarg);
1131 /* strictly positve number options */
1134 if ((NULL == optarg) && (argc > optind))
1138 for (cp = argv[optind]; isdigit(cp[0]); ++cp)
1140 optarg = argv[optind++];
1143 /* the number option is specified */
1146 int i = atoi(optarg);
1149 for (cp = optarg; isdigit(cp[0]); ++cp);
1151 if (i < 1 || cp[0] != '\0')
1153 ERROR2("Option %c \"%s\" requires an strictly positive integer as argument",entry->c, entry->long_name);
1154 exit_code = ENOTPOSITIVENUM;
1159 *(int*)entry->value = i;
1161 /* the number option is specified but has no arg, use the optional value*/
1163 *(int*)entry->value = *(int*) entry->optional_value;
1182 stream = exit_code ? stderr : stdout;
1184 fprintf (stream, "Usage: tesh [options] [file] ...\n");
1186 for (cpp = usage; *cpp; ++cpp)
1187 fputs (*cpp, stream);
1189 fprintf(stream, "\nReport bugs to <martin.quinson@loria.fr | malek.cherier@loria.fr>\n");
1195 /* TODO : display the version of tesh */
1196 printf("Version :\n");
1197 printf(" tesh version %s : Mini shell specialized in running test units by Martin Quinson \n", version);
1198 printf(" and Malek Cherier\n");
1199 printf(" Copyright (c) 2007, 2008 Martin Quinson, Malek Cherier\n");
1200 printf(" All rights reserved\n");
1201 printf(" This program is free software; you can redistribute it and/or modify it\n");
1202 printf(" under the terms of the license (GNU LGPL) which comes with this package.\n\n");
1204 if(!print_usage_flag)
1205 printf("Report bugs to <martin.quinson@loria.fr | malek.cherier@loria.fr>");
1214 FILE* stream = fopen("examples/README.tesh", "r");
1218 ERROR0("Unable to locate the README.txt file");
1219 exit_code = EREADMENOTFOUND;
1224 while(getline(&line, &len, stream) != -1)