2023-10-16 18:20:08 +07:00

449 lines
12 KiB
C

/*
* SPDX-License-Identifier: ISC
*
* Copyright (C) 2018-2021 Michael Drake <tlsa@netsurf-browser.org>
*/
#include <stdbool.h>
#include <assert.h>
#include <stdio.h>
#include <cyaml/cyaml.h>
#include "ttest.h"
#include "test.h"
/**
* Unit test context data.
*/
typedef struct test_data {
cyaml_data_t **data;
unsigned *seq_count;
const struct cyaml_config *config;
const struct cyaml_schema_value *schema;
} test_data_t;
/**
* Common clean up function to free data loaded by tests.
*
* \param[in] data The unit test context data.
*/
static void cyaml_cleanup(void *data)
{
struct test_data *td = data;
unsigned seq_count = 0;
if (td->seq_count != NULL) {
seq_count = *(td->seq_count);
}
if (td->data != NULL) {
cyaml_free(td->config, td->schema, *(td->data), seq_count);
}
}
/**
* Test loading a non-existent file.
*
* \param[in] report The test report context.
* \param[in] config The CYAML config to use for the test.
* \return true if test passes, false otherwise.
*/
static bool test_file_load_bad_path(
ttest_report_ctx_t *report,
const cyaml_config_t *config)
{
struct target_struct {
char *cakes;
} *data_tgt = NULL;
static const struct cyaml_schema_field mapping_schema[] = {
CYAML_FIELD_END
};
static const struct cyaml_schema_value top_schema = {
CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER,
struct target_struct, mapping_schema),
};
test_data_t td = {
.data = (cyaml_data_t **) &data_tgt,
.config = config,
.schema = &top_schema,
};
cyaml_err_t err;
ttest_ctx_t tc;
if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) {
return true;
}
err = cyaml_load_file("/cyaml/path/shouldn't/exist.yaml",
config, &top_schema, (cyaml_data_t **) &data_tgt, NULL);
if (err != CYAML_ERR_FILE_OPEN) {
return ttest_fail(&tc, cyaml_strerror(err));
}
return ttest_pass(&tc);
}
/**
* Test loading a non-existent file.
*
* \param[in] report The test report context.
* \param[in] config The CYAML config to use for the test.
* \return true if test passes, false otherwise.
*/
static bool test_file_save_bad_path(
ttest_report_ctx_t *report,
const cyaml_config_t *config)
{
struct target_struct {
char *cakes;
} *data = NULL;
static const struct cyaml_schema_field mapping_schema[] = {
CYAML_FIELD_END
};
static const struct cyaml_schema_value top_schema = {
CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER,
struct target_struct, mapping_schema),
};
test_data_t td = {
.data = NULL,
.config = config,
.schema = &top_schema,
};
cyaml_err_t err;
ttest_ctx_t tc;
if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) {
return true;
}
err = cyaml_save_file("/cyaml/path/shouldn't/exist.yaml",
config, &top_schema, data, 0);
if (err != CYAML_ERR_FILE_OPEN) {
return ttest_fail(&tc, cyaml_strerror(err));
}
return ttest_pass(&tc);
}
/**
* Test loading the basic YAML file.
*
* \param[in] report The test report context.
* \param[in] config The CYAML config to use for the test.
* \return true if test passes, false otherwise.
*/
static bool test_file_load_basic(
ttest_report_ctx_t *report,
const cyaml_config_t *config)
{
struct animal {
char *kind;
char **sounds;
unsigned sounds_count;
};
struct target_struct {
struct animal *animals;
unsigned animals_count;
char **cakes;
unsigned cakes_count;
} *data_tgt = NULL;
static const struct cyaml_schema_value sounds_entry_schema = {
CYAML_VALUE_STRING(CYAML_FLAG_POINTER, char, 0, CYAML_UNLIMITED),
};
static const struct cyaml_schema_field animal_mapping_schema[] = {
CYAML_FIELD_STRING_PTR("kind", CYAML_FLAG_POINTER,
struct animal, kind, 0, CYAML_UNLIMITED),
CYAML_FIELD_SEQUENCE("sounds", CYAML_FLAG_POINTER,
struct animal, sounds,
&sounds_entry_schema, 0, CYAML_UNLIMITED),
CYAML_FIELD_END
};
static const struct cyaml_schema_value animals_entry_schema = {
CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT,
struct animal, animal_mapping_schema),
};
static const struct cyaml_schema_value cakes_entry_schema = {
CYAML_VALUE_STRING(CYAML_FLAG_POINTER, char, 0, CYAML_UNLIMITED),
};
static const struct cyaml_schema_field mapping_schema[] = {
CYAML_FIELD_SEQUENCE("animals", CYAML_FLAG_POINTER,
struct target_struct, animals,
&animals_entry_schema, 0, CYAML_UNLIMITED),
CYAML_FIELD_SEQUENCE("cakes", CYAML_FLAG_POINTER,
struct target_struct, cakes,
&cakes_entry_schema, 0, CYAML_UNLIMITED),
CYAML_FIELD_END
};
static const struct cyaml_schema_value top_schema = {
CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER,
struct target_struct, mapping_schema),
};
test_data_t td = {
.data = (cyaml_data_t **) &data_tgt,
.config = config,
.schema = &top_schema,
};
cyaml_err_t err;
ttest_ctx_t tc;
if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) {
return true;
}
err = cyaml_load_file("test/data/basic.yaml", config, &top_schema,
(cyaml_data_t **) &data_tgt, NULL);
if (err != CYAML_OK) {
return ttest_fail(&tc, cyaml_strerror(err));
}
return ttest_pass(&tc);
}
/**
* Test loading and then saving the basic YAML file.
*
* \param[in] report The test report context.
* \param[in] config The CYAML config to use for the test.
* \return true if test passes, false otherwise.
*/
static bool test_file_load_save_basic(
ttest_report_ctx_t *report,
const cyaml_config_t *config)
{
struct animal {
char *kind;
char **sounds;
unsigned sounds_count;
};
struct target_struct {
struct animal *animals;
unsigned animals_count;
char **cakes;
unsigned cakes_count;
} *data_tgt = NULL;
static const struct cyaml_schema_value sounds_entry_schema = {
CYAML_VALUE_STRING(CYAML_FLAG_POINTER, char, 0, CYAML_UNLIMITED),
};
static const struct cyaml_schema_field animal_mapping_schema[] = {
CYAML_FIELD_STRING_PTR("kind", CYAML_FLAG_POINTER,
struct animal, kind, 0, CYAML_UNLIMITED),
CYAML_FIELD_SEQUENCE("sounds", CYAML_FLAG_POINTER,
struct animal, sounds,
&sounds_entry_schema, 0, CYAML_UNLIMITED),
CYAML_FIELD_END
};
static const struct cyaml_schema_value animals_entry_schema = {
CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT,
struct animal, animal_mapping_schema),
};
static const struct cyaml_schema_value cakes_entry_schema = {
CYAML_VALUE_STRING(CYAML_FLAG_POINTER, char, 0, CYAML_UNLIMITED),
};
static const struct cyaml_schema_field mapping_schema[] = {
CYAML_FIELD_SEQUENCE("animals", CYAML_FLAG_POINTER,
struct target_struct, animals,
&animals_entry_schema, 0, CYAML_UNLIMITED),
CYAML_FIELD_SEQUENCE("cakes", CYAML_FLAG_POINTER,
struct target_struct, cakes,
&cakes_entry_schema, 0, CYAML_UNLIMITED),
CYAML_FIELD_END
};
static const struct cyaml_schema_value top_schema = {
CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER,
struct target_struct, mapping_schema),
};
test_data_t td = {
.data = (cyaml_data_t **) &data_tgt,
.config = config,
.schema = &top_schema,
};
cyaml_err_t err;
ttest_ctx_t tc;
if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) {
return true;
}
err = cyaml_load_file("test/data/basic.yaml", config, &top_schema,
(cyaml_data_t **) &data_tgt, NULL);
if (err != CYAML_OK) {
return ttest_fail(&tc, cyaml_strerror(err));
}
err = cyaml_save_file("build/load_save.yaml", config, &top_schema,
data_tgt, 0);
if (err != CYAML_OK) {
return ttest_fail(&tc, cyaml_strerror(err));
}
return ttest_pass(&tc);
}
/**
* Test loading the basic YAML file, with a mismatching schema.
*
* \param[in] report The test report context.
* \param[in] config The CYAML config to use for the test.
* \return true if test passes, false otherwise.
*/
static bool test_file_load_basic_invalid(
ttest_report_ctx_t *report,
const cyaml_config_t *config)
{
struct animal {
char *kind;
int *sounds;
unsigned sounds_count;
};
struct target_struct {
struct animal *animals;
unsigned animals_count;
char **cakes;
unsigned cakes_count;
} *data_tgt = NULL;
static const struct cyaml_schema_value sounds_entry_schema = {
/* The data has a string, but we're expecting int here. */
CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int),
};
static const struct cyaml_schema_field animal_mapping_schema[] = {
CYAML_FIELD_STRING_PTR("kind", CYAML_FLAG_POINTER,
struct animal, kind, 0, CYAML_UNLIMITED),
CYAML_FIELD_SEQUENCE("sounds", CYAML_FLAG_POINTER,
struct animal, sounds,
&sounds_entry_schema, 0, CYAML_UNLIMITED),
CYAML_FIELD_END
};
static const struct cyaml_schema_value animals_entry_schema = {
CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT,
struct animal, animal_mapping_schema),
};
static const struct cyaml_schema_value cakes_entry_schema = {
CYAML_VALUE_STRING(CYAML_FLAG_POINTER, char, 0, CYAML_UNLIMITED),
};
static const struct cyaml_schema_field mapping_schema[] = {
CYAML_FIELD_SEQUENCE("animals", CYAML_FLAG_POINTER,
struct target_struct, animals,
&animals_entry_schema, 0, CYAML_UNLIMITED),
CYAML_FIELD_SEQUENCE("cakes", CYAML_FLAG_POINTER,
struct target_struct, cakes,
&cakes_entry_schema, 0, CYAML_UNLIMITED),
CYAML_FIELD_END
};
static const struct cyaml_schema_value top_schema = {
CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER,
struct target_struct, mapping_schema),
};
test_data_t td = {
.data = (cyaml_data_t **) &data_tgt,
.config = config,
.schema = &top_schema,
};
cyaml_err_t err;
ttest_ctx_t tc;
if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) {
return true;
}
err = cyaml_load_file("test/data/basic.yaml", config, &top_schema,
(cyaml_data_t **) &data_tgt, NULL);
if (err != CYAML_ERR_INVALID_VALUE) {
return ttest_fail(&tc, cyaml_strerror(err));
}
return ttest_pass(&tc);
}
/**
* Test saving to a file when an erro occurs.
*
* \param[in] report The test report context.
* \param[in] config The CYAML config to use for the test.
* \return true if test passes, false otherwise.
*/
static bool test_file_save_basic_invalid(
ttest_report_ctx_t *report,
const cyaml_config_t *config)
{
static const struct target_struct {
int value;
} data = {
.value = 9,
};
static const struct cyaml_schema_field mapping_schema[] = {
{
.key = "key",
.value = {
.type = CYAML_INT,
.flags = CYAML_FLAG_DEFAULT,
.data_size = 0,
},
.data_offset = offsetof(struct target_struct, value),
},
CYAML_FIELD_END
};
static const struct cyaml_schema_value top_schema = {
CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER,
struct target_struct, mapping_schema),
};
test_data_t td = {
.config = config,
};
cyaml_err_t err;
ttest_ctx_t tc;
if (!ttest_start(report, __func__, cyaml_cleanup, &td, &tc)) {
return true;
}
err = cyaml_save_file("build/save.yaml", config, &top_schema, &data, 0);
if (err != CYAML_ERR_INVALID_DATA_SIZE) {
return ttest_fail(&tc, cyaml_strerror(err));
}
return ttest_pass(&tc);
}
/**
* Run the YAML file tests.
*
* \param[in] rc The ttest report context.
* \param[in] log_level CYAML log level.
* \param[in] log_fn CYAML logging function, or NULL.
* \return true iff all unit tests pass, otherwise false.
*/
bool file_tests(
ttest_report_ctx_t *rc,
cyaml_log_t log_level,
cyaml_log_fn_t log_fn)
{
bool pass = true;
cyaml_config_t config = {
.log_fn = log_fn,
.mem_fn = cyaml_mem,
.log_level = log_level,
.flags = CYAML_CFG_DEFAULT,
};
ttest_heading(rc, "File loading tests");
pass &= test_file_load_basic(rc, &config);
pass &= test_file_load_save_basic(rc, &config);
/* Since we expect loads of error logging for these tests,
* suppress log output if required log level is greater
* than \ref CYAML_LOG_INFO.
*/
if (log_level > CYAML_LOG_INFO) {
config.log_fn = NULL;
}
pass &= test_file_load_bad_path(rc, &config);
pass &= test_file_save_bad_path(rc, &config);
pass &= test_file_load_basic_invalid(rc, &config);
pass &= test_file_save_basic_invalid(rc, &config);
return pass;
}