Files
qdl/tests/test_program_load_xml.c
Igor Opaniuk 65711e63a1 ux: own the program name used in diagnostics
The error prefix comes from __progname, a BSD-ism that glibc and the
Apple libc export but Windows lacks, so qdl.c and every unit test that
links util.c define their own copy. The nbdkit plugin links neither.

Keep the name in ux.c with a "qdl" default and set it through
ux_set_progname(): from argv[0] in main() and from the plugin's load
hook. Move print_version() into qdl.c, its only caller, so util.c and
the tests no longer need the symbol.

Signed-off-by: Igor Opaniuk <igor.opaniuk@oss.qualcomm.com>
2026-10-01 10:21:20 +02:00

434 lines
9.6 KiB
C

// SPDX-License-Identifier: BSD-3-Clause
#define _FILE_OFFSET_BITS 64
#if defined(__APPLE__)
#define _DARWIN_C_SOURCE
#endif
#define _XOPEN_SOURCE 700
#include <errno.h>
#include <limits.h>
#include <setjmp.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <cmocka.h>
#include <libxml/parser.h>
#include <libxml/tree.h>
#include "file.h"
#include "firehose.h"
#include "list.h"
#include "pathbuf.h"
#include "program.h"
#include "qdl.h"
#include "sparse.h"
#include "common.h"
#define TEST_INCDIR "/mock/incdir"
#define TEST_XMLDIR "/mock/xml"
#define TEST_PROGRAM_FILE TEST_XMLDIR "/program.xml"
#define TEST_PAYLOAD "payload.bin"
#define TEST_INCDIR_PAYLOAD TEST_INCDIR "/" TEST_PAYLOAD
#define TEST_XMLDIR_PAYLOAD TEST_XMLDIR "/" TEST_PAYLOAD
bool qdl_debug;
static const char *existing_paths[4];
static void set_existing_paths(const char *path0, const char *path1, const char *path2)
{
existing_paths[0] = path0;
existing_paths[1] = path1;
existing_paths[2] = path2;
existing_paths[3] = NULL;
}
static bool mock_path_exists(const char *path)
{
for (size_t i = 0; existing_paths[i]; i++) {
if (!strcmp(path, existing_paths[i]))
return true;
}
return false;
}
int mock_access(const char *path, int mode)
{
assert_int_equal(mode, F_OK);
if (mock_path_exists(path))
return 0;
errno = ENOENT;
return -1;
}
int qdl_file_open(struct qdl_zip *qzip, const char *filename, struct qdl_file *file)
{
assert_null(qzip);
if (!mock_path_exists(filename))
return -1;
file->type = QDL_FILE_TYPE_POSIX;
file->size = 1;
file->fd = -1;
file->zip_file = NULL;
return 0;
}
void qdl_file_close(struct qdl_file *file)
{
file->type = QDL_FILE_TYPE_UNKNOWN;
}
void *qdl_file_load(struct qdl_file *file, size_t *len)
{
(void)file;
(void)len;
return NULL;
}
size_t qdl_file_getsize(struct qdl_file *file)
{
return file->size;
}
off_t qdl_file_seek(struct qdl_file *file, off_t offset, int whence)
{
(void)file;
(void)offset;
(void)whence;
return -1;
}
ssize_t qdl_file_read(struct qdl_file *file, void *buf, size_t len)
{
(void)file;
(void)buf;
(void)len;
return -1;
}
struct qdl_zip *qdl_zip_get(struct qdl_zip *qzip)
{
return qzip;
}
void qdl_zip_put(struct qdl_zip *qzip)
{
(void)qzip;
}
int sparse_header_parse(struct qdl_file *file, sparse_header_t *sparse_header)
{
(void)file;
(void)sparse_header;
return -1;
}
int sparse_chunk_header_parse(struct qdl_file *file,
sparse_header_t *sparse_header,
uint64_t *chunk_size,
uint32_t *value, off_t *offset)
{
(void)file;
(void)sparse_header;
(void)chunk_size;
(void)value;
(void)offset;
return -1;
}
void ux_init(void)
{
}
void ux_err(const char *fmt, ...)
{
(void)fmt;
}
void ux_info(const char *fmt, ...)
{
(void)fmt;
}
void ux_log(const char *fmt, ...)
{
(void)fmt;
}
void ux_debug(const char *fmt, ...)
{
(void)fmt;
}
void ux_progress(const char *fmt, unsigned int value, unsigned int size, ...)
{
(void)fmt;
(void)value;
(void)size;
}
struct firehose_op *firehose_alloc_op(int type)
{
struct firehose_op *op;
op = calloc(1, sizeof(*op));
if (!op)
return NULL;
op->type = type;
return op;
}
void firehose_free_ops(struct list_head *ops)
{
struct firehose_op *next;
struct firehose_op *op;
list_for_each_entry_safe(op, next, ops, node) {
list_del(&op->node);
qdl_zip_put(op->zip);
free((void *)op->filename);
free((void *)op->label);
free((void *)op->start_sector);
free((void *)op->gpt_partition);
free((void *)op->value);
free((void *)op->what);
free(op);
}
}
int contents_resolve_path(struct contents_filter *filter, const char *filename, struct pathbuf *path)
{
(void)filter;
(void)filename;
(void)path;
return 0;
}
static xmlDoc *build_program_doc(const char *filename)
{
char xml[1024];
int ret;
ret = snprintf(xml, sizeof(xml),
"<data>"
"<program "
"SECTOR_SIZE_IN_BYTES=\"512\" "
"filename=\"%s\" "
"label=\"test\" "
"num_partition_sectors=\"1\" "
"physical_partition_number=\"0\" "
"start_sector=\"0\" "
"file_sector_offset=\"0\""
"/>"
"</data>",
filename);
assert_true(ret > 0 && (size_t)ret < sizeof(xml));
return xmlReadMemory(xml, ret, "program.xml", NULL, 0);
}
static struct firehose_op *get_only_op(struct list_head *ops)
{
struct firehose_op *op;
struct list_head *it;
size_t count = 0;
list_for_each(it, ops)
count++;
if (count != 1)
fail_msg("expected exactly one firehose op, got %zu", count);
op = list_entry_first(ops, struct firehose_op, node);
if (op->type != FIREHOSE_OP_PROGRAM)
fail_msg("expected firehose op type PROGRAM, got %d", op->type);
return op;
}
static void test_incdir_takes_precedence(void **state)
{
struct firehose_op *op;
struct list_head ops = LIST_INIT(ops);
xmlDoc *doc;
int ret;
(void)state;
set_existing_paths(TEST_INCDIR_PAYLOAD, TEST_XMLDIR_PAYLOAD, NULL);
doc = build_program_doc(TEST_PAYLOAD);
if (!doc)
fail_msg("failed to parse synthetic program XML");
ret = program_load_xml(&ops, doc, NULL, TEST_PROGRAM_FILE, false, false, NULL, TEST_INCDIR);
if (ret)
fail_msg("incdir payload should be accepted, got %d", ret);
op = get_only_op(&ops);
if (!op->filename || strcmp(op->filename, TEST_INCDIR_PAYLOAD))
fail_msg("incdir should take precedence, got '%s'",
op->filename ? op->filename : "(null)");
firehose_free_ops(&ops);
xmlFreeDoc(doc);
}
static void test_program_xml_directory_is_used(void **state)
{
struct firehose_op *op;
struct list_head ops = LIST_INIT(ops);
xmlDoc *doc;
int ret;
(void)state;
set_existing_paths(TEST_XMLDIR_PAYLOAD, NULL, NULL);
doc = build_program_doc(TEST_PAYLOAD);
if (!doc)
fail_msg("failed to parse synthetic program XML");
ret = program_load_xml(&ops, doc, NULL, TEST_PROGRAM_FILE, false, false, NULL, TEST_INCDIR);
if (ret)
fail_msg("XML-adjacent payload should be accepted, got %d", ret);
op = get_only_op(&ops);
if (!op->filename || strcmp(op->filename, TEST_XMLDIR_PAYLOAD))
fail_msg("program XML directory should be used, got '%s'",
op->filename ? op->filename : "(null)");
firehose_free_ops(&ops);
xmlFreeDoc(doc);
}
static void test_program_xml_directory_takes_precedence_over_current_directory(void **state)
{
struct firehose_op *op;
struct list_head ops = LIST_INIT(ops);
xmlDoc *doc;
int ret;
(void)state;
set_existing_paths(TEST_XMLDIR_PAYLOAD, TEST_PAYLOAD, NULL);
doc = build_program_doc(TEST_PAYLOAD);
if (!doc)
fail_msg("failed to parse synthetic program XML");
ret = program_load_xml(&ops, doc, NULL, TEST_PROGRAM_FILE, false, false, NULL, TEST_INCDIR);
if (ret)
fail_msg("XML-adjacent payload should beat current-directory fallback, got %d", ret);
op = get_only_op(&ops);
if (!op->filename || strcmp(op->filename, TEST_XMLDIR_PAYLOAD))
fail_msg("program XML directory should take precedence over current directory, got '%s'",
op->filename ? op->filename : "(null)");
firehose_free_ops(&ops);
xmlFreeDoc(doc);
}
static void test_current_directory_is_used_when_path_resolution_misses(void **state)
{
struct firehose_op *op;
struct list_head ops = LIST_INIT(ops);
xmlDoc *doc;
int ret;
(void)state;
set_existing_paths(TEST_PAYLOAD, NULL, NULL);
doc = build_program_doc(TEST_PAYLOAD);
if (!doc)
fail_msg("failed to parse synthetic program XML");
/* Compatibility fallback: leave the original relative filename intact. */
ret = program_load_xml(&ops, doc, NULL, TEST_PROGRAM_FILE, false, false, NULL, TEST_INCDIR);
if (ret)
fail_msg("relative fallback payload should be accepted, got %d", ret);
op = get_only_op(&ops);
if (!op->filename || strcmp(op->filename, TEST_PAYLOAD))
fail_msg("unresolved relative filename should be preserved, got '%s'",
op->filename ? op->filename : "(null)");
firehose_free_ops(&ops);
xmlFreeDoc(doc);
}
static void test_missing_file_fails_without_allow_missing(void **state)
{
struct list_head ops = LIST_INIT(ops);
xmlDoc *doc;
int ret;
(void)state;
set_existing_paths(NULL, NULL, NULL);
doc = build_program_doc(TEST_PAYLOAD);
if (!doc)
fail_msg("failed to parse synthetic program XML");
ret = program_load_xml(&ops, doc, NULL, TEST_PROGRAM_FILE, false, false, NULL, TEST_INCDIR);
if (ret != -1)
fail_msg("missing payload should fail without allow_missing, got %d", ret);
if (!list_empty(&ops))
fail_msg("missing payload without allow_missing should not append firehose ops");
xmlFreeDoc(doc);
}
static void test_missing_file_is_tolerated_with_allow_missing(void **state)
{
struct firehose_op *op;
struct list_head ops = LIST_INIT(ops);
xmlDoc *doc;
int ret;
(void)state;
set_existing_paths(NULL, NULL, NULL);
doc = build_program_doc(TEST_PAYLOAD);
if (!doc)
fail_msg("failed to parse synthetic program XML");
ret = program_load_xml(&ops, doc, NULL, TEST_PROGRAM_FILE, false, true, NULL, TEST_INCDIR);
if (ret)
fail_msg("missing payload should be tolerated with allow_missing, got %d", ret);
op = get_only_op(&ops);
if (op->filename)
fail_msg("allow_missing should clear the missing program filename, got '%s'",
op->filename);
firehose_free_ops(&ops);
xmlFreeDoc(doc);
}
int main(void)
{
const struct CMUnitTest tests[] = {
cmocka_unit_test(test_incdir_takes_precedence),
cmocka_unit_test(test_program_xml_directory_is_used),
cmocka_unit_test(test_program_xml_directory_takes_precedence_over_current_directory),
cmocka_unit_test(test_current_directory_is_used_when_path_resolution_misses),
cmocka_unit_test(test_missing_file_fails_without_allow_missing),
cmocka_unit_test(test_missing_file_is_tolerated_with_allow_missing),
};
return cmocka_run_group_tests(tests, NULL, NULL);
}