From e0d5dabcaa7170fe4733bec764dac013fb3505e6 Mon Sep 17 00:00:00 2001 From: Andreas Waidler Date: Mon, 4 Apr 2011 07:50:51 +0200 Subject: [PATCH] Initial commit. Includes: * an almost complete set of acceptance tests * a few unit tests * generic and rss-handling code that passes all of the above (tested only on GNU/Linux) --- .gitignore | 15 ++ BUGS | 1 + Makefile | 29 ++++ TODO | 11 ++ agg.c | 30 ++++ agg_modify | 8 + bool.h | 8 + config.h | 8 + expat.c | 35 +++++ expat.h | 11 ++ fail.c | 7 + fail.h | 15 ++ fs.c | 124 ++++++++++++++++ fs.h | 14 ++ layer.c | 12 ++ layer.h | 14 ++ rss.c | 86 +++++++++++ rss.h | 6 + stack.c | 51 +++++++ stack.h | 11 ++ tests.rss | 31 ++++ tests_dev.c | 138 ++++++++++++++++++ tests_long.rss | 8 + tests_simple_descriptions.rss | 8 + tests_simple_titles.rss | 8 + tests_titlelate.rss | 10 ++ tests_titleless.rss | 9 ++ tests_usr.sh | 332 ++++++++++++++++++++++++++++++++++++++++++ text.c | 42 ++++++ text.h | 11 ++ 30 files changed, 1093 insertions(+) create mode 100644 .gitignore create mode 100644 BUGS create mode 100644 Makefile create mode 100644 TODO create mode 100644 agg.c create mode 100755 agg_modify create mode 100644 bool.h create mode 100644 config.h create mode 100644 expat.c create mode 100644 expat.h create mode 100644 fail.c create mode 100644 fail.h create mode 100644 fs.c create mode 100644 fs.h create mode 100644 layer.c create mode 100644 layer.h create mode 100644 rss.c create mode 100644 rss.h create mode 100644 stack.c create mode 100644 stack.h create mode 100644 tests.rss create mode 100644 tests_dev.c create mode 100644 tests_long.rss create mode 100644 tests_simple_descriptions.rss create mode 100644 tests_simple_titles.rss create mode 100644 tests_titlelate.rss create mode 100644 tests_titleless.rss create mode 100755 tests_usr.sh create mode 100644 text.c create mode 100644 text.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d0c41c0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*~ +*.swp +*.Po +*.Plo +*.o +*.so +*.lo +*.a +*.la +*.pc +*.BASE.* +*.LOCAL.* +*.REMOTE.* +agg +tests_dev diff --git a/BUGS b/BUGS new file mode 100644 index 0000000..cea5662 --- /dev/null +++ b/BUGS @@ -0,0 +1 @@ +* Uses fixed size buffers. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3ea6fb8 --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +CFLAGS=-W -Wall -Werror -Wfatal-errors -Wextra -pedantic-errors -std=c89 +LDFLAGS=-lexpat +LIBS=expat.o fail.o rss.o stack.o text.o layer.o fs.o +POBJ=agg.o +POUT=agg +TOBJ=tests_dev.o +TOUT=tests_dev +TUSR=tests_usr.sh + +all: src_build +tests: tests_dev tests_usr + +src_build: $(LIBS) $(POBJ) + $(CC) $(LDFLAGS) $(LIBS) $(POBJ) -o $(POUT) + +tests_dev: tests_dev_build + ./$(TOUT) + +tests_usr: src_build + ./$(TUSR) + +tests_dev_build: $(LIBS) $(TOBJ) + $(CC) $(LDFLAGS) $(LIBS) $(TOBJ) -o $(TOUT) + +clean: + rm -f $(LIBS) $(POBJ) $(POUT) $(TOBJ) $(TOUT) + +%.o: %.c + $(CC) $(CFLAGS) -c $< diff --git a/TODO b/TODO new file mode 100644 index 0000000..894bd8a --- /dev/null +++ b/TODO @@ -0,0 +1,11 @@ +* acceptance tests: + * feed name too long + * feed/title/desc injection not possible + * if no item date, set to TS = 0 + * check whether dates are handled properly (timezones!) + +* refactoring + * split into subdirectories + * test driven refactoring + * more assertions + * replace assertions by proper error handling diff --git a/agg.c b/agg.c new file mode 100644 index 0000000..0255074 --- /dev/null +++ b/agg.c @@ -0,0 +1,30 @@ +#include +#include +#include "config.h" +#include "expat.h" +#include "bool.h" +#include "fail.h" + +int main() +{ + char buf[READ_BUFFER_SIZE]; + bool done = false; + unsigned len; + + XML_Parser p = XML_ParserCreate(NULL); + expat_setup(&p); + + do { + len = fread(buf, 1, READ_BUFFER_SIZE, stdin); + done = feof(stdin); + + if (!XML_Parse(p, buf, len, done)) { + fail(ERR_EXPAT); + done = true; + } + } while (!done); + + XML_ParserFree(p); + + return 0; +} diff --git a/agg_modify b/agg_modify new file mode 100755 index 0000000..e4ac102 --- /dev/null +++ b/agg_modify @@ -0,0 +1,8 @@ +#!/bin/sh + +MTIME="`stat -c %y .`" +$1 +res=$? +touch -md "$MTIME" . + +exit $res diff --git a/bool.h b/bool.h new file mode 100644 index 0000000..57c204d --- /dev/null +++ b/bool.h @@ -0,0 +1,8 @@ +#ifndef AGG_BOOL_H +#define AGG_BOOL_H + +#define bool unsigned char +#define true 1 +#define false 0 + +#endif diff --git a/config.h b/config.h new file mode 100644 index 0000000..9752fa4 --- /dev/null +++ b/config.h @@ -0,0 +1,8 @@ +#ifndef AGG_CONFIG_H +#define AGG_CONFIG_H + +#define READ_BUFFER_SIZE 16 +#define TEXT_BUFFER_SIZE 8192 +#define FILE_NAME_LENGTH 32 + +#endif diff --git a/expat.c b/expat.c new file mode 100644 index 0000000..1537330 --- /dev/null +++ b/expat.c @@ -0,0 +1,35 @@ +#include +#include "expat.h" +#include "stack.h" +#include "text.h" + +bool expat_use_text_buffer = false; + +void XMLCALL ec_text(void* data, const XML_Char* s, int len) +{ + (void) data; + + if (!expat_use_text_buffer) return; + + text_buffer(s, len * sizeof(XML_Char)); +} + +void XMLCALL ec_enter(void* data, const char* elem, const char** attr) +{ + (void) data; + + stack_top()->enter(elem, attr); +} + +void XMLCALL ec_leave(void* data, const char* elem) +{ + (void) data; + + stack_top()->leave(elem); +} + +void expat_setup(XML_Parser* p) +{ + XML_SetCharacterDataHandler(*p, ec_text); + XML_SetElementHandler(*p, ec_enter, ec_leave); +} diff --git a/expat.h b/expat.h new file mode 100644 index 0000000..e58a008 --- /dev/null +++ b/expat.h @@ -0,0 +1,11 @@ +#ifndef AGG_EXPAT_H +#define AGG_EXPAT_H + +#include +#include "bool.h" + +extern bool expat_use_text_buffer; + +void expat_setup(XML_Parser*); + +#endif diff --git a/fail.c b/fail.c new file mode 100644 index 0000000..df22b88 --- /dev/null +++ b/fail.c @@ -0,0 +1,7 @@ +#include +#include "fail.h" + +void fail(enum error type) +{ + exit(type); +} diff --git a/fail.h b/fail.h new file mode 100644 index 0000000..e697312 --- /dev/null +++ b/fail.h @@ -0,0 +1,15 @@ +#ifndef AGG_FAIL_H +#define AGG_FAIL_H + +enum error +{ + ERR_TYPE = 1, /* unknown feed type */ + ERR_INVALID = 2, /* invalid feed contents */ + ERR_OOPS = 10, /* something bad happened */ + ERR_EXPAT = 11, /* expat's XML_Parse failed */ + ERR_STACK = 12 /* internal: Stack fucked up */ +}; + +void fail(enum error type); + +#endif diff --git a/fs.c b/fs.c new file mode 100644 index 0000000..d073c9e --- /dev/null +++ b/fs.c @@ -0,0 +1,124 @@ +#define _XOPEN_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include "config.h" +#include "bool.h" +#include "fail.h" +#include "fs.h" + +char feed_title[TEXT_BUFFER_SIZE] = { 0 }; +time_t feed_date = 0; +time_t feed_date_new = 0; +bool feed_is_open = false; + +char item_title[TEXT_BUFFER_SIZE] = { 0 }; +char item_link [TEXT_BUFFER_SIZE] = { 0 }; +char item_desc [TEXT_BUFFER_SIZE] = { 0 }; +time_t item_date = 0; + +void sanitize(char* str) +{ + char* evil; + while ((evil = strchr(str, '/'))) *evil = '\\'; +} + +void set_item_date(const char* date) +{ + struct tm t; + assert(strptime(date, "%a, %d %b %Y %T %z", &t)); + item_date = mktime(&t); + assert(item_date != -1); + if (item_date <= feed_date) { + printf("No new feeds assumed.\n"); + fail(0); + } else if (item_date > feed_date_new) { + feed_date_new = item_date; + } +} + +void set_item_link(const char* link) +{ + strncpy(item_link, link, TEXT_BUFFER_SIZE); +} + +void set_item_desc(const char* desc) +{ + strncpy(item_desc, desc, TEXT_BUFFER_SIZE); +} + +void set_item_title(const char* title) +{ + strncpy(item_title, title, TEXT_BUFFER_SIZE); + sanitize(item_title); +} + +void feed_open(const char* title) +{ + struct stat st; + + strncpy(feed_title, title, TEXT_BUFFER_SIZE); + sanitize(feed_title); + + if (!stat(feed_title, &st)) feed_date = st.st_mtime; + mkdir(feed_title, 0755); + assert(!chdir(feed_title)); + feed_is_open = true; +} + + + +void feed_flush() +{ + struct timeval times[2]; + times[0].tv_sec = feed_date_new; + times[0].tv_usec = 0; + times[1].tv_sec = feed_date_new; + times[1].tv_usec = 0; + + if (!feed_is_open) return; + + assert(!chdir("..")); + assert(!utimes(feed_title, times)); + feed_is_open = false; +} + +void item_flush() +{ + struct timeval times[2]; + FILE* f; + char buf[FILE_NAME_LENGTH + 1]; + assert(feed_is_open); + + if (item_title[0]) { + strncpy(buf, item_title, 32); + } else { + assert(item_desc[0]); + strncpy(buf, item_desc, 32); + } + + assert(f = fopen(buf, "w")); + if (item_title[0]) { + fprintf(f, "%s\n\n", item_title); + } + if (item_desc[0]) { + fprintf(f, "%s\n\n", item_desc); + } + if (item_link[0]) { + fprintf(f, "Link: %s\n", item_link, item_link); + } + fclose(f); + + /* FIXME: item date might not have been set. */ + times[0].tv_sec = item_date; + times[0].tv_usec = 0; + times[1].tv_sec = item_date; + times[1].tv_usec = 0; + + assert(!utimes(buf, times)); +} diff --git a/fs.h b/fs.h new file mode 100644 index 0000000..059a45d --- /dev/null +++ b/fs.h @@ -0,0 +1,14 @@ +#ifndef AGG_FS_H +#define AGG_FS_H + +void set_item_title(const char*); +void set_item_desc(const char*); +void set_item_date(const char*); +void set_item_link(const char*); + +void feed_open(const char*); +void feed_flush(); + +void item_flush(); + +#endif diff --git a/layer.c b/layer.c new file mode 100644 index 0000000..c217bf3 --- /dev/null +++ b/layer.c @@ -0,0 +1,12 @@ +#include "layer.h" + +struct Layer layer( + void (*enter)(const char*, const char**), + void (*leave)(const char*)) +{ + struct Layer result; + result.enter = enter; + result.leave = leave; + + return result; +} diff --git a/layer.h b/layer.h new file mode 100644 index 0000000..ba59489 --- /dev/null +++ b/layer.h @@ -0,0 +1,14 @@ +#ifndef AGG_LAYER_H +#define AGG_LAYER_H + +struct Layer +{ + void (*enter)(const char*, const char**); + void (*leave)(const char*); +}; + +struct Layer layer( + void (*enter)(const char*, const char**), + void (*leave)(const char*)); + +#endif diff --git a/rss.c b/rss.c new file mode 100644 index 0000000..ff8fcb1 --- /dev/null +++ b/rss.c @@ -0,0 +1,86 @@ +#include +#include "stack.h" +#include "text.h" +#include "fail.h" +#include "rss.h" +#include "fs.h" + + + + +void rss_global_enter(const char* elem, const char** attr) +{ + (void) attr; + + if (strcmp("channel", elem) != 0) fail(ERR_INVALID); + stack_next(); +} + +void rss_global_leave(const char* elem) +{ + if (strcmp("rss", elem) != 0) fail(ERR_INVALID); +} + +void rss_channel_enter(const char* elem, const char** attr) +{ + (void) attr; + + if (strcmp("item", elem) == 0) { + stack_next(); + } else if (strcmp("title", elem) == 0) { + text_enable(); + } +} + +void rss_channel_leave(const char* elem) +{ + if (strcmp("channel", elem) == 0) { + feed_flush(); + stack_prev(); + } else if (strcmp("title", elem) == 0) { + feed_open(text_get()); + text_disable(); + } +} + +void rss_item_enter(const char* elem, const char** attr) +{ + (void) attr; + + if (strcmp("title", elem) == 0) { + text_enable(); + } else if (strcmp("link", elem) == 0) { + text_enable(); + } else if (strcmp("pubDate", elem) == 0) { + text_enable(); + } else if (strcmp("description", elem) == 0) { + text_enable(); + } +} + +void rss_item_leave(const char* elem) +{ + if (strcmp("item", elem) == 0) { + item_flush(); + stack_prev(); + } else if (strcmp("title", elem) == 0) { + set_item_title(text_get()); + text_disable(); + } else if (strcmp("link", elem) == 0) { + set_item_link(text_get()); + text_disable(); + } else if (strcmp("pubDate", elem) == 0) { + set_item_date(text_get()); + text_disable(); + } else if (strcmp("description", elem) == 0) { + set_item_desc(text_get()); + text_disable(); + } +} + +void rss_setup() +{ + stack_set(0, layer(rss_global_enter, rss_global_leave)); + stack_set(1, layer(rss_channel_enter, rss_channel_leave)); + stack_set(2, layer(rss_item_enter, rss_item_leave)); +} diff --git a/rss.h b/rss.h new file mode 100644 index 0000000..57f9662 --- /dev/null +++ b/rss.h @@ -0,0 +1,6 @@ +#ifndef AGG_RSS_H +#define AGG_RSS_H + +void rss_setup(); + +#endif diff --git a/stack.c b/stack.c new file mode 100644 index 0000000..21e3c3d --- /dev/null +++ b/stack.c @@ -0,0 +1,51 @@ +#include +#include "stack.h" +#include "fail.h" +#include "rss.h" + +#define STACK_LAYERS 3 + +void unknown_enter(const char* elem, const char** attr); +void unknown_leave(const char* elem); + +struct Layer _stack[STACK_LAYERS] = { { unknown_enter, unknown_leave } }; +unsigned int _top = 0; + +const struct Layer* stack_top() +{ + return &_stack[_top]; +} + +void stack_next() +{ + ++_top; +} + +void stack_prev() +{ + --_top; +} + +void stack_set(unsigned i, struct Layer l) +{ + if (i >= STACK_LAYERS) fail(ERR_STACK); + + _stack[i].enter = l.enter; + _stack[i].leave = l.leave; +} + +void unknown_enter(const char* elem, const char** attr) +{ + (void) attr; + + if (strcmp("rss", elem) != 0) fail(ERR_TYPE); + + rss_setup(); +} + +void unknown_leave(const char* elem) +{ + (void) elem; + + fail(ERR_STACK); +} diff --git a/stack.h b/stack.h new file mode 100644 index 0000000..bd9029f --- /dev/null +++ b/stack.h @@ -0,0 +1,11 @@ +#ifndef AGG_STACK_H +#define AGG_STACK_H + +#include "layer.h" + +void stack_set(unsigned i, struct Layer l); +void stack_next(); +void stack_prev(); +const struct Layer* stack_top(); + +#endif diff --git a/tests.rss b/tests.rss new file mode 100644 index 0000000..c3d20ac --- /dev/null +++ b/tests.rss @@ -0,0 +1,31 @@ + + + + agg test feed + TODO + + Feed that is used for + verifying AGG's behavior + (acceptance test). + + + + Item 1 + Sat, 02 Oct 2010 22:43:23 +0200 + Random item. + /dev/random + + + Item 2 + Fri, 02 Apr 2010 12:10:00 +0200 + Not so random item. + /dev/urandom + + + Item 3 + Thu, 01 Apr 2010 12:54:06 +0200 + No item. + /dev/null + + + diff --git a/tests_dev.c b/tests_dev.c new file mode 100644 index 0000000..9813967 --- /dev/null +++ b/tests_dev.c @@ -0,0 +1,138 @@ +#include +#include +#include + +#include "text.h" +#include "layer.h" +#include "stack.h" + +#define TEST(expr) test(#expr, expr) +void test(const char* expr, int res); + +int test_text_is_empty(); +int test_text_enable(); +int test_text_disable(); +int test_text_write(); +int test_text_clear(); +int test_text_append(); +int test_text_fill(); + +int test_stack_set_top(); +int test_stack_next_prev(); +int test_stack_set_next(); + +int main() +{ + TEST(test_text_is_empty()); + TEST(test_text_enable()); + TEST(test_text_disable()); + TEST(test_text_write()); + TEST(test_text_clear()); + TEST(test_text_append()); + TEST(test_text_fill()); + + TEST(test_stack_set_top()); + TEST(test_stack_next_prev()); + TEST(test_stack_set_next()); + + return 0; +} + +void test(const char* expr, int res) +{ + printf(" %-32s: %s\n", expr, res ? "OK" : "FAIL"); + if (!res) exit(1); +} + + + +int test_text_is_empty() +{ + return strcmp("", text_get()) == 0; +} + +int test_text_enable() +{ + text_enable(); + return 1; +} + +int test_text_disable() +{ + text_disable(); + return 1; +} + +int test_text_write() +{ + text_buffer("foobar", 3); + return strcmp("foo", text_get()) == 0; +} + +int test_text_clear() +{ + text_enable(); + if (!test_text_is_empty()) return 0; + + test_text_write(); + text_disable(); + return test_text_is_empty(); +} + +int test_text_append() +{ + text_buffer("foobar", 3); + text_buffer("foobar", 3); + return strcmp("foofoo", text_get()) == 0; +} + +int test_text_fill() +{ + /* buffer must be >= text buffer. */ + #define BUFSIZE (1024 * 1024) + char buf[BUFSIZE]; + memset(buf, ' ', BUFSIZE); + text_buffer(buf, BUFSIZE); + + return strncmp(buf, text_get(), BUFSIZE) != 0; +} + + + +int test_stack_set_top() +{ + struct Layer mock; + mock.enter = (void (*)(const char*, const char**)) 0x7E57AB1E; + mock.leave = (void (*)(const char*)) 0xCA11AB1E; + + stack_set(0, mock); + return stack_top()->enter == mock.enter + && stack_top()->leave == mock.leave; +} + +int test_stack_next_prev() +{ + struct Layer mock; + mock.enter = (void (*)(const char*, const char**)) 0x7E57AB1E; + mock.leave = (void (*)(const char*)) 0xCA11AB1E; + + /* Mock still set from method before. */ + + stack_next(); + stack_prev(); + + return stack_top()->enter == mock.enter + && stack_top()->leave == mock.leave; +} + +int test_stack_set_next() +{ + struct Layer mock; + mock.enter = (void (*)(const char*, const char**)) 0x7E57AB1E; + mock.leave = (void (*)(const char*)) 0xCA11AB1E; + + stack_set(1, mock); + stack_next(); + return stack_top()->enter == mock.enter + && stack_top()->leave == mock.leave; +} diff --git a/tests_long.rss b/tests_long.rss new file mode 100644 index 0000000..1e8c282 --- /dev/null +++ b/tests_long.rss @@ -0,0 +1,8 @@ + + + + long feed + A feed containing a very large item. + ________________________________. + + diff --git a/tests_simple_descriptions.rss b/tests_simple_descriptions.rss new file mode 100644 index 0000000..0db3106 --- /dev/null +++ b/tests_simple_descriptions.rss @@ -0,0 +1,8 @@ + + + + simple feed d + A simple feed using descriptions only. + item + + diff --git a/tests_simple_titles.rss b/tests_simple_titles.rss new file mode 100644 index 0000000..fe6f770 --- /dev/null +++ b/tests_simple_titles.rss @@ -0,0 +1,8 @@ + + + + simple feed t + A simple feed using titles only. + item + + diff --git a/tests_titlelate.rss b/tests_titlelate.rss new file mode 100644 index 0000000..0d2117a --- /dev/null +++ b/tests_titlelate.rss @@ -0,0 +1,10 @@ + + + + Feed with title after items. + + item + + Lazy Title + + diff --git a/tests_titleless.rss b/tests_titleless.rss new file mode 100644 index 0000000..3491138 --- /dev/null +++ b/tests_titleless.rss @@ -0,0 +1,9 @@ + + + + Feed without title. + + item + + + diff --git a/tests_usr.sh b/tests_usr.sh new file mode 100755 index 0000000..92daefa --- /dev/null +++ b/tests_usr.sh @@ -0,0 +1,332 @@ +#!/bin/sh + +FEED="agg test feed" +ITEM1="Item 1" +ITEM2="Item 2" +ITEM3="Item 3" + +function agg_run() +{ + cat "$1" | ./agg + res=$? + if [ $res -ne 0 ]; then + echo "Failed to run agg ($res)." + exit 1 + fi +} + +function agg_fail() +{ + cat "$1" | ./agg + res=$? + if [ $res -eq 0 ]; then + echo "Failed to fail running agg." + exit 1 + fi +} + +function agg_clean() +{ + cd "$FEED" + ../agg_modify "rm *" + res=$? + if [ $res -ne 0 ]; then + echo "Failed to run agg_modify." + exit 1 + fi + cd .. +} + +function cleanup() +{ + rm -rf "$FEED" item "simple feed" "simple feed t" "simple feed d" "long feed" || exit 1 +} + +function t() +{ + printf " %-32s: " "$1" + $1 + result=$? + if [ $result -ne 0 ]; then + echo "FAIL" + exit $result + else + echo "OK" + fi +} + +function test_feed_exists() +{ + [ -d "$1" ] && return 0 + return 1 +} + +function test_feed_empty() +{ + [ `ls "$1" | wc -l` = "0" ] && return 0 + return 1 +} + +function test_feed_date() +{ + [ `stat -c %Y "$1"` = "$2" ] && return 0 + return 1 +} + +function test_item_exists() +{ + [ -f "$1" ] && return 0 + return 1 +} + +function test_item_date() +{ + [ `stat -c %Y "$1"` = "$2" ] && return 0 + return 1 +} + +function test_item_missing() +{ + [ ! -e "$1" ] && return 0 + return 1 +} + +function test_item_contents() +{ + # BUG: "`cat`" drops trailing \n. + [ "`cat \"$1\"`" = "$2" ] && return 0 + return 1 +} + +################################################################# + +function test_simple_t_feed_exists() +{ + test_feed_exists "simple feed t" + return $? +} + +function test_simple_t_item_exists() +{ + test_item_exists "simple feed t/item" + return $? +} + +function test_simple_t_item_contents() +{ + test_item_contents "simple feed t/item" "item" + return $? +} + +function test_simple_d_feed_exists() +{ + test_feed_exists "simple feed d" + return $? +} + +function test_simple_d_item_contents() +{ + test_item_contents "simple feed d/item" "item" + return $? +} + +function test_complete_feed_exists() +{ + test_feed_exists "$FEED" + return $? +} + +function test_complete_feed_empty() +{ + test_feed_empty "$FEED" + return $? +} + +function test_complete_feed_date() +{ + test_feed_date "$FEED" 1286052203 + return $? +} + +function test_complete_item1_exists() +{ + test_item_exists "$FEED/$ITEM1" + return $? +} + +function test_complete_item2_exists() +{ + test_item_exists "$FEED/$ITEM2" + return $? +} + +function test_complete_item3_exists() +{ + test_item_exists "$FEED/$ITEM3" + return $? +} + +function test_complete_item1_date() +{ + test_item_date "$FEED/$ITEM1" 1286052203 + return $? +} + +function test_complete_item2_date() +{ + test_item_date "$FEED/$ITEM2" 1270203000 + return $? +} + +function test_complete_item3_date() +{ + test_item_date "$FEED/$ITEM3" 1270119246 + return $? +} + +function test_complete_item1_contents() +{ + exp="\ +Item 1 + +Random item. + +Link: /dev/random" + test_item_contents "$FEED/$ITEM1" "$exp" + return $? +} + +function test_complete_item2_contents() +{ + exp="\ +Item 2 + +Not so random item. + +Link: /dev/urandom" + test_item_contents "$FEED/$ITEM2" "$exp" + return $? +} + +function test_complete_item3_contents() +{ + exp="\ +Item 3 + +No item. + +Link: /dev/null" + test_item_contents "$FEED/$ITEM3" "$exp" + return $? +} + +function test_long_feed_exists() +{ + test_feed_exists "long feed" + return $? +} + +LO=________________________________. +LC=________________________________ + +function test_long_item_exists() +{ + test_item_exists "long feed/$LC" + return $? +} + +function test_long_item_contents +{ + test_item_contents "long feed/$LC" "$LO" + return $? +} + +############################################### + +echo "Cleaning directory..." +cleanup + +echo "Running agg on feed without title..." +agg_fail tests_titleless.rss +t test_item_missing + +echo "Running agg on feed with title after items..." +agg_fail tests_titlelate.rss +t test_item_missing + +echo "Running agg on simple feed (titles)..." +agg_run tests_simple_titles.rss +t test_simple_t_feed_exists +t test_simple_t_item_exists +t test_simple_t_item_contents + +echo "Running agg on simple feed (descriptions)..." +agg_run tests_simple_descriptions.rss +t test_simple_d_feed_exists +t test_simple_d_item_contents + +echo "Running agg on missing directory..." +agg_run tests.rss +t test_complete_feed_exists +t test_complete_feed_date +t test_complete_item1_exists +t test_complete_item2_exists +t test_complete_item3_exists +t test_complete_item1_date +t test_complete_item2_date +t test_complete_item3_date +t test_complete_item1_contents +t test_complete_item2_contents +t test_complete_item3_contents + +echo "Running agg on up-to-date directory..." +agg_run tests.rss +t test_complete_feed_exists +t test_complete_feed_date +t test_complete_item1_exists +t test_complete_item2_exists +t test_complete_item3_exists +t test_complete_item1_date +t test_complete_item2_date +t test_complete_item3_date +t test_complete_item1_contents +t test_complete_item2_contents +t test_complete_item3_contents + +echo "Deleting old news..." +agg_clean +t test_complete_feed_exists +t test_complete_feed_date +t test_complete_feed_empty + +echo "Running agg on clean up-to-date directory..." +agg_run tests.rss +t test_complete_feed_exists +t test_complete_feed_date +t test_complete_feed_empty + +echo "Changing mtime..." +touch -md "1970-01-01 00:00:00.000000000 +0000" "$FEED" + +echo "Running agg on clean outdated directory..." +agg_run tests.rss +t test_complete_feed_exists +t test_complete_feed_date +t test_complete_item1_exists +t test_complete_item2_exists +t test_complete_item3_exists +t test_complete_item1_date +t test_complete_item2_date +t test_complete_item3_date +t test_complete_item1_contents +t test_complete_item2_contents +t test_complete_item3_contents + +echo "Running agg on feed with long items..." +agg_run tests_long.rss +t test_long_feed_exists +t test_long_item_exists +t test_long_item_contents + +echo "Success! Cleaning up..." +cleanup diff --git a/text.c b/text.c new file mode 100644 index 0000000..39957c1 --- /dev/null +++ b/text.c @@ -0,0 +1,42 @@ +#include +#include "config.h" +#include "stack.h" +#include "expat.h" +#include "text.h" + +char buffer[TEXT_BUFFER_SIZE] = { 0 }; +char* cursor = buffer; + +const char* text_get() +{ + return buffer; +} + +void text_buffer(const char* str, size_t len) +{ + /* We want to append a trailing \0, so we have one + * element less. Beware, this will underflow! */ + size_t free = buffer + TEXT_BUFFER_SIZE - cursor - 1; + size_t n = len < free ? len : free; + + /* Prevent overflow in memcpy(). */ + if (cursor == buffer + TEXT_BUFFER_SIZE) return; + + memcpy(cursor, str, n); + cursor += n; + *cursor = 0; +} + +void text_enable() +{ + expat_use_text_buffer = true; + cursor = buffer; + *cursor = 0; +} + +void text_disable() +{ + expat_use_text_buffer = false; + cursor = buffer; + *cursor = 0; +} diff --git a/text.h b/text.h new file mode 100644 index 0000000..0ef629d --- /dev/null +++ b/text.h @@ -0,0 +1,11 @@ +#ifndef AGG_TEXT_H +#define AGG_TEXT_H + +#include + +const char* text_get(); +void text_buffer(const char* str, size_t len); +void text_enable(); +void text_disable(); + +#endif -- 2.11.4.GIT