minix/external/bsd/kyua-testers/dist/cli.c
Lionel Sambuc 11be35a165 Importing NetBSD "Kyua" test framework
To do so, a few dependencies have been imported:

 * external/bsd/lutok
 * external/mit/lua
 * external/public-domain/sqlite
 * external/public-domain/xz

The Kyua framework is the new generation of ATF (Automated Test
Framework), it is composed of:

 * external/bsd/atf
 * external/bsd/kyua-atf-compat
 * external/bsd/kyua-cli
 * external/bsd/kyua-tester
 * tests

Kyua/ATF being written in C++, it depends on libstdc++ which is
provided by GCC. As this is not part of the sources, Kyua is only
compiled when the native GCC utils are installed.

To install Kyua do the following:

 * In a cross-build enviromnent, add the following to the build.sh
   commandline: -V MKBINUTILS=yes -V MKGCCCMDS=yes

WARNING:
  At this point the import is still experimental, and not supported
  on native builds (a.k.a make build).

Change-Id: I26aee23c5bbd2d64adcb7c1beb98fe0d479d7ada
2013-07-23 20:43:41 +02:00

260 lines
8.1 KiB
C

// Copyright 2012 Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
// * Neither the name of Google Inc. nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#if defined(HAVE_CONFIG_H)
#include "config.h"
#endif
#include "cli.h"
#include <assert.h>
#include <err.h>
#include <errno.h>
#include <limits.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "defs.h"
#include "error.h"
#include "run.h"
#if !defined(GID_MAX)
# define GID_MAX INT_MAX
#endif
#if !defined(UID_MAX)
# define UID_MAX INT_MAX
#endif
#if defined(HAVE_GETOPT_GNU)
# define GETOPT_PLUS "+"
#else
# define GETOPT_PLUS
#endif
/// Terminates execution if the given error is set.
///
/// \param error The error to validate.
///
/// \post The program terminates if the given error is set.
static void
check_error(kyua_error_t error)
{
if (kyua_error_is_set(error)) {
const bool usage_error = kyua_error_is_type(error,
kyua_usage_error_type);
char buffer[1024];
kyua_error_format(error, buffer, sizeof(buffer));
kyua_error_free(error);
errx(usage_error ? EXIT_USAGE_ERROR : EXIT_INTERNAL_ERROR,
"%s", buffer);
}
}
/// Converts a string to an unsigned long.
///
/// \param str The string to convert.
/// \param message Part of the error message to print if the string does not
/// represent a valid unsigned long number.
/// \param max Maximum accepted value.
///
/// \return The converted numerical value.
///
/// \post The program terminates if the value is invalid.
static unsigned long
parse_ulong(const char* str, const char* message, const unsigned long max)
{
char *endptr;
errno = 0;
const unsigned long value = strtoul(str, &endptr, 10);
if (str[0] == '\0' || *endptr != '\0')
errx(EXIT_USAGE_ERROR, "%s '%s' (not a number)", message, str);
else if (errno == ERANGE || value == LONG_MAX || value > max)
errx(EXIT_USAGE_ERROR, "%s '%s' (out of range)", message, str);
return value;
}
/// Clears getopt(3) state to allow calling the function again.
static void
reset_getopt(void)
{
opterr = 0;
optind = GETOPT_OPTIND_RESET_VALUE;
#if defined(HAVE_GETOPT_WITH_OPTRESET)
optreset = 1;
#endif
}
/// Prints the list of test cases and their metadata in a test program.
///
/// \param argc Number of arguments to the command, including the command name.
/// \param argv Arguments to the command, including the command name.
/// \param tester Description of the tester implemented by this binary.
/// \param run_params Execution parameters to configure the test process.
///
/// \return An exit status to indicate the success or failure of the listing.
///
/// \post Usage errors terminate the execution of the program right away.
static int
list_command(const int argc, char* const* const argv,
const kyua_cli_tester_t* tester,
const kyua_run_params_t* run_params)
{
if (argc < 2)
errx(EXIT_USAGE_ERROR, "No test program provided");
else if (argc > 2)
errx(EXIT_USAGE_ERROR, "Only one test program allowed");
const char* test_program = argv[1];
check_error(tester->list_test_cases(test_program, run_params));
return EXIT_SUCCESS;
}
/// Runs and cleans up a single test case.
///
/// \param argc Number of arguments to the command, including the command name.
/// \param argv Arguments to the command, including the command name.
/// \param tester Description of the tester implemented by this binary.
/// \param run_params Execution parameters to configure the test process.
///
/// \return An exit status to indicate the success or failure of the test case
/// execution.
///
/// \post Usage errors terminate the execution of the program right away.
static int
test_command(int argc, char* const* argv, const kyua_cli_tester_t* tester,
const kyua_run_params_t* run_params)
{
# define MAX_USER_VARIABLES 256
const char* user_variables[MAX_USER_VARIABLES];
const char** last_variable = user_variables;
int ch;
while ((ch = getopt(argc, argv, GETOPT_PLUS":v:")) != -1) {
switch (ch) {
case 'v':
*last_variable++ = optarg;
break;
case ':':
errx(EXIT_USAGE_ERROR, "%s's -%c requires an argument", argv[0],
optopt);
case '?':
errx(EXIT_USAGE_ERROR, "Unknown %s option -%c", argv[0], optopt);
default:
assert(false);
}
}
argc -= optind;
argv += optind;
*last_variable = NULL;
if (argc != 3)
errx(EXIT_USAGE_ERROR, "Must provide a test program, a test case name "
"and a result file");
const char* test_program = argv[0];
const char* test_case = argv[1];
const char* result_file = argv[2];
bool success;
check_error(tester->run_test_case(test_program, test_case, result_file,
user_variables, run_params, &success));
return success ? EXIT_SUCCESS : EXIT_FAILURE;
}
/// Generic entry point to the tester's command-line interface.
///
/// \param argc Verbatim argc passed to main().
/// \param argv Verbatim argv passed to main().
/// \param tester Description of the tester implemented by this binary.
///
/// \return An exit status.
///
/// \post Usage errors terminate the execution of the program right away.
int
kyua_cli_main(int argc, char* const* argv, const kyua_cli_tester_t* tester)
{
kyua_run_params_t run_params;
kyua_run_params_init(&run_params);
int ch;
while ((ch = getopt(argc, argv, GETOPT_PLUS":g:t:u:")) != -1) {
switch (ch) {
case 'g':
run_params.unprivileged_group = (uid_t)parse_ulong(
optarg, "Invalid GID", GID_MAX);
break;
case 't':
run_params.timeout_seconds = parse_ulong(
optarg, "Invalid timeout value", LONG_MAX);
break;
case 'u':
run_params.unprivileged_user = (uid_t)parse_ulong(
optarg, "Invalid UID", UID_MAX);
break;
case ':':
errx(EXIT_USAGE_ERROR, "-%c requires an argument", optopt);
case '?':
errx(EXIT_USAGE_ERROR, "Unknown option -%c", optopt);
default:
assert(false);
}
}
argc -= optind;
argv += optind;
reset_getopt();
if (argc == 0)
errx(EXIT_USAGE_ERROR, "Must provide a command");
const char* command = argv[0];
// Keep sorted by order of likelyhood (yeah, micro-optimization).
if (strcmp(command, "test") == 0)
return test_command(argc, argv, tester, &run_params);
else if (strcmp(command, "list") == 0)
return list_command(argc, argv, tester, &run_params);
else
errx(EXIT_USAGE_ERROR, "Unknown command '%s'", command);
}