chiark / gitweb /
Test infrastructure.
authorDan Sheppard <dan.sheppard.circle@gmail.com>
Tue, 22 Apr 2025 14:58:22 +0000 (15:58 +0100)
committerDan Sheppard <dan.sheppard.circle@gmail.com>
Tue, 22 Apr 2025 14:58:22 +0000 (15:58 +0100)
Makefile
src/coquet.c
src/coquet.h
src/hmac.c [deleted file]
src/superblock.c
src/superblock.h
src/test.c [new file with mode: 0644]
src/testvfs.c [new file with mode: 0644]
src/unix.c
src/vfs.h

index dc3a9e17dff7e2afdbf805fafb177f580a06b50f..9f57f48833532db926737cc22b8b28eef8f3fe62 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,36 +1,55 @@
 SRCDIR := src
 BINDIR := bin
-OBJDIR := obj
+OBJDIR := obj/main
+TESTOBJDIR := obj/test
+
+SRCFILES := unix.c util.c coquet.c superblock.c sha2.c testvfs.c
 
-TARGET := $(BINDIR)/coquet
-SRCFILES := main.c unix.c util.c coquet.c superblock.c sha2.c
 SRC := $(addprefix $(SRCDIR)/,$(SRCFILES))
+DEPSRC := $(wildcard $(SRCDIR)/*.c)
+
+DEPS = $(DEPSRC:$(SRCDIR)/%.c=$(OBJDIR)/%.d)
 OBJ := $(SRC:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
-DEPS := $(SRCFILES:%.c=$(OBJDIR)/%.d)
 
-# TARGET = coquet
+TESTDEPS = $(DEPSRC:$(SRCDIR)/%.c=$(TESTOBJDIR)/%.d)
+TESTOBJ := $(SRC:$(SRCDIR)/%.c=$(TESTOBJDIR)/%.o)
+
 LIBS =
 CC = gcc
 CFLAGS = -g -Wall --std=c99
 
-.PHONY: default all clean
+.PHONY: default all clean test
 
-default: $(TARGET)
+default: $(BINDIR)/coquet-cli $(BINDIR)/coquet-test
 all: default
 
+obj/test:
+       -mkdir obj/test
+
 $(OBJDIR)/%.o: $(SRCDIR)/%.c
        $(CC) $(CFLAGS) -c $< -o $@
 
+$(TESTOBJDIR)/%.o: $(SRCDIR)/%.c obj/test
+       $(CC) $(CFLAGS) -DCOQUET_TEST -c $< -o $@
+
 $(OBJDIR)/%.d: $(SRCDIR)/%.c
        $(CC) -MM -MP -MT $(@:.d=.o) $< > $@
 
-$(TARGET): $(OBJ)
-       $(CC) $(OBJ) -Wall $(LIBS) -o $@
+$(TESTOBJDIR)/%.d: $(SRCDIR)/%.c obj/test
+       $(CC) -DCOQUET_TEST -MM -MP -MT $(@:.d=.o) $< > $@
+
+$(BINDIR)/coquet-cli: $(OBJ) $(OBJDIR)/main.o
+       $(CC) $(OBJ) $(OBJDIR)/main.o -Wall $(LIBS) -o $@
+
+$(BINDIR)/coquet-test: $(TESTOBJ) $(TESTOBJDIR)/test.o
+       $(CC) $(TESTOBJ) $(TESTOBJDIR)/test.o -Wall $(LIBS) -o $@
 
 # .PRECIOUS: $(TARGET) $(OBJECTS)
 
 clean:
        -rm -f $(OBJDIR)/*
-       -rm -f $(TARGET)
+       -rm -f $(TESTOBJDIR)/*
+       -rm -f $(BINDIR)/*
 
--include $(SRC:$(SRCDIR)/%.c=$(OBJDIR)/%.d)
+-include $(DEPS)
+-include $(TESTDEPS)
index 220b976bc0e945dd5fdcd9f4b81d736eb19e5d2d..0da0fcb6427a091dd2fe1fcebc8330c0d0c999df 100644 (file)
@@ -4,14 +4,12 @@
 #include "constants.h"
 #include "coquet.h"
 #include "util.h"
-
-extern vfs_t vfs_unix;
+#include "vfs.h"
 
 /* VFS null is used as a placeholder prior to initialisation, so that
  * subsequent tidies can be clean.
  */
 vfs_t vfs_null = {
-    .make = NULL,
     .start = NULL,
     .get_error_text = NULL,
     .lock = NULL,
@@ -22,19 +20,52 @@ vfs_t vfs_null = {
     .finish = NULL
 };
 
+/* Teardown and free vfs layer. */
+static int finish_vfs(coquet_t *cq) {
+    int r;
+
+    if(cq->vfs_funcs.finish != NULL) {
+        r = (cq->vfs_funcs.finish)(cq->vfs_data);
+        if(r != COQUET_RET_OK) {
+            return r;
+        }
+        cq->vfs_funcs.finish = NULL;
+    }
+
+    return COQUET_RET_OK;
+}
+
+#ifdef COQUET_TEST
+static int testvfs_wrap(coquet_t *cq) {
+    cq->vfs_data = testvfs_make(&cq->vfs_funcs,cq->vfs_data);
+    if(cq->vfs_data == NULL)
+        return COQUET_RET_HEAPERR;
+
+    cq->vfs_funcs = vfs_test;
+    
+    return COQUET_RET_OK;
+}
+#endif
+
 /* Allocate and initialise vfs layer. */
 static int init_vfs(coquet_t *cq, char *basename) {
     int r;
 
     cq->vfs_funcs = vfs_unix;
-
-    /* Allocate memory */
-    cq->vfs_data = (cq->vfs_funcs.make)();
+    cq->vfs_data = unix_make();
     if(cq->vfs_data == NULL) {
         cq->vfs_funcs = vfs_null;
         return COQUET_RET_HEAPERR;
     }
 
+#ifdef COQUET_TEST
+    r = testvfs_wrap(cq);
+    if(r != COQUET_RET_OK) {
+        finish_vfs(cq);
+        return r;
+    }
+#endif
+
     /* Initialise */
     r = (cq->vfs_funcs.start)(cq->vfs_data,basename);
     if(r != COQUET_RET_OK) {
@@ -59,21 +90,6 @@ int coquet_init(coquet_t *cq, char * basename) {
     return COQUET_RET_OK;
 }
 
-/* Teardown and free vfs layer. */
-static int finish_vfs(coquet_t *cq) {
-    int r;
-
-    if(cq->vfs_funcs.make != NULL) {
-        r = (cq->vfs_funcs.finish)(cq->vfs_data);
-        if(r != COQUET_RET_OK) {
-            return r;
-        }
-        cq->vfs_funcs.make = NULL;
-    }
-
-    return COQUET_RET_OK;
-}
-
 /* Tear down whole library. Idempotent to allow crash recovery. */
 int coquet_finish(coquet_t *cq) {
     int r;
index 4cd6547ec2424051f82d0fe3c123127f2e00139f..9118face23a29020d11f20cf61bebfb76f23fb11 100644 (file)
@@ -29,4 +29,9 @@ int coquet_finish(coquet_t *cq);
  */
 char * coquet_error_string(coquet_t *cq, int error);
 
+#ifdef COQUET_TEST
+void test_bail(coquet_t * cq, int error_code);
+void test_unlink(char *path);
+#endif
+
 #endif
diff --git a/src/hmac.c b/src/hmac.c
deleted file mode 100644 (file)
index e69de29..0000000
index ad953125b109102284e93b10c60380eb3e0171ed..3eaa86ed36f6ed06af0b3ceb0a6f41bb6ffc6c89 100644 (file)
  * +-------------------+
  * | magic             |    0         8
  * | version           |    8         8
- * | sb_hash           |   16        32
- * | sb_serial         |   48         8
+ * | global_iv         |   16        32
+ * | sb_hash           |   48        32
+ * | sb_serial         |   80         8
  * +-------------------+
- * | unused            |   56       968
+ * | unused            |   88       936
  * +-------------------+
  * | current           | 1024       512
  * +-------------------+
  *
  *                       offset    length
  * +-------------------+
- * | global_iv         |  0           32
- * | block_size        |  32           1
- * | nursery_size      |  33           1
+ * | block_size        |   0           1
+ * | nursery_size      |   1           1
  * +-------------------+
- * | unused            |  34         477
+ * | unused            |   2         509
  * +-------------------+
  * | reserved          | 511           1
  * +-------------------+
@@ -42,8 +42,9 @@
  */
 
 struct cq_super default_super = {
-    .current = {{0,},12,10},
-    .desired = {{0,},12,10},
+    .current = {12,10},
+    .desired = {12,10},
+    .global_iv = {0,},
     .sb_serial = 0,
     .sb_hash = {0,},
     .version = COQUET_VERSION,
@@ -54,9 +55,8 @@ struct cq_super default_super = {
  * at this point, so don't trust the data yet.
  */
 static void extract_config(uint8_t *data, struct cq_super_config *sc) {
-    memcpy(sc->global_iv,data,HASH_LEN);
-    sc->block_size = be_decode(data+32,1);
-    sc->nursery_size = be_decode(data+33,1);
+    sc->block_size = be_decode(data,1);
+    sc->nursery_size = be_decode(data+1,1);
 }
 
 static int extract_half(uint8_t *data, struct cq_super *super) {
@@ -70,18 +70,20 @@ static int extract_half(uint8_t *data, struct cq_super *super) {
         return 0;
     }
     super->version = be_decode(data+8,8);
-    /* All versions are fine for now. */
+    /* (all versions are fine for now). */
+
+    memcpy(super->global_iv,data+16,GLOBAL_IV_LEN);
     memset(super->sb_hash,0,HASH_LEN);
-    super->sb_serial = be_decode(data+48,8);
+    super->sb_serial = be_decode(data+80,8);
     
     extract_config(data+1024,&(super->current));
     extract_config(data+1536,&(super->desired));
 
     /* verify */
-    memcpy(hash,data+16,HASH_LEN);
-    memset(data+16,0,HASH_LEN);
+    memcpy(hash,data+48,HASH_LEN);
+    memset(data+48,0,HASH_LEN);
     sha2_init_hmac(&sha2, SHA2_VARIETY_512_256,
-        super->current.global_iv, GLOBAL_IV_LEN);
+        super->global_iv, GLOBAL_IV_LEN);
     sha2_more(&sha2,data,HALF_BYTES);
     sha2_finish(&sha2,cmp,HASH_LEN);
     if(memcmp(hash,cmp,HASH_LEN)) {
@@ -95,11 +97,10 @@ static int super_init(coquet_t *cq, struct cq_super *super) {
     int r;
 
     r = (cq->vfs_funcs.random)(cq->vfs_data,
-            super->current.global_iv, GLOBAL_IV_LEN);
+            super->global_iv, GLOBAL_IV_LEN);
     if(r != COQUET_RET_OK) {
         return r;
     }
-    memcpy(super->desired.global_iv,super->current.global_iv,GLOBAL_IV_LEN);    
     return COQUET_RET_OK;
 }
 
@@ -156,28 +157,28 @@ static int lock_super(coquet_t *cq, int mode, bool_t wait) {
 
 static void intract_config(uint8_t *data,
                           struct cq_super_config *sc) {
-    memcpy(data,sc->global_iv,HASH_LEN);
-    be_encode(data+32,sc->block_size,1);
-    be_encode(data+33,sc->nursery_size,1);
+    be_encode(data,sc->block_size,1);
+    be_encode(data+1,sc->nursery_size,1);
 }
 
 static void intract(uint8_t *data, struct cq_super *super, uint64_t serial) {
     struct sha2_ctx_t sha2;
     
     /* intract */
-    memset(data,0,HASH_LEN);
+    memset(data,0,HALF_BYTES);
     be_encode(data,COQUET_MAGIC,8);
     be_encode(data+8,COQUET_VERSION,8);
-    be_encode(data+48,serial,8);
+    memcpy(data+16,super->global_iv,GLOBAL_IV_LEN);
+    be_encode(data+80,serial,8);
     
     intract_config(data+1024,&(super->current));
     intract_config(data+1536,&(super->desired));
 
     /* set hash */
     sha2_init_hmac(&sha2, SHA2_VARIETY_512_256,
-            super->current.global_iv, GLOBAL_IV_LEN);
+            super->global_iv, GLOBAL_IV_LEN);
     sha2_more(&sha2,data,HALF_BYTES);
-    sha2_finish(&sha2,data+16,HASH_LEN);
+    sha2_finish(&sha2,data+48,HASH_LEN);
 }
 
 static int super_write(coquet_t *cq, struct cq_super *super,
@@ -224,3 +225,37 @@ int cq_super_save(coquet_t *cq, struct cq_super *super, bool_t wait) {
 
     return ret;
 }
+
+#ifdef COQUET_TEST
+
+#include <stdio.h>
+
+void test_superblock() {
+    coquet_t cq;
+    int r;
+    struct cq_super super;
+
+    printf("testing superblock\n");
+    r = coquet_init(&cq,"tmp/test");
+    test_bail(&cq,r);
+    
+    test_unlink("tmp/test.coquet");
+    r = (cq.vfs_funcs.open)(cq.vfs_data,COQUET_FILE_MAIN,COQUET_CMODE_EITHER);
+    test_bail(&cq,r);
+
+    testvfs_fakerandom(cq.vfs_data,0xA5);
+    r = cq_super_load(&cq,&super,1);
+    test_bail(&cq,r);
+
+    cq_super_save(&cq,&super,1);
+    test_bail(&cq,r);
+    testvfs_fakerandom(cq.vfs_data,-1);
+
+    r = (cq.vfs_funcs.close)(cq.vfs_data,COQUET_FILE_MAIN);
+    test_bail(&cq,r);
+
+    r = coquet_finish(&cq);
+    test_bail(&cq,r);
+}
+
+#endif
index b9426c4f5e6fe226d46ba6f48c25d30b9d1bd992..314e75c533488f6103241783bcef8c25eeb3ae79 100644 (file)
@@ -9,7 +9,6 @@
 #define SUPERBLOCK_LAST_OFFSET (SUPER_BYTES-1)
 
 struct cq_super_config {
-    uint8_t global_iv[GLOBAL_IV_LEN];
     uint8_t block_size; /* log2 bytes */
     uint8_t nursery_size; /* log2 blocks */
 };
@@ -18,6 +17,7 @@ struct cq_super {
     struct cq_super_config current, desired;
     uint64_t version;
     uint64_t sb_serial;
+    uint8_t global_iv[GLOBAL_IV_LEN];
     uint8_t sb_hash[32]; /* HMAC SHA512-256 using global_iv */
     int from_b; /* true if loaded from b, false if from a */
 };
@@ -27,4 +27,8 @@ struct cq_super {
 int cq_super_save(coquet_t *cq, struct cq_super *super, bool_t wait);
 int cq_super_load(coquet_t *cq, struct cq_super *super, bool_t create);
 
+#ifdef COQUET_TEST
+void test_superblock();
+#endif
+
 #endif
\ No newline at end of file
diff --git a/src/test.c b/src/test.c
new file mode 100644 (file)
index 0000000..bbc8db7
--- /dev/null
@@ -0,0 +1,38 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include "superblock.h"
+
+void test_unlink(char *path) {
+    int r;
+
+    r = unlink(path);
+    if(r && errno != ENOENT) {
+        fprintf(stderr,"Couldn't unlink '%s': %s\n",path,strerror(errno));
+        exit(1);
+    }
+}
+
+void test_bail(coquet_t * cq, int error_code) {
+    char *msg;
+
+    if(error_code == COQUET_RET_OK) {
+        return;
+    }
+    msg = coquet_error_string(cq,error_code);
+    if(!msg) {
+        fprintf(stderr,"Heap exhausted");
+        exit(1);
+    }
+    fprintf(stderr,"%s\n",msg);
+    free(msg);
+    coquet_finish(cq);
+    exit(1);
+}
+
+int main() {
+    printf("testing\n");
+    test_superblock();
+    return 0;
+}
diff --git a/src/testvfs.c b/src/testvfs.c
new file mode 100644 (file)
index 0000000..fb7b7c6
--- /dev/null
@@ -0,0 +1,128 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "vfs.h"
+#include "coquet.h"
+
+struct test_data {
+    int fake_random;
+    vfs_t vfs_funcs;
+    void *vfs_data;
+};
+
+void * testvfs_make(vfs_t *vfs_funcs, void *vfs_data) {
+    struct test_data *td;
+
+    td = malloc(sizeof(struct test_data));
+    if(td == NULL)
+        return NULL;
+    td->fake_random = -1;
+    td->vfs_funcs = *vfs_funcs;
+    td->vfs_data = vfs_data;
+    return td;
+}
+
+static int test_start(void * vfs_data, char *filename) {
+    struct test_data * td = (struct test_data *)vfs_data;
+
+    printf("Using test VFS\n");
+    return (td->vfs_funcs.start)(td->vfs_data,filename);
+}
+
+static int test_finish(void *vfs_data) {
+    struct test_data * td = (struct test_data *)vfs_data;
+    int r;
+
+    r = (td->vfs_funcs.finish)(td->vfs_data);
+    if(r != COQUET_RET_OK)
+        return r;
+    free(td);
+    return COQUET_RET_OK;
+}
+
+static char * test_get_error_text(void * vfs_data) {
+    struct test_data * td = (struct test_data *)vfs_data;
+
+    return (td->vfs_funcs.get_error_text)(td->vfs_data);
+}
+
+static int test_open(void * vfs_data, int which_file, 
+                      int mode) {
+    struct test_data * td = (struct test_data *)vfs_data;
+
+    return (td->vfs_funcs.open)(td->vfs_data,which_file,mode);
+}
+
+static int test_close(void * vfs_data, int which_file) {
+    struct test_data * td = (struct test_data *)vfs_data;
+
+    return (td->vfs_funcs.close)(td->vfs_data,which_file);
+}
+
+static int test_write(void * vfs_data, int which_file, uint8_t * data,
+                       off_t offset, uint64_t length) {
+    struct test_data * td = (struct test_data *)vfs_data;
+
+    return (td->vfs_funcs.write)(td->vfs_data,
+                                 which_file, data, offset, length);
+}
+
+static int test_read(void * vfs_data, int which_file, uint8_t * data,
+                      uint64_t offset, uint64_t length) {
+    struct test_data * td = (struct test_data *)vfs_data;
+
+    return (td->vfs_funcs.read)(td->vfs_data,
+                                which_file, data, offset, length);
+}
+
+static int test_lock(void * vfs_data, int which_lock, int lock_mode,
+                      bool_t wait) {
+    struct test_data * td = (struct test_data *)vfs_data;
+
+    return (td->vfs_funcs.lock)(td->vfs_data,
+                                 which_lock, lock_mode, wait);
+}
+
+static int test_delete(void *vfs_data, int which_file) {
+    struct test_data * td = (struct test_data *)vfs_data;
+
+    return (td->vfs_funcs.delete)(td->vfs_data, which_file);
+}
+
+static int test_sync(void * vfs_data, int which_file, bool_t data_only) {
+    struct test_data * td = (struct test_data *)vfs_data;
+
+    return (td->vfs_funcs.sync)(td->vfs_data, which_file, data_only);
+}
+
+static int test_random(void * vfs_data, uint8_t *out, int len) {
+    struct test_data * td = (struct test_data *)vfs_data;
+
+    if(td->fake_random < 0) {
+        return (td->vfs_funcs.random)(td->vfs_data, out, len);
+    } else {
+        memset(out,td->fake_random,len);
+        return COQUET_RET_OK;
+    }
+}
+
+void testvfs_fakerandom(void * vfs_data, int setting) {
+    struct test_data * td = (struct test_data *)vfs_data;
+
+    td->fake_random = setting;
+}
+
+vfs_t vfs_test = {
+    .start = test_start,
+    .get_error_text = test_get_error_text,
+    .lock = test_lock,
+    .open = test_open,
+    .close = test_close,
+    .write = test_write,
+    .read = test_read,
+    .finish = test_finish,
+    .delete = test_delete,
+    .sync = test_sync,
+    .random = test_random
+};
index ab48a019be70f65f6fffc0bf7b83f16f2b9f2e84..87bdd747aa4fb3cfd15f31adc40e5857ff4a1148 100644 (file)
@@ -1,5 +1,4 @@
 #define _GNU_SOURCE
-#include <stdio.h> /* During testing */
 #include <stdlib.h>
 #include <fcntl.h>
 #include <errno.h>
@@ -36,10 +35,12 @@ static void set_error(struct unix_data * pd, char *error,
     }
 }
 
-static void * unix_make() {
+void * unix_make() {
     struct unix_data * pd;
 
     pd = malloc(sizeof(struct unix_data));
+    if(pd == NULL)
+        return NULL;
     pd->seen_error = 0;
     pd->error_text = NULL;
     pd->filename = pd->dirname_buf = NULL;
@@ -455,7 +456,6 @@ static int unix_random(void * vfs_data, uint8_t *out, int len) {
 }
 
 vfs_t vfs_unix = {
-    .make = unix_make,
     .start = unix_start,
     .get_error_text = unix_get_error_text,
     .lock = unix_lock,
index 6d677dd541bb85c0d29e84d62835f23b9e7c9847..005b484a7ad43ce0817c0e4c8c255d7eb79b183e 100644 (file)
--- a/src/vfs.h
+++ b/src/vfs.h
@@ -9,14 +9,6 @@
  * COQUET_RET_VFS or COQUET_RET_HEAPERR unless docs here indicate otherwise.
 */
 typedef struct vfs {
-
-    /* Returning vfs_data payload which will then be
-     * owned by caller until passed to finish on shutdown. Initialise
-     * is then called. This is split in two to allow errors to be reported
-     * in the regular way by init. May be null if heap allocation failed.
-     */
-    void * (*make)();
-
     /* Initialise VFS layer. Use basename as filepath in all calls.
      * basename = a/b/foo => a/b/foo.coquet, a/b/foo.coquet.tmp, etc.
      * basename is still owned by caller.
@@ -89,4 +81,17 @@ typedef struct vfs {
 
 } vfs_t;
 
+extern vfs_t vfs_unix;
+
+void * unix_make();
+
+#ifdef COQUET_TEST
+
+extern vfs_t vfs_test;
+
+void * testvfs_make(vfs_t *vfs_funcs, void *vfs_data);
+void testvfs_fakerandom(void * vfs_data, int setting);
+
+#endif /* COQUET_TEST */
+
 #endif