Skip to content

Commit

Permalink
implement testgen iterator
Browse files Browse the repository at this point in the history
  • Loading branch information
jon-codes committed Oct 5, 2024
1 parent 105c4f8 commit 9904ed1
Show file tree
Hide file tree
Showing 9 changed files with 393 additions and 169 deletions.
19 changes: 10 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ TESTGEN_DEBUG_BIN := $(BINDIR)/testgen-debug
TESTGEN_SRCS := $(wildcard $(TESTGEN_SRCDIR)/*.c)
TESTGEN_OBJS := $(patsubst $(TESTGEN_SRCDIR)/%.c,$(OBJDIR)/%.o,$(TESTGEN_SRCS))
TESTGEN_DEBUG_OBJS := $(patsubst $(TESTGEN_SRCDIR)/%.c,$(OBJDIR)/%_debug.o,$(TESTGEN_SRCS))
TESTGEN_INCL := -I$(TESTGEN_SRCDIR) -I/usr/include -ljansson
TESTGEN_INCL := -I$(TESTGEN_SRCDIR) -I/usr/include
TESTGEN_LIBS := -ljansson
TESTGEN_INPUT := $(TESTGEN_DATADIR)/cases.json
TESTGEN_OUTPUT := $(TESTGEN_DATADIR)/fixtures.json

Expand Down Expand Up @@ -90,25 +91,25 @@ testgen-build: $(TESTGEN_BIN)
testgen-debug: $(TESTGEN_DEBUG_BIN)

$(TESTGEN_BIN): $(TESTGEN_OBJS) | $(BINDIR)
$(CC) $(CFLAGS) -o $@ $(TESTGEN_OBJS)
$(CC) $(CFLAGS) -o $@ $(TESTGEN_OBJS) $(TESTGEN_LIBS)

$(TESTGEN_DEBUG_BIN): $(TESTGEN_DEBUG_OBJS) | $(BINDIR)
$(CC) $(CFLAGS_DEBUG) -o $@ $(TESTGEN_DEBUG_OBJS)
$(CC) $(CFLAGS_DEBUG) -o $@ $(TESTGEN_DEBUG_OBJS) $(TESTGEN_LIBS)

$(OBJDIR)/%.o: $(TESTGEN_SRCDIR)/%.c | $(OBJDIR)
$(CC) $(CFLAGS) -c $< -o $@

$(OBJDIR)/%_debug.o: $(TESTGEN_SRCDIR)/%.c | $(OBJDIR)
$(CC) $(CFLAGS_DEBUG) -c $< -o $@

## valgrind: run testgen with valgrind
.PHONY: valgrind
valgrind: $(TESTGEN_DEBUG_BIN)
## testgen-check: run testgen with valgrind
.PHONY: testgen-check
testgen-check: $(TESTGEN_DEBUG_BIN)
valgrind --leak-check=full $< -o $(TESTGEN_OUTPUT) $(TESTGEN_INPUT)

## gdb: run testgen with gdb
.PHONY: gdb
gdb: $(TESTGEN_DEBUG_BIN)
## testgen-debug: run testgen with gdb
.PHONY: testgen-debug
testgen-debug: $(TESTGEN_DEBUG_BIN)
gdb --args $< -o $(TESTGEN_OUTPUT) $(TESTGEN_INPUT)

## help: display this help
Expand Down
114 changes: 114 additions & 0 deletions testgen/case.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#include "case.h"

#include <jansson.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

void case_clear(Case *c) {
if (c != NULL) {
free(c->label);
free(c->opts);
free(c->lopts);
if (c->argv != NULL) {
for (int i = 0; i < c->argc; i++) {
free(c->argv[i]);
}
free(c->argv);
}

c->label = NULL;
c->opts = NULL;
c->lopts = NULL;
c->argv = NULL;
c->argc = 0;
}
}

bool read_case(Case *dest, FILE *src) {
size_t flags = JSON_DISABLE_EOF_CHECK;
json_t *json_value;
json_error_t json_err;

json_value = json_loadf(src, flags, &json_err);
if (!json_value) {
fprintf(stderr, "JSON parsing error: %s\n", json_err.text);
return true;
}

json_t *args;
const char *label, *opts, *lopts;
int err = json_unpack_ex(json_value, &json_err, 0, "{s:s, s:o, s:s, s:s}",
"label", &label,
"args", &args,
"opts", &opts,
"lopts", &lopts);

if (err != 0) {
fprintf(stderr, "JSON parsing error: %s %s\n", json_err.text, json_err.source);
json_decref(json_value);
return true;
}

if (!json_is_array(args)) {
fprintf(stderr, "expected args to be an array\n");
json_decref(json_value);
return true;
}

dest->label = strdup(label);
dest->opts = strdup(opts);
dest->lopts = strdup(lopts);

if (!dest->label || !dest->opts || !dest->lopts) {
fprintf(stderr, "Memory allocation error\n");
free(dest->label);
free(dest->opts);
free(dest->lopts);
json_decref(json_value);
return true;
}

dest->argc = json_array_size(args);
dest->argv = malloc(dest->argc * sizeof(char *));
if (dest->argv == NULL) {
fprintf(stderr, "Memory allocation error for args\n");
free(dest->label);
free(dest->opts);
free(dest->lopts);
json_decref(json_value);
return true;
}

for (int i = 0; i < dest->argc; i++) {
json_t *el = json_array_get(args, i);
if (!json_is_string(el)) {
fprintf(stderr, "expected args element to be a string\n");
for (int j = 0; j < i; j++) {
free(dest->argv[j]);
}
free(dest->argv);
free(dest->label);
free(dest->opts);
free(dest->lopts);
json_decref(json_value);
return true;
}
dest->argv[i] = strdup(json_string_value(el));
if (dest->argv[i] == NULL) {
fprintf(stderr, "Memory allocation error for arg %d\n", i);
for (int j = 0; j < i; j++) {
free(dest->argv[j]);
}
free(dest->argv);
free(dest->label);
free(dest->opts);
free(dest->lopts);
json_decref(json_value);
return true;
}
}

json_decref(json_value);
return false;
}
18 changes: 18 additions & 0 deletions testgen/case.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#ifndef CASE_H
#define CASE_H

#include <stdbool.h>
#include <stdio.h>

typedef struct Case {
char *label;
char *opts;
char *lopts;
char **argv;
int argc;
} Case;

bool read_case(Case *dest, FILE *src);
void case_clear(Case *c);

#endif
79 changes: 79 additions & 0 deletions testgen/config.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@

#include "config.h"

#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void print_usage(const char *name);

Config *create_config(int argc, char *argv[]) {
Config *cfg = calloc(1, sizeof(Config));
if (cfg == NULL) {
fprintf(stderr, "error allocating config: %s\n", strerror(errno));
return NULL;
}

int opt;
while ((opt = getopt(argc, argv, ":o:")) != -1) {
switch (opt) {
case 'o':
cfg->outpath = strdup(optarg);
if (cfg->outpath == NULL) {
fprintf(stderr, "error allocating config: %s\n", strerror(errno));
config_destroy(cfg);
return NULL;
}
break;
case '?':
fprintf(stderr, "error: Unknown option \"%c\"\n", optopt);
print_usage(argv[0]);
config_destroy(cfg);
return NULL;
case ':':
fprintf(stderr, "error: Option \"%c\" requires an argument\n", optopt);
print_usage(argv[0]);
config_destroy(cfg);
return NULL;
default:
break;
}
}

if (cfg->outpath == NULL) {
fprintf(stderr, "error: Option -o is required\n");
print_usage(argv[0]);
config_destroy(cfg);
return NULL;
}

if (optind < argc) {
cfg->inpath = strdup(argv[optind]);
if (cfg->inpath == NULL) {
fprintf(stderr, "error allocating config: %s\n", strerror(errno));
config_destroy(cfg);
return NULL;
}
} else {
fprintf(stderr, "error: missing required infile parameter\n");
print_usage(argv[optind]);
config_destroy(cfg);
return NULL;
}

return cfg;
}

void config_destroy(Config *cfg) {
if (cfg != NULL) {
free((char *)cfg->outpath);
free((char *)cfg->inpath);
free(cfg);
}
}

static void print_usage(const char *name) {
fprintf(stderr, "usage: %s -o <outfile> <infile>\n", name);
}
12 changes: 12 additions & 0 deletions testgen/config.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#ifndef CONFIG_H
#define CONFIG_H

typedef struct Config {
const char *inpath;
const char *outpath;
} Config;

Config *create_config(int argc, char *argv[]);
void config_destroy(Config *cfg);

#endif
4 changes: 4 additions & 0 deletions testgen/generate.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#ifndef GENERATE_H
#define GENERATE_H

#endif
117 changes: 117 additions & 0 deletions testgen/iterate.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
#include "iterate.h"

#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "case.h"
#include "config.h"
#include "stdbool.h"

static bool seek_array_start(FILE *f);
static enum IteratorStatus seek_next_element(FILE *f);
static FILE *prepare_infile(const Config *cfg);

Iterator *create_iterator(Config *cfg) {
Iterator *iter = calloc(1, sizeof(Iterator));
if (iter == NULL) {
fprintf(stderr, "error allocating iterator: %s\n", strerror(errno));
return NULL;
}

iter->src = prepare_infile(cfg);
if (iter->src == NULL) {
free(iter);
return NULL;
}

iter->status = ITER_OK;
iter->index = -1;

return iter;
}

void iterator_destroy(Iterator *iter) {
if (iter != NULL) {
if (iter->src != NULL) {
fclose(iter->src);
}
case_clear(&iter->current);
free(iter);
}
}

void iterator_next(Iterator *iter) {
if (iter->status != ITER_OK) {
return;
}

if (iter->index != -1) {
iter->status = seek_next_element(iter->src);
if (iter->status != ITER_OK) {
return;
}
}

case_clear(&iter->current);
if (read_case(&iter->current, iter->src)) {
iter->status = ITER_ERROR;
return;
}

iter->index++;
}

static bool seek_array_start(FILE *f) {
for (;;) {
int c = fgetc(f);
if (c == EOF || !isspace(c)) {
if (c != '[') {
fprintf(stderr, "error: Input is not a valid json array\n");
return true;
}
break;
}
}
return false;
}

static enum IteratorStatus seek_next_element(FILE *f) {
if (f == NULL) {
fprintf(stderr, "error: Invalid file pointer\n");
return ITER_ERROR;
}

int c;
while ((c = fgetc(f)) != EOF) {
switch (c) {
case ']':
return ITER_DONE;
case ',':
return ITER_OK;
default:
if (!isspace(c)) {
fprintf(stderr, "error: Input is not a valid json array\n");
return ITER_ERROR;
}
}
}
fprintf(stderr, "error: Unexpected EOF\n");
return ITER_ERROR;
}

static FILE *prepare_infile(const Config *cfg) {
FILE *f = fopen(cfg->inpath, "r");
if (f == NULL) {
fprintf(stderr, "error opening iterator source %s: %s\n", cfg->inpath, strerror(errno));
return NULL;
}

if (seek_array_start(f) != 0) {
return NULL;
}

return f;
}
Loading

0 comments on commit 9904ed1

Please sign in to comment.