piece-chain

Research & implementation of a Piece Chain
git clone git://src.gearsix.net/piece-chain.git
Log | Files | Refs | Atom | README

commit 89b16fb6fa73c0d2b5b014258213c46bc6fd7b88
parent 2f3ea34be6b3b10dcf43e89941a6a35394c3c2af
Author: gearsix <gearsix@tuta.io>
Date:   Sat,  9 Jul 2022 23:18:19 +0100

cleanup; added some doc, CATCH macro to buf.c; removed undo, redo.

Diffstat:
MMakefile | 5+++--
Dbuf.c | 212-------------------------------------------------------------------------------
Dbuf.h | 44--------------------------------------------
Abuffer.c | 220+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abuffer.h | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Amain.c | 5+++++
Mtest.c | 162++-----------------------------------------------------------------------------
Atest.h | 2++
Atest_buffer.c | 182+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 470 insertions(+), 417 deletions(-)

diff --git a/Makefile b/Makefile @@ -1,8 +1,9 @@ -SRC := buf.c +CC := gcc +SRC := buffer.c OUT := ec OPTS := -g -Wall -Wpedantic -std=c89 all: ${CC} $(OPTS) $(SRC) main.c -o $(OUT) test: - ${CC} $(OPTS) $(SRC) test.c -o $(OUT)-test + ${CC} $(OPTS) $(SRC) test*c -o $(OUT)-test diff --git a/buf.c b/buf.c @@ -1,212 +0,0 @@ - -#include "buf.h" - -#include <stdio.h> -#include <stdlib.h> -#include <string.h> -#include <errno.h> - -static Piece *pieceblk = NULL; -static Piece **freeblk = NULL; - -static void pclean() -{ - free(freeblk); - free(pieceblk); -} - -/* adds `p` to `freeblk` and returns the last added `freeblk` item. - If `p` is NULL, the last added `freeblk` item will be returned, - or NULL if `freeblk` is empty. - `freeblk` is dynamically allocated. */ -static Piece *pfree(Piece *p) -{ - static size_t nblk = 0, nfree = 0; - static const size_t blksiz = BUFSIZ/sizeof(Piece *); - - if (!p) return (nfree == 0) ? NULL : freeblk[--nfree]; - - if (nfree + 1 > blksiz * nblk) - freeblk = realloc(freeblk, (++nblk * blksiz) * sizeof(Piece *)); - if (!freeblk) { perror("failed to allocate freeblk"); exit(1); } - - return freeblk[nfree++] = p; -} - -/* returns the next available `Piece` item in `pieceblk`. - If `freeblk` is not empty, it's last item pointed to by it will be - returned (recycling `pieceblk` items). The returned `Piece` will - always have it's values set to 0. `pieceblk` is dynamically - allocated. - `atexit(pclean)` is called the first time the function is called. */ -static Piece *palloc() -{ - Piece *p; - - static size_t nblk = 0, nalloc = 0; - static const size_t blksiz = BUFSIZ/sizeof(Piece); - - static int hook = 0; - if (!hook) { atexit(pclean); hook = 1; } - - if ( !(p = pfree(NULL)) ) { - if (nalloc + 1 > blksiz * nblk) - pieceblk = realloc(pieceblk, (++nblk * blksiz) * sizeof(Piece)); - if (!pieceblk) { perror("failed to allocate pieceblk"); exit(1); } - p = &pieceblk[nalloc++]; - } - - return memset(p, 0, sizeof(Piece)); -} - -/* splits `p` into two `Piece` items. The values of `p` will be - modified to fit the first `Piece`; a new `Piece` will be allocated - for the second. - The `prev` and `next` items of these & associated `Piece` items will - be updated to reflect the new `Piece`. - `offset` should be the offset within `p->len` to split at. If offset - is not within the boundry of `p->len`, then `p` will be returned. - The *first* `Piece` in the split is returned. */ -static Piece *psplit(Piece *p, long int offset) -{ - Piece *q = palloc(); - if (offset > 0 && offset <= (int)p->len) { - memcpy(q, p, sizeof(Piece)); - - q->off += offset; - q->len -= p->len = offset; - q->next = p->next; - q->prev = p; - p->next = q; - if (q->next) q->next->prev = q; - - p = q; - } - return p; -} - -struct Buf *bufinit(FILE *read, FILE *append) -{ - Buf *b = calloc(1, sizeof(Buf)); - - if (!append) return NULL; - - b->read = read; - b->append = append; - - b->tail = b->pos = b->head = palloc(); - b->pos->f = b->read; - fseek(b->read, 0, SEEK_END); - b->size = b->pos->len = ftell(b->read); - - return b; -} - -void buffree(Buf *b) -{ - Piece *p = b->tail->next; - while (p) { - pfree(p->prev); - p = p->next; - } - free(b); -} - -Piece *bufidx(Buf *b, size_t pos) -{ - size_t idx = b->idx, offset; - Piece *p = b->pos; - - if (pos >= idx) { - while (pos >= idx + p->len && p->next) { - idx += p->len; - p = p->next; - } - } else { - do { - p = p->prev; - idx -= p->len; - } while(pos > idx && p->prev); - } - - if (idx == pos) { - b->idx = idx; - b->pos = p; - } else { - offset = (idx >= pos) ? idx - pos : pos - idx; - b->pos = psplit(p, offset); - if (!b->pos->next) b->head = b->pos; - b->idx = pos; - } - - return b->pos; -} - -size_t bufins(Buf *b, size_t pos, const char *s) -{ - const size_t slen = strlen(s); - Piece *p = palloc(); - - b->pos = bufidx(b, pos)->prev; - - p->f = b->append; - p->off = ftell(b->append); - p->len = slen; - p->prev = b->pos; - p->next = b->pos->next; - - b->pos->next = b->pos->next->prev = p; - - fprintf(b->append, "%s", s); - if (ferror(b->append)) { - perror("failed to write to append file"); - pfree(p); - p = 0; - } else { - b->idx += slen; - b->pos = p->next; - } - - return (p == 0) ? b->size : (b->size += slen); -} - -size_t bufdel(Buf *b, size_t pos, size_t num) -{ - Piece *pre, *post; - size_t end = pos+num; - - pre = bufidx(b, (pos == 0) ? 0 : pos-1); - post = bufidx(b, (end > b->size) ? b->size : end)->next; - if (!post) post = b->head; - - pre->next = post; - post->prev = pre; - - b->idx = pos; - return (b->size -= num); -} - -int bufout(Buf *b, FILE *f) -{ - size_t n, fsiz = 0; - char buf[BUFSIZ]; - Piece *p = b->tail; - - do { - if ((size_t)ftell(p->f) != p->off) - fseek(p->f, p->off, SEEK_SET); - - n = 0; - do { - buf[0] = '\0'; - fread(buf, 1, p->len, p->f); - if (ferror(p->f)) { perror("bufout: fread failed"); break; } - fsiz += fwrite(buf, 1, p->len, f); - if (ferror(f)) { perror("bufout: fwrite failed"); break; } - } while (p->len - (BUFSIZ*n++) > BUFSIZ); - - p = p->next; - } while(p); - - return fsiz; -} diff --git a/buf.h b/buf.h @@ -1,44 +0,0 @@ -/* TODO */ -#include <stdio.h> - -/* Points to a file (`f`), which contains a string which - starts within `f` at `off` and has a length of `len`. - `prev` and `next` point to the previous and next - `Piece` elements in the intended double-linked list. */ -typedef struct Piece { - FILE *f; - size_t off, len; - - struct Piece *prev, *next; -} Piece; - -typedef struct Buf { - FILE *read, *append; - size_t size, idx; - struct Piece *tail, *pos, *head; -} Buf; - -/* Allocates & initialises a `Buf`. If `append` is NULL, - nothing is allocated or initialised and NULL is - returned. */ -Buf *bufinit(FILE *read, FILE *append); - -/* Frees `b` and all `Piece` items associated with it. */ -void buffree(Buf *b); - -/* Set `b->idx` to `pos` and `b->pos` to the `Piece` - in the chain, where the start of `b->pos->next` - is `pos`. */ -Piece *bufidx(Buf *b, size_t pos); - -/* Adds a new `Piece` to the chain, at `pos` (found - using `bufidx`). `s` will be appended to `b->append` - and the new `Piece` will reflect the appended data. */ -size_t bufins(Buf *b, size_t pos, const char *s); - -/* Removed all pieces from index `pos` to `pos+num`. - `pos` and `pos+num` are found using `bufidx`. */ -size_t bufdel(Buf *b, size_t pos, size_t num); - -/* writes all data in `b` to `f`. */ -int bufout(Buf *b, FILE *f); diff --git a/buffer.c b/buffer.c @@ -0,0 +1,220 @@ + +#include "buffer.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> + +#define CATCH(condition, msg, action) \ + if (condition) { perror(msg); action; } + +static Piece *pieceblk = NULL; +static Piece **freeblk = NULL; + +static void pclean() +{ + if (freeblk) free(freeblk); + if (pieceblk) free(pieceblk); +} + +/* adds `p` to `freeblk` and returns the last added `freeblk` item. + If `p` is NULL, the last added `freeblk` item will be + returned, or NULL if `freeblk` is empty. + `freeblk` is dynamically allocated. */ +static Piece *pfree(Piece *p) +{ + static size_t nblk = 0, nfree = 0, blksiz = 0; + static const size_t allocsiz = BUFSIZ/sizeof(Piece *); + + if (!p) return (nfree == 0) ? NULL : freeblk[--nfree]; + + if (nfree + 1 > allocsiz * nblk) { + blksiz = (++nblk * allocsiz) * sizeof(Piece *); + freeblk = realloc(freeblk, blksiz); + } + CATCH(!freeblk, "failed to allocate freeblk", + exit(EXIT_FAILURE)); + + return freeblk[nfree++] = p; +} + +/* returns the next available `Piece` item in `pieceblk`. + If `freeblk` is not empty, it's last item pointed to by it + will be returned (recycling `pieceblk` items). The returned + `Piece` will always have it's values set to 0. + `pieceblk` is dynamically allocated. */ +static Piece *palloc() +{ + Piece *p; + + static size_t nblk = 0, nalloc = 0, blksiz = 0; + static const size_t allocsiz = BUFSIZ/sizeof(Piece); + + static int hook = 0; + + if ( !(p = pfree(NULL)) ) { + if (nalloc + 1 > allocsiz * nblk) { + blksiz = (++nblk * allocsiz) * sizeof(Piece); + pieceblk = realloc(pieceblk, blksiz); + } + CATCH(!pieceblk, "failed to allocate pieceblk", + exit(EXIT_FAILURE)); + if (hook == 0) hook = !atexit(pclean); + + p = &pieceblk[nalloc++]; + } + + return memset(p, 0, sizeof(Piece)); +} + +/* splits `p` into two `Piece` items. The values of `p` will be + modified to fit the first `Piece`; a new `Piece` will be + allocated for the second. + The `prev` and `next` values of these & associated `Piece` + items will be updated to reflect the split. + `offset` should be the offset within `p->len` to split at. + If offset is not within the boundry of `p->len`, then `p` + will be returned. The *first* `Piece` in the split is + returned. */ +static Piece *psplit(Piece *p, long int offset) +{ + Piece *q = palloc(); + if (offset > 0 && offset <= (int)p->len) { + memcpy(q, p, sizeof(Piece)); + + q->off += offset; + q->len -= p->len = offset; + q->next = p->next; + q->prev = p; + p->next = q; + if (q->next) q->next->prev = q; + + p = q; + } + return p; +} + +struct Buffer *bufinit(FILE *read, FILE *append) +{ + Buffer *b = calloc(1, sizeof(Buffer)); + + if (!append) return NULL; + + b->read = read; + b->append = append; + + b->tail = b->pos = b->head = palloc(); + b->pos->f = b->read; + fseek(b->read, 0, SEEK_END); + b->size = b->pos->len = ftell(b->read); + + return b; +} + +void buffree(Buffer *b) +{ + Piece *p = b->tail->next; + while (p) { + pfree(p->prev); + p = p->next; + } + free(b); +} + +Piece *bufidx(Buffer *b, size_t pos) +{ + size_t idx = b->idx, offset; + Piece *p = b->pos; + + if (pos >= idx) { + while (pos >= idx + p->len && p->next) { + idx += p->len; + p = p->next; + } + } else { + do { + p = p->prev; + idx -= p->len; + } while(pos > idx && p->prev); + } + + if (idx == pos) { + b->idx = idx; + b->pos = p; + } else { + offset = (idx >= pos) ? idx - pos : pos - idx; + b->pos = psplit(p, offset); + if (!b->pos->next) b->head = b->pos; + b->idx = pos; + } + + return b->pos; +} + +size_t bufins(Buffer *b, size_t pos, const char *s) +{ + const size_t slen = strlen(s); + Piece *p = palloc(); + + b->pos = bufidx(b, pos)->prev; + + p->f = b->append; + p->off = ftell(b->append); + p->len = slen; + p->prev = b->pos; + p->next = b->pos->next; + + b->pos->next = b->pos->next->prev = p; + + fprintf(b->append, "%s", s); + CATCH(ferror(b->append), "bufins: write to append", + { pfree(p); p = 0; }); + if (p != 0) { + b->idx += slen; + b->pos = p->next; + } + + return (p == 0) ? b->size : (b->size += slen); +} + +size_t bufdel(Buffer *b, size_t pos, size_t num) +{ + Piece *pre, *post; + size_t end = pos+num; + + pre = bufidx(b, (pos == 0) ? 0 : pos-1); + post = bufidx(b, (end > b->size) ? b->size : end)->next; + if (!post) post = b->head; + + pre->next = post; + post->prev = pre; + + b->idx = pos; + return (b->size -= num); +} + +int bufout(Buffer *b, FILE *f) +{ + size_t n, fsiz = 0; + char buf[BUFSIZ]; + Piece *p = b->tail; + + do { + if ((size_t)ftell(p->f) != p->off) + fseek(p->f, p->off, SEEK_SET); + + n = 0; + do { + buf[0] = '\0'; + fread(buf, 1, p->len, p->f); + CATCH(ferror(p->f), "bufout: fread failed", break); + fsiz += fwrite(buf, 1, p->len, f); + CATCH(ferror(f), "bufout: fwrite failed", break); + } while (p->len - (BUFSIZ*n++) > BUFSIZ); + + p = p->next; + } while(p); + + return fsiz; +} diff --git a/buffer.h b/buffer.h @@ -0,0 +1,55 @@ +/* A piece chain implementation. + gearsix, 2022 */ +#include <stdio.h> + +/* Points to a file (`f`), which contains a string which starts + within `f` at `off` and has a length of `len`. + `prev` and `next` point to the previous and next `Piece` + elements in the intended doubly-linked list. */ +typedef struct Piece { + FILE *f; + size_t off, len; + struct Piece *prev, *next; +} Piece; + +/* Holds a doubly-linked `Piece` list, starting at `tail` and ending + at `head`. `pos` points to the last addressed `Piece`. + `size` is the sum length of all `Piece` `len` values in the + chain. `idx` is the last addressed index in the chain. + `read` and `append` point to the original file (`read`) and + any data to be added `append`. */ +typedef struct Buffer { + FILE *read, *append; + size_t size, idx; + struct Piece *tail, *pos, *head; +} Buffer; + +/* Allocates & initialises a `Buffer`. If `append` is NULL, nothing is + allocated or initialised and NULL is returned. */ +Buffer * +bufinit(FILE *read, FILE *append); + +/* Frees `b` and all `Piece` items associated with it. */ +void +buffree(Buffer *b); + +/* Set `b->idx` to `pos` and `b->pos` to the `Piece` in the chain, + where the start of `b->pos->next` is `pos`. */ +Piece * +bufidx(Buffer *b, size_t pos); + +/* Adds a new `Piece` to the chain, at `pos` (found using `bufidx`). + `s` will be appended to `b->append` and the new `Piece` will + reflect the appended data. */ +size_t +bufins(Buffer *b, size_t pos, const char *s); + +/* Removed all pieces from index `pos` to `pos+num`. + `pos` and `pos+num` are found using `bufidx`. */ +size_t +bufdel(Buffer *b, size_t pos, size_t num); + +/* writes all data in `b` to `f`. */ +int +bufout(Buffer *b, FILE *f); + diff --git a/main.c b/main.c @@ -0,0 +1,5 @@ + +int main() +{ + return 0; +} diff --git a/test.c b/test.c @@ -1,167 +1,11 @@ -#include "buf.h" -#include <stdio.h> -#include <stdlib.h> -#include <assert.h> -#include <string.h> - -#define INFILE "in.txt" -#define INBUF "hello world" -#define OUTFILE "out.txt" -#define OUTBUF "hey buddy!" /* tests will aim to change INBUF to this */ - -Buf *b; -FILE *in, *tmp, *out; - -void setup() -{ - in = fopen(INFILE, "w+b"); - if (ferror(in)) perror("fopen in.txt"); - fputs(INBUF, in); - - tmp = tmpfile(); - - out = fopen(OUTFILE, "w+b"); - if (ferror(in)) perror("fopen out.txt"); - - return; -} - -void setdown() -{ - buffree(b); - fclose(out); - fclose(in); - fclose(tmp); -} - -void print() -{ - Piece *p = b->tail; - - printf("Buf: %p\n", (void *)b); - printf("size: %lu, index: %lu\n", b->size, b->idx); - do { - printf("%p", (void *)p); - if (p == b->tail) printf("<-tail"); - if (p == b->pos) printf("<-pos"); - if (p == b->head) printf("<-head"); - puts(""); - printf("\tf: %s, off: %lu, len %lu\n", - (p->f == b->read) ? "read" : "append", p->off, p->len); - } while ((p = p->next)); - printf("\n"); - fflush(stdout); -} - -void test_bufinit() -{ - Piece *p; - - b = bufinit(in, tmp); - - assert(b->read); - assert(b->append); - assert(b->pos == b->tail); - assert(b->pos == b->head); - - assert((p = b->pos)); - assert(p->f == b->read); - assert(p->off == 0); - assert(p->len == strlen(INBUF)); - assert(p->prev == NULL); - assert(p->next == NULL); -} - -void test_bufidx() -{ - Piece *p; size_t idx = 5; - - p = bufidx(b, idx); +#include "test.h" - assert(p == b->pos); - assert(p == b->head); - assert(p->prev == b->tail); - assert(p->f == b->read); - assert(p->off == idx); - assert(p->len == strlen(INBUF) - idx); -} - -void test_bufins() -{ - const char *buf = "y"; - const size_t idx = 2, len = strlen(buf); - - bufins(b, idx, buf); - - assert(b->size == strlen(INBUF) + len); - assert(b->idx == idx + len); - assert(b->pos == b->tail->next->next); - - assert(b->pos->f == b->read); - assert(b->pos->off == 2); - assert(b->pos->len == 3); - assert(b->pos->prev == b->tail->next); - assert(b->pos->next == b->head); -} - -void test_bufdel() -{ - const size_t idx = 3, len = 9; - - bufdel(b, idx, len); - - assert(b->size == strlen(INBUF)+1-len); /* +1 for bufins */ - assert(b->idx == idx); - assert(b->pos == b->head); - assert(b->tail->next->next == b->pos); - - assert(b->pos->f == b->read); - assert(b->pos->off == 11); - assert(b->pos->len == 0); - assert(b->pos->prev == b->tail->next); - assert(b->pos->next == NULL); -} - -void test_bufins1() -{ - const char *buf = " buddy!"; - const size_t idx = 3, len = strlen(buf), bsiz = b->size; - - bufins(b, idx, buf); - - assert(b->size == bsiz + len); - assert(b->idx == idx + len); - assert(b->pos == b->head); - assert(b->pos == b->tail->next->next->next); - - assert(b->pos->prev->f == b->append); - assert(b->pos->prev->off == 1); - assert(b->pos->prev->len == len); - assert(b->pos->prev == b->tail->next->next); - assert(b->pos->next == NULL); -} - -void test_bufout() -{ - char buf[BUFSIZ] = {0}; - - bufout(b, out); +#include <stdio.h> - rewind(out); - assert(fread(buf, 1, BUFSIZ, out) > 0); - assert(strcmp(buf, OUTBUF) == 0); -} int main() { - setup(); - test_bufinit(); - test_bufidx(); - test_bufins(); - test_bufdel(); - test_bufins1(); - test_bufout(); - setdown(); + test_buffer(); puts("success - no assertions failed"); return 0; } diff --git a/test.h b/test.h @@ -0,0 +1,2 @@ + +void test_buffer(); diff --git a/test_buffer.c b/test_buffer.c @@ -0,0 +1,182 @@ +#include "test.h" +#include "buffer.h" + +#include <stdio.h> +#include <string.h> +#include <assert.h> + +static Buffer *B; + +static FILE *in_f; +static const char *in_p = "in.txt", *in_buf = "hello world"; +static FILE *out_f; +static const char *out_p = "out.txt", *out_buf = "hey buddy!"; +static FILE *tmp_f; +static const char *tmp_p = "tmp.txt"; + + +static void setup(); +static void setdown(); +static void test_bufinit(); +static void test_bufidx(); +static void test_bufins(); +static void test_bufdel(); +static void test_bufins1(); +static void test_bufout(); + + +void test_buffer() +{ + printf("test_buffer: "); + setup(); + test_bufinit(); + test_bufidx(); + test_bufins(); + test_bufdel(); + test_bufins1(); + test_bufout(); + setdown(); + puts("success"); +} + +static void debugprint() +{ + Piece *p = B->tail; + + printf("Buf: %p\n", (void *)B); + printf("size: %d, index: %d\n", (int)B->size, (int)B->idx); + do { + printf("%p", (void *)p); + if (p == B->tail) printf("<-tail"); + if (p == B->head) printf("<-head"); + if (p == B->pos) printf("<-pos"); + printf("\n\toff: %d, len %d, f: %s\n", (int)p->off, (int)p->len, + (!p->f) ? "0" : (p->f == B->read) ? "read" : "append"); + } while ((p = p->next)); + printf("\n"); +} + +static void setup() +{ + in_f = fopen(in_p, "w+b"); + if (ferror(in_f)) perror("fopen in"); + fputs(in_buf, in_f); + + out_f = fopen(out_p, "w+b"); + if (ferror(out_f)) perror("fopen out"); + + tmp_f = fopen(tmp_p, "w+b"); + if (ferror(tmp_f)) perror("fopen tmp"); +} + +static void setdown() +{ + buffree(B); + fclose(out_f); + fclose(in_f); + fclose(tmp_f); + remove(in_p); + remove(out_p); + remove(tmp_p); +} + +static void test_bufinit() +{ + Piece *p; + + B = bufinit(in_f, tmp_f); + + assert(B->read); + assert(B->append); + assert(B->pos == B->tail); + assert(B->pos == B->head); + + assert((p = B->pos)); + assert(p->f == B->read); + assert(p->off == 0); + assert(p->len == strlen(in_buf)); + assert(p->prev == NULL); + assert(p->next == NULL); +} + +static void test_bufidx() +{ + Piece *p; size_t idx = 5; + + p = bufidx(B, idx); + + assert(p == B->pos); + assert(p == B->head); + assert(p->prev == B->tail); + assert(p->f == B->read); + assert(p->off == idx); + assert(p->len == strlen(in_buf) - idx); +} + +static void test_bufins() +{ + const char *buf = "y"; + const size_t idx = 2, len = strlen(buf); + + bufins(B, idx, buf); + + assert(B->size == strlen(in_buf) + len); + assert(B->idx == idx + len); + assert(B->pos == B->tail->next->next); + + assert(B->pos->f == B->read); + assert(B->pos->off == 2); + assert(B->pos->len == 3); + assert(B->pos->prev == B->tail->next); + assert(B->pos->next == B->head); +} + +static void test_bufdel() +{ + const size_t idx = 3, len = 9; + + bufdel(B, idx, len); + + assert(B->size == strlen(in_buf)+1-len); /* +1 for bufins */ + assert(B->idx == idx); + assert(B->pos == B->head); + assert(B->tail->next->next == B->pos); + + assert(B->pos->f == B->read); + assert(B->pos->off == 11); + assert(B->pos->len == 0); + assert(B->pos->prev == B->tail->next); + assert(B->pos->next == NULL); +} + +static void test_bufins1() +{ + const char *buf = " buddy!"; + const size_t idx = 3, len = strlen(buf), bsiz = B->size; + + bufins(B, idx, buf); + + assert(B->size == bsiz + len); + assert(B->idx == idx + len); + assert(B->pos == B->head); + assert(B->pos == B->tail->next->next->next); + + assert(B->pos->prev->f == B->append); + assert(B->pos->prev->off == 1); + assert(B->pos->prev->len == len); + assert(B->pos->prev == B->tail->next->next); + assert(B->pos->next == NULL); +} + +static void test_bufout() +{ + size_t n = 0; + char buf[BUFSIZ] = {0}; + + bufout(B, out_f); + rewind(out_f); + n = fread(buf, 1, BUFSIZ, out_f); + + assert(n > 0); + assert(strcmp(buf, out_buf) == 0); +}