diff --git a/include/config.h b/include/config.h index 6c9e6a4..7e6b21b 100644 --- a/include/config.h +++ b/include/config.h @@ -20,6 +20,10 @@ enum ConfigError { CE_ENV_ROOT_DIR_INVALID, // The target argument is invalid. CE_TARGET_INVALID, + // No spec with the given name was found. + CE_TARGET_NOT_FOUND, + // The spec is not a directory. + CE_TARGET_NOT_DIR, }; enum ConfigError config_load( diff --git a/main.c b/main.c index 12c5489..fa1b610 100644 --- a/main.c +++ b/main.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -7,76 +8,99 @@ #include "parser.h" #include "validator.h" -const char *ENV_BOOTSTRAP_ROOT_DIR = "BOOTSTRAP_ROOT_DIR"; - -int main(int argc, char **argv) { - if (argc != 2) { - fprintf(stderr, "Usage: bootstrap \n"); - exit(EXIT_FAILURE); - } - - int retval = EXIT_SUCCESS; +static int run(const char *root_dir, const char *target) { + int retval = EXIT_FAILURE; char *cwd = getcwd(0, 0); - const char *root_dir = getenv(ENV_BOOTSTRAP_ROOT_DIR); - const char *target = argv[1]; - - // `cwd` must be free'd. + if (!root_dir) { + root_dir = getenv("BOOTSTRAP_ROOT_DIR"); + } struct Config *config = 0; switch (config_load(cwd, root_dir, target, &config)) { case CE_ENV_CWD_INVALID: - fprintf(stderr, "Could not retrieve the $CWD value."); - retval = EXIT_FAILURE; - goto cwd_cleanup; + fprintf(stderr, "Could not retrieve $CWD.\n"); + goto cleanup_cwd; case CE_ENV_ROOT_DIR_INVALID: - fprintf(stderr, "Must specify $BOOTSTRAP_ROOT_DIR environment variable."); - retval = EXIT_FAILURE; - goto cwd_cleanup; + fprintf( + stderr, + "Either supply a value to `-d` or specify the $BOOTSTRAP_ROOT_DIR " + "environment variable.\n" + ); + goto cleanup_cwd; case CE_TARGET_INVALID: - fprintf(stderr, "Target spec `%s` is invalid.", argv[1]); - retval = EXIT_FAILURE; - goto cwd_cleanup; + fprintf(stderr, "Spec `%s` is invalid.\n", target); + goto cleanup_cwd; + case CE_TARGET_NOT_FOUND: + fprintf(stderr, "Spec `%s` not found.\n", target); + goto cleanup_cwd; + case CE_TARGET_NOT_DIR: + fprintf(stderr, "Spec `%s` is not a directory.\n", target); + goto cleanup_cwd; } - // `config` must be free'd. - cJSON *parsed = 0; switch (parse_spec_json(config, &parsed)) { case SPE_CANNOT_OPEN: - fprintf(stderr, "Found `spec.json` but could not open."); - retval = EXIT_FAILURE; - goto config_cleanup; + fprintf(stderr, "Cannot open `%s/spec.json`.\n", target); + goto cleanup_config; case SPE_INVALID_SYNTAX: - fprintf(stderr, "`spec.json` does not conform to bootstrap format."); - retval = EXIT_FAILURE; - goto config_cleanup; + fprintf(stderr, "`%s/spec.json` is not valid JSON.\n", target); + goto cleanup_config; } - // `parsed` must be free'd. - struct DynArray *prompts = 0; switch (validate_spec_json(parsed, &prompts)) { case SVE_NOT_TOPLEVEL_OBJECT: - fprintf(stderr, "`spec.json` top-level JSON value must be object."); - retval = EXIT_FAILURE; - goto parsed_cleanup; + fprintf(stderr, "`%s/spec.json` is not a JSON object.\n", target); + goto cleanup_parsed; case SVE_INVALID_VALUE: - fprintf(stderr, "Encountered unknown `spec.json` value type."); - retval = EXIT_FAILURE; - goto parsed_cleanup; + fprintf(stderr, "unknown value type found in `%s/spec.json`.\n", target); + goto cleanup_parsed; } // TODO: Extract the prompts out of the `spec.json` file. // TODO: Load in the curses interface. // TODO: Run `run.sh`. -parsed_cleanup: + retval = EXIT_SUCCESS; + +cleanup_parsed: cJSON_Delete(parsed); -config_cleanup: +cleanup_config: config_free(config); -cwd_cleanup: +cleanup_cwd: free(cwd); return retval; } + +int main(int argc, char **argv) { + char *root_dir = 0; + char *target = 0; + + int opt; + while ((opt = getopt(argc, argv, "d:")) != -1) { + switch (opt) { + case 'd': + root_dir = optarg; + break; + } + } + + for (int index = optind; index < argc; index++) { + if (target == 0) { + target = argv[index]; + } else { + fprintf(stderr, "Usage: bootstrap [-d ] \n"); + return EXIT_FAILURE; + } + } + + if (!target) { + fprintf(stderr, "Usage: bootstrap [-d ] \n"); + return EXIT_FAILURE; + } + + return run(root_dir, target); +} diff --git a/src/config.c b/src/config.c index 4db0451..c871689 100644 --- a/src/config.c +++ b/src/config.c @@ -1,7 +1,10 @@ +#include #include #include +#include #include "config.h" +#include "path.h" enum ConfigError config_load( const char *cwd, @@ -19,6 +22,27 @@ enum ConfigError config_load( return CE_TARGET_INVALID; } + { // Check if the specified directory exists. + struct stat sb; + + const char *segments[] = {root_dir, target}; + char *filepath = + join_path_segments(sizeof(segments) / sizeof(char *), segments); + + int stat_res = stat(filepath, &sb); + free(filepath); + + if (stat_res == -1) { + if (errno == ENOENT) { + return CE_TARGET_NOT_FOUND; + } + return CE_TARGET_INVALID; + } + if (!S_ISDIR(sb.st_mode)) { + return CE_TARGET_NOT_DIR; + } + } + *config = malloc(sizeof(struct Config)); (*config)->cwd = cwd; (*config)->root_dir = root_dir; diff --git a/test/runner.c b/test/runner.c index 556f3d5..d0f0aa6 100644 --- a/test/runner.c +++ b/test/runner.c @@ -9,12 +9,13 @@ int main(int argc, char *argv[]) { sput_start_testing(); sput_enter_suite("config"); - sput_run_test(test_config_load_root_dir_invalid); - sput_run_test(test_config_load_target_invalid); + sput_run_test(test_config_load_invalid_args); + sput_run_test(test_config_load_spec_not_found); + sput_run_test(test_config_load_spec_not_dir); sput_run_test(test_config_load_success); sput_enter_suite("dyn_array"); - sput_run_test(test_dyn_array_empty); + sput_run_test(test_dyn_array_zero_capacity); sput_run_test(test_dyn_array_nonzero_capacity); sput_enter_suite("path"); diff --git a/test/specs/not_dir b/test/specs/not_dir new file mode 100644 index 0000000..e69de29 diff --git a/test/test_config.h b/test/test_config.h index d726c10..6512a72 100644 --- a/test/test_config.h +++ b/test/test_config.h @@ -1,58 +1,94 @@ #ifndef _BOOTSTRAP_TEST_CONFIG #define _BOOTSTRAP_TEST_CONFIG +#include + #include "config.h" +#include "path.h" #include "sput.h" -static const char *SAMPLE_CWD = "/home/jrpotter/Documents/bootstrap"; -static const char *SAMPLE_ROOT_DIR = "/usr/local/share/specs"; -static const char *SAMPLE_TARGET = "example-target"; +struct TestConfigFixture { + char *cwd; + char *root_dir; + char *target; +}; + +static struct TestConfigFixture *test_config_setup() { + char *cwd = getcwd(0, 0); + const char *segments[] = {cwd, "test", "specs"}; + char *root_dir = + join_path_segments(sizeof(segments) / sizeof(char *), segments); + + struct TestConfigFixture *fixture = malloc(sizeof(struct TestConfigFixture)); + fixture->cwd = getcwd(0, 0); + fixture->root_dir = root_dir; + fixture->target = "minimal_spec_json"; + + return fixture; +} + +static void test_config_teardown(struct TestConfigFixture *fixture) { + free(fixture->cwd); + free(fixture->root_dir); + free(fixture); +} + +static void test_config_load_invalid_args() { + struct TestConfigFixture *fixture = test_config_setup(); -/* -A valid $CWD environment variable value must be supplied. -*/ -static void test_config_load_cwd_invalid() { struct Config *config = 0; - enum ConfigError retval = - config_load(0, SAMPLE_ROOT_DIR, SAMPLE_TARGET, &config); + enum ConfigError retval = 0; + + retval = config_load(0, fixture->root_dir, fixture->target, &config); sput_fail_unless(retval == CE_ENV_CWD_INVALID, "target == 0"); -} - -/* -A valid $BOOTSTRAP_ROOT_DIR environment variable value must be supplied. -*/ -static void test_config_load_root_dir_invalid() { - struct Config *config = 0; - enum ConfigError retval = config_load(SAMPLE_CWD, 0, SAMPLE_TARGET, &config); + retval = config_load(fixture->cwd, 0, fixture->target, &config); sput_fail_unless(retval == CE_ENV_ROOT_DIR_INVALID, "root_dir == 0"); -} - -/* -A valid target value must be supplied. -*/ -static void test_config_load_target_invalid() { - struct Config *config = 0; - enum ConfigError retval = - config_load(SAMPLE_CWD, SAMPLE_ROOT_DIR, 0, &config); + retval = config_load(fixture->cwd, fixture->root_dir, 0, &config); sput_fail_unless(retval == CE_TARGET_INVALID, "target == 0"); + + test_config_teardown(fixture); } -/* -If the supplied arguments to `config_load` are non-NULL, we should be able to -successfully construct a new @Config instance. -*/ -static void test_config_load_success() { +static void test_config_load_spec_not_found() { + struct TestConfigFixture *fixture = test_config_setup(); + struct Config *config = 0; enum ConfigError retval = - config_load(SAMPLE_CWD, SAMPLE_ROOT_DIR, SAMPLE_TARGET, &config); + config_load(fixture->cwd, fixture->root_dir, "not_found", &config); + sput_fail_unless(retval == CE_TARGET_NOT_FOUND, "target not found"); + + test_config_teardown(fixture); +} + +static void test_config_load_spec_not_dir() { + struct TestConfigFixture *fixture = test_config_setup(); + + struct Config *config = 0; + enum ConfigError retval = + config_load(fixture->cwd, fixture->root_dir, "not_dir", &config); + sput_fail_unless(retval == CE_TARGET_NOT_DIR, "target not dir"); + + test_config_teardown(fixture); +} + +static void test_config_load_success() { + struct TestConfigFixture *fixture = test_config_setup(); + + struct Config *config = 0; + enum ConfigError retval = + config_load(fixture->cwd, fixture->root_dir, fixture->target, &config); sput_fail_unless(retval == 0, "config_load() success"); - sput_fail_unless(strcmp(config->root_dir, SAMPLE_ROOT_DIR) == 0, - "config_load() root_dir"); - sput_fail_unless(strcmp(config->target, SAMPLE_TARGET) == 0, - "config_load() target"); + sput_fail_unless(strcmp(config->cwd, fixture->cwd) == 0, "config_load() cwd"); + sput_fail_unless( + strcmp(config->root_dir, fixture->root_dir) == 0, "config_load() root_dir" + ); + sput_fail_unless( + strcmp(config->target, fixture->target) == 0, "config_load() target" + ); config_free(config); + test_config_teardown(fixture); } #endif /* _BOOTSTRAP_TEST_CONFIG */ diff --git a/test/test_dyn_array.h b/test/test_dyn_array.h index 9cdb115..61bb4de 100644 --- a/test/test_dyn_array.h +++ b/test/test_dyn_array.h @@ -7,7 +7,7 @@ /* A @DynArray with zero capacity can be instantiated and have entries pushed onto. */ -static void test_dyn_array_empty() { +static void test_dyn_array_zero_capacity() { struct DynArray *a = dyn_array_new(0); sput_fail_unless(a->size == 0, "a->size == 0"); sput_fail_unless(a->_capacity == 1, "a->_capacity == 1"); diff --git a/test/test_parser.h b/test/test_parser.h index f72d4b8..ed32429 100644 --- a/test/test_parser.h +++ b/test/test_parser.h @@ -9,76 +9,75 @@ #include "path.h" #include "sput.h" +struct TestParserFixture { + char *cwd; + char *root_dir; + const char *target; + struct Config config; +}; + +static struct TestParserFixture *test_parser_setup(const char *target) { + char *cwd = getcwd(0, 0); + const char *segments[] = {cwd, "test", "specs"}; + char *root_dir = + join_path_segments(sizeof(segments) / sizeof(char *), segments); + + struct TestParserFixture *fixture = malloc(sizeof(struct TestParserFixture)); + fixture->cwd = getcwd(0, 0); + fixture->root_dir = root_dir; + fixture->target = target; + + // Reproduce in `Config` instance for convenience. + fixture->config.cwd = fixture->cwd; + fixture->config.root_dir = fixture->root_dir; + fixture->config.target = fixture->target; + + return fixture; +} + +static void test_parser_teardown(struct TestParserFixture *fixture) { + free(fixture->cwd); + free(fixture->root_dir); + free(fixture); +} + /* A missing `spec.json` file is not an error. Our parsed @cJSON instance should be set to NULL in this case. */ static void test_parse_spec_json_missing() { - char *cwd = getcwd(0, 0); - - const char *segments[] = {cwd, "test", "specs"}; - char *root_dir = - join_path_segments(sizeof(segments) / sizeof(char *), segments); - - struct Config config = { - cwd, - root_dir, - "no_spec_json", - }; + struct TestParserFixture *fixture = test_parser_setup("no_spec_json"); cJSON *parsed = 0; - enum SpecParseError retval = parse_spec_json(&config, &parsed); + enum SpecParseError retval = parse_spec_json(&fixture->config, &parsed); sput_fail_unless(retval == 0, "no spec.json, success"); sput_fail_unless(parsed == 0, "no spec.json, no parsed"); - free(cwd); - free(root_dir); + test_parser_teardown(fixture); } static void test_parse_spec_json_minimal() { - char *cwd = getcwd(0, 0); - - const char *segments[] = {cwd, "test", "specs"}; - char *root_dir = - join_path_segments(sizeof(segments) / sizeof(char *), segments); - - struct Config config = { - cwd, - root_dir, - "minimal_spec_json", - }; + struct TestParserFixture *fixture = test_parser_setup("minimal_spec_json"); cJSON *parsed = 0; - enum SpecParseError retval = parse_spec_json(&config, &parsed); + enum SpecParseError retval = parse_spec_json(&fixture->config, &parsed); sput_fail_unless(retval == 0, "minimal spec.json, success"); sput_fail_unless(parsed != 0, "minimal spec.json, parsed"); - free(cwd); - free(root_dir); + test_parser_teardown(fixture); } static void test_parse_spec_json_invalid() { - char *cwd = getcwd(0, 0); - - const char *segments[] = {cwd, "test", "specs"}; - char *root_dir = - join_path_segments(sizeof(segments) / sizeof(char *), segments); - - struct Config config = { - cwd, - root_dir, - "invalid_spec_json", - }; + struct TestParserFixture *fixture = test_parser_setup("invalid_spec_json"); cJSON *parsed = 0; - enum SpecParseError retval = parse_spec_json(&config, &parsed); + enum SpecParseError retval = parse_spec_json(&fixture->config, &parsed); sput_fail_unless( retval == SPE_INVALID_SYNTAX, "invalid spec.json, INVALID_SYNTAX" ); sput_fail_unless(parsed == 0, "invalid spec.json, not parsed"); - free(cwd); - free(root_dir); + test_parser_teardown(fixture); } #endif /* _BOOTSTRAP_TEST_PARSER */