test: started porting cron tests

remotes/1725063243225097762/tmp_refs/heads/main
Jef Roosens 2023-01-18 13:53:50 +01:00
parent c018d8d86c
commit 30e086ad6b
6 changed files with 97 additions and 119 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
build/ build/
compile_commands.json compile_commands.json
.cache/

View File

@ -60,11 +60,11 @@ $(BUILD_DIR)/$(TEST_DIR)/%.c.o: $(TEST_DIR)/%.c
# =====MAINTENANCE===== # =====MAINTENANCE=====
.PHONY: lint .PHONY: lint
lint: lint:
clang-format -n --Werror $(SRCS) $(SRCS_H) $(SRCS_TEST) clang-format -n --Werror $(SRCS) $(SRCS_H)
.PHONY: fmt .PHONY: fmt
fmt: fmt:
clang-format -i $(SRCS) $(SRCS_H) $(SRCS_TEST) clang-format -i $(SRCS) $(SRCS_H)
.PHONY: clean .PHONY: clean
clean: clean:

View File

@ -7,16 +7,16 @@
#include <string.h> #include <string.h>
#include <time.h> #include <time.h>
typedef enum cron_parse_error { typedef enum vieter_cron_parse_error {
cron_parse_ok = 0, vieter_cron_parse_ok = 0,
cron_parse_invalid_expression = 1, vieter_cron_parse_invalid_expression = 1,
cron_parse_invalid_number = 2, vieter_cron_parse_invalid_number = 2,
cron_parse_out_of_range = 3, vieter_cron_parse_out_of_range = 3,
cron_parse_too_many_parts = 4, vieter_cron_parse_too_many_parts = 4,
cron_parse_not_enough_parts = 5 vieter_cron_parse_not_enough_parts = 5
} cron_parse_error; } vieter_cron_parse_error;
typedef struct cron_expression { typedef struct vieter_cron_expression {
uint8_t *minutes; uint8_t *minutes;
uint8_t *hours; uint8_t *hours;
uint8_t *days; uint8_t *days;
@ -25,26 +25,29 @@ typedef struct cron_expression {
uint8_t hour_count; uint8_t hour_count;
uint8_t day_count; uint8_t day_count;
uint8_t month_count; uint8_t month_count;
} cron_expression; } vieter_cron_expression;
typedef struct cron_simple_time { typedef struct vieter_cron_simple_time {
int year; int year;
int month; int month;
int day; int day;
int hour; int hour;
int minute; int minute;
} cron_simple_time; } vieter_cron_simple_time;
cron_expression *ce_init(); vieter_cron_expression *ce_init();
void cron_ce_free(cron_expression *ce); void vieter_cron_ce_free(vieter_cron_expression *ce);
void cron_ce_next(cron_simple_time *out, cron_expression *ce, void vieter_cron_ce_next(vieter_cron_simple_time *out,
cron_simple_time *ref); vieter_cron_expression *ce,
vieter_cron_simple_time *ref);
void cron_ce_next_from_now(cron_simple_time *out, cron_expression *ce); void vieter_cron_ce_next_from_now(vieter_cron_simple_time *out,
vieter_cron_expression *ce);
enum cron_parse_error cron_ce_parse_expression(cron_expression *out, enum vieter_cron_parse_error
vieter_cron_parse_expression(vieter_cron_expression *out,
const char *expression); const char *expression);
#endif #endif

View File

@ -3,9 +3,11 @@
const uint8_t month_days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; const uint8_t month_days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
cron_expression *ce_init() { return malloc(sizeof(cron_expression)); } vieter_cron_expression *vieter_cron_expression_init() {
return malloc(sizeof(vieter_cron_expression));
}
void ce_free(cron_expression *ce) { void ce_free(vieter_cron_expression *ce) {
free(ce->months); free(ce->months);
free(ce->days); free(ce->days);
free(ce->hours); free(ce->hours);
@ -13,8 +15,8 @@ void ce_free(cron_expression *ce) {
free(ce); free(ce);
} }
void ce_next(cron_simple_time *out, cron_expression *ce, void vieter_cron_next(vieter_cron_simple_time *out, vieter_cron_expression *ce,
cron_simple_time *ref) { vieter_cron_simple_time *ref) {
// For all of these values, the rule is the following: if their value is // For all of these values, the rule is the following: if their value is
// the length of their respective array in the CronExpression object, that // the length of their respective array in the CronExpression object, that
// means we've looped back around. This means that the "bigger" value has // means we've looped back around. This means that the "bigger" value has
@ -98,12 +100,13 @@ void ce_next(cron_simple_time *out, cron_expression *ce,
} }
} }
void ce_next_from_now(cron_simple_time *out, cron_expression *ce) { void vieter_cron_next_from_now(vieter_cron_simple_time *out,
vieter_cron_expression *ce) {
time_t t = time(NULL); time_t t = time(NULL);
struct tm gm; struct tm gm;
gmtime_r(&t, &gm); gmtime_r(&t, &gm);
cron_simple_time ref = {// tm_year contains years since 1900 vieter_cron_simple_time ref = {// tm_year contains years since 1900
.year = 1900 + gm.tm_year, .year = 1900 + gm.tm_year,
// tm_mon goes from 0 to 11 // tm_mon goes from 0 to 11
.month = gm.tm_mon + 1, .month = gm.tm_mon + 1,
@ -111,5 +114,5 @@ void ce_next_from_now(cron_simple_time *out, cron_expression *ce) {
.hour = gm.tm_hour, .hour = gm.tm_hour,
.minute = gm.tm_min}; .minute = gm.tm_min};
ce_next(out, ce, &ref); vieter_cron_next(out, ce, &ref);
} }

View File

@ -16,10 +16,10 @@ const uint8_t max_parts = 4;
#define SAFE_ATOI(v, s, min, max) \ #define SAFE_ATOI(v, s, min, max) \
int _##v = atoi(s); \ int _##v = atoi(s); \
if ((_##v) == 0 && strcmp((s), "0") != 0) { \ if ((_##v) == 0 && strcmp((s), "0") != 0) { \
return cron_parse_invalid_number; \ return vieter_cron_parse_invalid_number; \
} \ } \
if (((_##v) < (min)) || ((_##v) > (max))) { \ if (((_##v) < (min)) || ((_##v) > (max))) { \
return cron_parse_out_of_range; \ return vieter_cron_parse_out_of_range; \
} \ } \
v = (uint8_t)(_##v); v = (uint8_t)(_##v);
@ -41,7 +41,8 @@ const uint8_t max_parts = 4;
* - a/c * - a/c
* - a-b/c * - a-b/c
*/ */
cron_parse_error ce_parse_range(uint64_t *out, char *s, uint8_t min, vieter_cron_parse_error vieter_cron_expression_parse_range(uint64_t *out,
char *s, uint8_t min,
uint8_t max) { uint8_t max) {
size_t slash_index = 0, dash_index = 0; size_t slash_index = 0, dash_index = 0;
size_t s_index = 0; size_t s_index = 0;
@ -54,12 +55,12 @@ cron_parse_error ce_parse_range(uint64_t *out, char *s, uint8_t min,
(cur_char >= '0' && cur_char <= '9'); (cur_char >= '0' && cur_char <= '9');
if (!is_valid_character) { if (!is_valid_character) {
return cron_parse_invalid_expression; return vieter_cron_parse_invalid_expression;
} }
if (cur_char == '/') { if (cur_char == '/') {
if (s_index == 0 || slash_index != 0) { if (s_index == 0 || slash_index != 0) {
return cron_parse_invalid_expression; return vieter_cron_parse_invalid_expression;
} }
slash_index = s_index; slash_index = s_index;
@ -68,7 +69,7 @@ cron_parse_error ce_parse_range(uint64_t *out, char *s, uint8_t min,
} else if (cur_char == '-') { } else if (cur_char == '-') {
// At most one dash is allowed, and it must be before the slash // At most one dash is allowed, and it must be before the slash
if (s_index == 0 || dash_index != 0 || slash_index != 0) { if (s_index == 0 || dash_index != 0 || slash_index != 0) {
return cron_parse_invalid_expression; return vieter_cron_parse_invalid_expression;
} }
dash_index = s_index; dash_index = s_index;
@ -85,7 +86,7 @@ cron_parse_error ce_parse_range(uint64_t *out, char *s, uint8_t min,
if (s[0] == '*') { if (s[0] == '*') {
if (s[1] != '\0' || dash_index != 0) { if (s[1] != '\0' || dash_index != 0) {
return cron_parse_invalid_expression; return vieter_cron_parse_invalid_expression;
} }
start = min; start = min;
@ -112,7 +113,7 @@ cron_parse_error ce_parse_range(uint64_t *out, char *s, uint8_t min,
} }
} }
return cron_parse_ok; return vieter_cron_parse_ok;
} }
/* /*
@ -120,19 +121,19 @@ cron_parse_error ce_parse_range(uint64_t *out, char *s, uint8_t min,
* min-max range the part represents. A part consists of one or more range * min-max range the part represents. A part consists of one or more range
* expressions, separated by commas. * expressions, separated by commas.
*/ */
cron_parse_error ce_parse_part(uint64_t *out, char *s, uint8_t min, vieter_cron_parse_error ce_parse_part(uint64_t *out, char *s, uint8_t min,
uint8_t max) { uint8_t max) {
*out = 0; *out = 0;
char *next; char *next;
cron_parse_error res; vieter_cron_parse_error res;
while ((next = strchr(s, ',')) != NULL) { while ((next = strchr(s, ',')) != NULL) {
next[0] = '\0'; next[0] = '\0';
res = ce_parse_range(out, s, min, max); res = vieter_cron_expression_parse_range(out, s, min, max);
if (res != cron_parse_ok) { if (res != vieter_cron_parse_ok) {
return res; return res;
} }
@ -140,7 +141,7 @@ cron_parse_error ce_parse_part(uint64_t *out, char *s, uint8_t min,
} }
// Make sure to parse the final range as well // Make sure to parse the final range as well
return ce_parse_range(out, s, min, max); return vieter_cron_expression_parse_range(out, s, min, max);
} }
/* /*
@ -196,13 +197,14 @@ uint8_t bf_to_nums(uint8_t **out, uint64_t bf, uint8_t min, uint8_t max) {
/* /*
* Parse a cron expression string into a cron_expression struct. * Parse a cron expression string into a cron_expression struct.
*/ */
cron_parse_error ce_parse_expression(cron_expression *out, vieter_cron_parse_error
vieter_cron_parse_expression(vieter_cron_expression *out,
const char *expression) { const char *expression) {
// The parsing functions modify the input string in-place // The parsing functions modify the input string in-place
char *s = strdup(expression); char *s = strdup(expression);
char *orig_s = s; char *orig_s = s;
cron_parse_error res = cron_parse_ok; vieter_cron_parse_error res = vieter_cron_parse_ok;
// First we divide the input string into its parts, divided by spaces. // First we divide the input string into its parts, divided by spaces.
// Each part is delimited by a NULL byte. // Each part is delimited by a NULL byte.
@ -237,7 +239,7 @@ cron_parse_error ce_parse_expression(cron_expression *out,
// s[0] isn't '\0', there's still another part before the end of the string. // s[0] isn't '\0', there's still another part before the end of the string.
if (s[0] != '\0') { if (s[0] != '\0') {
if (part_count == max_parts) { if (part_count == max_parts) {
res = cron_parse_too_many_parts; res = vieter_cron_parse_too_many_parts;
goto end; goto end;
} }
@ -246,7 +248,7 @@ cron_parse_error ce_parse_expression(cron_expression *out,
} }
if (part_count < min_parts) { if (part_count < min_parts) {
res = cron_parse_not_enough_parts; res = vieter_cron_parse_not_enough_parts;
goto end; goto end;
} }
@ -259,7 +261,7 @@ cron_parse_error ce_parse_expression(cron_expression *out,
if (part_count >= 4) { if (part_count >= 4) {
res = ce_parse_part(&bit_field, parts[3], min[3], max[3]); res = ce_parse_part(&bit_field, parts[3], min[3], max[3]);
if (res != cron_parse_ok) { if (res != vieter_cron_parse_ok) {
goto end; goto end;
} }
@ -283,7 +285,7 @@ cron_parse_error ce_parse_expression(cron_expression *out,
res = ce_parse_part(&bit_field, parts[2], min[2], max_day_value); res = ce_parse_part(&bit_field, parts[2], min[2], max_day_value);
if (res != cron_parse_ok) { if (res != vieter_cron_parse_ok) {
free(out->months); free(out->months);
goto end; goto end;
@ -301,7 +303,7 @@ cron_parse_error ce_parse_expression(cron_expression *out,
res = ce_parse_part(&bit_field, parts[1], min[1], max[1]); res = ce_parse_part(&bit_field, parts[1], min[1], max[1]);
if (res != cron_parse_ok) { if (res != vieter_cron_parse_ok) {
free(out->months); free(out->months);
free(out->days); free(out->days);
@ -315,7 +317,7 @@ cron_parse_error ce_parse_expression(cron_expression *out,
res = ce_parse_part(&bit_field, parts[0], min[0], max[0]); res = ce_parse_part(&bit_field, parts[0], min[0], max[0]);
if (res != cron_parse_ok) { if (res != vieter_cron_parse_ok) {
free(out->months); free(out->months);
free(out->days); free(out->days);
free(out->hours); free(out->hours);

View File

@ -1,69 +1,38 @@
#include "acutest.h" #include "acutest.h"
#include "vieter_cron.h" #include "vieter_cron.h"
void test_tutorial(void) { void test_not_allowed() {
void *mem; char *expressions[] = {
"4 *-7",
"4 *-7/4",
"4 7/*",
"0 0 30 2",
"0 /5",
"0 ",
"0",
" 0",
" 0 ",
"1 2 3 4~9",
"1 1-3-5",
"0 5/2-5",
"",
"1 1/2/3",
"*5 8",
"x 8",
NULL
};
mem = malloc(10); int i = 0;
TEST_CHECK(mem != NULL);
mem = realloc(mem, 20); while (expressions[i] != NULL) {
TEST_CHECK(mem != NULL); vieter_cron_expression out;
TEST_CHECK_(vieter_cron_parse_expression(&out, expressions[i]) != vieter_cron_parse_ok, "%s should error", expressions[i]);
free(mem); i++;
}
} }
void test_fail(void) { TEST_LIST = {
int a, b; {"not_allowed", test_not_allowed},
{NULL, NULL}
/* This condition is designed to fail so you can see what the failed test };
* output looks like. */
a = 1;
b = 2;
TEST_CHECK(a + b == 5);
/* Here is TEST_CHECK_ in action. */
TEST_CHECK_(a + b == 5, "%d + %d == 5", a, b);
/* We may also show more information about the failure. */
if (!TEST_CHECK(a + b == 5)) {
TEST_MSG("a: %d", a);
TEST_MSG("b: %d", b);
}
/* The macro TEST_MSG() only outputs something when the preceding
* condition fails, so we can avoid the 'if' statement. */
TEST_CHECK(a + b == 3);
TEST_MSG("a: %d", a);
TEST_MSG("b: %d", b);
}
static void helper(void) {
/* Kill the current test with a condition which is never true. */
TEST_ASSERT(1 == 2);
/* This never happens because the test is aborted above. */
TEST_CHECK(1 + 2 == 2 + 1);
}
void test_abort(void) {
helper();
/* This test never happens because the test is aborted inside the helper()
* function. */
TEST_CHECK(1 * 2 == 2 * 1);
}
void test_crash(void) {
int *invalid = ((int *)NULL) + 0xdeadbeef;
*invalid = 42;
TEST_CHECK_(1 == 1, "This should never execute, due to a write into "
"an invalid address.");
}
TEST_LIST = {{"tutorial", test_tutorial},
{"fail", test_fail},
{"abort", test_abort},
{"crash", test_crash},
{NULL, NULL}};