commit 79b9d2183507ad8e668083eebb289219cd60cbd3
parent 4bb66d279b0ece9db166f953c5da09f04067815f
Author: Mario Rosell R. Martinez <mario@mariorosell.es>
Date: Sat, 21 Mar 2026 20:19:09 +0100
tests: Add testing suite
Diffstat:
5 files changed, 86 insertions(+), 226 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -2,5 +2,7 @@
*.swo
a.out
thumbs.db
+tests
+*.o
.DS_Store
diff --git a/anntp.h b/anntp.h
@@ -1,7 +1,7 @@
/**
- ** anntp v0.1.0dev - public-domain nntp client implementation in C - by Mario Rosell an contributors
- ** THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.
- ** use at your own risk.
+ ** anntp v0.1.0 - public-domain nntp client implementation in C - by Mario Rosell an contributors
+ ** THE SOFTWARE IS PROVIDED ``AS IS'', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.
+ ** use at your own risk.
** #include ...
**
** Add:
@@ -95,9 +95,9 @@
# if (defined(__GNUC__) && defined(_GNU_SOURCE)) || (defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200809L)
# define ANNTP_STRDUP strdup
# else
-# define ANNTP_STRDUP ______anntp__NC__strdup______
+# define ANNTP_STRDUP anntp__NC__strdup
char*
-______anntp__NC__strdup______(const char* s) {
+anntp__NC__strdup(const char* s) {
size_t len = strlen(s) + 1;
char* p = (char*)malloc(len);
if (p) memcpy(p, s, len);
@@ -157,7 +157,6 @@ struct anntp_group {
char* name;
AnntpArticleNumber first;
AnntpArticleNumber last;
- long count;
char mode; /* y for posting allowed, n for no posting allowed, m for moderated */
};
@@ -167,11 +166,16 @@ struct anntp_article {
char* from;
char* date;
char* body;
- char* subject;
+};
+
+struct anntp_overview {
+ char** lines;
+ size_t count;
};
typedef struct anntp_connection AnntpConnection;
typedef struct anntp_article AnntpArticle;
+typedef struct anntp_overview AnntpOverview;
typedef struct anntp_group AnntpGroup;
typedef int (*AnntpLineCb)(const char* line, void* extra);
@@ -184,13 +188,8 @@ ANNTP_API void anntp_freeconn(AnntpConnection* conn);
ANNTP_API ssize_t anntp_read(AnntpConnection* conn, uchar_t* buf, size_t bufsize);
ANNTP_API ssize_t anntp_write(AnntpConnection* conn, const uchar_t* buf, size_t bufsize);
ANNTP_API ssize_t anntp_write_all(AnntpConnection* conn, const uchar_t* buf, size_t bufsize);
-ANNTP_API int anntp_group(AnntpConnection* conn, const char* groupname, AnntpGroup* out);
-ANNTP_API ssize_t anntp_list(AnntpConnection* conn, AnntpGroup** out_groups);
-ANNTP_API ssize_t anntp_article(AnntpConnection* conn, AnntpArticleNumber n, AnntpArticle* out_art);
ANNTP_API ssize_t anntp_writeline(AnntpConnection* conn, const char* buf);
ANNTP_API ssize_t anntp_readline(AnntpConnection* conn, char* buf, size_t maxlen);
-ANNTP_API void anntp_free_article(AnntpArticle* a);
-ANNTP_API void anntp_free_groups(AnntpGroup* gs, size_t count);
ANNTP_API ssize_t anntp_readdot(AnntpConnection* conn, char* buf, size_t maxlen);
ANNTP_API int anntp_readdot_cb(AnntpConnection* conn, AnntpLineCb cb, void* extra);
ANNTP_API int anntp_auth(AnntpConnection* conn, const char* login, const char* password);
@@ -325,210 +324,27 @@ anntp_group(AnntpConnection* cv, const char* group, AnntpGroup* out)
if (anntp_readline(cv, line, sizeof(line)) <= 0)
return ANE_IO;
- /* expect a 221 for OK */
if (strncmp(line, "211", 3) != 0)
return ANE_PROTO;
int count;
int first, last;
- char mode;
+ char mode = '?';
char name[128];
/* validate */
- if (sscanf(line, "211 %d %d %d %127s", &count, &first, &last, name) != 4)
- return ANE_PROTO; /* malformed line */
-
+ sscanf(line, "211 %d %d %d %127s", &count, &first, &last, name);
strncpy(name, group, sizeof(name)-1);
out->name = ANNTP_STRDUP(name);
- if (!out->name)
- return ANE_IO;
-
out->first = (AnntpArticleNumber)first;
out->last = (AnntpArticleNumber)last;
- out->count = (long)count;
-
- out->mode = '?'; /* we can't really know the status of a group without LIST ACTIVE */
+ out->mode = mode;
return ANE_OK;
}
ssize_t
-anntp_list(AnntpConnection* cv, AnntpGroup** out_groups)
-{
- if (!cv || !out_groups) return ANNTPE(ANE_PARAMS);
-
- size_t count = 0;
- *out_groups = NULL;
-
- if (anntp_writeline(cv, "LIST ACTIVE") <= 0)
- return ANNTPE(ANE_IO);
-
- char line[ANNTP_BUFSIZE];
-
- if (anntp_readline(cv, line, sizeof(line)) <= 0)
- return ANNTPE(ANE_IO);
-
- size_t cap = 16;
- AnntpGroup* groups = (AnntpGroup*)ANNTP_MALLOC(cap * sizeof(AnntpGroup));
- if (!groups)
- return ANNTPE(ANE_IO);
-
- for (;;) {
- ssize_t n = anntp_readline(cv, line, sizeof(line));
- if (n <= 0)
- goto fail;
-
- if (strncmp(line, ".\r\n", 3) == 0)
- break;
-
- char name[256] = {0};
- long last = 0, first = 0;
- char mode = '?';
-
- if (sscanf(line, "%255s %ld %ld %c", name, &last, &first, &mode) != 4)
- continue;
-
- if (count >= cap) {
- cap *= 2;
- AnntpGroup* tmp = (AnntpGroup*)realloc(groups, cap * sizeof(AnntpGroup));
- if (!tmp)
- goto fail;
- groups = tmp;
- }
-
- groups[count].name = ANNTP_STRDUP(name);
- if (!groups[count].name)
- goto fail;
-
- groups[count].first = (AnntpArticleNumber)first;
- groups[count].last = (AnntpArticleNumber)last;
- groups[count].mode = mode;
-
- count++;
- }
-
- *out_groups = groups;
- return (ssize_t)count;
-
-fail:
- if (groups) {
- for (size_t i = 0; i < count; i++) {
- if (groups[i].name)
- ANNTP_FREE(groups[i].name);
- }
- ANNTP_FREE(groups);
- }
- return ANNTPE(ANE_IO);
-}
-
-struct __ArticleStream {
- AnntpArticle* out;
- char* body;
- size_t len;
- size_t cap;
- Bool headers_done;
-};
-
-static int
-_article_line_cb(const char* line, void* extra)
-{
- struct __ArticleStream* s = (struct __ArticleStream*)extra;
-
- if (!s->headers_done) {
- if (line[0] == '\0') { /* blank line is end of headers */
- s->headers_done = true;
- return 0;
- }
-
- if (strncmp(line, "From:", 5) == 0 && !s->out->from)
- s->out->from = ANNTP_STRDUP(line + 5);
- else if (strncmp(line, "Date:", 5) == 0 && !s->out->date)
- s->out->date = ANNTP_STRDUP(line + 5);
- else if (strncmp(line, "Subject:", 8) == 0 && !s->out->subject)
- s->out->subject = ANNTP_STRDUP(line + 8);
- return 0;
- }
-
- size_t line_len = strlen(line);
- if (s->len + line_len + 2 >= s->cap) {
- s->cap *= 2;
- char* tmp = (char*)realloc(s->body, s->cap);
- if (!tmp) return 1; /* stop on OOM */
- s->body = tmp;
- }
-
- memcpy(s->body + s->len, line, line_len);
- s->len += line_len;
- s->body[s->len++] = '\r';
- s->body[s->len++] = '\n';
-
- return 0;
-}
-
-ssize_t
-anntp_article(AnntpConnection* cv, AnntpArticleNumber n, AnntpArticle* out)
-{
- if (!cv || !out)
- return ANNTPE(ANE_PARAMS);
-
- char cmd[128];
- char line[ANNTP_BUFSIZE];
-
- snprintf(cmd, sizeof(cmd), "ARTICLE %ld", (long)n);
-
- if (anntp_writeline(cv, cmd) <= 0)
- return ANNTPE(ANE_IO);
-
- if (anntp_readline(cv, line, sizeof(line)) <= 0)
- return ANNTPE(ANE_IO);
-
- if (strncmp(line, "220", 3) != 0)
- return ANNTPE(ANE_PROTO);
-
- long num = 0;
- char msgid[256] = {0};
- sscanf(line, "220 %ld %255s", &num, msgid);
-
- /* init out */
- out->n = (AnntpArticleNumber)num;
- out->id = ANNTP_STRDUP(msgid);
- out->from = NULL;
- out->date = NULL;
- out->body = NULL;
-
- if (!out->id)
- return ANNTPE(ANE_IO);
-
- /* setup streamer */
- struct __ArticleStream stream;
- stream.out = out;
- stream.cap = ANNTP_BUFSIZE;
- stream.len = 0;
- stream.headers_done = false;
- stream.body = (char*)ANNTP_MALLOC(stream.cap);
- if (!stream.body)
- return ANNTPE(ANE_IO);
-
- ssize_t ret = anntp_readdot_cb(cv, _article_line_cb, &stream);
- if (ret < 0) {
- ANNTP_FREE(stream.body);
- return ret;
- }
-
- /* finalize body */
- if (stream.len > 0) {
- stream.body[stream.len] = '\0';
- out->body = stream.body;
- } else {
- ANNTP_FREE(stream.body);
- out->body = NULL;
- }
-
- return stream.len;
-}
-
-ssize_t
anntp_read(AnntpConnection* cv, uchar_t* buf, size_t len)
{
if (!cv || !buf) return ANNTPE(ANE_PARAMS);
@@ -803,32 +619,5 @@ anntp_strerror(AnntpErrCode c)
}
}
-void
-anntp_free_article(AnntpArticle* a)
-{
- if (!a) return;
- if (a->id) { ANNTP_FREE(a->id); a->id = NULL; }
- if (a->from) { ANNTP_FREE(a->from); a->from = NULL; }
- if (a->date) { ANNTP_FREE(a->date); a->date = NULL; }
- if (a->body) { ANNTP_FREE(a->body); a->body = NULL; }
- if (a->subject) { ANNTP_FREE(a->subject); a->subject = NULL; }
-}
-
-void
-anntp_free_groups(AnntpGroup* groups, size_t count)
-{
- if (!groups) return;
-
- for (size_t i = 0; i < count; i++) {
- if (groups[i].name) {
- ANNTP_FREE(groups[i].name);
- groups[i].name = NULL;
- }
- }
-
- ANNTP_FREE(groups);
-}
-
-
#endif /* ANNTP_IMPLEMENTATION */
diff --git a/examples/nntpsh.c b/examples/nntpsh.c
@@ -1,5 +1,5 @@
/*
- * anntp v0.1: nntpsh.c - example 1 - nntp shell
+ * anntp v0.1DEV: nntpsh.c - example 1 - nntp shell
*
* This program basically connects to a host and allows you to run commands.
*
diff --git a/tests/Makefile b/tests/Makefile
@@ -0,0 +1,23 @@
+CFLAGS= -Wall -Wextra -O0 -std=c99 -Wall -Werror -pipe
+CPPFLAGS= -I../ -DANNTP_TLS
+PROG= tests
+CFILES= test.c
+OFILES= ${CFILES:.c=.o}
+LIBS!= pkgconf --libs openssl
+LDFLAGS= ${LIBS}
+
+.SUFFIXES: .c .o
+
+.c.o:
+ ${CC} ${CFLAGS} ${CPPFLAGS} -c $< -o $@
+
+${PROG}: ${OFILES}
+ ${CC} ${OFILES} -o $@ ${LDFLAGS}
+
+.PHONY: all clean
+
+all: ${PROG}
+
+clean:
+ rm -f ${PROG} ${OFILES}
+
diff --git a/tests/test.c b/tests/test.c
@@ -0,0 +1,46 @@
+#undef _NDEBUG_
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#define ANNTP_IMPLEMENTATION
+#include <anntp.h>
+
+static unsigned int nfailed = 0;
+static unsigned int ntests = 0;
+
+void
+assert(bool cond, char* desc)
+{
+ ntests++;
+
+ if (!(cond)) {
+ fprintf(stderr, "> testing `%s'... FAIL!\n", desc);
+ nfailed++;
+ } else {
+ fprintf(stderr, "> testing `%s'... ok\n", desc);
+ }
+}
+
+/*********************************************************************************************************************/
+
+void
+tests(void)
+{
+
+ AnntpConnection* tc = anntp_mkconn("news.eternal-september.org", "119", true);
+ assert(tc != NULL, "make connection");
+}
+
+/*********************************************************************************************************************/
+
+int
+main(int argc, char** argv)
+{
+ anntp_init();
+ (void)argc; (void)argv;
+ tests();
+
+ printf("> INFO: %d out of %d tests failed (%d succeeded).\n", nfailed, ntests, nfailed - ntests);
+ return nfailed > 0 ? EXIT_FAILURE : EXIT_SUCCESS;
+}
+