test/wsgidav.c

Sat, 24 Jan 2026 13:56:19 +0100

author
Olaf Wintermann <olaf.wintermann@gmail.com>
date
Sat, 24 Jan 2026 13:56:19 +0100
branch
dav-2
changeset 902
06fa328989ee
permissions
-rw-r--r--

integrate wsgidav into test suite

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2026 Olaf Wintermann. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   1. Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *   2. Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in the
 *      documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "wsgidav.h"

#include <stdio.h>
#include <stdlib.h>

//ifndef _WIN32

#include <unistd.h>
#include <sys/fcntl.h>

#include <spawn.h>
#include <sys/wait.h>
#include <sys/stat.h>

#include <cx/string.h>
#include <cx/buffer.h>
#include <cx/streams.h>
#include <cx/printf.h>


// -------------------------- Process Utils --------------------------------

typedef struct {
    pid_t pid;
    int in;
    int out;
    int err;
} Process;

static Process process_spawn(const char *exec, char **args) {
    Process proc = { 0 };
    
    int pin[2];
    int pout[2];
    int perr[2];
    
    // child process stdin/stdout pipes
    if(pipe(pin)) {
        perror("pipe");
        return proc;
    }
    if(pipe(pout)) {
        perror("pipe");
        close(pin[0]);
        close(pin[1]);
    }
    if(pipe(perr)) {
        perror("pipe");
        close(pin[0]);
        close(pin[1]);
        close(pout[0]);
        close(pout[1]);
    }
    
    posix_spawn_file_actions_t actions;
    posix_spawn_file_actions_init(&actions);
    posix_spawn_file_actions_adddup2(&actions, pin[0], 0);
    posix_spawn_file_actions_adddup2(&actions, pout[1], 1);
    posix_spawn_file_actions_adddup2(&actions, perr[1], 2);
    
    // child environment variables
    char *path = getenv("PATH");
    if(!path) {
        path = "/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin";
    }
    
    cxmutstr env_path = cx_asprintf("PATH=%s", path);
    
    char *env[10];
    env[0] = "LC_ALL=C";
    env[1] = env_path.ptr;
    env[2] = NULL; // array must be null-terminated
    
    // execute posix_spawn to create a child process
    pid_t child;
    int ret = posix_spawn(
            &child,
            exec,
            &actions,
            NULL,            // attributes
            args,
            env);
    posix_spawn_file_actions_destroy(&actions);
    
    close(pin[0]);
    close(pout[1]);
    close(perr[1]);
    
    if(ret) {
        // posix spawn failed
        perror("posix_spawn");
        // close remaining pipe fds
        close(pin[1]);
        close(pout[0]);
        close(perr[0]);
    } else {
        proc.pid = child;
        proc.in = pin[1];
        proc.out = pout[0];
        proc.err = perr[0];
    }
    
    return proc;
}

int process_close(Process *p) {
    if(p->pid == 0) {
        return -1;
    }
    close(p->in);
    close(p->out);
    int status = -1;
    waitpid(p->pid, &status, 0);
    return status;
}

size_t process_read(void *buf, size_t size, size_t n, Process *p) {
    ssize_t r = read(p->out, buf, size*n);
    return r > 0 ? r : 0;
}

size_t process_err_read(void *buf, size_t size, size_t n, Process *p) {
    ssize_t r = read(p->err, buf, size*n);
    return r > 0 ? r : 0;
}

static cxmutstr read_str(Process *p, cx_read_func readf) {
    if(p->pid == 0) {
        return CX_NULLSTR;
    }
    CxBuffer buffer;
    cxBufferInit(&buffer, NULL, NULL, 1024, CX_BUFFER_AUTO_EXTEND);
    cx_stream_copy(p, &buffer, readf, (cx_write_func)cxBufferWrite);
    cxBufferTerminate(&buffer);
    return cx_mutstrn(buffer.space, buffer.size);
}

cxmutstr process_read_string(Process *p) {
    return read_str(p, (cx_read_func)process_read);
}

cxmutstr process_err_read_string(Process *p) {
    return read_str(p, (cx_read_func)process_err_read);
}


// ------------------------------ wsgidav exec -----------------------------

static char *wsgidav_exec_path;
static Process wsgidav_process;
static int runtest_root_dir = 0;

int wsgidav_is_available(void) {
    // check if the wsgidava binary is available
    char *args[] = {
        "which",
        "wsgidav",
        NULL
    };
    Process p = process_spawn("/usr/bin/which", args);
    if(p.pid == 0) {
        return 0;
    }
    
    cxmutstr path = process_read_string(&p);
    int status = process_close(&p);
    if(status == 0) {
        wsgidav_exec_path = cx_strdup(cx_strtrim(path)).ptr;
    }
    
    free(path.ptr);
    
    if(status != 0) {
        return 0;
    }
    
    // check if the wsgidav config file is available
    struct stat s;
    if(!stat("test/wsgidav/wsgidav.yaml", &s)) {
        runtest_root_dir = 1; // dav root directory
    } else {
        if(!stat("../test/wsgidav/wsgidav.yaml", &s)) {
            runtest_root_dir = 2; // build directory
        } else {
            fprintf(stderr, "Error: wsgidav found, but the config file wsgidav.yaml was not found\nTry running the tests from the dav root directory\n");
            return 0;
        }
    }
    
    if(runtest_root_dir == 1) {
        if(!stat("build", &s)) {
            if(!S_ISDIR(s.st_mode)) {
                fprintf(stderr, "Error: build is not a directory: wsgidav disabled\n");
                return 0;
            }
        } else {
            fprintf(stderr, "Error: build directory not found: wsgidav disabled\n");
            return 0;
        }
    }
    
    return 1;
}

int wsgidav_start(void) {
    if(!wsgidav_exec_path) {
        return 1; // wsgidav_start called but wsgidav_is_available return 0
    }
    
    if(runtest_root_dir == 1) {
        if(chdir("build")) {
            perror("chdir");
            return 1;
        }
    }
    
    // delete previous testrepo directory
    char *args1[] = { "rm", "-Rf", "testrepo", NULL };
    Process p_rm = process_spawn("/bin/rm", args1);
    cxmutstr err = process_err_read_string(&p_rm);
    if(process_close(&p_rm) != 0) {
        cxmutstr s = cx_strtrim(err);
        fprintf(stderr, "rm failed: %.*s\n", (int)s.length, s.ptr);
        return 1;
    }
    free(err.ptr);
    
    // copy testrepo to the build directory
    char *args2[] = { "cp", "-R", "../test/wsgidav/testrepo", "testrepo", NULL };
    Process p_cp = process_spawn("/bin/cp", args2);
    err = process_err_read_string(&p_cp);
    if(process_close(&p_cp) != 0) {
        cxmutstr s = cx_strtrim(err);
        fprintf(stderr, "cp failed: %.*s\n", (int)s.length, s.ptr);
        return 1;
    }
    free(err.ptr);
    
    // start wsgidav
    char *args3[] = { "wsgidav", "-c", "../test/wsgidav/wsgidav.yaml", NULL };
    wsgidav_process = process_spawn(wsgidav_exec_path, args3);
    
    // TODO: parse log or try to connect to localhost
    sleep(1);
    
    return 0;
}

int wsgidav_stop(void) {
    if(wsgidav_process.pid == 0) {
        return 0;
    }
    kill(wsgidav_process.pid, SIGKILL);
    (void)process_close(&wsgidav_process);
    //if(status > 2) {
    //    printf("wsgidav exit: %d\n", status);
    //}
    return 0;
}

/*
#else

int wsgidav_is_available(void) {
    return 0;
}

int wsgidav_start(void) {
    return 1;
}

int wsgidav_stop(void) {
    return 1;
}

#endif

*/

mercurial