diff options
author | Linnnus <[email protected]> | 2024-02-03 23:39:15 +0100 |
---|---|---|
committer | Linnnus <[email protected]> | 2024-02-04 20:26:17 +0100 |
commit | 2967d8f1b6636b46ec648e9efba2f79573c20da8 (patch) | |
tree | 9f2f714f2e7eddd652bc72c8aabda8a1981b6d35 /src/creole-test.c | |
parent | d38f82f6462af4e5aad6a2c776f5c00ce5b13c87 (diff) |
test(creole): move creole tests to static data
This removes *a ton* of code for dealing with reading files and what
not. Now instead we just store the test cases as a huge array in the
code.
I have no idea why I didn't just do this from the start??
Diffstat (limited to 'src/creole-test.c')
-rw-r--r-- | src/creole-test.c | 550 |
1 files changed, 300 insertions, 250 deletions
diff --git a/src/creole-test.c b/src/creole-test.c index 18d24a2..3bf820f 100644 --- a/src/creole-test.c +++ b/src/creole-test.c @@ -1,273 +1,323 @@ #include "creole.h" -#include <assert.h> -#include <dirent.h> #include <stdio.h> -#include <glob.h> -#include <stdlib.h> -#include <stdbool.h> #include <string.h> -#include <errno.h> -//#include <ftw.h> -#define RED "\x1b[31m" -#define GREEN "\x1b[32m" -#define YELLOW "\x1b[33m" -#define CYAN "\x1b[96m" -#define BOLD "\x1b[39;1m" -#define DIM "\x1b[39;2m" -#define CLEAR "\x1b[0m" - -#define LENGTH(a) (sizeof(a)/sizeof((a)[0])) - -#define CHUNK_SIZE 5 - -int read_file(const char *file_path, char **out_buffer, size_t *out_length) { - assert(out_buffer != NULL && *out_buffer == NULL); - assert(file_path != NULL); - - FILE *fp = fopen(file_path, "r"); - if (fp == NULL) { - return -1; - } - - char *buffer = NULL; - size_t allocated = 0; - size_t used = 0; - while (true) { - // Grow buffer, if needed. - if (used + CHUNK_SIZE > allocated) { - // Grow exponentially to guarantee O(log(n)) performance. - allocated = (allocated == 0) ? CHUNK_SIZE : allocated * 2; - - // Overflow check. Some ANSI C compilers may optimize this away, though. - if (allocated <= used) { - free(buffer); - fclose(fp); - errno = EOVERFLOW; - return -1; - } - - char *temp = realloc(buffer, allocated); - if (temp == NULL) { - int old_errno = errno; - free(buffer); // free() may not set errno - fclose(fp); // fclose() may set errno - errno = old_errno; - return -1; - } - buffer = temp; - } - - size_t nread = fread(buffer + used, 1, CHUNK_SIZE, fp); - if (nread == 0) { - // End-of-file or errnor has occured. - // FIXME: Should we be checking (nread < CHUNK_SIZE)? - // https://stackoverflow.com/a/39322170 - break; - } - used += nread; - } - - if (ferror(fp)) { - int old_errno = errno; - free(buffer); // free() may not set errno - fclose(fp); // fclose() may set errno - errno = old_errno; - return -1; - } - - // Reallocate to optimal size. - char *temp = realloc(buffer, used + 1); - if (temp == NULL) { - int old_errno = errno; - free(buffer); // free() may not set errno - fclose(fp); // fclose() may set errno - errno = old_errno; - return -1; - } - buffer = temp; - - // Null-terminate the buffer. Note that buffers may still contain \0, - // so strlen(buffer) == length may not be true. - buffer[used] = '\0'; - - // Return buffer. - *out_buffer = buffer; - if (out_length != NULL) { - *out_length = used; - } - fclose(fp); - return 0; -} - -// https://stackoverflow.com/a/779960 -char *replace(const char *orig, char *rep, char *with) { - assert(orig != NULL); - assert(rep != NULL); - - char *tmp; // varies - - int len_rep = strlen(rep); // length of rep (the string to remove) - if (len_rep == 0) { - errno = EINVAL; // empty rep causes infinite loop during count - return NULL; - } - - int len_with; // length of with (the string to replace rep with) - if (with == NULL) - with = ""; - len_with = strlen(with); - - // count the number of replacements needed - const char *ins; // the next insert point - int count; // number of replacements - ins = orig; - for (count = 0; (tmp = strstr(ins, rep)) != NULL; ++count) { - ins = tmp + len_rep; - } - - char *result; // the return string - tmp = result = malloc(strlen(orig) + (len_with - len_rep) * count + 1); - if (!result) { - return NULL; +#define COUNT(arr) (sizeof(arr)/sizeof((arr)[0])) + +#define strneq(a, b, n) (strncmp(a, b, n) == 0) + +struct { + const char *name, *input, *output; +} tests[] = { + { + .name = "Basic paragraph markup", + .input = "Basic paragraph test with <, >, & and \"", + .output = "<p>Basic paragraph test with <, >, & and "</p>" + }, +#if 0 + { + .name = "Simple unordered list", + .input = "* list item\n*list item 2", + .output = "<ul><li> list item</li>\n<li>list item 2</li></ul>" + }, + { + .name = "Simple ordered list", + .input = "# list item\n#list item 2", + .output = "<ol><li> list item</li>\n<li>list item 2</li></ol>" + }, + { + .name = "Unordered item with unordered sublist", + .input = "* Item\n** Subitem", + .output = "<ul><li> Item<ul>\n<li> Subitem</li></ul></li></ul>" + }, + { + .name = "Unordered sublist without initial tag", + .input = "** Sublist item", + .output = "<p>** Sublist item</p>" + }, + { + .name = "Ordered item with ordered sublist", + .input = "# Item\n## Subitem", + .output = "<ol><li> Item<ol>\n<li> Subitem</li></ol></li></ol>" + }, + { + .name = "Ordered sublist without initial tag", + .input = "## Sublist item", + .output = "<p>## Sublist item</p>" + }, + { + .name = "Unordered item with ordered sublist", + .input = "* Item\n*# Subitem", + .output = "<ul><li> Item<ol>\n<li> Subitem</li></ol></li></ul>" + }, + { + .name = "Horizontal rule", + .input = "Some text\n----\nSome more text", + .output = "<p>Some text</p><hr /><p>Some more text</p>" + }, + { + .name = "Preformatted block", + .input = "{{{\nPreformatted block\n}}}", + .output = "<pre>Preformatted block\n</pre>" + }, + { + .name = "Two preformatted blocks", + .input = "{{{\nPreformatted block\n}}}\n{{{Block 2}}}", + .output = "<pre>Preformatted block\n</pre><pre>Block 2</pre>" + }, + { + .name = "h1", + .input = "= Header =", + .output = "<h1>Header</h1>" + }, + { + .name = "h2", + .input = "== Header =", + .output = "<h2>Header</h2>" + }, + { + .name = "h3", + .input = "=== Header =", + .output = "<h3>Header</h3>" + }, + { + .name = "h4", + .input = "==== Header =", + .output = "<h4>Header</h4>" + }, + { + .name = "h5", + .input = "===== Header", + .output = "<h5>Header</h5>" + }, + { + .name = "h6", + .input = "====== Header =", + .output = "<h6>Header</h6>" + }, + { + .name = ">h6", + .input = "======= Header =", + .output = "<p>======= Header =</p>" + }, + { + .name = "Tables", + .input = "| A | B |\n| //C// | **D** \\\\ E |", + .output = "<table><tr><td> A </td><td> B </td></tr>" + "<tr><td> <em>C</em> </td>" + "<td> <strong>D</strong> <br /> E </td></tr></table>" + }, + { + .name = "Raw URL", + .input = "http //example.com/examplepage", + .output = "<p><a href=\"http //example.com/examplepage\">" + "http //example.com/examplepage</a></p>" + }, + { + .name = "Unnamed URL", + .input = "[[http //example.com/examplepage]]", + .output = "<p><a href=\"http //example.com/examplepage\">" + "http //example.com/examplepage</a></p>" + }, + { + .name = "Named URL", + .input = "[[http //example.com/examplepage|Example Page]]", + .output = "<p>" + "<a href=\"http //example.com/examplepage\">Example Page</a></p>" + }, + { + .name = "Unnamed link", + .input = "[[MyPage]]", + .output = "<p><a href=\"MyPage\">MyPage</a></p>" + }, + { + .name = "Named link", + .input = "[[MyPage|My page]]", + .output = "<p><a href=\"MyPage\">My page</a></p>" + }, + { + .name = "Image", + .input = "{{image.gif|my image}}", + .output = "<p><img src=\"image.gif\" alt=\"my image\"/></p>" + }, + { + .name = "Inline tt", + .input = "Inline {{{tt}}} example {{{here}}}!", + .output = "<p>Inline <tt>tt</tt> example <tt>here</tt>!</p>" + }, + { + .name = "Strong", + .input = "**Strong**", + .output = "<p><strong>Strong</strong></p>" + }, + { + .name = "Emphasis", + .input = "//Emphasis//", + .output = "<p><em>Emphasis</em></p>" + }, + { + .name = "Multi-line emphasis", + .input = "Bold and italics should //be\nable// to cross lines.\n\n" + "But, should //not be...\n\n...able// to cross paragraphs.", + .output = "<p>Bold and italics should <em>be\nable</em> to cross lines.\n</p>" + "<p>\nBut, should //not be...\n</p>" + "<p>\n...able// to cross paragraphs.</p>" + }, + { + .name = "URL/emphasis ambiguity", + .input = "This is an //italic// text. This is a url " + "http //www.wikicreole.org. This is what can go wrong //this " + "should be an italic text//.", + .output = "<p>This is an <em>italic</em> text. This is a url " + "<a href=\"http //www.wikicreole.org\">" + "http //www.wikicreole.org</a>. This is what can go wrong " + "<em>this should be an italic text</em>.</p>" + }, + { + .name = "Difficult emphasis #1", + .input = "// http //www.link.org //", + .output = "<p><em> <a href=\"http //www.link.org\">" + "http //www.link.org</a> </em></p>" + }, + { + .name = "Difficult emphasis #2", + .input = "// http //", + .output = "<p><em> http </em></p>" + }, + { + .name = "Difficult emphasis #3", + .input = "// httphpthtpht //", + .output = "<p><em> httphpthtpht </em></p>" + }, + { + .name = "Difficult emphasis #4", + .input = "// http //", + .output = "<p><em> http </em></p>" + }, + { + .name = "Difficult emphasis #5", + .input = "// http //", + .output = "<p>// <a href=\"http //\">http //</a></p>" + }, + { + .name = "Difficult emphasis #6", + .input = "// http ////", + .output = "<p><em> <a href=\"http //\">http //</a></em></p>" + }, + { + .name = "Difficult emphasis #7", + .input = "//httphpthtphtt//", + .output = "<p><em>httphpthtphtt</em></p>" + }, + { + .name = "Difficult emphasis #8", + .input = "//http //link.org//", + .output = "<p><em><a href=\"http //link.org\">" + "http //link.org</a></em></p>" + }, + { + .name = "Difficult emphasis #9", + .input = "// ftp //www.link.org //", + .output = "<p><em> <a href=\"ftp //www.link.org\">" + "ftp //www.link.org</a> </em></p>" + }, + { + .name = "Difficult emphasis #10", + .input = "// ftp //", + .output = "<p><em> ftp </em></p>" + }, + { + .name = "Difficult emphasis #11", + .input = "// fttpfptftpft //", + .output = "<p><em> fttpfptftpft </em></p>" + }, + { + .name = "Difficult emphasis #12", + .input = "// ftp //", + .output = "<p><em> ftp </em></p>" + }, + { + .name = "Difficult emphasis #13", + .input = "// ftp //", + .output = "<p>// <a href=\"ftp //\">ftp //</a></p>" + }, + { + .name = "Difficult emphasis #14", + .input = "// ftp ////", + .output = "<p><em> <a href=\"ftp //\">ftp //</a></em></p>" + }, + { + .name = "Difficult emphasis #15", + .input = "//fttpfptftpftt//", + .output = "<p><em>fttpfptftpftt</em></p>" + }, + { + .name = "Difficult emphasis #16", + .input = "//ftp //link.org//", + .output = "<p><em><a href=\"ftp //link.org\">" + "ftp //link.org</a></em></p>" } - - // first time through the loop, all the variable are set correctly - // from here on, - // tmp points to the end of the result string - // ins points to the next occurrence of rep in orig - // orig points to the remainder of orig after "end of rep" - while (count--) { - ins = strstr(orig, rep); - int len_front = ins - orig; - tmp = strncpy(tmp, orig, len_front) + len_front; - tmp = strcpy(tmp, with) + len_with; - orig += len_front + len_rep; // move to next "end of rep" - } - strcpy(tmp, orig); - return result; -} +#endif +}; int print_escaped(FILE *fp, const char *string, size_t length) { - static struct { - char from; - const char *to; - } replacements[] = { - { '\t', "\\t" }, - { '\n', "\\n" }, - { '"', "\\\"" }, - }; - - if (fputc('"', fp) == EOF) { - return -1; - } - - for (size_t i = 0; i < length; ++i) { - for (size_t j = 0; j < LENGTH(replacements); ++j) { - if (string[i] == replacements[j].from) { - if (fprintf(fp, "%s", replacements[j].to) < 0) { - return -1; - } - goto next_char; - } - } - if (fputc(string[i], fp) == EOF) { - return -1; - } + static struct { + char from; + const char *to; + } replacements[] = { + { '\t', "\\t" }, + { '\n', "\\n" }, + { '"', "\\\"" }, + }; + + if (fputc('"', fp) == EOF) { + return -1; + } + + for (size_t i = 0; i < length; ++i) { + for (size_t j = 0; j < COUNT(replacements); ++j) { + if (string[i] == replacements[j].from) { + if (fprintf(fp, "%s", replacements[j].to) < 0) { + return -1; + } + goto next_char; + } + } + if (fputc(string[i], fp) == EOF) { + return -1; + } next_char: - ; - } + ; + } - if (fputc('"', fp) == EOF) { - return -1; - } + if (fputc('"', fp) == EOF) { + return -1; + } - return 0; + return 0; } -int main(int argc, char *argv[]) { - if (argc != 1) { - fprintf(stderr, "Usage: %s\n", argv[0]); - fprintf(stderr, "Takes no arguments, must be invoked in parent of test dir.\n"); - return EXIT_FAILURE; - } - - glob_t glob_result; - if (glob("./test/*.input.txt", GLOB_ERR, NULL, &glob_result) < 0) { - perror("Glob failed"); - return EXIT_FAILURE; - } - - unsigned ok = 0; - unsigned failures = 0; - unsigned errors = 0; +int main(void) { + for (size_t i = 0; i < COUNT(tests); ++i) { + printf("Running test: %s... ", tests[i].name); - for (int i = 0; i < glob_result.gl_matchc; ++i) { - char *input_name = glob_result.gl_pathv[i]; - - int input_name_len = strlen(input_name); - int prefix_len = strlen("./test/"); - int sufix_len = strlen(".input.txt"); - printf("Running: " BOLD "%.*s" CLEAR "... ", input_name_len - prefix_len - sufix_len, input_name + prefix_len); - - char *input_buffer = NULL; - size_t input_length; - if (read_file(input_name, &input_buffer, &input_length) < 0) { - printf(RED "internal error!" CLEAR "\n " CYAN "error:" CLEAR " reading %s: %s\n", input_name, strerror(errno)); - errors += 1; - goto fail_input_buffer; - } - - // TODO: replace() is a bit overkill. Just strcpy to buffer of size (strlen(input_name) - strlen(".input.txt") + strlen(".output.txt")). - char *output_name = replace(input_name, ".input.txt", ".output.txt"); - if (output_name == NULL) { - printf(RED "internal error!" CLEAR "\n " CYAN "error:" CLEAR " generating output name: %s\n", strerror(errno)); - errors += 1; - goto fail_output_name; - } - char *output_buffer = NULL; - size_t output_length; - if (read_file(output_name, &output_buffer, &output_length) < 0) { - printf(RED "internal error!" CLEAR "\n " CYAN "error:" CLEAR " reading %s: %s\n", output_name, strerror(errno)); - errors += 1; - goto fail_output_buffer; - } - - // Do actual render. static char buffer[1024]; FILE *fp = fmemopen(buffer, sizeof(buffer), "wb"); - render_creole(fp, input_buffer, input_length); + render_creole(fp, tests[i].input, strlen(tests[i].input)); long buffer_length = ftell(fp); fclose(fp); - bool success = strcmp(output_buffer, buffer) == 0; - if (success) { - ok += 1; - printf(GREEN "ok" CLEAR "\n"); - } else { - failures += 1; - printf(RED "unexpected output!" CLEAR); - printf(CYAN "\n input: " CLEAR); - print_escaped(stdout, input_buffer, input_length); - printf(CYAN "\n want: " CLEAR); - print_escaped(stdout, output_buffer, output_length); - printf(CYAN"\n got: " CLEAR); - print_escaped(stdout, buffer, buffer_length); // TODO: rendered + if (!strneq(buffer, tests[i].output, buffer_length)) { + printf("\x1b[31merror\x1b[0m\n"); + printf("├── expected: "); + print_escaped(stdout, tests[i].output, strlen(tests[i].output)); + putchar('\n'); + printf("└─────── got: "); + print_escaped(stdout, buffer, buffer_length); putchar('\n'); + } else { + printf("\x1b[32mok\x1b[0m\n"); } - - free(output_buffer); -fail_output_buffer: - free(output_name); -fail_output_name: - free(input_buffer); -fail_input_buffer: - ; } - - printf("Summary: " YELLOW "%u" CLEAR " errors, " RED "%u" CLEAR " failures and " GREEN "%u" CLEAR " successes\n", errors, failures, ok); - - globfree(&glob_result); - return (failures == 0 && errors == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } |