// 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.
#include "cli.h"
#include <assert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <atf-c.h>
#include "defs.h"
#include "error.h"
#include "run.h"
/// Dumps the contents of a run_params object to stdout.
///
/// We only print the settings that are relevant for testing purposes.
///
/// \param run_params The run parameters to be printed.
static void
dump_run_params(const kyua_run_params_t* run_params)
{
printf("timeout_seconds: %lu\n", run_params->timeout_seconds);
if (run_params->unprivileged_user == getuid())
printf("unprivileged_user: self\n");
else
printf("unprivileged_user: %ld\n", (long)run_params->unprivileged_user);
if (run_params->unprivileged_group == getgid())
printf("unprivileged_group: self\n");
else
printf("unprivileged_group: %ld\n",
(long)run_params->unprivileged_group);
}
/// Helper to validate argument passing to the list_test_cases method.
///
/// This prints the value of all arguments to stdout so that the caller can
/// compare the printed output to the expected values.
///
/// \param test_program Test program path.
/// \param run_params Execution parameters to configure the test process.
///
/// \return An error if the test_program is set to the magic keyword 'error'; OK
/// otherwise.
static kyua_error_t
list_test_cases(const char* test_program, const kyua_run_params_t* run_params)
{
if (strcmp(test_program, "error") == 0)
return kyua_oom_error_new();
else {
printf("test_program: %s\n", test_program);
dump_run_params(run_params);
return kyua_error_ok();
}
}
/// Helper to validate argument passing to the run_test_cases method.
///
/// This prints the value of all arguments to stdout so that the caller can
/// compare the printed output to the expected values.
///
/// \param test_program Test program path.
/// \param test_case Test case name.
/// \param result_file Path to the result file.
/// \param user_variables User configuration variables.
/// \param run_params Execution parameters to configure the test process.
/// \param [out] success Whether the test case returned with a successful result
/// or not. Set to true if result_file is the magic word 'pass'.
///
/// \return An error if the test_program is set to the magic keyword 'error'; OK
/// otherwise.
static kyua_error_t
run_test_case(const char* test_program, const char* test_case,
const char* result_file, const char* const user_variables[],
const kyua_run_params_t* run_params, bool* success)
{
if (strcmp(test_program, "error") == 0)
return kyua_oom_error_new();
else {
printf("test_program: %s\n", test_program);
printf("test_case: %s\n", test_case);
printf("result_file: %s\n", result_file);
const char* const* iter;
for (iter = user_variables; *iter != NULL; ++iter)
printf("variable: %s\n", *iter);
dump_run_params(run_params);
*success = strcmp(result_file, "pass") == 0;
return kyua_error_ok();
}
}
/// Definition of a mock tester.
static kyua_cli_tester_t mock_tester = {
.list_test_cases = list_test_cases,
.run_test_case = run_test_case,
};
/// Definition of a tester with invalid values.
///
/// This is to be used when the called code is not supposed to invoke any of the
/// tester methods.
static kyua_cli_tester_t unused_tester = {
.list_test_cases = NULL,
.run_test_case = NULL,
};
/// Counts the number of arguments in an argv vector.
///
/// \param argv The NULL-terminated arguments vector to be passed to the
/// kyua_cli_main function.
///
/// \return The number of arguments in argv.
static int
count_argv(char* const* const argv)
{
int argc = 0;
char* const* arg;
for (arg = argv; *arg != NULL; arg++)
argc++;
return argc;
}
ATF_TC_WITHOUT_HEAD(main__unknown_option);
ATF_TC_BODY(main__unknown_option, tc)
{
const pid_t pid = atf_utils_fork();
if (pid == 0) {
char arg0[] = "unused-progname";
char arg1[] = "-Z";
char* const argv[] = {arg0, arg1, NULL};
exit(kyua_cli_main(count_argv(argv), argv, &unused_tester));
}
atf_utils_wait(pid, EXIT_USAGE_ERROR, "", "cli_test: Unknown option -Z\n");
}
ATF_TC_WITHOUT_HEAD(main__missing_option_argument);
ATF_TC_BODY(main__missing_option_argument, tc)
{
const pid_t pid = atf_utils_fork();
if (pid == 0) {
char arg0[] = "unused-progname";
char arg1[] = "-t";
char* const argv[] = {arg0, arg1, NULL};
exit(kyua_cli_main(count_argv(argv), argv, &unused_tester));
}
atf_utils_wait(pid, EXIT_USAGE_ERROR, "", "cli_test: -t requires an "
"argument\n");
}
ATF_TC_WITHOUT_HEAD(main__unknown_command);
ATF_TC_BODY(main__unknown_command, tc)
{
const pid_t pid = atf_utils_fork();
if (pid == 0) {
char arg0[] = "unused-progname";
char arg1[] = "foobar";
char* const argv[] = {arg0, arg1, NULL};
exit(kyua_cli_main(count_argv(argv), argv, &unused_tester));
}
atf_utils_wait(pid, EXIT_USAGE_ERROR, "", "cli_test: Unknown command "
"'foobar'\n");
}
ATF_TC_WITHOUT_HEAD(main__missing_command);
ATF_TC_BODY(main__missing_command, tc)
{
const pid_t pid = atf_utils_fork();
if (pid == 0) {
char arg0[] = "unused-progname";
char* const argv[] = {arg0, NULL};
exit(kyua_cli_main(count_argv(argv), argv, &unused_tester));
}
atf_utils_wait(pid, EXIT_USAGE_ERROR, "", "cli_test: Must provide a "
"command\n");
}
/// Checks that a textual argument to a numerical flag raises an error.
///
/// \param flag The generic flag to test.
/// \param error_message The expected error message when the flag is invalid.
static void
check_flag_not_a_number(const char flag, const char *error_message)
{
const pid_t pid = atf_utils_fork();
if (pid == 0) {
char arg0[] = "unused-progname";
char arg1[] = "-?foo";
arg1[1] = flag;
char* const argv[] = {arg0, arg1, NULL};
exit(kyua_cli_main(count_argv(argv), argv, &unused_tester));
}
char experr[256];
snprintf(experr, sizeof(experr), "cli_test: %s 'foo' (not a number)\n",
error_message);
atf_utils_wait(pid, EXIT_USAGE_ERROR, "", experr);
}
/// Checks that an out of range value to a numerical flag raises an error.
///
/// \param flag The generic flag to test.
/// \param error_message The expected error message when the flag is invalid.
static void
check_flag_out_of_range(const char flag, const char *error_message)
{
const pid_t pid = atf_utils_fork();
if (pid == 0) {
char arg0[] = "unused-progname";
char arg1[] = "-?99999999999999999999";
arg1[1] = flag;
char* const argv[] = {arg0, arg1, NULL};
exit(kyua_cli_main(count_argv(argv), argv, &unused_tester));
}
char experr[256];
snprintf(experr, sizeof(experr), "cli_test: %s '99999999999999999999' "
"(out of range)\n", error_message);
atf_utils_wait(pid, EXIT_USAGE_ERROR, "", experr);
}
ATF_TC_WITHOUT_HEAD(main__gflag__not_a_number);
ATF_TC_BODY(main__gflag__not_a_number, tc)
{
check_flag_not_a_number('g', "Invalid GID");
}
ATF_TC_WITHOUT_HEAD(main__gflag__out_of_range);
ATF_TC_BODY(main__gflag__out_of_range, tc)
{
check_flag_out_of_range('g', "Invalid GID");
}
ATF_TC_WITHOUT_HEAD(main__tflag__not_a_number);
ATF_TC_BODY(main__tflag__not_a_number, tc)
{
check_flag_not_a_number('t', "Invalid timeout value");
}
ATF_TC_WITHOUT_HEAD(main__tflag__out_of_range);
ATF_TC_BODY(main__tflag__out_of_range, tc)
{
check_flag_out_of_range('t', "Invalid timeout value");
}
ATF_TC_WITHOUT_HEAD(main__uflag__not_a_number);
ATF_TC_BODY(main__uflag__not_a_number, tc)
{
check_flag_not_a_number('u', "Invalid UID");
}
ATF_TC_WITHOUT_HEAD(main__uflag__out_of_range);
ATF_TC_BODY(main__uflag__out_of_range, tc)
{
check_flag_out_of_range('u', "Invalid UID");
}
ATF_TC_WITHOUT_HEAD(list__ok);
ATF_TC_BODY(list__ok, tc)
{
const pid_t pid = atf_utils_fork();
if (pid == 0) {
char arg0[] = "unused-progname";
char arg1[] = "list";
char arg2[] = "the-program";
char* const argv[] = {arg0, arg1, arg2, NULL};
exit(kyua_cli_main(count_argv(argv), argv, &mock_tester));
}
atf_utils_wait(pid, EXIT_SUCCESS,
"test_program: the-program\n"
"timeout_seconds: 60\n"
"unprivileged_user: self\n"
"unprivileged_group: self\n",
"");
}
ATF_TC_WITHOUT_HEAD(list__custom_run_params);
ATF_TC_BODY(list__custom_run_params, tc)
{
const pid_t pid = atf_utils_fork();
if (pid == 0) {
char arg0[] = "unused-progname";
char arg1[] = "-g987";
char arg2[] = "-t123";
char arg3[] = "-u45";
char arg4[] = "list";
char arg5[] = "the-program";
char* const argv[] = {arg0, arg1, arg2, arg3, arg4, arg5, NULL};
exit(kyua_cli_main(count_argv(argv), argv, &mock_tester));
}
atf_utils_wait(pid, EXIT_SUCCESS,
"test_program: the-program\n"
"timeout_seconds: 123\n"
"unprivileged_user: 45\n"
"unprivileged_group: 987\n",
"");
}
ATF_TC_WITHOUT_HEAD(list__error);
ATF_TC_BODY(list__error, tc)
{
const pid_t pid = atf_utils_fork();
if (pid == 0) {
char arg0[] = "unused-progname";
char arg1[] = "list";
char arg2[] = "error";
char* const argv[] = {arg0, arg1, arg2, NULL};
exit(kyua_cli_main(count_argv(argv), argv, &mock_tester));
}
atf_utils_wait(pid, EXIT_INTERNAL_ERROR, "", "cli_test: Not enough "
"memory\n");
}
ATF_TC_WITHOUT_HEAD(list__missing_arguments);
ATF_TC_BODY(list__missing_arguments, tc)
{
const pid_t pid = atf_utils_fork();
if (pid == 0) {
char arg0[] = "unused-progname";
char arg1[] = "list";
char* const argv[] = {arg0, arg1, NULL};
exit(kyua_cli_main(count_argv(argv), argv, &unused_tester));
}
atf_utils_wait(pid, EXIT_USAGE_ERROR, "", "cli_test: No test program "
"provided\n");
}
ATF_TC_WITHOUT_HEAD(list__too_many_arguments);
ATF_TC_BODY(list__too_many_arguments, tc)
{
const pid_t pid = atf_utils_fork();
if (pid == 0) {
char arg0[] = "unused-progname";
char arg1[] = "list";
char arg2[] = "first";
char arg3[] = "second";
char* const argv[] = {arg0, arg1, arg2, arg3, NULL};
exit(kyua_cli_main(count_argv(argv), argv, &unused_tester));
}
atf_utils_wait(pid, EXIT_USAGE_ERROR, "", "cli_test: Only one test program "
"allowed\n");
}
ATF_TC_WITHOUT_HEAD(test__ok__pass);
ATF_TC_BODY(test__ok__pass, tc)
{
const pid_t pid = atf_utils_fork();
if (pid == 0) {
char arg0[] = "unused-progname";
char arg1[] = "test";
char arg2[] = "the-program";
char arg3[] = "the-test-case";
char arg4[] = "pass";
char* const argv[] = {arg0, arg1, arg2, arg3, arg4, NULL};
exit(kyua_cli_main(count_argv(argv), argv, &mock_tester));
}
atf_utils_wait(pid, EXIT_SUCCESS,
"test_program: the-program\n"
"test_case: the-test-case\n"
"result_file: pass\n"
"timeout_seconds: 60\n"
"unprivileged_user: self\n"
"unprivileged_group: self\n",
"");
}
ATF_TC_WITHOUT_HEAD(test__ok__fail);
ATF_TC_BODY(test__ok__fail, tc)
{
const pid_t pid = atf_utils_fork();
if (pid == 0) {
char arg0[] = "unused-progname";
char arg1[] = "test";
char arg2[] = "the-program";
char arg3[] = "the-test-case";
char arg4[] = "fail";
char* const argv[] = {arg0, arg1, arg2, arg3, arg4, NULL};
exit(kyua_cli_main(count_argv(argv), argv, &mock_tester));
}
atf_utils_wait(pid, EXIT_FAILURE,
"test_program: the-program\n"
"test_case: the-test-case\n"
"result_file: fail\n"
"timeout_seconds: 60\n"
"unprivileged_user: self\n"
"unprivileged_group: self\n",
"");
}
ATF_TC_WITHOUT_HEAD(test__custom_run_params);
ATF_TC_BODY(test__custom_run_params, tc)
{
const pid_t pid = atf_utils_fork();
if (pid == 0) {
char arg0[] = "unused-progname";
char arg1[] = "-g987";
char arg2[] = "-t123";
char arg3[] = "-u45";
char arg4[] = "test";
char arg5[] = "the-program";
char arg6[] = "the-test-case";
char arg7[] = "pass";
char* const argv[] = {arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7,
NULL};
exit(kyua_cli_main(count_argv(argv), argv, &mock_tester));
}
atf_utils_wait(pid, EXIT_SUCCESS,
"test_program: the-program\n"
"test_case: the-test-case\n"
"result_file: pass\n"
"timeout_seconds: 123\n"
"unprivileged_user: 45\n"
"unprivileged_group: 987\n",
"");
}
ATF_TC_WITHOUT_HEAD(test__config_variables);
ATF_TC_BODY(test__config_variables, tc)
{
const pid_t pid = atf_utils_fork();
if (pid == 0) {
char arg0[] = "unused-progname";
char arg1[] = "test";
char arg2[] = "-vfoo=bar";
char arg3[] = "-va=c";
char arg4[] = "the-program";
char arg5[] = "the-test-case";
char arg6[] = "pass";
char* const argv[] = {arg0, arg1, arg2, arg3, arg4, arg5, arg6, NULL};
exit(kyua_cli_main(count_argv(argv), argv, &mock_tester));
}
atf_utils_wait(pid, EXIT_SUCCESS,
"test_program: the-program\n"
"test_case: the-test-case\n"
"result_file: pass\n"
"variable: foo=bar\n"
"variable: a=c\n"
"timeout_seconds: 60\n"
"unprivileged_user: self\n"
"unprivileged_group: self\n",
"");
}
ATF_TC_WITHOUT_HEAD(test__error);
ATF_TC_BODY(test__error, tc)
{
const pid_t pid = atf_utils_fork();
if (pid == 0) {
char arg0[] = "unused-progname";
char arg1[] = "test";
char arg2[] = "error";
char* const argv[] = {arg0, arg1, arg2, arg2, arg2, NULL};
exit(kyua_cli_main(count_argv(argv), argv, &mock_tester));
}
atf_utils_wait(pid, EXIT_INTERNAL_ERROR, "", "cli_test: Not enough "
"memory\n");
}
/// Checks that the test command validates the right number of arguments.
///
/// \param count Number of arguments to pass to the test command.
static void
check_test_invalid_arguments(const unsigned int count)
{
printf("Checking with %d arguments\n", count);
const pid_t pid = atf_utils_fork();
if (pid == 0) {
char arg0[] = "unused-progname";
char arg1[] = "test";
char argX[] = "arg";
assert(count <= 4);
char* argv[] = {arg0, arg1, argX, argX, argX, argX, NULL};
argv[2 + count] = NULL;
exit(kyua_cli_main(2 + count, argv, &unused_tester));
}
atf_utils_wait(pid, EXIT_USAGE_ERROR, "", "cli_test: Must provide a test "
"program, a test case name and a result file\n");
}
ATF_TC_WITHOUT_HEAD(test__invalid_arguments);
ATF_TC_BODY(test__invalid_arguments, tc)
{
check_test_invalid_arguments(0);
check_test_invalid_arguments(1);
check_test_invalid_arguments(2);
check_test_invalid_arguments(4);
}
ATF_TC_WITHOUT_HEAD(test__unknown_option);
ATF_TC_BODY(test__unknown_option, tc)
{
const pid_t pid = atf_utils_fork();
if (pid == 0) {
char arg0[] = "unused-progname";
char arg1[] = "test";
char arg2[] = "-Z";
char* const argv[] = {arg0, arg1, arg2, NULL};
exit(kyua_cli_main(count_argv(argv), argv, &unused_tester));
}
atf_utils_wait(pid, EXIT_USAGE_ERROR, "", "cli_test: Unknown test option "
"-Z\n");
}
ATF_TC_WITHOUT_HEAD(test__missing_option_argument);
ATF_TC_BODY(test__missing_option_argument, tc)
{
const pid_t pid = atf_utils_fork();
if (pid == 0) {
char arg0[] = "unused-progname";
char arg1[] = "test";
char arg2[] = "-v";
char* const argv[] = {arg0, arg1, arg2, NULL};
exit(kyua_cli_main(count_argv(argv), argv, &unused_tester));
}
atf_utils_wait(pid, EXIT_USAGE_ERROR, "", "cli_test: test's -v requires an "
"argument\n");
}
ATF_TP_ADD_TCS(tp)
{
ATF_TP_ADD_TC(tp, main__unknown_option);
ATF_TP_ADD_TC(tp, main__missing_option_argument);
ATF_TP_ADD_TC(tp, main__unknown_command);
ATF_TP_ADD_TC(tp, main__missing_command);
ATF_TP_ADD_TC(tp, main__gflag__not_a_number);
ATF_TP_ADD_TC(tp, main__gflag__out_of_range);
ATF_TP_ADD_TC(tp, main__tflag__not_a_number);
ATF_TP_ADD_TC(tp, main__tflag__out_of_range);
ATF_TP_ADD_TC(tp, main__uflag__not_a_number);
ATF_TP_ADD_TC(tp, main__uflag__out_of_range);
ATF_TP_ADD_TC(tp, list__ok);
ATF_TP_ADD_TC(tp, list__custom_run_params);
ATF_TP_ADD_TC(tp, list__error);
ATF_TP_ADD_TC(tp, list__missing_arguments);
ATF_TP_ADD_TC(tp, list__too_many_arguments);
ATF_TP_ADD_TC(tp, test__ok__pass);
ATF_TP_ADD_TC(tp, test__ok__fail);
ATF_TP_ADD_TC(tp, test__custom_run_params);
ATF_TP_ADD_TC(tp, test__config_variables);
ATF_TP_ADD_TC(tp, test__error);
ATF_TP_ADD_TC(tp, test__invalid_arguments);
ATF_TP_ADD_TC(tp, test__unknown_option);
ATF_TP_ADD_TC(tp, test__missing_option_argument);
return atf_no_error();
}