Add libcyaml

This commit is contained in:
Joseph Ferano 2023-10-16 18:20:08 +07:00
parent 3f1e47b054
commit 286eca58d6
39 changed files with 29590 additions and 0 deletions

1
libs/libcyaml/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build/

114
libs/libcyaml/CHANGES.md Normal file
View File

@ -0,0 +1,114 @@
LibCYAML: Change Log
====================
## LibCYAML v1.4.1
* **Loading**:
* Log valid enum values on error parsing enums.
* Split out storage of scaler values to client data structure.
* **General**:
* Updated code coverage CI to latest action versions.
No changes are required for client applications to upgrade.
## LibCYAML v1.4.0
* **Loading**:
* Reject numerical values with trailing junk.
* New `CYAML_CFG_IGNORED_KEY_WARNING` to generate warnings when mapping
keys are ignored either explicitly by the `CYAML_IGNORE` type or implicitly
by the `CYAML_CFG_IGNORE_UNKNOWN_KEYS` config setting.
* **Buildsystem**:
* Avoid using GNU `-D` install flag.
* **General**:
* Improved API documentation for `CYAML_SEQUENCE_FIXED`.
* Minor optimisation for host endian detection for older GCCs.
* Minor CI updates.
No changes are required for client applications to upgrade.
## LibCYAML v1.3.1
* **Loading**:
* Fixed value out-of-range detection limits for signed integers.
No changes are required for client applications to upgrade.
## LibCYAML v1.3.0
* **Saving**:
* New flags allow control over scalar output style.
- For example to force single or double quote style.
* **General**:
* Buildsystem changes to allow use of CPPFLAGS from the environment.
No changes are required for client applications to upgrade.
## LibCYAML v1.2.1
* **General**:
* Support for dynamic library build on Mac OS X.
* Ordered designated initialisers in public header for C++ compatibility.
No changes are required for client applications to upgrade.
## LibCYAML v1.2.0
* **Loading**:
* Allow mappings with zero fields in the schema.
* Improved logging of errors.
* `CYAML_BOOL` type now treats "Off" as false.
* Allow loading of float values that overflow or underflow unless
`CYAML_FLAG_STRICT` set.
* Added line and column numbers to backtraces.
* **General**:
* Update tests to handle libyaml 0.2.5 output format change.
* Buildsystem improvements.
* Made public header C++ compatible.
* Test runner supports running individual tests.
No changes are required for client applications to upgrade.
## LibCYAML v1.1.0
* **Loading**:
* Significantly optimised handling of aliases and anchors.
* Fixed handling of duplicate mapping keys.
* **Saving**:
* Increased precision for double precision floating point values.
* **General**:
* Fixed data handling on big endian systems.
No changes are required for client applications to upgrade.
## LibCYAML v1.0.2
* **Loading**:
* Fixed invalid read on error path for bitfield handling.
* **Buildsystem**:
* Fixed to link against libraries after listing objects.
* Added `check` target as alias for `test`.
No changes are required for client applications to upgrade.
## LibCYAML v1.0.1
* **Loading**:
* Fixed mapping and sequence values with `CYAML_FLAG_POINTER_NULL`.
* **Buildsystem**:
* Installation: Explicitly create leading directories.
No changes are required for client applications to upgrade.
## LibCYAML v1.0.0
* Initial release.

14
libs/libcyaml/LICENSE Normal file
View File

@ -0,0 +1,14 @@
Copyright (c) 2017-2021, Michael Drake
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.

209
libs/libcyaml/Makefile Normal file
View File

@ -0,0 +1,209 @@
# SPDX-License-Identifier: ISC
#
# Copyright (C) 2017-2023 Michael Drake <tlsa@netsurf-browser.org>
# CYAML's versioning is <MAJOR>.<MINOR>.<PATCH>[-DEVEL]
# The main branch will usually be DEVEL. The release process is:
# 0. Create branch for release.
# 1. Update the CHANGES.md file.
# 2. Set MAJOR, MINOR, and PATCH versions appropriately for changes.
# 3. Create PR, review, and merge to main.
# 4. Set VESION_DEVEL to 0, commit to main.
# 5. Tag the release: `git tag -a vN.N.N -m "libcyaml N.N.N"`
# 6. Set VESION_DEVEL to 1, commit to main.
VERSION_MAJOR = 1
VERSION_MINOR = 4
VERSION_PATCH = 1
VERSION_DEVEL = 0
VERSION_STR = $(VERSION_MAJOR).$(VERSION_MINOR).$(VERSION_PATCH)
# Default variant depends on whether it's a development build.
ifeq ($(VERSION_DEVEL), 1)
VARIANT = debug
else
VARIANT = release
endif
# Unfortunately ASan is incompatible with valgrind, so we have a special
# variant for running with sanitisers.
VALID_VARIANTS := release debug san
ifneq ($(filter $(VARIANT),$(VALID_VARIANTS)),)
else
$(error VARIANT must be 'debug' (default), 'san', or 'release')
endif
UNAME_S := $(shell uname -s)
LIB_NAME = libcyaml
LIB_PKGCON = $(LIB_NAME).pc
LIB_STATIC = $(LIB_NAME).a
LIB_SHARED = $(LIB_NAME).so
LIB_SH_MAJ = $(LIB_SHARED).$(VERSION_MAJOR)
LIB_SH_VER = $(LIB_SHARED).$(VERSION_STR)
.IMPLICIT =
PREFIX ?= /usr/local
LIBDIR ?= lib
INCLUDEDIR ?= include
Q ?= @
CC ?= gcc
AR ?= ar
MKDIR = mkdir -p
INSTALL ?= install -c
VALGRIND = valgrind --leak-check=full --track-origins=yes
VERSION_FLAGS = -DVERSION_MAJOR=$(VERSION_MAJOR) \
-DVERSION_MINOR=$(VERSION_MINOR) \
-DVERSION_PATCH=$(VERSION_PATCH) \
-DVERSION_DEVEL=$(VERSION_DEVEL)
LIBYAML = yaml-0.1
LIBYAML_CFLAGS := $(if $(PKG_CONFIG),$(shell $(PKG_CONFIG) --cflags $(LIBYAML)),)
LIBYAML_LIBS := $(if $(PKG_CONFIG),$(shell $(PKG_CONFIG) --libs $(LIBYAML)),-lyaml)
INCLUDE = -I include
CPPFLAGS += $(VERSION_FLAGS) -MMD -MP
CFLAGS += $(INCLUDE) $(LIBYAML_CFLAGS)
CFLAGS += -std=c11 -Wall -Wextra -pedantic \
-Wconversion -Wwrite-strings -Wcast-align -Wpointer-arith \
-Winit-self -Wshadow -Wstrict-prototypes -Wmissing-prototypes \
-Wredundant-decls -Wundef -Wvla -Wdeclaration-after-statement
LDFLAGS += $(LIBYAML_LIBS)
LDFLAGS_SHARED = -Wl,-soname=$(LIB_SH_MAJ) -shared
ifeq ($(VARIANT), debug)
CFLAGS += -O0 -g
else ifeq ($(VARIANT), san)
CFLAGS += -O0 -g -fsanitize=address -fsanitize=undefined -fno-sanitize-recover
LDFLAGS += -fsanitize=address -fsanitize=undefined -fno-sanitize-recover
else
CFLAGS += -O2 -DNDEBUG
endif
ifneq ($(filter coverage,$(MAKECMDGOALS)),)
BUILDDIR = build/coverage/$(VARIANT)
CFLAGS_COV = --coverage -DNDEBUG
LDFLAGS_COV = --coverage
else
BUILDDIR = build/$(VARIANT)
CFLAGS_COV =
LDFLAGS_COV =
endif
BUILDDIR_SHARED = $(BUILDDIR)/shared
BUILDDIR_STATIC = $(BUILDDIR)/static
LIB_SRC_FILES = mem.c free.c load.c save.c util.c utf8.c
LIB_SRC := $(addprefix src/,$(LIB_SRC_FILES))
LIB_OBJ = $(patsubst %.c,%.o, $(addprefix $(BUILDDIR)/,$(LIB_SRC)))
LIB_DEP = $(patsubst %.c,%.d, $(addprefix $(BUILDDIR)/,$(LIB_SRC)))
LIB_OBJ_SHARED = $(patsubst $(BUILDDIR)%,$(BUILDDIR_SHARED)%,$(LIB_OBJ))
LIB_OBJ_STATIC = $(patsubst $(BUILDDIR)%,$(BUILDDIR_STATIC)%,$(LIB_OBJ))
LIB_DEP_SHARED = $(patsubst $(BUILDDIR)%,$(BUILDDIR_SHARED)%,$(LIB_DEP))
LIB_DEP_STATIC = $(patsubst $(BUILDDIR)%,$(BUILDDIR_STATIC)%,$(LIB_DEP))
LIB_PATH = LD_LIBRARY_PATH=$(BUILDDIR)
ifeq ($(UNAME_S),Darwin)
LIB_SHARED = $(LIB_NAME).dylib
LIB_SH_MAJ = $(LIB_NAME).$(VERSION_MAJOR).dylib
LIB_SH_VER = $(LIB_NAME).$(VERSION_STR).dylib
LDFLAGS_SHARED = -dynamiclib -install_name "$(LIB_SH_MAJ)" -current_version $(VERSION_STR)
LIB_PATH = DYLD_FALLBACK_LIBRARY_PATH=$(BUILDDIR)
endif
TEST_SRC_FILES = units/free.c units/load.c units/test.c units/util.c \
units/errs.c units/file.c units/save.c units/utf8.c
TEST_SRC := $(addprefix test/,$(TEST_SRC_FILES))
TEST_OBJ = $(patsubst %.c,%.o, $(addprefix $(BUILDDIR)/,$(TEST_SRC)))
TEST_DEP = $(patsubst %.c,%.d, $(addprefix $(BUILDDIR)/,$(TEST_SRC)))
TEST_BINS = \
$(BUILDDIR)/test/units/cyaml-shared \
$(BUILDDIR)/test/units/cyaml-static
all: $(BUILDDIR)/$(LIB_SH_MAJ) $(BUILDDIR)/$(LIB_STATIC) examples
coverage: test-verbose
$(Q)$(MKDIR) $(BUILDDIR)
$(Q)gcovr -e 'test/.*' -r .
$(Q)gcovr -e 'test/.*' -x -o build/coverage.xml -r .
$(Q)gcovr -e 'test/.*' --html --html-details -o build/coverage.html -r .
test test-quiet test-verbose test-debug: $(TEST_BINS)
$(Q)for i in $(^); do $(LIB_PATH) $$i $(subst test,,$(subst test-,--,$@)) "$(TESTLIST)" || exit; done
valgrind valgrind-quiet valgrind-verbose valgrind-debug: $(TEST_BINS)
$(Q)for i in $(^); do $(LIB_PATH) $(VALGRIND) $$i $(subst valgrind,,$(subst valgrind-,--,$@)) "$(TESTLIST)" || exit; done
check: test
$(BUILDDIR)/$(LIB_PKGCON): $(LIB_PKGCON).in
sed \
-e 's#PREFIX#$(PREFIX)#' \
-e 's#LIBDIR#$(LIBDIR)#' \
-e 's#INCLUDEDIR#$(INCLUDEDIR)#' \
-e 's#VERSION#$(VERSION_STR)#' \
$(LIB_PKGCON).in >$(BUILDDIR)/$(LIB_PKGCON)
$(BUILDDIR)/$(LIB_STATIC): $(LIB_OBJ_STATIC)
$(AR) -rcs $@ $^
$(BUILDDIR)/$(LIB_SH_MAJ): $(LIB_OBJ_SHARED)
$(CC) -o $@ $^ $(LDFLAGS) $(LDFLAGS_COV) $(LDFLAGS_SHARED)
$(LIB_OBJ_STATIC): $(BUILDDIR_STATIC)/%.o : %.c
$(Q)$(MKDIR) $(dir $@)
$(CC) $(CPPFLAGS) $(CFLAGS) $(CFLAGS_COV) -c -o $@ $<
$(LIB_OBJ_SHARED): $(BUILDDIR_SHARED)/%.o : %.c
$(Q)$(MKDIR) $(dir $@)
$(CC) $(CPPFLAGS) $(CFLAGS) -fPIC $(CFLAGS_COV) -c -o $@ $<
docs:
$(MKDIR) build/docs/api
$(MKDIR) build/docs/devel
doxygen docs/api.doxygen.conf
doxygen docs/devel.doxygen.conf
clean:
rm -rf build/
install: $(BUILDDIR)/$(LIB_SH_MAJ) $(BUILDDIR)/$(LIB_STATIC) $(BUILDDIR)/$(LIB_PKGCON)
$(INSTALL) -d $(DESTDIR)$(PREFIX)/$(LIBDIR)
$(INSTALL) $(BUILDDIR)/$(LIB_SH_MAJ) $(DESTDIR)$(PREFIX)/$(LIBDIR)/$(LIB_SH_VER)
(cd $(DESTDIR)$(PREFIX)/$(LIBDIR) && { ln -s -f $(LIB_SH_VER) $(LIB_SH_MAJ) || { rm -f $(LIB_SH_MAJ) && ln -s $(LIB_SH_VER) $(LIB_SH_MAJ); }; })
(cd $(DESTDIR)$(PREFIX)/$(LIBDIR) && { ln -s -f $(LIB_SH_VER) $(LIB_SHARED) || { rm -f $(LIB_SHARED) && ln -s $(LIB_SH_VER) $(LIB_SHARED); }; })
$(INSTALL) $(BUILDDIR)/$(LIB_STATIC) $(DESTDIR)$(PREFIX)/$(LIBDIR)/$(LIB_STATIC)
chmod 644 $(DESTDIR)$(PREFIX)/$(LIBDIR)/$(LIB_STATIC)
$(INSTALL) -d $(DESTDIR)$(PREFIX)/$(INCLUDEDIR)/cyaml
$(INSTALL) -m 644 include/cyaml/* $(DESTDIR)$(PREFIX)/$(INCLUDEDIR)/cyaml
$(INSTALL) -d $(DESTDIR)$(PREFIX)/$(LIBDIR)/pkgconfig
$(INSTALL) -m 644 $(BUILDDIR)/$(LIB_PKGCON) $(DESTDIR)$(PREFIX)/$(LIBDIR)/pkgconfig/$(LIB_PKGCON)
examples: $(BUILDDIR)/planner $(BUILDDIR)/numerical
$(BUILDDIR)/planner: examples/planner/main.c $(BUILDDIR)/$(LIB_STATIC)
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $^ $(LDFLAGS)
$(BUILDDIR)/numerical: examples/numerical/main.c $(BUILDDIR)/$(LIB_STATIC)
$(CC) $(CPPFLAGS) $(CFLAGS) -o $@ $^ $(LDFLAGS)
-include $(LIB_DEP_SHARED) $(LIB_DEP_STATIC) $(TEST_DEP)
.PHONY: all test test-quiet test-verbose test-debug \
valgrind valgrind-quiet valgrind-verbose valgrind-debug \
clean coverage docs install examples check
$(BUILDDIR)/test/units/cyaml-static: $(TEST_OBJ) $(BUILDDIR)/$(LIB_STATIC)
$(CC) $(LDFLAGS_COV) -o $@ $^ $(LDFLAGS)
$(BUILDDIR)/test/units/cyaml-shared: $(TEST_OBJ) $(BUILDDIR)/$(LIB_SH_MAJ)
$(CC) $(LDFLAGS_COV) -o $@ $^ $(LDFLAGS)
$(TEST_OBJ): $(BUILDDIR)/%.o : %.c
$(Q)$(MKDIR) $(dir $@)
$(CC) $(CPPFLAGS) $(CFLAGS) $(CFLAGS_COV) -c -o $@ $<

112
libs/libcyaml/README.md Normal file
View File

@ -0,0 +1,112 @@
LibCYAML: Schema-based YAML parsing and serialisation
=====================================================
[![Build Status](https://github.com/tlsa/libcyaml/workflows/CI/badge.svg)](https://github.com/tlsa/libcyaml/actions) [![Static Analysis](https://github.com/tlsa/libcyaml/actions/workflows/static-analysis.yaml/badge.svg)](https://github.com/tlsa/libcyaml/actions/workflows/static-analysis.yaml) [![Code Coverage](https://codecov.io/gh/tlsa/libcyaml/branch/main/graph/badge.svg)](https://codecov.io/gh/tlsa/libcyaml)
LibCYAML is a C library for reading and writing structured YAML documents.
It is written in ISO C11 and licensed under the ISC licence.
Overview
--------
The fundamental idea behind CYAML is to allow applications to construct
schemas which describe both the permissible structure of the YAML documents
to read/write, and the C data structure(s) in which the loaded data is
arranged in memory.
### Goals
* Make it easy to load YAML into client's custom C data structures.
* Good compromise between flexibility and simplicity.
### Features
* Load, Save and Free functions.
* Reads and writes arbitrary client data structures.
* Schema-driven, allowing control over permitted YAML, for example:
- Required / optional mapping fields.
- Allowed / disallowed values.
- Minimum / maximum sequence entry count.
- etc...
* Enumerations and flag words.
* YAML backtraces make it simple for users to fix their YAML to
conform to your schema.
* Uses standard [`libyaml`](https://github.com/yaml/libyaml) library for
low-level YAML read / write.
* Support for YAML aliases and anchors.
Building
--------
To build the library, run:
make
You can control the optimisation and building of asserts by setting
the build variant:
make VARIANT=debug
make VARIANT=release
Another debug build variant which is built with address sanitiser (incompatible
with valgrind) can be built with:
make VARIANT=san
Installation
------------
To install a release version of the library, run:
make install VARIANT=release
It will install to the PREFIX `/usr/local` by default, and it will use
DESTDIR and PREFIX from the environment if set.
Testing
-------
To run the tests, run any of the following, which generate various
levels of output verbosity (optionally setting `VARIANT=release`, or
`VARIANT=san`):
make test
make test-quiet
make test-verbose
make test-debug
To run the tests under `valgrind`, a similar set of targets is available:
make valgrind
make valgrind-quiet
make valgrind-verbose
make valgrind-debug
To run a single test or a subset of tests, use the `TESTLIST` variable, which
expects a space and/or comma separated list of test names:
make test-debug TESTLIST=test_load_mapping_without_any_fields
make valgrind-debug TESTLIST="test_load_no_log test_util_state_invalid"
To generate a test coverage report, `gcovr` is required:
make coverage
Documentation
-------------
To generate both public API documentation, and documentation of CYAML's
internals, `doxygen` is required:
make docs
Alternatively, the read the API documentation directly from the
[cyaml.h](https://github.com/tlsa/libcyaml/blob/main/include/cyaml/cyaml.h)
header file.
There is also a [tutorial](docs/guide.md).
Examples
--------
In addition to the documentation, you can study the [examples](examples/).

View File

@ -0,0 +1,15 @@
# SPDX-License-Identifier: ISC
# This generates documentation for developers using libcyaml.
PROJECT_NAME = "CYAML"
OUTPUT_DIRECTORY = "build/docs/api"
OPTIMIZE_OUTPUT_FOR_C = YES
EXTRACT_STATIC = NO
MARKDOWN_SUPPORT = YES
INPUT = include README.md docs/guide.md
RECURSIVE = YES
USE_MDFILE_AS_MAINPAGE = README.md
GENERATE_HTML = YES
GENERATE_LATEX = NO
QUIET = YES

View File

@ -0,0 +1,15 @@
# SPDX-License-Identifier: ISC
# This generates documentation for developers developing libcyaml.
PROJECT_NAME = "CYAML Internals"
OUTPUT_DIRECTORY = "build/docs/devel"
OPTIMIZE_OUTPUT_FOR_C = YES
EXTRACT_STATIC = YES
MARKDOWN_SUPPORT = YES
INPUT = include src README.md docs/guide.md
RECURSIVE = YES
USE_MDFILE_AS_MAINPAGE = README.md
GENERATE_HTML = YES
GENERATE_LATEX = NO
QUIET = YES

160
libs/libcyaml/docs/guide.md Normal file
View File

@ -0,0 +1,160 @@
LibCYAML: Tutorial
==================
This document is intended for C developers wishing to make use of
[LibCYAML](https://github.com/tlsa/libcyaml).
Overview
--------
If you want to use LibCYAML you'll need to have two things:
1. A consistent structure to the sort of YAML you want to load/save.
2. Some C data structure to load/save to/from.
LibCYAML's aim is to make this as simple as possible for the programmer.
However, LibCYAML knows nothing about either your data structure or the
"shape" of the YAML you want to load. You provide this information by
defining "schemas", and passing them to LibCYAML.
> **Note**: If you need to handle arbitrary "free-form" YAML (e.g. for a tool
> to convert between YAML and JSON), then LibCYAML would not be much help.
> In such a case, I'd recommend using [libyaml](https://github.com/yaml/libyaml)
> directly.
A simple example: loading YAML
------------------------------
Let's say you want to load the following YAML document:
```yaml
name: Fibonacci
data:
- 1
- 1
- 2
- 3
- 5
- 8
```
And you want to load it into the following C data structure:
```c
struct numbers {
char *name;
int *data;
unsigned data_count;
};
```
Then we need to define a CYAML schema to describe these to LibCYAML.
> **Note**: Use the doxygen API documentation, or else the documentation in
> [cyaml.h](https://github.com/tlsa/libcyaml/blob/main/include/cyaml/cyaml.h)
> in conjunction with this guide.
At the top level of the YAML is a mapping with two fields, "name" and
"data".
```yaml
name:
data:
```
The first field is just a simple scalar value (it's neither
a mapping nor a sequence). The second field has a sequence value.
We'll start by defining the CYAML schema for the "data" sequence,
since since that's the "deepest" non-scalar type. The reason for
starting here will become clear later.
```c
/* CYAML value schema for entries of the data sequence. */
static const cyaml_schema_value_t data_entry = {
CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int),
};
```
Here we're making a `cyaml_schema_value_t` for the entries in the
sequence. There are various `CYAML_VALUE_{TYPE}` macros to assist with
this. Here we're using `CYAML_VALUE_INT`, because the value is a signed
integer. The parameters passed to the macro are `enum cyaml_flag`, and
the C data type of the value.
Now we can write the schema for the mapping. First we'll construct
an array of `cyaml_schema_field_t` entries that describe each
field in the mapping.
```c
/* CYAML mapping schema fields array for the top level mapping. */
static const cyaml_schema_field_t top_mapping_schema[] = {
CYAML_FIELD_STRING_PTR(
"name", CYAML_FLAG_POINTER, struct numbers, name, 0, CYAML_UNLIMITED),
CYAML_FIELD_SEQUENCE(
"data", CYAML_FLAG_POINTER, struct numbers, data, &data_entry, 0, CYAML_UNLIMITED),
CYAML_FIELD_END
};
```
There are `CYAML_FIELD_{TYPE}` helper macros to construct the mapping field
entries. The array must be terminated by a `CYAML_FIELD_END` entry.
The helper macro parameters are specific to each `CYAML_FIELD_{TYPE}` macro.
The entry for the name field is of type string pointer. You can consult the
documentation for the `CYAML_FIELD_{TYPE}` macros to see what the parameters
mean.
> **Note**: The field for the sequence takes a pointer to the sequence entry
> data type that we defined earlier as `data_entry`.
Finally we can define the schema for the top level value that gets passed to
the LibCYAML.
```c
/* CYAML value schema for the top level mapping. */
static const cyaml_schema_value_t top_schema = {
CYAML_VALUE_MAPPING(
CYAML_FLAG_POINTER, struct numbers, top_mapping_schema),
};
```
In this case our top level value is a mapping type. One of the parameters
needed for mappings is the array of field definitions. In this case we're
passing the `top_mapping_schema` that we defined above.
```c
/* Create our CYAML configuration. */
static const cyaml_config_t config = {
.log_fn = cyaml_log, /* Use the default logging function. */
.mem_fn = cyaml_mem, /* Use the default memory allocator. */
.log_level = CYAML_LOG_WARNING, /* Logging errors and warnings only. */
};
/* Where to store the loaded data */
struct numbers *n;
/* Load the file into n */
cyaml_err_t err = cyaml_load_file(argv[ARG_PATH_IN], &config,
&top_schema, (cyaml_data_t **)&n, NULL);
if (err != CYAML_OK) {
/* Handle error */
}
/* Use the data. */
printf("%s:\n", n->name);
for (unsigned i = 0; i < n->data_count; i++) {
printf(" - %i\n", n->data[i]);
}
/* Free the data */
err = cyaml_free(&config, &top_schema, n, 0);
if (err != CYAML_OK) {
/* Handle error */
}
```
And that's it, the YAML is loaded into the custom C data structure.
You can find the code for this in the "numerical" example in the
[examples](../examples) directory.

View File

@ -0,0 +1,7 @@
Simple LibCYAML example
=======================
This has a simple YAML document for the Fibonacci sequence, and an example
LibCYAML binding to load/free the data from C.
The [tutorial](../../docs/guide.md) explains how this works.

View File

@ -0,0 +1,8 @@
name: Fibonacci
data:
- 1
- 1
- 2
- 3
- 5
- 8

View File

@ -0,0 +1,107 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (C) 2018 Michael Drake <tlsa@netsurf-browser.org>
*/
#include <stdlib.h>
#include <stdio.h>
#include <cyaml/cyaml.h>
/******************************************************************************
* C data structure for storing a project plan.
*
* This is what we want to load the YAML into.
******************************************************************************/
/* Structure for storing numerical sequence */
struct numbers {
char *name;
int *data;
unsigned data_count;
};
/******************************************************************************
* CYAML schema to tell libcyaml about both expected YAML and data structure.
*
* (Our CYAML schema is just a bunch of static const data.)
******************************************************************************/
/* CYAML value schema for entries of the data sequence. */
static const cyaml_schema_value_t data_entry = {
CYAML_VALUE_INT(CYAML_FLAG_DEFAULT, int),
};
/* CYAML mapping schema fields array for the top level mapping. */
static const cyaml_schema_field_t top_mapping_schema[] = {
CYAML_FIELD_STRING_PTR("name", CYAML_FLAG_POINTER,
struct numbers, name,
0, CYAML_UNLIMITED),
CYAML_FIELD_SEQUENCE("data", CYAML_FLAG_POINTER,
struct numbers, data, &data_entry,
0, CYAML_UNLIMITED),
CYAML_FIELD_END
};
/* CYAML value schema for the top level mapping. */
static const cyaml_schema_value_t top_schema = {
CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER,
struct numbers, top_mapping_schema),
};
/******************************************************************************
* Actual code to load and save YAML doc using libcyaml.
******************************************************************************/
/* Our CYAML config.
*
* If you want to change it between calls, don't make it const.
*
* Here we have a very basic config.
*/
static const cyaml_config_t config = {
.log_fn = cyaml_log, /* Use the default logging function. */
.mem_fn = cyaml_mem, /* Use the default memory allocator. */
.log_level = CYAML_LOG_WARNING, /* Logging errors and warnings only. */
};
/* Main entry point from OS. */
int main(int argc, char *argv[])
{
cyaml_err_t err;
struct numbers *n;
enum {
ARG_PROG_NAME,
ARG_PATH_IN,
ARG__COUNT,
};
/* Handle args */
if (argc != ARG__COUNT) {
fprintf(stderr, "Usage:\n");
fprintf(stderr, " %s <INPUT>\n", argv[ARG_PROG_NAME]);
return EXIT_FAILURE;
}
/* Load input file. */
err = cyaml_load_file(argv[ARG_PATH_IN], &config,
&top_schema, (cyaml_data_t **)&n, NULL);
if (err != CYAML_OK) {
fprintf(stderr, "ERROR: %s\n", cyaml_strerror(err));
return EXIT_FAILURE;
}
/* Use the data. */
printf("%s:\n", n->name);
for (unsigned i = 0; i < n->data_count; i++) {
printf(" - %i\n", n->data[i]);
}
/* Free the data */
cyaml_free(&config, &top_schema, n, 0);
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,8 @@
Simple LibCYAML example
=======================
This has a simple YAML document for project planning, and an example
LibCYAML binding to load/modify/save/free the data from C.
The C source code is heavliy documented to help explain how to
write a CYAML schema data structure.

View File

@ -0,0 +1,468 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (C) 2019 Michael Drake <tlsa@netsurf-browser.org>
*/
#include <stdlib.h>
#include <stdio.h>
#include <cyaml/cyaml.h>
/******************************************************************************
* C data structure for storing a project plan.
*
* This is what we want to load the YAML into.
******************************************************************************/
/* Enumeration of months of the year */
enum months {
MONTH_JAN = 1,
MONTH_FEB,
MONTH_MAR,
MONTH_APR,
MONTH_MAY,
MONTH_JUN,
MONTH_JUL,
MONTH_AUG,
MONTH_SEP,
MONTH_OCT,
MONTH_NOV,
MONTH_DEC
};
/* Structure for storing dates */
struct date {
uint8_t day;
enum months month;
uint16_t year;
};
/* Structure for storing durations */
struct duration {
uint8_t hours;
unsigned days;
unsigned weeks;
unsigned years;
};
/* Enumeration of task flags */
enum task_flags {
FLAGS_NONE = 0,
FLAGS_IMPORTANT = (1 << 0),
FLAGS_ENGINEERING = (1 << 1),
FLAGS_DOCUMENTATION = (1 << 2),
FLAGS_MANAGEMENT = (1 << 3),
};
/* Structure for storing a task */
struct task {
const char *name;
enum task_flags flags;
struct duration estimate;
const char **depends;
unsigned depends_count;
const char **people;
unsigned n_people;
};
/* Top-level structure for storing a plan */
struct plan {
const char *name;
struct date *start;
const char **people;
unsigned n_people;
struct task *tasks;
uint64_t tasks_count;
};
/******************************************************************************
* CYAML schema to tell libcyaml about both expected YAML and data structure.
*
* (Our CYAML schema is just a bunch of static const data.)
******************************************************************************/
/* Mapping from "task_flags" strings to enum values for schema. */
static const cyaml_strval_t task_flags_strings[] = {
{ "None", FLAGS_NONE },
{ "Important", FLAGS_IMPORTANT },
{ "Engineering", FLAGS_ENGINEERING },
{ "Documentation", FLAGS_DOCUMENTATION },
{ "Management", FLAGS_MANAGEMENT },
};
/* Mapping from "month" strings to flag values for schema. */
static const cyaml_strval_t month_strings[] = {
{ "January", MONTH_JAN },
{ "February", MONTH_FEB },
{ "March", MONTH_MAR },
{ "April", MONTH_APR },
{ "May", MONTH_MAY },
{ "June", MONTH_JUN },
{ "July", MONTH_JUL },
{ "August", MONTH_AUG },
{ "September", MONTH_SEP },
{ "October", MONTH_OCT },
{ "November", MONTH_NOV },
{ "December", MONTH_DEC },
};
/* Schema for string pointer values (used in sequences of strings). */
static const cyaml_schema_value_t string_ptr_schema = {
CYAML_VALUE_STRING(CYAML_FLAG_POINTER, char, 0, CYAML_UNLIMITED),
};
/* The duration mapping's field definitions schema is an array.
*
* All the field entries will refer to their parent mapping's structure,
* in this case, `struct duration`.
*/
static const cyaml_schema_field_t duration_fields_schema[] = {
/* The fields here are all optional unsigned integers.
*
* Note that if an optional field is unset in the YAML, its value
* will be zero in the C data structure.
*
* In all cases here, the YAML mapping key name (first parameter to
* the macros) matches the name of the associated member of the
* `duration` structure (fourth parameter).
*/
CYAML_FIELD_UINT(
"hours", CYAML_FLAG_OPTIONAL,
struct duration, hours),
CYAML_FIELD_UINT(
"days", CYAML_FLAG_OPTIONAL,
struct duration, days),
CYAML_FIELD_UINT(
"weeks", CYAML_FLAG_OPTIONAL,
struct duration, weeks),
CYAML_FIELD_UINT(
"years", CYAML_FLAG_OPTIONAL,
struct duration, years),
/* The field array must be terminated by an entry with a NULL key.
* Here we use the CYAML_FIELD_END macro for that. */
CYAML_FIELD_END
};
/* The date mapping's field definitions schema is an array.
*
* All the field entries will refer to their parent mapping's structure,
* in this case, `struct date`.
*/
static const cyaml_schema_field_t date_fields_schema[] = {
/* The "day" and "year" fields are just normal UNIT CYAML types.
* See the comments for duration_fields_schema for details.
* The only difference is neither of these are optional.
* Note: The order of the fields in this array doesn't matter.
*/
CYAML_FIELD_UINT(
"day", CYAML_FLAG_DEFAULT,
struct date, day),
CYAML_FIELD_UINT(
"year", CYAML_FLAG_DEFAULT,
struct date, year),
/* The month field is an enum.
*
* YAML key: "month".
* C structure member for this key: "month".
* Array mapping strings to values: month_strings
*
* Its CYAML type is ENUM, so an array of cyaml_strval_t must be
* provided to map from string to values.
* Note that we're not setting the strict flag here so both strings and
* numbers are accepted in the YAML. (For example, both "4" and "April"
* would be accepted.)
*/
CYAML_FIELD_ENUM(
"month", CYAML_FLAG_DEFAULT,
struct date, month, month_strings,
CYAML_ARRAY_LEN(month_strings)),
/* The field array must be terminated by an entry with a NULL key.
* Here we use the CYAML_FIELD_END macro for that. */
CYAML_FIELD_END
};
/* The task mapping's field definitions schema is an array.
*
* All the field entries will refer to their parent mapping's structure,
* in this case, `struct task`.
*/
static const cyaml_schema_field_t task_fields_schema[] = {
/* The first field in the mapping is a task name.
*
* YAML key: "name".
* C structure member for this key: "name".
*
* Its CYAML type is string pointer, and we have no minimum or maximum
* string length constraints.
*/
CYAML_FIELD_STRING_PTR(
"name", CYAML_FLAG_POINTER,
struct task, name, 0, CYAML_UNLIMITED),
/* The flags field is a flags value.
*
* YAML key: "flags".
* C structure member for this key: "flags".
* Array mapping strings to values: task_flags_strings
*
* In the YAML a CYAML flags value should be a sequence of scalars.
* The values of each set scalar is looked up the in array of
* string/value mappings, and the values are bitwise ORed together.
*
* Note that we're setting the strict flag here so only strings
* present in task_flags_strings are allowed, and numbers are not.
*
* We make the field optional so when there are no flags set, the field
* can be omitted from the YAML.
*/
CYAML_FIELD_FLAGS(
"flags", CYAML_FLAG_OPTIONAL | CYAML_FLAG_STRICT,
struct task, flags, task_flags_strings,
CYAML_ARRAY_LEN(task_flags_strings)),
/* The next field is the task estimate.
*
* YAML key: "estimate".
* C structure member for this key: "estimate".
*
* Its CYAML type is a mapping.
*
* Since it's a mapping type, the schema for its mapping's fields must
* be provided too. In this case, it's `duration_fields_schema`.
*/
CYAML_FIELD_MAPPING(
"estimate", CYAML_FLAG_DEFAULT,
struct task, estimate, duration_fields_schema),
/* The next field is the tasks that this task depends on.
*
* YAML key: "depends".
* C structure member for this key: "depends".
*
* Its CYAML type is a sequence.
*
* Since it's a sequence type, the value schema for its entries must
* be provided too. In this case, it's string_ptr_schema.
*
* Since it's not a sequence of a fixed-length, we must tell CYAML
* where the sequence entry count is to be stored. In this case, it
* goes in the "depends_count" C structure member in `struct task`.
* Since this is "depends" with the "_count" postfix, we can use
* the following macro, which assumes a postfix of "_count" in the
* struct member name.
*/
CYAML_FIELD_SEQUENCE(
"depends", CYAML_FLAG_POINTER | CYAML_FLAG_OPTIONAL,
struct task, depends,
&string_ptr_schema, 0, CYAML_UNLIMITED),
/* The next field is the task people.
*
* YAML key: "people".
* C structure member for this key: "people".
*
* Its CYAML type is a sequence.
*
* Since it's a sequence type, the value schema for its entries must
* be provided too. In this case, it's string_ptr_schema.
*
* Since it's not a sequence of a fixed-length, we must tell CYAML
* where the sequence entry count is to be stored. In this case, it
* goes in the "n_people" C structure member in `struct plan`.
*/
CYAML_FIELD_SEQUENCE_COUNT(
"people", CYAML_FLAG_POINTER | CYAML_FLAG_OPTIONAL,
struct task, people, n_people,
&string_ptr_schema, 0, CYAML_UNLIMITED),
/* The field array must be terminated by an entry with a NULL key.
* Here we use the CYAML_FIELD_END macro for that. */
CYAML_FIELD_END
};
/* The value for a task is a mapping.
*
* Its fields are defined in task_fields_schema.
*/
static const cyaml_schema_value_t task_schema = {
CYAML_VALUE_MAPPING(CYAML_FLAG_DEFAULT,
struct task, task_fields_schema),
};
/* The plan mapping's field definitions schema is an array.
*
* All the field entries will refer to their parent mapping's structure,
* in this case, `struct plan`.
*/
static const cyaml_schema_field_t plan_fields_schema[] = {
/* The first field in the mapping is a project name.
*
* YAML key: "project".
* C structure member for this key: "name".
*
* Its CYAML type is string pointer, and we have no minimum or maximum
* string length constraints.
*/
CYAML_FIELD_STRING_PTR(
"project", CYAML_FLAG_POINTER,
struct plan, name, 0, CYAML_UNLIMITED),
/* The next field is the project start date.
*
* YAML key: "start".
* C structure member for this key: "start".
*
* Its CYAML type is a mapping pointer.
*
* Since it's a mapping type, the schema for its mapping's fields must
* be provided too. In this case, it's `date_fields_schema`.
*/
CYAML_FIELD_MAPPING_PTR(
"start", CYAML_FLAG_POINTER,
struct plan, start, date_fields_schema),
/* The next field is the project people.
*
* YAML key: "people".
* C structure member for this key: "people".
*
* Its CYAML type is a sequence.
*
* Since it's a sequence type, the value schema for its entries must
* be provided too. In this case, it's string_ptr_schema.
*
* Since it's not a sequence of a fixed-length, we must tell CYAML
* where the sequence entry count is to be stored. In this case, it
* goes in the "n_people" C structure member in `struct plan`.
*/
CYAML_FIELD_SEQUENCE_COUNT(
"people", CYAML_FLAG_POINTER,
struct plan, people, n_people,
&string_ptr_schema, 0, CYAML_UNLIMITED),
/* The next field is the project tasks.
*
* YAML key: "tasks".
* C structure member for this key: "tasks".
*
* Its CYAML type is a sequence.
*
* Since it's a sequence type, the value schema for its entries must
* be provided too. In this case, it's task_schema.
*
* Since it's not a sequence of a fixed-length, we must tell CYAML
* where the sequence entry count is to be stored. In this case, it
* goes in the "tasks_count" C structure member in `struct plan`.
* Since this is "tasks" with the "_count" postfix, we can use
* the following macro, which assumes a postfix of "_count" in the
* struct member name.
*/
CYAML_FIELD_SEQUENCE(
"tasks", CYAML_FLAG_POINTER,
struct plan, tasks,
&task_schema, 0, CYAML_UNLIMITED),
/* If the YAML contains a field that our program is not interested in
* we can ignore it, so the value of the field will not be loaded.
*
* Note that unless the OPTIONAL flag is set, the ignored field must
* be present.
*
* Another way of handling this would be to use the config flag
* to ignore unknown keys. This config is passed to libcyaml
* separately from the schema.
*/
CYAML_FIELD_IGNORE("irrelevant", CYAML_FLAG_OPTIONAL),
/* The field array must be terminated by an entry with a NULL key.
* Here we use the CYAML_FIELD_END macro for that. */
CYAML_FIELD_END
};
/* Top-level schema. The top level value for the plan is a mapping.
*
* Its fields are defined in plan_fields_schema.
*/
static const cyaml_schema_value_t plan_schema = {
CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER,
struct plan, plan_fields_schema),
};
/******************************************************************************
* Actual code to load and save YAML doc using libcyaml.
******************************************************************************/
/* Our CYAML config.
*
* If you want to change it between calls, don't make it const.
*
* Here we have a very basic config.
*/
static const cyaml_config_t config = {
.log_fn = cyaml_log, /* Use the default logging function. */
.mem_fn = cyaml_mem, /* Use the default memory allocator. */
.log_level = CYAML_LOG_WARNING, /* Logging errors and warnings only. */
};
/* Main entry point from OS. */
int main(int argc, char *argv[])
{
cyaml_err_t err;
struct plan *plan;
enum {
ARG_PROG_NAME,
ARG_PATH_IN,
ARG_PATH_OUT,
ARG__COUNT,
};
/* Handle args */
if (argc != ARG__COUNT) {
fprintf(stderr, "Usage:\n");
fprintf(stderr, " %s <INPUT> <OUTPUT>\n", argv[ARG_PROG_NAME]);
return EXIT_FAILURE;
}
/* Load input file. */
err = cyaml_load_file(argv[ARG_PATH_IN], &config,
&plan_schema, (void **) &plan, NULL);
if (err != CYAML_OK) {
fprintf(stderr, "ERROR: %s\n", cyaml_strerror(err));
return EXIT_FAILURE;
}
/* Use the data. */
printf("Project: %s\n", plan->name);
for (unsigned i = 0; i < plan->tasks_count; i++) {
printf("%u. %s\n", i + 1, plan->tasks[i].name);
}
/* Modify the data */
plan->tasks[0].estimate.days += 3;
plan->tasks[0].estimate.weeks += 1;
/* Save data to new YAML file. */
err = cyaml_save_file(argv[ARG_PATH_OUT], &config,
&plan_schema, plan, 0);
if (err != CYAML_OK) {
fprintf(stderr, "ERROR: %s\n", cyaml_strerror(err));
cyaml_free(&config, &plan_schema, plan, 0);
return EXIT_FAILURE;
}
/* Free the data */
cyaml_free(&config, &plan_schema, plan, 0);
return EXIT_SUCCESS;
}

View File

@ -0,0 +1,58 @@
project: Write new browser layout engine.
start:
day: 1
month: June
year: 2018
people:
- Stephen
- Neil
- Alex
irrelevant:
- details:
- the app doesn't need this stuff
- so it should be able to ignore it
tasks:
- name: Read the HTML and CSS specs.
flags:
- Important
estimate:
weeks: 2
people:
- Stephen
- Neil
- name: Think of name for library.
estimate:
hours: 1
- name: Create project repo.
estimate:
hours: 1
depends:
- Think of name for library.
people:
- Alex
- name: Initial design of library API.
flags:
- Important
- Engineering
depends:
- Read the HTML and CSS specs.
- Create project repo.
estimate:
days: 1
- name: Plan the initial implementation
flags:
- Engineering
- Documentation
- Management
depends:
- Initial design of library API.
estimate:
days: 6
people:
- Stephen
- Alex

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,10 @@
prefix=PREFIX
exec_prefix=${prefix}
libdir=${exec_prefix}/LIBDIR
includedir=${prefix}/INCLUDEDIR
Name: libcyaml
Description: Schema-based YAML parsing and serialisation
Version: VERSION
Libs: -L${libdir} -lcyaml -lyaml
Cflags: -I${includedir}

126
libs/libcyaml/src/data.h Normal file
View File

@ -0,0 +1,126 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (C) 2017-2021 Michael Drake <tlsa@netsurf-browser.org>
*/
/**
* \file
* \brief CYAML functions for manipulating client data structures.
*/
#ifndef CYAML_DATA_H
#define CYAML_DATA_H
#include "cyaml/cyaml.h"
#include "util.h"
/**
* Write a value of up to eight bytes to data_target.
*
* \param[in] value The value to write.
* \param[in] entry_size The number of bytes of value to write.
* \param[in] data_tgt The address to write to.
* \return \ref CYAML_OK on success, or appropriate error code otherwise.
*/
static inline cyaml_err_t cyaml_data_write(
uint64_t value,
uint64_t entry_size,
uint8_t *data_tgt)
{
const uint8_t *value_bytes = (uint8_t *)&value;
if (entry_size == 0 || entry_size > sizeof(value)) {
return CYAML_ERR_INVALID_DATA_SIZE;
}
if (cyaml__host_is_big_endian()) {
value_bytes += sizeof(value) - entry_size;
}
memcpy(data_tgt, value_bytes, entry_size);
return CYAML_OK;
}
/**
* Write a pointer to data.
*
* This is a wrapper for \ref cyaml_data_write that does a compile time
* assertion on the pointer size, so it can never return a runtime error.
*
* \param[in] ptr The pointer address to write.
* \param[in] data_target The address to write to.
*/
static inline void cyaml_data_write_pointer(
const void *ptr,
uint8_t *data_target)
{
/* Refuse to build on platforms where sizeof pointer would
* lead to \ref CYAML_ERR_INVALID_DATA_SIZE. */
static_assert(sizeof(char *) > 0, "Incompatible pointer size.");
static_assert(sizeof(char *) <= sizeof(uint64_t),
"Incompatible pointer size.");
CYAML_UNUSED(cyaml_data_write((uint64_t)ptr, sizeof(ptr), data_target));
return;
}
/**
* Read a value of up to eight bytes from data.
*
* \param[in] entry_size The number of bytes to read.
* \param[in] data The address to read from.
* \param[out] error_out Returns the error code. \ref CYAML_OK on success,
* or appropriate error otherwise.
* \return On success, returns the value read from data.
* On failure, returns 0.
*/
static inline uint64_t cyaml_data_read(
uint64_t entry_size,
const uint8_t *data,
cyaml_err_t *error_out)
{
uint64_t ret = 0;
uint8_t *ret_bytes = (uint8_t *)&ret;
if (entry_size == 0 || entry_size > sizeof(ret)) {
*error_out = CYAML_ERR_INVALID_DATA_SIZE;
return ret;
}
if (cyaml__host_is_big_endian()) {
ret_bytes += sizeof(ret) - entry_size;
}
memcpy(ret_bytes, data, entry_size);
*error_out = CYAML_OK;
return ret;
}
/**
* Read a pointer from data.
*
* This is a wrapper for \ref cyaml_data_read that does a compile time
* assertion on the pointer size, so it can never return a runtime error.
*
* \param[in] data The address to read from.
* \return Returns the value read from data.
*/
static inline uint8_t * cyaml_data_read_pointer(
const uint8_t *data)
{
cyaml_err_t err;
/* Refuse to build on platforms where sizeof pointer would
* lead to \ref CYAML_ERR_INVALID_DATA_SIZE. */
static_assert(sizeof(char *) > 0, "Incompatible pointer size.");
static_assert(sizeof(char *) <= sizeof(uint64_t),
"Incompatible pointer size.");
return (void *)cyaml_data_read(sizeof(char *), data, &err);
}
#endif

161
libs/libcyaml/src/free.c Normal file
View File

@ -0,0 +1,161 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (C) 2017-2019 Michael Drake <tlsa@netsurf-browser.org>
*/
/**
* \file
* \brief Free data structures created by the CYAML load functions.
*
* As described in the public API for \ref cyaml_free(), it is preferable for
* clients to write their own free routines, tailored for their data structure.
*
* Recursion and stack usage
* -------------------------
*
* This generic CYAML free routine is implemented using recursion, rather
* than iteration with a heap-allocated stack. This is because recursion
* seems less bad than allocating within the free code, and the stack-cost
* of these functions isn't huge. The maximum recursion depth is of course
* bound by the schema, however schemas for recursively nesting data structures
* are unbound, e.g. for a data tree structure.
*/
#include <stdbool.h>
#include <assert.h>
#include <string.h>
#include "data.h"
#include "util.h"
#include "mem.h"
/**
* Internal function for freeing a CYAML-parsed data structure.
*
* \param[in] cfg The client's CYAML library config.
* \param[in] schema The schema describing how to free `data`.
* \param[in] data The data structure to be freed.
* \param[in] count If data is of type \ref CYAML_SEQUENCE, this is the
* number of entries in the sequence.
*/
static void cyaml__free_value(
const cyaml_config_t *cfg,
const cyaml_schema_value_t *schema,
uint8_t * data,
uint64_t count);
/**
* Internal function for freeing a CYAML-parsed sequence.
*
* \param[in] cfg The client's CYAML library config.
* \param[in] sequence_schema The schema describing how to free `data`.
* \param[in] data The data structure to be freed.
* \param[in] count The sequence's entry count.
*/
static void cyaml__free_sequence(
const cyaml_config_t *cfg,
const cyaml_schema_value_t *sequence_schema,
uint8_t * const data,
uint64_t count)
{
const cyaml_schema_value_t *schema = sequence_schema->sequence.entry;
uint32_t data_size = schema->data_size;
cyaml__log(cfg, CYAML_LOG_DEBUG,
"Free: Freeing sequence with count: %u\n", count);
if (schema->flags & CYAML_FLAG_POINTER) {
data_size = sizeof(data);
}
for (unsigned i = 0; i < count; i++) {
cyaml__log(cfg, CYAML_LOG_DEBUG,
"Free: Freeing sequence entry: %u\n", i);
cyaml__free_value(cfg, schema, data + data_size * i, 0);
}
}
/**
* Internal function for freeing a CYAML-parsed mapping.
*
* \param[in] cfg The client's CYAML library config.
* \param[in] mapping_schema The schema describing how to free `data`.
* \param[in] data The data structure to be freed.
*/
static void cyaml__free_mapping(
const cyaml_config_t *cfg,
const cyaml_schema_value_t *mapping_schema,
uint8_t * const data)
{
const cyaml_schema_field_t *schema = mapping_schema->mapping.fields;
while (schema->key != NULL) {
uint64_t count = 0;
cyaml__log(cfg, CYAML_LOG_DEBUG,
"Free: Freeing key: %s (at offset: %u)\n",
schema->key, (unsigned)schema->data_offset);
if (schema->value.type == CYAML_SEQUENCE) {
cyaml_err_t err;
count = cyaml_data_read(schema->count_size,
data + schema->count_offset, &err);
if (err != CYAML_OK) {
return;
}
}
cyaml__free_value(cfg, &schema->value,
data + schema->data_offset, count);
schema++;
}
}
/* This function is documented at the forward declaration above. */
static void cyaml__free_value(
const cyaml_config_t *cfg,
const cyaml_schema_value_t *schema,
uint8_t * data,
uint64_t count)
{
if (schema->flags & CYAML_FLAG_POINTER) {
data = cyaml_data_read_pointer(data);
if (data == NULL) {
return;
}
}
if (schema->type == CYAML_MAPPING) {
cyaml__free_mapping(cfg, schema, data);
} else if (schema->type == CYAML_SEQUENCE ||
schema->type == CYAML_SEQUENCE_FIXED) {
if (schema->type == CYAML_SEQUENCE_FIXED) {
count = schema->sequence.max;
}
cyaml__free_sequence(cfg, schema, data, count);
}
if (schema->flags & CYAML_FLAG_POINTER) {
cyaml__log(cfg, CYAML_LOG_DEBUG, "Free: Freeing: %p\n", data);
cyaml__free(cfg, data);
}
}
/* Exported function, documented in include/cyaml/cyaml.h */
cyaml_err_t cyaml_free(
const cyaml_config_t *config,
const cyaml_schema_value_t *schema,
cyaml_data_t *data,
unsigned seq_count)
{
if (config == NULL) {
return CYAML_ERR_BAD_PARAM_NULL_CONFIG;
}
if (config->mem_fn == NULL) {
return CYAML_ERR_BAD_CONFIG_NULL_MEMFN;
}
if (schema == NULL) {
return CYAML_ERR_BAD_PARAM_NULL_SCHEMA;
}
cyaml__log(config, CYAML_LOG_DEBUG, "Free: Top level data: %p\n", data);
cyaml__free_value(config, schema, (void *)&data, seq_count);
return CYAML_OK;
}

2751
libs/libcyaml/src/load.c Normal file

File diff suppressed because it is too large Load Diff

35
libs/libcyaml/src/mem.c Normal file
View File

@ -0,0 +1,35 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (C) 2018 Michael Drake <tlsa@netsurf-browser.org>
*/
/**
* \file
* \brief CYAML memory allocation handling.
*/
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "mem.h"
/** Macro to squash unused variable compiler warnings. */
#define CYAML_UNUSED(_x) ((void)(_x))
/* Exported function, documented in include/cyaml/cyaml.h */
void * cyaml_mem(
void *ctx,
void *ptr,
size_t size)
{
CYAML_UNUSED(ctx);
if (size == 0) {
free(ptr);
return NULL;
}
return realloc(ptr, size);
}

109
libs/libcyaml/src/mem.h Normal file
View File

@ -0,0 +1,109 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (C) 2018-2020 Michael Drake <tlsa@netsurf-browser.org>
*/
/**
* \file
* \brief CYAML memory allocation handling.
*/
#ifndef CYAML_MEM_H
#define CYAML_MEM_H
#include "cyaml/cyaml.h"
/**
* Helper for freeing using the client's choice of allocator routine.
*
* \param[in] config The CYAML client config.
* \param[in] ptr Pointer to allocation to free.
*/
static inline void cyaml__free(
const cyaml_config_t *config,
void *ptr)
{
config->mem_fn(config->mem_ctx, ptr, 0);
}
/**
* Helper for new allocations using the client's choice of allocator routine.
*
* \note On failure, any existing allocation is still owned by the caller, and
* they are responsible for freeing it.
*
* \param[in] config The CYAML client config.
* \param[in] ptr The existing allocation or NULL.
* \param[in] current_size Size of the current allocation. (Only needed if
* `clean != false`).
* \param[in] new_size The number of bytes to resize allocation to.
* \param[in] clean Only applies if `new_size > current_size`.
* If `false`, the new memory is uninitialised,
* if `true`, the new memory is initialised to zero.
* \return Pointer to allocation on success, or `NULL` on failure.
*/
static inline void * cyaml__realloc(
const cyaml_config_t *config,
void *ptr,
size_t current_size,
size_t new_size,
bool clean)
{
uint8_t *temp = config->mem_fn(config->mem_ctx, ptr, new_size);
if (temp == NULL) {
return NULL;
}
if (clean && (new_size > current_size)) {
memset(temp + current_size, 0, new_size - current_size);
}
return temp;
}
/**
* Helper for new allocations using the client's choice of allocator routine.
*
* \param[in] config The CYAML client config.
* \param[in] size The number of bytes to allocate.
* \param[in] clean If `false`, the memory is uninitialised, if `true`,
* the memory is initialised to zero.
* \return Pointer to allocation on success, or `NULL` on failure.
*/
static inline void * cyaml__alloc(
const cyaml_config_t *config,
size_t size,
bool clean)
{
return cyaml__realloc(config, NULL, 0, size, clean);
}
/**
* Helper for string duplication using the client's choice of allocator routine.
*
* \param[in] config The CYAML client config.
* \param[in] str The string to duplicate.
* \param[in] len_out If non-NULL, updated to length of string on success.
* \return Pointer to new string on success, or `NULL` on failure.
*/
static inline char * cyaml__strdup(
const cyaml_config_t *config,
const char *str,
size_t *len_out)
{
size_t len = strlen(str) + 1;
char *dup = cyaml__alloc(config, len, false);
if (dup == NULL) {
return NULL;
}
memcpy(dup, str, len);
if (len_out != NULL) {
*len_out = len - 1;
}
return dup;
}
#endif

1516
libs/libcyaml/src/save.c Normal file

File diff suppressed because it is too large Load Diff

259
libs/libcyaml/src/utf8.c Normal file
View File

@ -0,0 +1,259 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (C) 2018-2019 Michael Drake <tlsa@netsurf-browser.org>
*/
/**
* \file
* \brief CYAML functions for handling utf8 text.
*/
#include <assert.h>
#include <stdint.h>
#include <stdbool.h>
#include "utf8.h"
/**
* Get expected byte-length of UTF8 character.
*
* Finds the number of bytes expected for the UTF8 sequence starting with
* the given byte.
*
* \param[in] b First byte of UTF8 sequence.
* \return the byte width of the character or 0 if invalid.
*/
static inline unsigned cyaml_utf8_char_len(uint8_t b)
{
if (!(b & 0x80)) {
return 1;
} else switch (b >> 3) {
case 0x18:
case 0x19:
case 0x1a:
case 0x1b:
return 2;
case 0x1c:
case 0x1d:
return 3;
case 0x1e:
return 4;
}
return 0; /* Invalid */
}
/* Exported function, documented in utf8.h. */
unsigned cyaml_utf8_get_codepoint(
const uint8_t *s,
unsigned *len)
{
unsigned c = 0;
bool sf = false;
if (*len == 1) {
return s[0];
} else if ((*len > 1) && (*len <= 4)) {
/* Compose first byte into codepoint. */
c |= (s[0] & ((1u << (7 - *len)) - 1u)) << ((*len - 1) * 6);
/* Handle continuation bytes. */
for (unsigned i = 1; i < *len; i++) {
/* Check continuation byte begins with 0b10xxxxxx. */
if ((s[i] & 0xc0) != 0x80) {
/* Need to shorten length so we don't consume
* this byte with this invalid character. */
*len -= i;
goto invalid;
}
/* Compose continuation byte into codepoint. */
c |= (0x3fu & s[i]) << ((*len - i - 1) * 6);
}
}
/* Non-shortest forms are forbidden.
*
* The following table shows the bits available for each
* byte sequence length, as well as the bit-delta to the
* shorter sequence.
*
* | Bytes | Bits | Bit delta | Data |
* | ----- | ---- | --------- | ----------------------- |
* | 1 | 7 | N/A | 0xxxxxxx |
* | 2 | 11 | 4 | 110xxxxx + 10xxxxxx x 1 |
* | 3 | 16 | 5 | 1110xxxx + 10xxxxxx x 2 |
* | 4 | 21 | 5 | 11110xxx + 10xxxxxx x 3 |
*
* So here we check that the top "bit-delta" bits are not all
* clear for the byte length,
*/
switch (*len) {
case 2: sf = (c & (((1 << 4) - 1) << (11 - 4))) != 0; break;
case 3: sf = (c & (((1 << 5) - 1) << (16 - 5))) != 0; break;
case 4: sf = (c & (((1 << 5) - 1) << (21 - 5))) != 0; break;
default: goto invalid;
}
if (!sf) {
/* Codepoint representation was not shortest-form. */
goto invalid;
}
return c;
invalid:
return 0xfffd; /* REPLACEMENT CHARACTER */
}
/**
* Convert a Unicode codepoint to lower case.
*
* \note This only handles some of the Unicode blocks.
* (Currently the Latin ones.)
*
* \param[in] c Codepoint to convert to lower-case, if applicable.
* \return the lower-cased codepoint.
*/
static unsigned cyaml_utf8_to_lower(unsigned c)
{
if (((c >= 0x0041) && (c <= 0x005a)) /* Basic Latin */ ||
((c >= 0x00c0) && (c <= 0x00d6)) /* Latin-1 Supplement */ ||
((c >= 0x00d8) && (c <= 0x00de)) /* Latin-1 Supplement */ ) {
return c + 32u;
} else if (((c >= 0x0100) && (c <= 0x012f)) /* Latin Extended-A */ ||
((c >= 0x0132) && (c <= 0x0137)) /* Latin Extended-A */ ||
((c >= 0x014a) && (c <= 0x0177)) /* Latin Extended-A */ ||
((c >= 0x0182) && (c <= 0x0185)) /* Latin Extended-B */ ||
((c >= 0x01a0) && (c <= 0x01a5)) /* Latin Extended-B */ ||
((c >= 0x01de) && (c <= 0x01ef)) /* Latin Extended-B */ ||
((c >= 0x01f8) && (c <= 0x021f)) /* Latin Extended-B */ ||
((c >= 0x0222) && (c <= 0x0233)) /* Latin Extended-B */ ||
((c >= 0x0246) && (c <= 0x024f)) /* Latin Extended-B */ ) {
return c & ~0x1u;
} else if (((c >= 0x0139) && (c <= 0x0148) /* Latin Extended-A */) ||
((c >= 0x0179) && (c <= 0x017e) /* Latin Extended-A */) ||
((c >= 0x01b3) && (c <= 0x01b6) /* Latin Extended-B */) ||
((c >= 0x01cd) && (c <= 0x01dc) /* Latin Extended-B */)) {
return (c + 1) & ~0x1u;
} else switch (c) {
case 0x0178: return 0x00ff; /* Latin Extended-A */
case 0x0187: return 0x0188; /* Latin Extended-B */
case 0x018b: return 0x018c; /* Latin Extended-B */
case 0x018e: return 0x01dd; /* Latin Extended-B */
case 0x0191: return 0x0192; /* Latin Extended-B */
case 0x0198: return 0x0199; /* Latin Extended-B */
case 0x01a7: return 0x01a8; /* Latin Extended-B */
case 0x01ac: return 0x01ad; /* Latin Extended-B */
case 0x01af: return 0x01b0; /* Latin Extended-B */
case 0x01b7: return 0x0292; /* Latin Extended-B */
case 0x01b8: return 0x01b9; /* Latin Extended-B */
case 0x01bc: return 0x01bd; /* Latin Extended-B */
case 0x01c4: return 0x01c6; /* Latin Extended-B */
case 0x01c5: return 0x01c6; /* Latin Extended-B */
case 0x01c7: return 0x01c9; /* Latin Extended-B */
case 0x01c8: return 0x01c9; /* Latin Extended-B */
case 0x01ca: return 0x01cc; /* Latin Extended-B */
case 0x01cb: return 0x01cc; /* Latin Extended-B */
case 0x01f1: return 0x01f3; /* Latin Extended-B */
case 0x01f2: return 0x01f3; /* Latin Extended-B */
case 0x01f4: return 0x01f5; /* Latin Extended-B */
case 0x01f7: return 0x01bf; /* Latin Extended-B */
case 0x0220: return 0x019e; /* Latin Extended-B */
case 0x023b: return 0x023c; /* Latin Extended-B */
case 0x023d: return 0x019a; /* Latin Extended-B */
case 0x0241: return 0x0242; /* Latin Extended-B */
case 0x0243: return 0x0180; /* Latin Extended-B */
}
return c;
}
/**
* Find the difference between two codepoints.
*
* \param a First codepoint.
* \param b Second codepoint.
* \return the difference.
*/
static inline int cyaml_utf8_difference(unsigned a, unsigned b)
{
return (((int)a) - ((int)b));
}
/* Exported function, documented in utf8.h. */
int cyaml_utf8_casecmp(
const void * const str1,
const void * const str2)
{
const uint8_t *s1 = str1;
const uint8_t *s2 = str2;
while (true) {
unsigned len1;
unsigned len2;
unsigned cmp1;
unsigned cmp2;
/* Check for end of strings. */
if ((*s1 == 0) && (*s2 == 0)) {
return 0; /* Both strings ended; match. */
} else if (*s1 == 0) {
return 1; /* String 1 has ended. */
} else if (*s2 == 0) {
return -1;/* String 2 has ended. */
}
/* Get byte lengths of these characters. */
len1 = cyaml_utf8_char_len(*s1);
len2 = cyaml_utf8_char_len(*s2);
/* Compare values. */
if ((len1 == 1) && (len2 == 1)) {
/* Common case: Both strings have ASCII values. */
if (*s1 != *s2) {
/* They're different; need to lower case. */
cmp1 = ((*s1 >= 'A') && (*s1 <= 'Z')) ?
(*s1 + 32u) : *s1;
cmp2 = ((*s2 >= 'A') && (*s2 <= 'Z')) ?
(*s2 + 32u) : *s2;
if (cmp1 != cmp2) {
return cyaml_utf8_difference(
cmp1, cmp2);
}
}
} else if ((len1 != 0) && (len2 != 0)) {
/* Neither string has invalid sequence;
* convert to UCS4 for comparison. */
cmp1 = cyaml_utf8_get_codepoint(s1, &len1);
cmp2 = cyaml_utf8_get_codepoint(s2, &len2);
if (cmp1 != cmp2) {
/* They're different; need to lower case. */
cmp1 = cyaml_utf8_to_lower(cmp1);
cmp2 = cyaml_utf8_to_lower(cmp2);
if (cmp1 != cmp2) {
return cyaml_utf8_difference(
cmp1, cmp2);
}
}
} else {
if (len1 | len2) {
/* One of the strings has invalid sequence. */
return cyaml_utf8_difference(len1, len2);
} else {
/* Both strings have an invalid sequence. */
len1 = len2 = 1;
}
}
/* Advance each string by their current character length. */
s1 += len1;
s2 += len2;
}
}

46
libs/libcyaml/src/utf8.h Normal file
View File

@ -0,0 +1,46 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (C) 2018 Michael Drake <tlsa@netsurf-browser.org>
*/
/**
* \file
* \brief CYAML functions for handling utf8 text.
*/
#ifndef CYAML_UTF8_H
#define CYAML_UTF8_H
/**
* Get a codepoint from the input string.
*
* Caller must provide the expected length given the first input byte.
*
* If a multi-byte character contains an invalid continuation byte, the
* character length will be updated on exit to the number of bytes consumed,
* and the replacement character, U+FFFD will be returned.
*
* \param[in] s String to read first codepoint from.
* \param[in,out] len Expected length of first character, updated on exit.
* \return The codepoint or `0xfffd` if character is invalid.
*/
unsigned cyaml_utf8_get_codepoint(
const uint8_t *s,
unsigned *len);
/**
* Case insensitive comparason.
*
* \note This has some limitations and only performs case insensitive
* comparason over some sectons of unicode.
*
* \param[in] str1 First string to be compared.
* \param[in] str2 Second string to be compared.
* \return 0 if and only if strings are equal.
*/
int cyaml_utf8_casecmp(
const void * const str1,
const void * const str2);
#endif

122
libs/libcyaml/src/util.c Normal file
View File

@ -0,0 +1,122 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (C) 2017-2019 Michael Drake <tlsa@netsurf-browser.org>
*/
/**
* \file
* \brief Utility functions.
*/
#include <stdbool.h>
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include "util.h"
/** Flag that indicates a release in \ref cyaml_version. */
#define CYAML_RELEASE_FLAG (1u << 31)
/** Stringification helper macro. */
#define CYAML_STR_HELPER(_x) #_x
/** Stringification macro. */
#define CYAML_STR(_x) CYAML_STR_HELPER(_x)
/* Version depends on whether we're a development build. */
#if VERSION_DEVEL
/** Version string is composed from components in Makefile. */
#define CYAML_VERSION_STR \
CYAML_STR(VERSION_MAJOR) "." \
CYAML_STR(VERSION_MINOR) "." \
CYAML_STR(VERSION_PATCH) "-DEVEL"
/* Exported constant, documented in include/cyaml/cyaml.h */
const uint32_t cyaml_version =
((VERSION_MAJOR << 16) |
(VERSION_MINOR << 8) |
(VERSION_PATCH << 0));
#else
/** Version string is composed from components in Makefile. */
#define CYAML_VERSION_STR \
CYAML_STR(VERSION_MAJOR) "." \
CYAML_STR(VERSION_MINOR) "." \
CYAML_STR(VERSION_PATCH)
/* Exported constant, documented in include/cyaml/cyaml.h */
const uint32_t cyaml_version =
((VERSION_MAJOR << 16) |
(VERSION_MINOR << 8) |
(VERSION_PATCH << 0) |
CYAML_RELEASE_FLAG);
#endif
/* Exported constant, documented in include/cyaml/cyaml.h */
const char *cyaml_version_str = CYAML_VERSION_STR;
/* Exported function, documented in include/cyaml/cyaml.h */
void cyaml_log(
cyaml_log_t level,
void *ctx,
const char *fmt,
va_list args)
{
static const char * const strings[] = {
[CYAML_LOG_DEBUG] = "DEBUG",
[CYAML_LOG_INFO] = "INFO",
[CYAML_LOG_NOTICE] = "NOTICE",
[CYAML_LOG_WARNING] = "WARNING",
[CYAML_LOG_ERROR] = "ERROR",
};
CYAML_UNUSED(ctx);
fprintf(stderr, "libcyaml: %7.7s: ", strings[level]);
vfprintf(stderr, fmt, args);
}
/* Exported function, documented in include/cyaml/cyaml.h */
const char * cyaml_strerror(
cyaml_err_t err)
{
static const char * const strings[CYAML_ERR__COUNT] = {
[CYAML_OK] = "Success",
[CYAML_ERR_OOM] = "Memory allocation failed",
[CYAML_ERR_ALIAS] = "YAML alias unsupported",
[CYAML_ERR_FILE_OPEN] = "Could not open file",
[CYAML_ERR_INVALID_KEY] = "Invalid key",
[CYAML_ERR_INVALID_VALUE] = "Invalid value",
[CYAML_ERR_INVALID_ALIAS] = "No anchor found for alias",
[CYAML_ERR_INTERNAL_ERROR] = "Internal error",
[CYAML_ERR_UNEXPECTED_EVENT] = "Unexpected event",
[CYAML_ERR_STRING_LENGTH_MIN] = "String length too short",
[CYAML_ERR_STRING_LENGTH_MAX] = "String length too long",
[CYAML_ERR_INVALID_DATA_SIZE] = "Data size must be 0 < X <= 8 bytes",
[CYAML_ERR_TOP_LEVEL_NON_PTR] = "Top-level schema value must be pointer",
[CYAML_ERR_BAD_TYPE_IN_SCHEMA] = "Schema contains invalid type",
[CYAML_ERR_BAD_MIN_MAX_SCHEMA] = "Bad schema: min exceeds max",
[CYAML_ERR_BAD_PARAM_SEQ_COUNT] = "Bad parameter: seq_count",
[CYAML_ERR_BAD_PARAM_NULL_DATA] = "Bad parameter: NULL data",
[CYAML_ERR_BAD_BITVAL_IN_SCHEMA] = "Bit value beyond bitfield size",
[CYAML_ERR_SEQUENCE_ENTRIES_MIN] = "Sequence with too few entries",
[CYAML_ERR_SEQUENCE_ENTRIES_MAX] = "Sequence with too many entries",
[CYAML_ERR_SEQUENCE_FIXED_COUNT] = "Sequence fixed has unequal min max",
[CYAML_ERR_SEQUENCE_IN_SEQUENCE] = "Non-fixed sequence in sequence",
[CYAML_ERR_MAPPING_FIELD_MISSING] = "Missing required mapping field",
[CYAML_ERR_BAD_CONFIG_NULL_MEMFN] = "Bad config: NULL mem function",
[CYAML_ERR_BAD_PARAM_NULL_CONFIG] = "Bad parameter: NULL config",
[CYAML_ERR_BAD_PARAM_NULL_SCHEMA] = "Bad parameter: NULL schema",
[CYAML_ERR_LIBYAML_EMITTER_INIT] = "libyaml emitter init failed",
[CYAML_ERR_LIBYAML_PARSER_INIT] = "libyaml parser init failed",
[CYAML_ERR_LIBYAML_EVENT_INIT] = "libyaml event init failed",
[CYAML_ERR_LIBYAML_EMITTER] = "libyaml emitter error",
[CYAML_ERR_LIBYAML_PARSER] = "libyaml parser error",
};
if ((unsigned)err >= CYAML_ERR__COUNT) {
return "Invalid error code";
}
assert(strings[err] != NULL);
return strings[err];
}

200
libs/libcyaml/src/util.h Normal file
View File

@ -0,0 +1,200 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (C) 2017-2021 Michael Drake <tlsa@netsurf-browser.org>
*/
/**
* \file
* \brief CYAML common utility functions.
*/
#ifndef CYAML_UTIL_H
#define CYAML_UTIL_H
#include "cyaml/cyaml.h"
#include "utf8.h"
/** Macro to squash unused variable compiler warnings. */
#define CYAML_UNUSED(_x) ((void)(_x))
/**
* Check whether the host is little endian.
*
* Checks whether least significant bit is in the first byte of a `uint16_t`.
*
* \return true if host is little endian.
*/
static inline bool cyaml__host_is_little_endian(void)
{
const uint16_t test = 1;
return ((const uint8_t *) &test)[0];
}
/**
* Check whether the host is big endian.
*
* \return true if host is big endian.
*/
static inline bool cyaml__host_is_big_endian(void)
{
return !cyaml__host_is_little_endian();
}
/** CYAML bitfield type. */
typedef uint32_t cyaml_bitfield_t;
/** Number of bits in \ref cyaml_bitfield_t. */
#define CYAML_BITFIELD_BITS (sizeof(cyaml_bitfield_t) * CHAR_BIT)
/** CYAML state machine states. */
enum cyaml_state_e {
CYAML_STATE_START, /**< Initial state. */
CYAML_STATE_IN_STREAM, /**< In a stream. */
CYAML_STATE_IN_DOC, /**< In a document. */
CYAML_STATE_IN_MAP_KEY, /**< In a mapping. */
CYAML_STATE_IN_MAP_VALUE, /**< In a mapping. */
CYAML_STATE_IN_SEQUENCE, /**< In a sequence. */
CYAML_STATE__COUNT, /**< Count of states, **not a valid
state itself**. */
};
/**
* Convert a CYAML state into a human readable string.
*
* \param[in] state The state to convert.
* \return String representing state.
*/
static inline const char * cyaml__state_to_str(enum cyaml_state_e state)
{
static const char * const strings[CYAML_STATE__COUNT] = {
[CYAML_STATE_START] = "start",
[CYAML_STATE_IN_STREAM] = "in stream",
[CYAML_STATE_IN_DOC] = "in doc",
[CYAML_STATE_IN_MAP_KEY] = "in mapping (key)",
[CYAML_STATE_IN_MAP_VALUE] = "in mapping (value)",
[CYAML_STATE_IN_SEQUENCE] = "in sequence",
};
if ((unsigned)state >= CYAML_STATE__COUNT) {
return "<invalid>";
}
return strings[state];
}
/**
* Convert a CYAML type into a human readable string.
*
* \param[in] type The state to convert.
* \return String representing state.
*/
static inline const char * cyaml__type_to_str(cyaml_type_e type)
{
static const char * const strings[CYAML__TYPE_COUNT] = {
[CYAML_INT] = "INT",
[CYAML_UINT] = "UINT",
[CYAML_BOOL] = "BOOL",
[CYAML_ENUM] = "ENUM",
[CYAML_FLAGS] = "FLAGS",
[CYAML_FLOAT] = "FLOAT",
[CYAML_STRING] = "STRING",
[CYAML_MAPPING] = "MAPPING",
[CYAML_BITFIELD] = "BITFIELD",
[CYAML_SEQUENCE] = "SEQUENCE",
[CYAML_SEQUENCE_FIXED] = "SEQUENCE_FIXED",
[CYAML_IGNORE] = "IGNORE",
};
if ((unsigned)type >= CYAML__TYPE_COUNT) {
return "<invalid>";
}
return strings[type];
}
/**
* Log to client's logging function, if provided.
*
* \param[in] cfg CYAML client config structure.
* \param[in] level Log level of message to log.
* \param[in] fmt Format string for message to log.
* \param[in] ... Additional arguments used by fmt.
*/
static inline void cyaml__log(
const cyaml_config_t *cfg,
cyaml_log_t level,
const char *fmt, ...)
{
if (level >= cfg->log_level && cfg->log_fn != NULL) {
va_list args;
va_start(args, fmt);
cfg->log_fn(level, cfg->log_ctx, fmt, args);
va_end(args);
}
}
/**
* Check if comparason should be case sensitive.
*
* As described in the API, schema flags take priority over config flags.
*
* \param[in] config Client's CYAML configuration structure.
* \param[in] schema The CYAML schema for the value to be compared.
* \return Whether to use case-sensitive comparason.
*/
static inline bool cyaml__is_case_sensitive(
const cyaml_config_t *config,
const cyaml_schema_value_t *schema)
{
if (schema->flags & CYAML_FLAG_CASE_INSENSITIVE) {
return false;
} else if (schema->flags & CYAML_FLAG_CASE_SENSITIVE) {
return true;
} else if (config->flags & CYAML_CFG_CASE_INSENSITIVE) {
return false;
}
return true;
}
/**
* Compare two strings.
*
* Depending on the client's configuration, and the value's schema,
* this will do either a case-sensitive or case-insensitive comparason.
*
* \param[in] config Client's CYAML configuration structure.
* \param[in] schema The CYAML schema for the value to be compared.
* \param[in] str1 First string to be compared.
* \param[in] str2 Second string to be compared.
* \return 0 if and only if strings are equal.
*/
static inline int cyaml__strcmp(
const cyaml_config_t *config,
const cyaml_schema_value_t *schema,
const void * const str1,
const void * const str2)
{
if (cyaml__is_case_sensitive(config, schema)) {
return strcmp(str1, str2);
}
return cyaml_utf8_casecmp(str1, str2);
}
/**
* Check of all the bits of a mask are set in a cyaml value flag word.
*
* \param[in] flags The value flags to test.
* \param[in] mask Mask of the bits to test for in flags.
* \return true if all bits of mask are set in flags.
*/
static inline bool cyaml__flag_check_all(
enum cyaml_flag flags,
enum cyaml_flag mask)
{
return ((flags & mask) == mask);
}
#endif

View File

@ -0,0 +1,19 @@
animals:
- kind: cat
sounds:
- meow
- purr
- kind: hippo
sounds:
- wheeze
- grunt
- roar
- kind: snake
sounds:
- hiss
cakes:
- salted caramel cake
- lemon drizzle cake
- victoria sponge
- carrot cake
- yule log

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,448 @@
/*
* 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;
}

View File

@ -0,0 +1,161 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (C) 2017-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"
/** Macro to squash unused variable compiler warnings. */
#define UNUSED(_x) ((void)(_x))
/**
* Test cyaml_free with NULL data.
*
* \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_free_null_data(
ttest_report_ctx_t *report,
const cyaml_config_t *config)
{
cyaml_err_t err;
struct target_struct {
int test_value_int;
};
static const struct cyaml_schema_field mapping_schema[] = {
CYAML_FIELD_INT("test_int", CYAML_FLAG_DEFAULT,
struct target_struct, test_value_int),
CYAML_FIELD_END
};
static const struct cyaml_schema_value top_schema = {
CYAML_VALUE_MAPPING(CYAML_FLAG_POINTER,
struct target_struct, mapping_schema),
};
ttest_ctx_t tc;
if (!ttest_start(report, __func__, NULL, NULL, &tc)) return true;
err = cyaml_free(config, &top_schema, NULL, 0);
if (err != CYAML_OK) {
return ttest_fail(&tc, "Free failed: %s", cyaml_strerror(err));
}
return ttest_pass(&tc);
}
/**
* Test cyaml_free with NULL config.
*
* \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_free_null_config(
ttest_report_ctx_t *report,
const cyaml_config_t *config)
{
cyaml_err_t err;
ttest_ctx_t tc;
if (!ttest_start(report, __func__, NULL, NULL, &tc)) return true;
UNUSED(config);
err = cyaml_free(NULL, NULL, NULL, 0);
if (err != CYAML_ERR_BAD_PARAM_NULL_CONFIG) {
return ttest_fail(&tc, "Free failed: %s", cyaml_strerror(err));
}
return ttest_pass(&tc);
}
/**
* Test cyaml_free with NULL memory allocation function.
*
* \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_free_null_mem_fn(
ttest_report_ctx_t *report,
const cyaml_config_t *config)
{
cyaml_err_t err;
cyaml_config_t cfg = *config;
ttest_ctx_t tc;
if (!ttest_start(report, __func__, NULL, NULL, &tc)) return true;
cfg.mem_fn = NULL;
err = cyaml_free(&cfg, NULL, NULL, 0);
if (err != CYAML_ERR_BAD_CONFIG_NULL_MEMFN) {
return ttest_fail(&tc, "Free failed: %s", cyaml_strerror(err));
}
return ttest_pass(&tc);
}
/**
* Test cyaml_free with NULL 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_free_null_schema(
ttest_report_ctx_t *report,
const cyaml_config_t *config)
{
cyaml_err_t err;
ttest_ctx_t tc;
if (!ttest_start(report, __func__, NULL, NULL, &tc)) return true;
err = cyaml_free(config, NULL, NULL, 0);
if (err != CYAML_ERR_BAD_PARAM_NULL_SCHEMA) {
return ttest_fail(&tc, "Free failed: %s", cyaml_strerror(err));
}
return ttest_pass(&tc);
}
/**
* Run the CYAML freeing unit 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 free_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, "Free tests");
pass &= test_free_null_data(rc, &config);
pass &= test_free_null_mem_fn(rc, &config);
pass &= test_free_null_config(rc, &config);
pass &= test_free_null_schema(rc, &config);
return pass;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,85 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (C) 2017-2021 Michael Drake <tlsa@netsurf-browser.org>
*/
#include <stdbool.h>
#include <assert.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <cyaml/cyaml.h>
#include "ttest.h"
#include "test.h"
/**
* Print program usage
*
* \param[in] prog_name Name of program.
*/
static void usage(const char *prog_name)
{
fprintf(stderr, "Usage: %s [-q|-v|-d]\n", prog_name);
}
/**
* Main entry point from OS.
*
* \param[in] argc Argument count.
* \param[in] argv Vector of string arguments.
* \return Program return code.
*/
int main(int argc, char *argv[])
{
bool pass = true;
bool quiet = false;
ttest_report_ctx_t rc;
const char *test_list = NULL;
cyaml_log_fn_t log_fn = cyaml_log;
cyaml_log_t log_level = CYAML_LOG_ERROR;
enum {
ARG_PROG_NAME,
ARG_VERBOSE,
ARG_TEST_LIST,
ARG__COUNT,
};
if (argc > ARG__COUNT) {
usage(argv[ARG_PROG_NAME]);
return EXIT_FAILURE;
}
for (int i = 1; i < argc; i++) {
if ((strcmp(argv[i], "-q") == 0) ||
(strcmp(argv[i], "--quiet") == 0)) {
quiet = true;
log_fn = NULL;
} else if ((strcmp(argv[i], "-v") == 0) ||
(strcmp(argv[i], "--verbose") == 0)) {
log_level = CYAML_LOG_INFO;
} else if ((strcmp(argv[i], "-d") == 0) ||
(strcmp(argv[i], "--debug") == 0)) {
log_level = CYAML_LOG_DEBUG;
} else {
test_list = argv[i];
}
}
rc = ttest_init(test_list, quiet);
pass &= utf8_tests(&rc, log_level, log_fn);
pass &= util_tests(&rc, log_level, log_fn);
pass &= free_tests(&rc, log_level, log_fn);
pass &= load_tests(&rc, log_level, log_fn);
pass &= errs_tests(&rc, log_level, log_fn);
pass &= file_tests(&rc, log_level, log_fn);
pass &= save_tests(&rc, log_level, log_fn);
ttest_report(&rc);
return (pass) ? EXIT_SUCCESS : EXIT_FAILURE;
}

View File

@ -0,0 +1,52 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (C) 2021 Michael Drake <tlsa@netsurf-browser.org>
*/
#ifndef TEST_H
#define TEST_H
/** In load.c */
extern bool load_tests(
ttest_report_ctx_t *rc,
cyaml_log_t log_level,
cyaml_log_fn_t log_fn);
/** In file.c */
extern bool file_tests(
ttest_report_ctx_t *rc,
cyaml_log_t log_level,
cyaml_log_fn_t log_fn);
/** In free.c */
extern bool free_tests(
ttest_report_ctx_t *rc,
cyaml_log_t log_level,
cyaml_log_fn_t log_fn);
/** In utf8.c */
extern bool utf8_tests(
ttest_report_ctx_t *rc,
cyaml_log_t log_level,
cyaml_log_fn_t log_fn);
/** In util.c */
extern bool util_tests(
ttest_report_ctx_t *rc,
cyaml_log_t log_level,
cyaml_log_fn_t log_fn);
/** In errs.c */
extern bool errs_tests(
ttest_report_ctx_t *rc,
cyaml_log_t log_level,
cyaml_log_fn_t log_fn);
/** In save.c */
extern bool save_tests(
ttest_report_ctx_t *rc,
cyaml_log_t log_level,
cyaml_log_fn_t log_fn);
#endif

View File

@ -0,0 +1,291 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (C) 2013-2019 Michael Drake <tlsa@netsurf-browser.org>
*/
#ifndef TTEST_H
#define TTEST_H
#include <string.h>
/**
* Test cleanup client callback.
*
* The caller passes this callback function to ttest_start, and it is called
* in whichever tlsa-test function finishes the test (ttest_pass, ttest_fail,
* or ttest_todo).
*
* \param[in] Client data to clean up.
*/
typedef void (*ttest_cleanup_fn)(void *data);
/**
* Internal tlsa-test report context.
*
* Clients should not touch this directly, it is only exposed in this
* header because tlsa-test is arranged to be header-only, for convenience
* of utilisation.
*/
typedef struct ttest_report_ctx {
/** Space/comma separated list of tests to run. */
const char *test_list;
size_t test_list_len;
bool quiet; /**< Whether to print only the report summary. */
unsigned tests; /**< Number of tests started. */
unsigned todo; /**< Number of tests marked as unimplemented. */
unsigned passed; /**< Number of tests passed. */
} ttest_report_ctx_t;
/**
* Internal tlsa-test test context.
*
* Clients should not touch this directly, it is only exposed in this
* header because tlsa-test is arranged to be header-only, for convenience
* of utilisation.
*/
typedef struct ttest_ctx {
ttest_report_ctx_t *report; /**< The tlsa-test report context. */
const char *name; /**< The unit test name. */
ttest_cleanup_fn cleanup; /**< Client's unit test cleanup function. */
void *cleanup_data; /**< Client's unit test cleanup context. */
} ttest_ctx_t;
/**
* Initialise a tlsa-test report context.
*
* \param[in] test_list Space/comma separated list of tests to run.
* \param[in] quiet Whether report should be a minimal summary.
* \return initialised tlsa-test report context.
*/
static inline ttest_report_ctx_t ttest_init(
const char *test_list,
bool quiet)
{
ttest_report_ctx_t rc = {
.test_list = test_list,
.test_list_len = 0,
.quiet = quiet,
};
if (test_list != NULL) {
rc.test_list_len = strlen(test_list);
}
return rc;
}
/**
* Determine whether test of given name should be run.
*
* \param[in] report The tlsa-test report context.
* \param[in] name The name of the test to consider.
* \return true if test should be run, false otherwise.
*/
static inline bool ttest__run_test(
ttest_report_ctx_t *report,
const char *name)
{
size_t len;
size_t pos = 0;
size_t name_len;
if (report->test_list == NULL || report->test_list_len == 0) {
return true;
}
name_len = strlen(name);
while (pos < report->test_list_len) {
/* Skip commas and spaces. */
pos += strspn(report->test_list + pos, ", ");
len = strcspn(report->test_list + pos, ", ");
if (len == name_len) {
if (memcmp(report->test_list + pos, name, len) == 0) {
return true;
}
}
pos += len;
}
return false;
}
/**
* Start a unit test.
*
* The when complete, the test must be competed by calling either ttest_pass,
* ttest_fail, or ttest_todo.
*
* \param[in] report The tlsa-test report context.
* \param[in] name Name for this unit test.
* \param[in] cleanup Cleanup function to call on test completion.
* \param[in] cleanup_data Pointer to client cleanup context.
* \param[out] test_ctx_out Returns the unit test context on success.
* The test context must be passed to the test
* completion function.
* \return true if the test should be started, false otherwise.
*/
static inline bool ttest_start(
ttest_report_ctx_t *report,
const char *name,
ttest_cleanup_fn cleanup,
void *cleanup_data,
ttest_ctx_t *test_ctx_out)
{
ttest_ctx_t tc = {
.name = name,
.report = report,
.cleanup = cleanup,
.cleanup_data = cleanup_data,
};
if (!ttest__run_test(report, name)) {
return false;
}
report->tests++;
*test_ctx_out = tc;
return true;
}
/**
* Pass a unit test.
*
* This function competes a unit test. It should be called when a test passes.
* This function always returns true.
*
* \param[in] tc Unit test context, returned by ttest_start.
* \return true.
*/
static inline bool ttest_pass(
const ttest_ctx_t *tc)
{
assert(tc != NULL);
assert(tc->report != NULL);
tc->report->passed++;
if (tc->cleanup != NULL) {
tc->cleanup(tc->cleanup_data);
}
if (tc->report->quiet == false) {
fprintf(stderr, " PASS: %s\n", tc->name);
}
return true;
}
/**
* Fail a unit test.
*
* This function competes a unit test. It should be called when a test fails.
* This function always returns false. Prints the test result.
*
* \param[in] tc Unit test context, returned by ttest_start.
* \param[in] reason Format string to explain reason for test failure.
* \param[in] ... Additional arguments for formatted rendering.
* \return false.
*/
static inline bool ttest_fail(
const ttest_ctx_t *tc,
const char *reason, ...)
{
va_list args;
assert(tc != NULL);
assert(tc->report != NULL);
fprintf(stderr, " FAIL: %s (", tc->name);
va_start(args, reason);
vfprintf(stderr, reason, args);
va_end(args);
fprintf(stderr, ")\n");
/* Cleanup after printing result, in case `reason` refers to cleaned up
* memory. */
if (tc->cleanup != NULL) {
tc->cleanup(tc->cleanup_data);
}
return false;
}
/**
* Make a unit test as unimplemented.
*
* This function competes a unit test. Should be called on unimplemented tests.
* This function always returns true.
*
* \param[in] tc Unit test context, returned by ttest_start.
* \return true.
*/
static inline bool ttest_todo(
const ttest_ctx_t *tc)
{
assert(tc != NULL);
assert(tc->report != NULL);
tc->report->todo++;
if (tc->cleanup != NULL) {
tc->cleanup(tc->cleanup_data);
}
if (tc->report->quiet == false) {
fprintf(stderr, " TODO: %s\n", tc->name);
}
return true;
}
/**
* Print a visual divider in the test output.
*/
static inline void ttest_divider(void)
{
fprintf(stderr, "========================================"
"========================================\n");
}
/**
* Print a test heading.
*
* \param[in] tr The tlsa-test report context.
* \param[in] heading The heading to print.
*/
static inline void ttest_heading(
const ttest_report_ctx_t *tr,
const char *heading)
{
if (tr->test_list == NULL || tr->test_list_len == 0) {
if (!tr->quiet) {
ttest_divider();
fprintf(stderr, "TEST: %s\n", heading);
ttest_divider();
}
}
}
/**
* Print the test report summary.
*
* \param[in] tr The tlsa-test report context.
*/
static inline void ttest_report(
const ttest_report_ctx_t *tr)
{
ttest_divider();
if (tr->todo > 0) {
fprintf(stderr, "TODO: %u test%s unimplemented.\n",
tr->todo, (tr->todo > 1) ? "s" : "");
}
fprintf(stderr, "%s: %u of %u tests passed.\n",
(tr->passed == tr->tests - tr->todo) ? "PASS" : "FAIL",
tr->passed, tr->tests - tr->todo);
ttest_divider();
}
#endif

View File

@ -0,0 +1,280 @@
/*
* 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 "../../src/utf8.h"
#include "ttest.h"
#include "test.h"
/** Helper macro to squash unused variable warnings. */
#define UNUSED(_x) ((void)(_x))
/** Helper macro to get the length of string string literals. */
#define SLEN(_s) (CYAML_ARRAY_LEN(_s) - 1)
/**
* Test utf-8 decoding.
*
* \param[in] report The test report context.
* \return true if test passes, false otherwise.
*/
static bool test_utf8_get_codepoint(
ttest_report_ctx_t *report)
{
static const struct tests {
unsigned c;
const char *s;
unsigned l;
} t[] = {
{ 0xfffd, "\ufffd", SLEN("\ufffd") },
{ 0xfffd, "\xC1\x9C", SLEN("\xC1\x9C") },
{ 0x1f638, u8"😸", SLEN(u8"😸") },
{ 0xfffd, u8"😸", 0 },
{ 0xfffd, u8"😸", 5 },
};
bool pass = true;
for (unsigned i = 0; i < CYAML_ARRAY_LEN(t); i++) {
unsigned l;
unsigned c;
ttest_ctx_t tc;
char name[sizeof(__func__) + 32];
sprintf(name, "%s_%u", __func__, i);
if (!ttest_start(report, name, NULL, NULL, &tc)) {
continue;
}
l = t[i].l;
c = cyaml_utf8_get_codepoint((uint8_t *)t[i].s, &l);
if (c != t[i].c) {
pass &= ttest_fail(&tc, "Incorrect codepoint for %s "
"(expecting %4.4x, got %4.4x)",
t[i].s, t[i].c, c);
continue;
}
pass &= ttest_pass(&tc);
}
return pass;
}
/**
* Test comparing the same strings.
*
* \param[in] report The test report context.
* \return true if test passes, false otherwise.
*/
static bool test_utf8_strcmp_same(
ttest_report_ctx_t *report)
{
const char *strings[] = {
"Simple",
"test",
"This is a LONGER string, if you see what I mean.",
"29087 lsdkfj </,.{}'#\"|@>",
u8"ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß",
u8"àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ",
u8"¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿",
"\xc3\0",
u8"αβγδε",
u8"¯\\_(ツ)_/¯",
"\xfa",
u8"😸",
};
bool pass = true;
for (unsigned i = 0; i < CYAML_ARRAY_LEN(strings); i++) {
ttest_ctx_t tc;
char name[sizeof(__func__) + 32];
sprintf(name, "%s_%u", __func__, i);
if (!ttest_start(report, name, NULL, NULL, &tc)) {
continue;
}
if (cyaml_utf8_casecmp(strings[i], strings[i]) != 0) {
pass &= ttest_fail(&tc, "Failed to match: %s",
strings[i]);
continue;
}
pass &= ttest_pass(&tc);
}
return pass;
}
/**
* Test comparing strings that match.
*
* \param[in] report The test report context.
* \return true if test passes, false otherwise.
*/
static bool test_utf8_strcmp_matches(
ttest_report_ctx_t *report)
{
static const struct string_pairs {
const char *a;
const char *b;
} pairs[] = {
{ "", "" },
{ "This is a TEST", "this is A test" },
{ u8"ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ", u8"àáâãäåæçèéêëìíîïðñòóôõö" },
{ u8"ĀĂĄĆĈĊČĎĐĒĔĖĘĚĜĞ", u8"āăąćĉċčďđēĕėęěĝğ" },
{ u8"ijĵķĸĺļľ", u8"IJĴĶĸĹĻĽ" },
{ u8"ŊŌŎŐŒŔŖŘŚŜŞŠŢŤŦŨŪŬŮŰŲŴŶ", u8"ŋōŏőœŕŗřśŝşšţťŧũūŭůűųŵŷ" },
{ u8"űųŵŷŸźżž", u8"űųŵŷÿŹŻŽ" },
{ u8"ƂƄ ơƣƥ", u8"ƃƅ ƠƢƤ" },
{ u8"ǞǠǢǤǦǨǪǬǮ", u8"ǟǡǣǥǧǩǫǭǯ" },
{ u8"ǸǺǼǾȀȂȄȆȈȊȌȎȐȒȔȖȘȚȜȞ", u8"ǹǻǽǿȁȃȅȇȉȋȍȏȑȓȕȗșțȝȟ" },
{ u8"ȢȤȦȨȪȬȮȰȲ", u8"ȣȥȧȩȫȭȯȱȳ" },
{ u8"ɇɉɋɍɏ", u8"ɆɈɊɌɎ" },
{ u8"ƯźżžƳƵ", u8"ưŹŻŽƴƶ" },
{ u8"ǍǏǑǓǕǗǙǛ", u8"ǎǐǒǔǖǘǚǜ" },
{ u8"\u0178", u8"\u00ff" },
{ u8"\u0187", u8"\u0188" },
{ u8"\u018b", u8"\u018c" },
{ u8"\u018e", u8"\u01dd" },
{ u8"\u0191", u8"\u0192" },
{ u8"\u0198", u8"\u0199" },
{ u8"\u01a7", u8"\u01a8" },
{ u8"\u01ac", u8"\u01ad" },
{ u8"\u01af", u8"\u01b0" },
{ u8"\u01b7", u8"\u0292" },
{ u8"\u01b8", u8"\u01b9" },
{ u8"\u01bc", u8"\u01bd" },
{ u8"\u01c4", u8"\u01c6" },
{ u8"\u01c5", u8"\u01c6" },
{ u8"\u01c7", u8"\u01c9" },
{ u8"\u01c8", u8"\u01c9" },
{ u8"\u01ca", u8"\u01cc" },
{ u8"\u01cb", u8"\u01cc" },
{ u8"\u01f1", u8"\u01f3" },
{ u8"\u01f2", u8"\u01f3" },
{ u8"\u01f4", u8"\u01f5" },
{ u8"\u01f7", u8"\u01bf" },
{ u8"\u0220", u8"\u019e" },
{ u8"\u023b", u8"\u023c" },
{ u8"\u023d", u8"\u019a" },
{ u8"\u0241", u8"\u0242" },
{ u8"\u0243", u8"\u0180" },
{ "\xF0\x9F\x98\xB8", "\xF0\x9F\x98\xB8" },
{ "\xF0\x00\x98\xB8", "\xF0\x00\x98\xB8" },
{ "\xF0\x9F\x00\xB8", "\xF0\x9F\x00\xB8" },
{ "\xF0\x9F\x98\x00", "\xF0\x9F\x98\x00" },
{ "\xE2\x9F\x9A", "\xE2\x9F\x9A" },
{ "\xE2\x00\x9A", "\xE2\x00\x9A" },
{ "\xE2\x9F\x00", "\xE2\x9F\x00" },
{ "A\xc2""C", "A\xc2""C" },
{ "A\xc2""C", u8"A\ufffdC" },
{ u8"A\ufffdC", "A\xc2""C" },
};
bool pass = true;
for (unsigned i = 0; i < CYAML_ARRAY_LEN(pairs); i++) {
ttest_ctx_t tc;
char name[sizeof(__func__) + 32];
sprintf(name, "%s_%u", __func__, i);
if (!ttest_start(report, name, NULL, NULL, &tc)) {
continue;
}
if (cyaml_utf8_casecmp(pairs[i].a, pairs[i].b) != 0) {
pass &= ttest_fail(&tc, "Failed to match strings: "
"%s and %s", pairs[i].a, pairs[i].b);
continue;
}
pass &= ttest_pass(&tc);
}
return pass;
}
/**
* Test comparing strings that match.
*
* \param[in] report The test report context.
* \return true if test passes, false otherwise.
*/
static bool test_utf8_strcmp_mismatches(
ttest_report_ctx_t *report)
{
static const struct string_pairs {
const char *a;
const char *b;
} pairs[] = {
{ "Invalid", "\xfa" },
{ "Cat", u8"😸" },
{ "cat", u8"😸" },
{ "1 cat", u8"😸" },
{ "[cat]", u8"😸" },
{ "Ü cat", u8"😸" },
{ "Ü cat", u8"😸" },
{ "\\", "\xC1\x9C" },
};
bool pass = true;
for (unsigned i = 0; i < CYAML_ARRAY_LEN(pairs); i++) {
ttest_ctx_t tc;
char name[sizeof(__func__) + 32];
sprintf(name, "%s_%u", __func__, i);
if (!ttest_start(report, name, NULL, NULL, &tc)) {
continue;
}
if (cyaml_utf8_casecmp(pairs[i].a, pairs[i].b) == 0) {
pass &= ttest_fail(&tc, "Failed to detect mismatch: "
"%s and %s", pairs[i].a, pairs[i].b);
continue;
}
pass &= ttest_pass(&tc);
}
return pass;
}
/**
* Run the CYAML util unit 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 utf8_tests(
ttest_report_ctx_t *rc,
cyaml_log_t log_level,
cyaml_log_fn_t log_fn)
{
bool pass = true;
UNUSED(log_level);
UNUSED(log_fn);
ttest_heading(rc, "UTF-8 tests: Codepoint composition");
pass &= test_utf8_get_codepoint(rc);
ttest_heading(rc, "UTF-8 tests: String comparison");
pass &= test_utf8_strcmp_same(rc);
pass &= test_utf8_strcmp_matches(rc);
pass &= test_utf8_strcmp_mismatches(rc);
return pass;
}

View File

@ -0,0 +1,300 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (C) 2018-2021 Michael Drake <tlsa@netsurf-browser.org>
*/
#include <stdbool.h>
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <cyaml/cyaml.h>
#include "../../src/mem.h"
#include "../../src/util.h"
#include "ttest.h"
#include "test.h"
/**
* Test cyaml memory functions.
*
* \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_util_memory_funcs(
ttest_report_ctx_t *report,
const cyaml_config_t *config)
{
ttest_ctx_t tc;
unsigned char *mem, *tmp;
if (!ttest_start(report, __func__, NULL, NULL, &tc)) return true;
/* Create test allocation */
mem = cyaml__alloc(config, 0xff, true);
if (mem == NULL) {
return ttest_fail(&tc, "Memory allocation failed.");
}
/* Check allocation was zeroed. */
for (unsigned i = 0; i < 0x7f; i++) {
if (mem[i] != 0) {
return ttest_fail(&tc, "Allocation not cleaned.");
}
}
/* Set our own known values */
for (unsigned i = 0; i < 0xff; i++) {
mem[i] = 0xff;
}
/* Shrink allocation */
tmp = cyaml__realloc(config, mem, 0xff, 0x7f, true);
if (tmp == NULL) {
return ttest_fail(&tc, "Allocation shrink failed.");
}
mem = tmp;
/* Check for our own values. */
for (unsigned i = 0; i < 0x7f; i++) {
if (mem[i] != 0xff) {
return ttest_fail(&tc, "Data trampled by realloc.");
}
}
/* Free test allocation. */
cyaml__free(config, mem);
return ttest_pass(&tc);
}
/**
* Test cyaml memory functions.
*
* \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_util_strdup(
ttest_report_ctx_t *report,
const cyaml_config_t *config)
{
ttest_ctx_t tc;
const char *orig = "Hello!";
char *copy;
if (!ttest_start(report, __func__, NULL, NULL, &tc)) return true;
/* Create test allocation */
copy = cyaml__strdup(config, orig, NULL);
if (copy == NULL) {
return ttest_fail(&tc, "Memory allocation failed.");
}
/* Confirm string duplication. */
if (strcmp(orig, copy) != 0) {
return ttest_fail(&tc, "String not copied correctly.");
}
/* Free test allocation. */
cyaml__free(config, copy);
return ttest_pass(&tc);
}
/**
* Test cyaml memory functions.
*
* \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_util_strdup_len(
ttest_report_ctx_t *report,
const cyaml_config_t *config)
{
ttest_ctx_t tc;
const char *orig = "Hello!";
char *copy;
size_t len;
if (!ttest_start(report, __func__, NULL, NULL, &tc)) return true;
/* Create test allocation */
copy = cyaml__strdup(config, orig, &len);
if (copy == NULL) {
return ttest_fail(&tc, "Memory allocation failed.");
}
/* Confirm string duplication. */
if (strcmp(orig, copy) != 0) {
return ttest_fail(&tc, "String not copied correctly.");
}
/* Confirm string length. */
if (strlen(orig) != len) {
return ttest_fail(&tc, "Bad string length.");
}
/* Free test allocation. */
cyaml__free(config, copy);
return ttest_pass(&tc);
}
/**
* Test invalid state machine state.
*
* \param[in] report The test report context.
* \return true if test passes, false otherwise.
*/
static bool test_util_state_invalid(
ttest_report_ctx_t *report)
{
ttest_ctx_t tc;
if (!ttest_start(report, __func__, NULL, NULL, &tc)) return true;
if (strcmp(cyaml__state_to_str(CYAML_STATE__COUNT),
"<invalid>") != 0) {
return ttest_fail(&tc, "Wrong string for invalid state.");
}
if (strcmp(cyaml__state_to_str(CYAML_STATE__COUNT + 1),
"<invalid>") != 0) {
return ttest_fail(&tc, "Wrong string for invalid state.");
}
return ttest_pass(&tc);
}
/**
* Test CYAML_OK error code.
*
* \param[in] report The test report context.
* \return true if test passes, false otherwise.
*/
static bool test_util_err_success_zero(
ttest_report_ctx_t *report)
{
ttest_ctx_t tc;
if (!ttest_start(report, __func__, NULL, NULL, &tc)) return true;
if (CYAML_OK != 0) {
return ttest_fail(&tc, "CYAML_OK value not zero");
}
return ttest_pass(&tc);
}
/**
* Test valid cyaml_strerror strings.
*
* \param[in] report The test report context.
* \return true if test passes, false otherwise.
*/
static bool test_util_err_strings_valid(
ttest_report_ctx_t *report)
{
ttest_ctx_t tc;
if (!ttest_start(report, __func__, NULL, NULL, &tc)) return true;
if (cyaml_strerror(CYAML_OK) == NULL) {
return ttest_fail(&tc, "CYAML_OK string is NULL");
}
if (strcmp(cyaml_strerror(CYAML_OK), "Success") != 0) {
return ttest_fail(&tc, "CYAML_OK string not 'Success'");
}
for (unsigned i = 1; i <= CYAML_ERR__COUNT; i++) {
if (cyaml_strerror(i) == NULL) {
return ttest_fail(&tc, "Error code %u string is NULL",
i);
}
for (unsigned j = 0; j < i; j++) {
if (strcmp(cyaml_strerror(j), cyaml_strerror(i)) == 0) {
return ttest_fail(&tc, "Two error codes "
"have same string (%u and %u)",
i, j);
}
}
}
return ttest_pass(&tc);
}
/**
* Test invalid cyaml_strerror strings.
*
* \param[in] report The test report context.
* \return true if test passes, false otherwise.
*/
static bool test_util_err_strings_invalid(
ttest_report_ctx_t *report)
{
ttest_ctx_t tc;
if (!ttest_start(report, __func__, NULL, NULL, &tc)) return true;
if (strcmp(cyaml_strerror(CYAML_ERR__COUNT),
"Invalid error code") != 0) {
return ttest_fail(&tc, "CYAML_ERR__COUNT string not "
"'Invalid error code'");
}
if (strcmp(cyaml_strerror(CYAML_ERR__COUNT + 1),
"Invalid error code") != 0) {
return ttest_fail(&tc, "CYAML_ERR__COUNT + 1 string not "
"'Invalid error code'");
}
if (strcmp(cyaml_strerror((cyaml_err_t)-1), "Invalid error code") != 0) {
return ttest_fail(&tc, "-1 string not 'Invalid error code'");
}
return ttest_pass(&tc);
}
/**
* Run the CYAML util unit 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 util_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, "Memory utility functions");
pass &= test_util_strdup(rc, &config);
pass &= test_util_strdup_len(rc, &config);
pass &= test_util_memory_funcs(rc, &config);
ttest_heading(rc, "Error code tests");
pass &= test_util_state_invalid(rc);
pass &= test_util_err_success_zero(rc);
pass &= test_util_err_strings_valid(rc);
pass &= test_util_err_strings_invalid(rc);
return pass;
}

1
objs/libcyaml.so Symbolic link
View File

@ -0,0 +1 @@
/home/joe/Development/Ennix/objs/libcyaml.so.1

BIN
objs/libcyaml.so.1 Executable file

Binary file not shown.