Merge pull request #332 from djmitche/cdylib

build a C interface to taskchampion
This commit is contained in:
Dustin J. Mitchell 2022-03-03 19:43:22 -05:00 committed by GitHub
commit a7f353bd6e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 9683 additions and 34 deletions

2
.cargo/config Normal file
View file

@ -0,0 +1,2 @@
[alias]
xtask = "run --package xtask --"

82
Cargo.lock generated
View file

@ -592,10 +592,29 @@ dependencies = [
]
[[package]]
name = "cc"
version = "1.0.68"
name = "cbindgen"
version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a72c244c1ff497a746a7e1fb3d14bd08420ecda70c8f25c7112f2781652d787"
checksum = "51e3973b165dc0f435831a9e426de67e894de532754ff7a3f307c03ee5dec7dc"
dependencies = [
"clap",
"heck",
"indexmap",
"log",
"proc-macro2",
"quote",
"serde",
"serde_json",
"syn",
"tempfile",
"toml",
]
[[package]]
name = "cc"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
dependencies = [
"jobserver",
]
@ -1501,6 +1520,24 @@ dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "integration-tests"
version = "0.4.1"
dependencies = [
"actix-rt",
"actix-web",
"anyhow",
"cc",
"env_logger 0.8.4",
"lazy_static",
"log",
"pretty_assertions",
"taskchampion",
"taskchampion-lib",
"taskchampion-sync-server",
"tempfile",
]
[[package]]
name = "iovec"
version = "0.1.4"
@ -1598,9 +1635,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.97"
version = "0.2.113"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "12b8adadd720df158f4d70dfe7ccc6adb0472d7c55ca83445f6a5ab3e36f8fb6"
checksum = "eef78b64d87775463c549fbd80e19249ef436ea3bf1de2a1eb7e717ec7fab1e9"
[[package]]
name = "libgit2-sys"
@ -2460,21 +2497,6 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "replica-server-tests"
version = "0.4.1"
dependencies = [
"actix-rt",
"actix-web",
"anyhow",
"env_logger 0.8.4",
"log",
"pretty_assertions",
"taskchampion",
"taskchampion-sync-server",
"tempfile",
]
[[package]]
name = "resolv-conf"
version = "0.7.0"
@ -3008,6 +3030,18 @@ dependencies = [
"toml_edit",
]
[[package]]
name = "taskchampion-lib"
version = "0.1.0"
dependencies = [
"anyhow",
"chrono",
"libc",
"pretty_assertions",
"taskchampion",
"uuid",
]
[[package]]
name = "taskchampion-sync-server"
version = "0.4.1"
@ -3762,6 +3796,14 @@ dependencies = [
"time 0.1.43",
]
[[package]]
name = "xtask"
version = "0.4.1"
dependencies = [
"anyhow",
"cbindgen",
]
[[package]]
name = "zeroize"
version = "1.3.0"

View file

@ -4,5 +4,7 @@ members = [
"taskchampion",
"cli",
"sync-server",
"replica-server-tests"
"lib",
"integration-tests",
"xtask",
]

View file

@ -17,12 +17,32 @@ But, if you just want to get some practice with Rust, we'd be happy to have you.
## Structure
There are four crates here:
There are five crates here:
* [taskchampion](./taskchampion) - the core of the tool
* [taskchampion-cli](./cli) - the command-line binary
* [taskchampion-sync-server](./sync-server) - the server against which `task sync` operates
* [replica-server-tests](./replica-server-tests) - integration tests covering both _taskchampion-cli_ and _taskchampion-sync-server_
* [taskchampion-lib](./lib) - glue code to use _taskchampion_ from C
* [integration-tests](./integration-tests) - integration tests covering _taskchampion-cli_, _taskchampion-sync-server_, and _taskchampion-lib_.
## Code Generation
The _taskchampion_lib_ crate uses a bit of code generation to create the `lib/taskchampion.h` header file.
To regenerate this file, run `cargo xtask codegen`.
## C libraries
The `taskchampion-lib` crate generates libraries suitable for use from C (or any C-compatible language).
The necessary bits are:
* a shared object in `target/$PROFILE/deps` (e.g., `target/debug/deps/libtaskchampion.so`)
* a static library in `target/$PROFILE` (e.g., `target/debug/libtaskchampion.a`)
* a header file, `lib/taskchampion.h`.
Downstream consumers may use either the static or dynamic library, as they prefer.
NOTE: on Windows, the "BCrypt" library must be included when linking to taskchampion.
## Documentation Generation

2
integration-tests/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
test-db
test-sync-server

View file

@ -1,15 +1,14 @@
[package]
name = "replica-server-tests"
name = "integration-tests"
version = "0.4.1"
authors = ["Dustin J. Mitchell <dustin@mozilla.com>"]
edition = "2018"
publish = false
build = "build.rs"
[dependencies.taskchampion-sync-server]
path = "../sync-server"
[dependencies.taskchampion]
path = "../taskchampion"
[dependencies]
taskchampion = { path = "../taskchampion" }
taskchampion-sync-server = { path = "../sync-server" }
[dev-dependencies]
anyhow = "1.0"
@ -19,3 +18,8 @@ tempfile = "3"
pretty_assertions = "1"
log = "^0.4.14"
env_logger = "^0.8.3"
lazy_static = "1"
[build-dependencies]
cc = "1.0.73"
taskchampion-lib = { path = "../lib" }

View file

@ -0,0 +1,30 @@
# Integration Tests for Taskchampion
## "Regular" Tests
Some of the tests in `tests/` are just regular integration tests.
Nothing exciting to see.
## Bindings Tests
The bindings tests are a bit more interesting, since they are written in C.
They are composed of a collection of "suites", each in one C file in `integration-tests/src/bindings_tests/`.
Each suite contains a number of tests (using [Unity](http://www.throwtheswitch.org/unity)) and an exported function named after the suite that returns an exit status (1 = failure).
The build script (`integration-tests/build.rs`) builds these files into a library that is linked with the `integration_tests` library crate.
This crate contains a `bindings_tests` module with a pub function for each suite.
Finally, the `integration-tests/tests/bindings.rs` test file calls each of those functions in a separate test case.
### Adding Tests
To add a test, select a suite and add a new test-case function.
Add a `RUN_TEST` invocation for your new function to the `.._tests` function at the bottom.
Keep the `RUN_TEST`s in the same order as the functions they call.
### Adding Suites
To add a suite,
1. Add a new C file in `integration-tests/src/bindings_tests/`, based off of one of the others.
1. Add a the suite name to `suites` in `integration-tests/build.rs`.

View file

@ -0,0 +1,73 @@
use std::env;
use std::fs;
use std::path::Path;
/// Link to the libtaskchampion library produced by the `taskchampion-lib` crate. This is done as
/// a build dependency, rather than a cargo dependency, so that the symbols are available to
/// bindings-tests.
fn link_libtaskchampion() {
// This crate has taskchampion-lib in its build-dependencies, so libtaskchampion.so should be
// built already.
//
// Shared libraries (crate-type=cdylib) appear to be placed in target/$PROFILE/deps.
let mut libtc_dir = env::current_dir().unwrap();
libtc_dir.pop();
libtc_dir.push("target");
libtc_dir.push(env::var("PROFILE").unwrap());
libtc_dir.push("deps");
let libtc_dir = libtc_dir.to_str().expect("path is valid utf-8");
println!("cargo:rustc-link-search={}", libtc_dir);
println!("cargo:rustc-link-lib=dylib=taskchampion");
// on windows, it appears that rust std requires BCrypt
if cfg!(target_os = "windows") {
println!("cargo:rustc-link-lib=dylib=bcrypt");
}
}
/// Build the Unity-based C test suite in `src/bindings_tests`, linking the result with this
/// package's library crate.
fn build_bindings_tests(suites: &[&'static str]) {
let mut build = cc::Build::new();
build.include("../lib"); // include path for taskchampion.h
build.include("src/bindings_tests/unity");
build.define("UNITY_OUTPUT_CHAR", "test_output");
build.define(
"UNITY_OUTPUT_CHAR_HEADER_DECLARATION",
"test_output(char c)",
);
build.file("src/bindings_tests/unity/unity.c");
let mut files = vec!["src/bindings_tests/test.c".to_string()];
for suite in suites {
files.push(format!("src/bindings_tests/{}.c", suite));
}
for file in files {
build.file(&file);
println!("cargo:rerun-if-changed={}", file);
}
build.compile("bindings-tests");
}
/// Make `bindings_test_suites.rs` listing all of the test suites, for use in building the
/// bindings-test binary.
fn make_suite_file(suites: &[&'static str]) {
let out_dir = env::var_os("OUT_DIR").unwrap();
let dest_path = Path::new(&out_dir).join("bindings_test_suites.rs");
let mut content = String::new();
for suite in suites {
content.push_str(format!("suite!({}_tests);\n", suite).as_ref());
}
fs::write(&dest_path, content).unwrap();
}
fn main() {
println!("cargo:rerun-if-changed=build.rs");
let suites = &["uuid", "string", "task", "replica"];
link_libtaskchampion();
build_bindings_tests(suites);
make_suite_file(suites);
}

View file

@ -0,0 +1,30 @@
use std::fs;
extern "C" {
// set up to send test output to TEST-OUTPUT
fn setup_output();
// close the output file
fn finish_output();
}
// Each suite is represented by a <name>_tests C function in <name>.c.
// All of these C files are built into a library that is linked to the crate -- but not to test
// crates. So, this macro produces a "glue function" that calls the C function, and that can be
// called from test crates.
macro_rules! suite(
{ $s:ident } => {
pub fn $s() -> (i32, String) {
extern "C" {
fn $s() -> i32;
}
unsafe { setup_output() };
let res = unsafe { $s() };
unsafe { finish_output() };
let output = fs::read_to_string("TEST-OUTPUT")
.unwrap_or_else(|e| format!("could not open TEST-OUTPUT: {}", e));
(res, output)
}
};
);
include!(concat!(env!("OUT_DIR"), "/bindings_test_suites.rs"));

View file

@ -0,0 +1,328 @@
#include <stdlib.h>
#include <sys/stat.h>
#include <string.h>
#include "taskchampion.h"
#include "unity.h"
// creating an in-memory replica does not crash
static void test_replica_creation(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NOT_NULL(rep);
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
tc_replica_free(rep);
}
// creating an on-disk replica does not crash
static void test_replica_creation_disk(void) {
TCReplica *rep = tc_replica_new_on_disk(tc_string_borrow("test-db"), NULL);
TEST_ASSERT_NOT_NULL(rep);
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
tc_replica_free(rep);
}
// undo on an empty in-memory TCReplica does nothing
static void test_replica_undo_empty(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
int undone;
int rv = tc_replica_undo(rep, &undone);
TEST_ASSERT_EQUAL(TC_RESULT_OK, rv);
TEST_ASSERT_EQUAL(0, undone);
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
tc_replica_free(rep);
}
// adding an undo point succeeds
static void test_replica_add_undo_point(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_add_undo_point(rep, true));
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
tc_replica_free(rep);
}
// working set operations succeed
static void test_replica_working_set(void) {
TCWorkingSet *ws;
TCTask *task1, *task2, *task3;
TCUuid uuid, uuid1, uuid2, uuid3;
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_rebuild_working_set(rep, true));
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
ws = tc_replica_working_set(rep);
TEST_ASSERT_EQUAL(0, tc_working_set_len(ws));
tc_working_set_free(ws);
task1 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("task1"));
TEST_ASSERT_NOT_NULL(task1);
uuid1 = tc_task_get_uuid(task1);
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_rebuild_working_set(rep, true));
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
task2 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("task2"));
TEST_ASSERT_NOT_NULL(task2);
uuid2 = tc_task_get_uuid(task2);
task3 = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("task3"));
TEST_ASSERT_NOT_NULL(task3);
uuid3 = tc_task_get_uuid(task3);
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_rebuild_working_set(rep, false));
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
// finish task2 to leave a "hole"
tc_task_to_mut(task2, rep);
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_done(task2));
tc_task_to_immut(task2);
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_replica_rebuild_working_set(rep, false));
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
tc_task_free(task1);
tc_task_free(task2);
tc_task_free(task3);
// working set should now be
// 0 -> None
// 1 -> uuid1
// 2 -> None
// 3 -> uuid3
ws = tc_replica_working_set(rep);
TEST_ASSERT_EQUAL(2, tc_working_set_len(ws));
TEST_ASSERT_EQUAL(3, tc_working_set_largest_index(ws));
TEST_ASSERT_FALSE(tc_working_set_by_index(ws, 0, &uuid));
TEST_ASSERT_TRUE(tc_working_set_by_index(ws, 1, &uuid));
TEST_ASSERT_EQUAL_MEMORY(uuid1.bytes, uuid.bytes, sizeof(uuid));
TEST_ASSERT_FALSE(tc_working_set_by_index(ws, 2, &uuid));
TEST_ASSERT_TRUE(tc_working_set_by_index(ws, 3, &uuid));
TEST_ASSERT_EQUAL_MEMORY(uuid3.bytes, uuid.bytes, sizeof(uuid));
TEST_ASSERT_EQUAL(1, tc_working_set_by_uuid(ws, uuid1));
TEST_ASSERT_EQUAL(0, tc_working_set_by_uuid(ws, uuid2));
TEST_ASSERT_EQUAL(3, tc_working_set_by_uuid(ws, uuid3));
tc_working_set_free(ws);
tc_replica_free(rep);
}
// When tc_replica_undo is passed NULL for undone_out, it still succeeds
static void test_replica_undo_empty_null_undone_out(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
int rv = tc_replica_undo(rep, NULL);
TEST_ASSERT_EQUAL(TC_RESULT_OK, rv);
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
tc_replica_free(rep);
}
// creating a task succeeds and the resulting task looks good
static void test_replica_task_creation(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
TCTask *task = tc_replica_new_task(
rep,
TC_STATUS_PENDING,
tc_string_borrow("my task"));
TEST_ASSERT_NOT_NULL(task);
TCUuid uuid = tc_task_get_uuid(task);
TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task));
TCString desc = tc_task_get_description(task);
TEST_ASSERT_NOT_NULL(desc.ptr);
TEST_ASSERT_EQUAL_STRING("my task", tc_string_content(&desc));
tc_string_free(&desc);
tc_task_free(task);
// get the task again and verify it
task = tc_replica_get_task(rep, uuid);
TEST_ASSERT_NOT_NULL(task);
TEST_ASSERT_EQUAL_MEMORY(uuid.bytes, tc_task_get_uuid(task).bytes, sizeof(uuid.bytes));
TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task));
tc_task_free(task);
tc_replica_free(rep);
}
// When tc_replica_undo is passed NULL for undone_out, it still succeeds
static void test_replica_sync_local(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
mkdir("test-sync-server", 0755); // ignore error, if dir already exists
TCString err;
TCServer *server = tc_server_new_local(tc_string_borrow("test-sync-server"), &err);
TEST_ASSERT_NOT_NULL(server);
TEST_ASSERT_NULL(err.ptr);
int rv = tc_replica_sync(rep, server, false);
TEST_ASSERT_EQUAL(TC_RESULT_OK, rv);
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
tc_server_free(server);
tc_replica_free(rep);
// test error handling
server = tc_server_new_local(tc_string_borrow("/no/such/directory"), &err);
TEST_ASSERT_NULL(server);
TEST_ASSERT_NOT_NULL(err.ptr);
tc_string_free(&err);
}
// When tc_replica_undo is passed NULL for undone_out, it still succeeds
static void test_replica_remote_server(void) {
TCString err;
TCServer *server = tc_server_new_remote(
tc_string_borrow("tc.freecinc.com"),
tc_uuid_new_v4(),
tc_string_borrow("\xf0\x28\x8c\x28"), // NOTE: not utf-8
&err);
TEST_ASSERT_NOT_NULL(server);
TEST_ASSERT_NULL(err.ptr);
// can't actually do anything with this server!
tc_server_free(server);
}
// a replica with tasks in it returns an appropriate list of tasks and list of uuids
static void test_replica_all_tasks(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
TCTask *task1 = tc_replica_new_task(
rep,
TC_STATUS_PENDING,
tc_string_borrow("task1"));
TEST_ASSERT_NOT_NULL(task1);
TCUuid uuid1 = tc_task_get_uuid(task1);
tc_task_free(task1);
TCTask *task2 = tc_replica_new_task(
rep,
TC_STATUS_PENDING,
tc_string_borrow("task2"));
TEST_ASSERT_NOT_NULL(task2);
TCUuid uuid2 = tc_task_get_uuid(task2);
tc_task_free(task2);
{
TCTaskList tasks = tc_replica_all_tasks(rep);
TEST_ASSERT_NOT_NULL(tasks.items);
TEST_ASSERT_EQUAL(2, tasks.len);
bool seen1, seen2 = false;
for (size_t i = 0; i < tasks.len; i++) {
TCTask *task = tasks.items[i];
TCString descr = tc_task_get_description(task);
if (0 == strcmp(tc_string_content(&descr), "task1")) {
seen1 = true;
} else if (0 == strcmp(tc_string_content(&descr), "task2")) {
seen2 = true;
}
tc_string_free(&descr);
}
TEST_ASSERT_TRUE(seen1);
TEST_ASSERT_TRUE(seen2);
tc_task_list_free(&tasks);
TEST_ASSERT_NULL(tasks.items);
}
{
TCUuidList uuids = tc_replica_all_task_uuids(rep);
TEST_ASSERT_NOT_NULL(uuids.items);
TEST_ASSERT_EQUAL(2, uuids.len);
bool seen1, seen2 = false;
for (size_t i = 0; i < uuids.len; i++) {
TCUuid uuid = uuids.items[i];
if (0 == memcmp(&uuid1, &uuid, sizeof(TCUuid))) {
seen1 = true;
} else if (0 == memcmp(&uuid2, &uuid, sizeof(TCUuid))) {
seen2 = true;
}
}
TEST_ASSERT_TRUE(seen1);
TEST_ASSERT_TRUE(seen2);
tc_uuid_list_free(&uuids);
TEST_ASSERT_NULL(uuids.items);
}
tc_replica_free(rep);
}
// importing a task succeeds and the resulting task looks good
static void test_replica_task_import(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
TCUuid uuid;
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_uuid_from_str(tc_string_borrow("23cb25e0-5d1a-4932-8131-594ac6d3a843"), &uuid));
TCTask *task = tc_replica_import_task_with_uuid(rep, uuid);
TEST_ASSERT_NOT_NULL(task);
TEST_ASSERT_EQUAL_MEMORY(uuid.bytes, tc_task_get_uuid(task).bytes, sizeof(uuid.bytes));
TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task));
TCString desc = tc_task_get_description(task);
TEST_ASSERT_NOT_NULL(desc.ptr);
TEST_ASSERT_EQUAL_STRING("", tc_string_content(&desc)); // default value
tc_string_free(&desc);
tc_task_free(task);
// get the task again and verify it
task = tc_replica_get_task(rep, uuid);
TEST_ASSERT_NOT_NULL(task);
TEST_ASSERT_EQUAL_MEMORY(uuid.bytes, tc_task_get_uuid(task).bytes, sizeof(uuid.bytes));
TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task));
tc_task_free(task);
tc_replica_free(rep);
}
// importing a task succeeds and the resulting task looks good
static void test_replica_get_task_not_found(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
TCUuid uuid;
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_uuid_from_str(tc_string_borrow("23cb25e0-5d1a-4932-8131-594ac6d3a843"), &uuid));
TCTask *task = tc_replica_get_task(rep, uuid);
TEST_ASSERT_NULL(task);
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
tc_replica_free(rep);
}
int replica_tests(void) {
UNITY_BEGIN();
// each test case above should be named here, in order.
RUN_TEST(test_replica_creation);
RUN_TEST(test_replica_creation_disk);
RUN_TEST(test_replica_undo_empty);
RUN_TEST(test_replica_add_undo_point);
RUN_TEST(test_replica_working_set);
RUN_TEST(test_replica_undo_empty_null_undone_out);
RUN_TEST(test_replica_task_creation);
RUN_TEST(test_replica_sync_local);
RUN_TEST(test_replica_remote_server);
RUN_TEST(test_replica_all_tasks);
RUN_TEST(test_replica_task_import);
RUN_TEST(test_replica_get_task_not_found);
return UNITY_END();
}

View file

@ -0,0 +1,125 @@
#include <stdlib.h>
#include <string.h>
#include "unity.h"
#include "taskchampion.h"
// creating strings does not crash
static void test_string_creation(void) {
TCString s = tc_string_borrow("abcdef");
tc_string_free(&s);
TEST_ASSERT_NULL(s.ptr);
}
// creating cloned strings does not crash
static void test_string_cloning(void) {
char *abcdef = strdup("abcdef");
TCString s = tc_string_clone(abcdef);
TEST_ASSERT_NOT_NULL(s.ptr);
free(abcdef);
TEST_ASSERT_EQUAL_STRING("abcdef", tc_string_content(&s));
tc_string_free(&s);
TEST_ASSERT_NULL(s.ptr);
}
// creating cloned strings with invalid utf-8 does not crash
// ..but content is NULL and content_and_len returns the value
static void test_string_cloning_invalid_utf8(void) {
TCString s = tc_string_clone("\xf0\x28\x8c\x28");
TEST_ASSERT_NOT_NULL(s.ptr);
// NOTE: this is not one of the cases where invalid UTF-8 results in NULL,
// but that may change.
size_t len;
const char *buf = tc_string_content_with_len(&s, &len);
TEST_ASSERT_NOT_NULL(buf);
TEST_ASSERT_EQUAL(4, len);
TEST_ASSERT_EQUAL_MEMORY("\xf0\x28\x8c\x28", buf, len);
tc_string_free(&s);
TEST_ASSERT_NULL(s.ptr);
}
// borrowed strings echo back their content
static void test_string_borrowed_strings_echo(void) {
TCString s = tc_string_borrow("abcdef");
TEST_ASSERT_NOT_NULL(s.ptr);
TEST_ASSERT_EQUAL_STRING("abcdef", tc_string_content(&s));
size_t len;
const char *buf = tc_string_content_with_len(&s, &len);
TEST_ASSERT_NOT_NULL(buf);
TEST_ASSERT_EQUAL(6, len);
TEST_ASSERT_EQUAL_MEMORY("abcdef", buf, len);
tc_string_free(&s);
TEST_ASSERT_NULL(s.ptr);
}
// cloned strings echo back their content
static void test_string_cloned_strings_echo(void) {
char *orig = strdup("abcdef");
TCString s = tc_string_clone(orig);
TEST_ASSERT_NOT_NULL(s.ptr);
free(orig);
TEST_ASSERT_EQUAL_STRING("abcdef", tc_string_content(&s));
size_t len;
const char *buf = tc_string_content_with_len(&s, &len);
TEST_ASSERT_NOT_NULL(buf);
TEST_ASSERT_EQUAL(6, len);
TEST_ASSERT_EQUAL_MEMORY("abcdef", buf, len);
tc_string_free(&s);
TEST_ASSERT_NULL(s.ptr);
}
// tc_clone_with_len can have NULs, and tc_string_content returns NULL for
// strings containing embedded NULs
static void test_string_content_null_for_embedded_nuls(void) {
TCString s = tc_string_clone_with_len("ab\0de", 5);
TEST_ASSERT_NOT_NULL(s.ptr);
TEST_ASSERT_NULL(tc_string_content(&s));
size_t len;
const char *buf = tc_string_content_with_len(&s, &len);
TEST_ASSERT_NOT_NULL(buf);
TEST_ASSERT_EQUAL(5, len);
TEST_ASSERT_EQUAL_MEMORY("ab\0de", buf, len);
tc_string_free(&s);
TEST_ASSERT_NULL(s.ptr);
}
// tc_string_clone_with_len will accept invalid utf-8, but then tc_string_content
// returns NULL.
static void test_string_clone_with_len_invalid_utf8(void) {
TCString s = tc_string_clone_with_len("\xf0\x28\x8c\x28", 4);
TEST_ASSERT_NOT_NULL(s.ptr);
TEST_ASSERT_NULL(tc_string_content(&s));
size_t len;
const char *buf = tc_string_content_with_len(&s, &len);
TEST_ASSERT_NOT_NULL(buf);
TEST_ASSERT_EQUAL(4, len);
TEST_ASSERT_EQUAL_MEMORY("\xf0\x28\x8c\x28", buf, len);
tc_string_free(&s);
TEST_ASSERT_NULL(s.ptr);
}
int string_tests(void) {
UNITY_BEGIN();
// each test case above should be named here, in order.
RUN_TEST(test_string_creation);
RUN_TEST(test_string_cloning);
RUN_TEST(test_string_cloning_invalid_utf8);
RUN_TEST(test_string_borrowed_strings_echo);
RUN_TEST(test_string_cloned_strings_echo);
RUN_TEST(test_string_content_null_for_embedded_nuls);
RUN_TEST(test_string_clone_with_len_invalid_utf8);
return UNITY_END();
}

View file

@ -0,0 +1,582 @@
#include <stdlib.h>
#include <string.h>
#include "unity.h"
#include "taskchampion.h"
// creating a task succeeds and the resulting task looks good
static void test_task_creation(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
TCTask *task = tc_replica_new_task(
rep,
TC_STATUS_PENDING,
tc_string_borrow("my task"));
TEST_ASSERT_NOT_NULL(task);
TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task));
TCString desc = tc_task_get_description(task);
TEST_ASSERT_NOT_NULL(desc.ptr);
TEST_ASSERT_EQUAL_STRING("my task", tc_string_content(&desc));
tc_string_free(&desc);
tc_task_free(task);
tc_replica_free(rep);
}
// freeing a mutable task works, marking it immutable
static void test_task_free_mutable_task(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
TCTask *task = tc_replica_new_task(
rep,
TC_STATUS_PENDING,
tc_string_borrow("my task"));
TEST_ASSERT_NOT_NULL(task);
TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task));
TCUuid uuid = tc_task_get_uuid(task);
tc_task_to_mut(task, rep);
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_status(task, TC_STATUS_DELETED));
TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task));
tc_task_free(task); // implicitly converts to immut
task = tc_replica_get_task(rep, uuid);
TEST_ASSERT_NOT_NULL(task);
TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task));
tc_task_free(task);
tc_replica_free(rep);
}
// updating status on a task works
static void test_task_get_set_status(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
TCTask *task = tc_replica_new_task(
rep,
TC_STATUS_PENDING,
tc_string_borrow("my task"));
TEST_ASSERT_NOT_NULL(task);
TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task));
tc_task_to_mut(task, rep);
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_status(task, TC_STATUS_DELETED));
TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task)); // while mut
tc_task_to_immut(task);
TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task)); // while immut
tc_task_free(task);
tc_replica_free(rep);
}
// updating description on a task works
static void test_task_get_set_description(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
TCTask *task = tc_replica_new_task(
rep,
TC_STATUS_PENDING,
tc_string_borrow("my task"));
TEST_ASSERT_NOT_NULL(task);
TCString desc;
tc_task_to_mut(task, rep);
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_description(task, tc_string_borrow("updated")));
desc = tc_task_get_description(task);
TEST_ASSERT_NOT_NULL(desc.ptr);
TEST_ASSERT_EQUAL_STRING("updated", tc_string_content(&desc));
tc_string_free(&desc);
tc_task_to_immut(task);
desc = tc_task_get_description(task);
TEST_ASSERT_NOT_NULL(desc.ptr);
TEST_ASSERT_EQUAL_STRING("updated", tc_string_content(&desc));
tc_string_free(&desc);
tc_task_free(task);
tc_replica_free(rep);
}
// updating entry on a task works
static void test_task_get_set_entry(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
TCTask *task = tc_replica_new_task(
rep,
TC_STATUS_PENDING,
tc_string_borrow("my task"));
TEST_ASSERT_NOT_NULL(task);
// creation of a task sets entry to current time
TEST_ASSERT_NOT_EQUAL(0, tc_task_get_entry(task));
tc_task_to_mut(task, rep);
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_entry(task, 1643679997));
TEST_ASSERT_EQUAL(1643679997, tc_task_get_entry(task));
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_entry(task, 0));
TEST_ASSERT_EQUAL(0, tc_task_get_entry(task));
tc_task_free(task);
tc_replica_free(rep);
}
// updating wait on a task works
static void test_task_get_set_wait_and_is_waiting(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
TCTask *task = tc_replica_new_task(
rep,
TC_STATUS_PENDING,
tc_string_borrow("my task"));
TEST_ASSERT_NOT_NULL(task);
// wait is not set on creation
TEST_ASSERT_EQUAL(0, tc_task_get_wait(task));
TEST_ASSERT_FALSE(tc_task_is_waiting(task));
tc_task_to_mut(task, rep);
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_wait(task, 3643679997)); // 2085
TEST_ASSERT_EQUAL(3643679997, tc_task_get_wait(task));
TEST_ASSERT_TRUE(tc_task_is_waiting(task));
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_wait(task, 643679997)); // THE PAST!
TEST_ASSERT_EQUAL(643679997, tc_task_get_wait(task));
TEST_ASSERT_FALSE(tc_task_is_waiting(task));
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_wait(task, 0));
TEST_ASSERT_EQUAL(0, tc_task_get_wait(task));
TEST_ASSERT_FALSE(tc_task_is_waiting(task));
tc_task_free(task);
tc_replica_free(rep);
}
// updating modified on a task works
static void test_task_get_set_modified(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
TCTask *task = tc_replica_new_task(
rep,
TC_STATUS_PENDING,
tc_string_borrow("my task"));
TEST_ASSERT_NOT_NULL(task);
// creation of a task sets modified to current time
TEST_ASSERT_NOT_EQUAL(0, tc_task_get_modified(task));
tc_task_to_mut(task, rep);
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_modified(task, 1643679997));
TEST_ASSERT_EQUAL(1643679997, tc_task_get_modified(task));
// zero is not allowed
TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_task_set_modified(task, 0));
tc_task_free(task);
tc_replica_free(rep);
}
// starting and stopping a task works, as seen by tc_task_is_active
static void test_task_start_stop_is_active(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
TCTask *task = tc_replica_new_task(
rep,
TC_STATUS_PENDING,
tc_string_borrow("my task"));
TEST_ASSERT_NOT_NULL(task);
TEST_ASSERT_FALSE(tc_task_is_active(task));
tc_task_to_mut(task, rep);
TEST_ASSERT_FALSE(tc_task_is_active(task));
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_start(task));
TEST_ASSERT_TRUE(tc_task_is_active(task));
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_stop(task));
TEST_ASSERT_FALSE(tc_task_is_active(task));
tc_task_free(task);
tc_replica_free(rep);
}
// tc_task_done and delete work and set the status
static void test_task_done_and_delete(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
TCTask *task = tc_replica_new_task(
rep,
TC_STATUS_PENDING,
tc_string_borrow("my task"));
TEST_ASSERT_NOT_NULL(task);
tc_task_to_mut(task, rep);
TEST_ASSERT_EQUAL(TC_STATUS_PENDING, tc_task_get_status(task));
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_done(task));
TEST_ASSERT_EQUAL(TC_STATUS_COMPLETED, tc_task_get_status(task));
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_delete(task));
TEST_ASSERT_EQUAL(TC_STATUS_DELETED, tc_task_get_status(task));
tc_task_free(task);
tc_replica_free(rep);
}
// adding and removing tags to a task works, and invalid tags are rejected
static void test_task_add_remove_has_tag(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
TCTask *task = tc_replica_new_task(
rep,
TC_STATUS_PENDING,
tc_string_borrow("my task"));
TEST_ASSERT_NOT_NULL(task);
tc_task_to_mut(task, rep);
TEST_ASSERT_FALSE(tc_task_has_tag(task, tc_string_borrow("next")));
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_tag(task, tc_string_borrow("next")));
TEST_ASSERT_NULL(tc_task_error(task).ptr);
TEST_ASSERT_TRUE(tc_task_has_tag(task, tc_string_borrow("next")));
// invalid - synthetic tag
TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_task_add_tag(task, tc_string_borrow("PENDING")));
TCString err = tc_task_error(task);
TEST_ASSERT_NOT_NULL(err.ptr);
tc_string_free(&err);
// invald - not a valid tag string
TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_task_add_tag(task, tc_string_borrow("my tag")));
err = tc_task_error(task);
TEST_ASSERT_NOT_NULL(err.ptr);
tc_string_free(&err);
// invald - not utf-8
TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_task_add_tag(task, tc_string_borrow("\xf0\x28\x8c\x28")));
err = tc_task_error(task);
TEST_ASSERT_NOT_NULL(err.ptr);
tc_string_free(&err);
TEST_ASSERT_TRUE(tc_task_has_tag(task, tc_string_borrow("next")));
// remove the tag
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_remove_tag(task, tc_string_borrow("next")));
TEST_ASSERT_NULL(tc_task_error(task).ptr);
TEST_ASSERT_FALSE(tc_task_has_tag(task, tc_string_borrow("next")));
tc_task_free(task);
tc_replica_free(rep);
}
// get_tags returns the list of tags
static void test_task_get_tags(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
TCTask *task = tc_replica_new_task(
rep,
TC_STATUS_PENDING,
tc_string_borrow("my task"));
TEST_ASSERT_NOT_NULL(task);
tc_task_to_mut(task, rep);
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_tag(task, tc_string_borrow("next")));
TCStringList tags = tc_task_get_tags(task);
int found_pending = false, found_next = false;
for (size_t i = 0; i < tags.len; i++) {
if (strcmp("PENDING", tc_string_content(&tags.items[i])) == 0) {
found_pending = true;
}
if (strcmp("next", tc_string_content(&tags.items[i])) == 0) {
found_next = true;
}
}
TEST_ASSERT_TRUE(found_pending);
TEST_ASSERT_TRUE(found_next);
tc_string_list_free(&tags);
TEST_ASSERT_NULL(tags.items);
tc_task_free(task);
tc_replica_free(rep);
}
// annotation manipulation (add, remove, list, free)
static void test_task_annotations(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
TCTask *task = tc_replica_new_task(
rep,
TC_STATUS_PENDING,
tc_string_borrow("my task"));
TEST_ASSERT_NOT_NULL(task);
TCAnnotationList anns = tc_task_get_annotations(task);
TEST_ASSERT_EQUAL(0, anns.len);
TEST_ASSERT_NOT_NULL(anns.items);
tc_annotation_list_free(&anns);
tc_task_to_mut(task, rep);
TCAnnotation ann;
ann.entry = 1644623411;
ann.description = tc_string_borrow("ann1");
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_annotation(task, &ann));
TEST_ASSERT_NULL(ann.description.ptr);
ann.entry = 1644623422;
ann.description = tc_string_borrow("ann2");
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_annotation(task, &ann));
TEST_ASSERT_NULL(ann.description.ptr);
anns = tc_task_get_annotations(task);
int found1 = false, found2 = false;
for (size_t i = 0; i < anns.len; i++) {
if (0 == strcmp("ann1", tc_string_content(&anns.items[i].description))) {
TEST_ASSERT_EQUAL(anns.items[i].entry, 1644623411);
found1 = true;
}
if (0 == strcmp("ann2", tc_string_content(&anns.items[i].description))) {
TEST_ASSERT_EQUAL(anns.items[i].entry, 1644623422);
found2 = true;
}
}
TEST_ASSERT_TRUE(found1);
TEST_ASSERT_TRUE(found2);
tc_annotation_list_free(&anns);
TEST_ASSERT_NULL(anns.items);
tc_task_free(task);
tc_replica_free(rep);
}
// UDA manipulation (add, remove, list, free)
static void test_task_udas(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
TCTask *task = tc_replica_new_task(
rep,
TC_STATUS_PENDING,
tc_string_borrow("my task"));
TEST_ASSERT_NOT_NULL(task);
tc_task_to_mut(task, rep);
TCString value;
TCUdaList udas;
TEST_ASSERT_NULL(tc_task_get_uda(task, tc_string_borrow("ns"), tc_string_borrow("u1")).ptr);
TEST_ASSERT_NULL(tc_task_get_legacy_uda(task, tc_string_borrow("leg1")).ptr);
udas = tc_task_get_udas(task);
TEST_ASSERT_NOT_NULL(udas.items);
TEST_ASSERT_EQUAL(0, udas.len);
tc_uda_list_free(&udas);
udas = tc_task_get_legacy_udas(task);
TEST_ASSERT_NOT_NULL(udas.items);
TEST_ASSERT_EQUAL(0, udas.len);
tc_uda_list_free(&udas);
TEST_ASSERT_EQUAL(TC_RESULT_OK,
tc_task_set_uda(task,
tc_string_borrow("ns"),
tc_string_borrow("u1"),
tc_string_borrow("vvv")));
value = tc_task_get_uda(task, tc_string_borrow("ns"), tc_string_borrow("u1"));
TEST_ASSERT_NOT_NULL(value.ptr);
TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(&value));
tc_string_free(&value);
TEST_ASSERT_NULL(tc_task_get_legacy_uda(task, tc_string_borrow("leg1")).ptr);
udas = tc_task_get_udas(task);
TEST_ASSERT_NOT_NULL(udas.items);
TEST_ASSERT_EQUAL(1, udas.len);
TEST_ASSERT_EQUAL_STRING("ns", tc_string_content(&udas.items[0].ns));
TEST_ASSERT_EQUAL_STRING("u1", tc_string_content(&udas.items[0].key));
TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(&udas.items[0].value));
tc_uda_list_free(&udas);
udas = tc_task_get_legacy_udas(task);
TEST_ASSERT_NOT_NULL(udas.items);
TEST_ASSERT_EQUAL(1, udas.len);
TEST_ASSERT_NULL(udas.items[0].ns.ptr);
TEST_ASSERT_EQUAL_STRING("ns.u1", tc_string_content(&udas.items[0].key));
TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(&udas.items[0].value));
tc_uda_list_free(&udas);
TEST_ASSERT_EQUAL(TC_RESULT_OK,
tc_task_set_legacy_uda(task,
tc_string_borrow("leg1"),
tc_string_borrow("legv")));
value = tc_task_get_uda(task, tc_string_borrow("ns"), tc_string_borrow("u1"));
TEST_ASSERT_NOT_NULL(value.ptr);
TEST_ASSERT_EQUAL_STRING("vvv", tc_string_content(&value));
tc_string_free(&value);
value = tc_task_get_legacy_uda(task, tc_string_borrow("leg1"));
TEST_ASSERT_NOT_NULL(value.ptr);
TEST_ASSERT_EQUAL_STRING("legv", tc_string_content(&value));
tc_string_free(&value);
udas = tc_task_get_udas(task);
TEST_ASSERT_NOT_NULL(udas.items);
TEST_ASSERT_EQUAL(2, udas.len);
tc_uda_list_free(&udas);
udas = tc_task_get_legacy_udas(task);
TEST_ASSERT_NOT_NULL(udas.items);
TEST_ASSERT_EQUAL(2, udas.len);
tc_uda_list_free(&udas);
TEST_ASSERT_EQUAL(TC_RESULT_OK,
tc_task_remove_uda(task,
tc_string_borrow("ns"),
tc_string_borrow("u1")));
TEST_ASSERT_NULL(tc_task_get_uda(task, tc_string_borrow("ns"), tc_string_borrow("u1")).ptr);
TEST_ASSERT_EQUAL(TC_RESULT_OK,
tc_task_remove_uda(task,
tc_string_borrow("ns"),
tc_string_borrow("u1")));
udas = tc_task_get_udas(task);
TEST_ASSERT_NOT_NULL(udas.items);
TEST_ASSERT_EQUAL(1, udas.len);
TEST_ASSERT_EQUAL_STRING("", tc_string_content(&udas.items[0].ns));
TEST_ASSERT_EQUAL_STRING("leg1", tc_string_content(&udas.items[0].key));
TEST_ASSERT_EQUAL_STRING("legv", tc_string_content(&udas.items[0].value));
tc_uda_list_free(&udas);
udas = tc_task_get_legacy_udas(task);
TEST_ASSERT_NOT_NULL(udas.items);
TEST_ASSERT_EQUAL(1, udas.len);
TEST_ASSERT_NULL(udas.items[0].ns.ptr);
TEST_ASSERT_EQUAL_STRING("leg1", tc_string_content(&udas.items[0].key));
TEST_ASSERT_EQUAL_STRING("legv", tc_string_content(&udas.items[0].value));
tc_uda_list_free(&udas);
TEST_ASSERT_EQUAL(TC_RESULT_OK,
tc_task_remove_legacy_uda(task,
tc_string_borrow("leg1")));
TEST_ASSERT_NULL(tc_task_get_legacy_uda(task, tc_string_borrow("leg1")).ptr);
TEST_ASSERT_EQUAL(TC_RESULT_OK,
tc_task_remove_legacy_uda(task,
tc_string_borrow("leg1")));
udas = tc_task_get_udas(task);
TEST_ASSERT_NOT_NULL(udas.items);
TEST_ASSERT_EQUAL(0, udas.len);
tc_uda_list_free(&udas);
udas = tc_task_get_legacy_udas(task);
TEST_ASSERT_NOT_NULL(udas.items);
TEST_ASSERT_EQUAL(0, udas.len);
tc_uda_list_free(&udas);
tc_task_free(task);
tc_replica_free(rep);
}
static void tckvlist_assert_key(TCKVList *list, char *key, char *value) {
TEST_ASSERT_NOT_NULL(list);
for (size_t i = 0; i < list->len; i++) {
if (0 == strcmp(tc_string_content(&list->items[i].key), key)) {
TEST_ASSERT_EQUAL_STRING(value, tc_string_content(&list->items[i].value));
return;
}
}
TEST_FAIL_MESSAGE("key not found");
}
// get_tags returns the list of tags
static void test_task_taskmap(void) {
TCReplica *rep = tc_replica_new_in_memory();
TEST_ASSERT_NULL(tc_replica_error(rep).ptr);
TCTask *task = tc_replica_new_task(rep, TC_STATUS_PENDING, tc_string_borrow("my task"));
TEST_ASSERT_NOT_NULL(task);
tc_task_to_mut(task, rep);
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_tag(task, tc_string_borrow("next")));
TCAnnotation ann;
ann.entry = 1644623411;
ann.description = tc_string_borrow("ann1");
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_add_annotation(task, &ann));
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_task_set_wait(task, 3643679997)); // 2085
TCKVList taskmap = tc_task_get_taskmap(task);
tckvlist_assert_key(&taskmap, "annotation_1644623411", "ann1");
tckvlist_assert_key(&taskmap, "tag_next", "");
tckvlist_assert_key(&taskmap, "status", "pending");
tckvlist_assert_key(&taskmap, "description", "my task");
tc_kv_list_free(&taskmap);
tc_task_free(task);
tc_replica_free(rep);
}
int task_tests(void) {
UNITY_BEGIN();
// each test case above should be named here, in order.
RUN_TEST(test_task_creation);
RUN_TEST(test_task_free_mutable_task);
RUN_TEST(test_task_get_set_status);
RUN_TEST(test_task_get_set_description);
RUN_TEST(test_task_get_set_entry);
RUN_TEST(test_task_get_set_modified);
RUN_TEST(test_task_get_set_wait_and_is_waiting);
RUN_TEST(test_task_start_stop_is_active);
RUN_TEST(test_task_done_and_delete);
RUN_TEST(test_task_add_remove_has_tag);
RUN_TEST(test_task_get_tags);
RUN_TEST(test_task_annotations);
RUN_TEST(test_task_udas);
RUN_TEST(test_task_taskmap);
return UNITY_END();
}

View file

@ -0,0 +1,30 @@
#include <stdio.h>
#include "unity.h"
// these functions are shared between all test "suites"
// and cannot be customized per-suite.
void setUp(void) { }
void tearDown(void) { }
static FILE *output = NULL;
// Set up for test_output, writing output to "TEST-OUTPUT" in the
// current directory. The Rust test harness reads this file to get
// the output and display it only on failure. This is called by
// the Rust test harness
void setup_output(void) {
output = fopen("TEST-OUTPUT", "w");
}
// Close the output file. Called by the Rust test harness.
void finish_output(void) {
fclose(output);
output = NULL;
}
// this replaces UNITY_OUTPUT_CHAR, and writes output to
// TEST-OUTPUT in the current directory; the Rust test harness
// will read this data if the test fails.
void test_output(char c) {
fputc(c, output);
}

View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) <year> 2007-21 Mike Karlesky, Mark VanderVoord, Greg Williams
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View file

@ -0,0 +1,3 @@
# Unity
This directory contains the src from https://github.com/ThrowTheSwitch/Unity, revision 8ba01386008196a92ef4fdbdb0b00f2434c79563.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,661 @@
/* ==========================================
Unity Project - A Test Framework for C
Copyright (c) 2007-21 Mike Karlesky, Mark VanderVoord, Greg Williams
[Released under MIT License. Please refer to license.txt for details]
========================================== */
#ifndef UNITY_FRAMEWORK_H
#define UNITY_FRAMEWORK_H
#define UNITY
#define UNITY_VERSION_MAJOR 2
#define UNITY_VERSION_MINOR 5
#define UNITY_VERSION_BUILD 4
#define UNITY_VERSION ((UNITY_VERSION_MAJOR << 16) | (UNITY_VERSION_MINOR << 8) | UNITY_VERSION_BUILD)
#ifdef __cplusplus
extern "C"
{
#endif
#include "unity_internals.h"
/*-------------------------------------------------------
* Test Setup / Teardown
*-------------------------------------------------------*/
/* These functions are intended to be called before and after each test.
* If using unity directly, these will need to be provided for each test
* executable built. If you are using the test runner generator and/or
* Ceedling, these are optional. */
void setUp(void);
void tearDown(void);
/* These functions are intended to be called at the beginning and end of an
* entire test suite. suiteTearDown() is passed the number of tests that
* failed, and its return value becomes the exit code of main(). If using
* Unity directly, you're in charge of calling these if they are desired.
* If using Ceedling or the test runner generator, these will be called
* automatically if they exist. */
void suiteSetUp(void);
int suiteTearDown(int num_failures);
/*-------------------------------------------------------
* Test Reset and Verify
*-------------------------------------------------------*/
/* These functions are intended to be called before during tests in order
* to support complex test loops, etc. Both are NOT built into Unity. Instead
* the test runner generator will create them. resetTest will run teardown and
* setup again, verifying any end-of-test needs between. verifyTest will only
* run the verification. */
void resetTest(void);
void verifyTest(void);
/*-------------------------------------------------------
* Configuration Options
*-------------------------------------------------------
* All options described below should be passed as a compiler flag to all files using Unity. If you must add #defines, place them BEFORE the #include above.
* Integers/longs/pointers
* - Unity attempts to automatically discover your integer sizes
* - define UNITY_EXCLUDE_STDINT_H to stop attempting to look in <stdint.h>
* - define UNITY_EXCLUDE_LIMITS_H to stop attempting to look in <limits.h>
* - If you cannot use the automatic methods above, you can force Unity by using these options:
* - define UNITY_SUPPORT_64
* - set UNITY_INT_WIDTH
* - set UNITY_LONG_WIDTH
* - set UNITY_POINTER_WIDTH
* Floats
* - define UNITY_EXCLUDE_FLOAT to disallow floating point comparisons
* - define UNITY_FLOAT_PRECISION to specify the precision to use when doing TEST_ASSERT_EQUAL_FLOAT
* - define UNITY_FLOAT_TYPE to specify doubles instead of single precision floats
* - define UNITY_INCLUDE_DOUBLE to allow double floating point comparisons
* - define UNITY_EXCLUDE_DOUBLE to disallow double floating point comparisons (default)
* - define UNITY_DOUBLE_PRECISION to specify the precision to use when doing TEST_ASSERT_EQUAL_DOUBLE
* - define UNITY_DOUBLE_TYPE to specify something other than double
* - define UNITY_EXCLUDE_FLOAT_PRINT to trim binary size, won't print floating point values in errors
* Output
* - by default, Unity prints to standard out with putchar. define UNITY_OUTPUT_CHAR(a) with a different function if desired
* - define UNITY_DIFFERENTIATE_FINAL_FAIL to print FAILED (vs. FAIL) at test end summary - for automated search for failure
* Optimization
* - by default, line numbers are stored in unsigned shorts. Define UNITY_LINE_TYPE with a different type if your files are huge
* - by default, test and failure counters are unsigned shorts. Define UNITY_COUNTER_TYPE with a different type if you want to save space or have more than 65535 Tests.
* Test Cases
* - define UNITY_SUPPORT_TEST_CASES to include the TEST_CASE macro, though really it's mostly about the runner generator script
* Parameterized Tests
* - you'll want to create a define of TEST_CASE(...) which basically evaluates to nothing
* Tests with Arguments
* - you'll want to define UNITY_USE_COMMAND_LINE_ARGS if you have the test runner passing arguments to Unity
*-------------------------------------------------------
* Basic Fail and Ignore
*-------------------------------------------------------*/
#define TEST_FAIL_MESSAGE(message) UNITY_TEST_FAIL(__LINE__, (message))
#define TEST_FAIL() UNITY_TEST_FAIL(__LINE__, NULL)
#define TEST_IGNORE_MESSAGE(message) UNITY_TEST_IGNORE(__LINE__, (message))
#define TEST_IGNORE() UNITY_TEST_IGNORE(__LINE__, NULL)
#define TEST_MESSAGE(message) UnityMessage((message), __LINE__)
#define TEST_ONLY()
#ifdef UNITY_INCLUDE_PRINT_FORMATTED
#define TEST_PRINTF(message, ...) UnityPrintF(__LINE__, (message), __VA_ARGS__)
#endif
/* It is not necessary for you to call PASS. A PASS condition is assumed if nothing fails.
* This method allows you to abort a test immediately with a PASS state, ignoring the remainder of the test. */
#define TEST_PASS() TEST_ABORT()
#define TEST_PASS_MESSAGE(message) do { UnityMessage((message), __LINE__); TEST_ABORT(); } while (0)
/* This macro does nothing, but it is useful for build tools (like Ceedling) to make use of this to figure out
* which files should be linked to in order to perform a test. Use it like TEST_FILE("sandwiches.c") */
#define TEST_FILE(a)
/*-------------------------------------------------------
* Test Asserts (simple)
*-------------------------------------------------------*/
/* Boolean */
#define TEST_ASSERT(condition) UNITY_TEST_ASSERT( (condition), __LINE__, " Expression Evaluated To FALSE")
#define TEST_ASSERT_TRUE(condition) UNITY_TEST_ASSERT( (condition), __LINE__, " Expected TRUE Was FALSE")
#define TEST_ASSERT_UNLESS(condition) UNITY_TEST_ASSERT( !(condition), __LINE__, " Expression Evaluated To TRUE")
#define TEST_ASSERT_FALSE(condition) UNITY_TEST_ASSERT( !(condition), __LINE__, " Expected FALSE Was TRUE")
#define TEST_ASSERT_NULL(pointer) UNITY_TEST_ASSERT_NULL( (pointer), __LINE__, " Expected NULL")
#define TEST_ASSERT_NOT_NULL(pointer) UNITY_TEST_ASSERT_NOT_NULL((pointer), __LINE__, " Expected Non-NULL")
#define TEST_ASSERT_EMPTY(pointer) UNITY_TEST_ASSERT_EMPTY( (pointer), __LINE__, " Expected Empty")
#define TEST_ASSERT_NOT_EMPTY(pointer) UNITY_TEST_ASSERT_NOT_EMPTY((pointer), __LINE__, " Expected Non-Empty")
/* Integers (of all sizes) */
#define TEST_ASSERT_EQUAL_INT(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_INT8(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT8((expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_INT16(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT16((expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_INT32(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT32((expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_INT64(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT64((expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_UINT(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT( (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_UINT8(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT8( (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_UINT16(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT16( (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_UINT32(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT32( (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_UINT64(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT64( (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_size_t(expected, actual) UNITY_TEST_ASSERT_EQUAL_UINT((expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_HEX(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_HEX8(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX8( (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_HEX16(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX16((expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_HEX32(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_HEX64(expected, actual) UNITY_TEST_ASSERT_EQUAL_HEX64((expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_CHAR(expected, actual) UNITY_TEST_ASSERT_EQUAL_CHAR((expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_BITS(mask, expected, actual) UNITY_TEST_ASSERT_BITS((mask), (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_BITS_HIGH(mask, actual) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT)(-1), (actual), __LINE__, NULL)
#define TEST_ASSERT_BITS_LOW(mask, actual) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT)(0), (actual), __LINE__, NULL)
#define TEST_ASSERT_BIT_HIGH(bit, actual) UNITY_TEST_ASSERT_BITS(((UNITY_UINT)1 << (bit)), (UNITY_UINT)(-1), (actual), __LINE__, NULL)
#define TEST_ASSERT_BIT_LOW(bit, actual) UNITY_TEST_ASSERT_BITS(((UNITY_UINT)1 << (bit)), (UNITY_UINT)(0), (actual), __LINE__, NULL)
/* Integer Not Equal To (of all sizes) */
#define TEST_ASSERT_NOT_EQUAL_INT(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_NOT_EQUAL_INT8(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT8((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_NOT_EQUAL_INT16(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT16((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_NOT_EQUAL_INT32(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT32((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_NOT_EQUAL_INT64(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_INT64((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_NOT_EQUAL_UINT(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_NOT_EQUAL_UINT8(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT8((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_NOT_EQUAL_UINT16(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT16((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_NOT_EQUAL_UINT32(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT32((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_NOT_EQUAL_UINT64(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT64((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_NOT_EQUAL_size_t(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_UINT((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_NOT_EQUAL_HEX8(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_HEX8((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_NOT_EQUAL_HEX16(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_HEX16((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_NOT_EQUAL_HEX32(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_HEX32((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_NOT_EQUAL_HEX64(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_HEX64((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_NOT_EQUAL_CHAR(threshold, actual) UNITY_TEST_ASSERT_NOT_EQUAL_CHAR((threshold), (actual), __LINE__, NULL)
/* Integer Greater Than/ Less Than (of all sizes) */
#define TEST_ASSERT_GREATER_THAN(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_THAN_INT(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_THAN_INT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT8((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_THAN_INT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT16((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_THAN_INT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT32((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_THAN_INT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_INT64((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_THAN_UINT(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_THAN_UINT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT8((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_THAN_UINT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT16((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_THAN_UINT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT32((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_THAN_UINT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT64((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_THAN_size_t(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_THAN_HEX8(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX8((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_THAN_HEX16(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX16((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_THAN_HEX32(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX32((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_THAN_HEX64(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_HEX64((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_THAN_CHAR(threshold, actual) UNITY_TEST_ASSERT_GREATER_THAN_CHAR((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_THAN(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_THAN_INT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_THAN_INT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT8((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_THAN_INT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT16((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_THAN_INT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT32((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_THAN_INT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_INT64((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_THAN_UINT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_THAN_UINT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT8((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_THAN_UINT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT16((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_THAN_UINT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT32((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_THAN_UINT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT64((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_THAN_size_t(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_THAN_HEX8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX8((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_THAN_HEX16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX16((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_THAN_HEX32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX32((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_THAN_HEX64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_HEX64((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_THAN_CHAR(threshold, actual) UNITY_TEST_ASSERT_SMALLER_THAN_CHAR((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_OR_EQUAL(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_OR_EQUAL_INT(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_OR_EQUAL_INT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT8((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_OR_EQUAL_INT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT16((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_OR_EQUAL_INT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT32((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_OR_EQUAL_INT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_OR_EQUAL_UINT(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_OR_EQUAL_UINT8(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_OR_EQUAL_UINT16(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_OR_EQUAL_UINT32(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_OR_EQUAL_UINT64(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_OR_EQUAL_size_t(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_OR_EQUAL_HEX8(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_OR_EQUAL_HEX16(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_OR_EQUAL_HEX32(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_OR_EQUAL_HEX64(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_GREATER_OR_EQUAL_CHAR(threshold, actual) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_CHAR((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_OR_EQUAL(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_OR_EQUAL_INT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_OR_EQUAL_INT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT8((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_OR_EQUAL_INT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT16((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_OR_EQUAL_INT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT32((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_OR_EQUAL_INT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_OR_EQUAL_UINT(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_OR_EQUAL_UINT8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_OR_EQUAL_UINT16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_OR_EQUAL_UINT32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_OR_EQUAL_UINT64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_OR_EQUAL_size_t(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_OR_EQUAL_HEX8(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_OR_EQUAL_HEX16(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_OR_EQUAL_HEX32(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_OR_EQUAL_HEX64(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, NULL)
#define TEST_ASSERT_LESS_OR_EQUAL_CHAR(threshold, actual) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_CHAR((threshold), (actual), __LINE__, NULL)
/* Integer Ranges (of all sizes) */
#define TEST_ASSERT_INT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT_WITHIN((delta), (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_INT8_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT8_WITHIN((delta), (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_INT16_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT16_WITHIN((delta), (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_INT32_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT32_WITHIN((delta), (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_INT64_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_INT64_WITHIN((delta), (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_UINT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_UINT8_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT8_WITHIN((delta), (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_UINT16_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT16_WITHIN((delta), (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_UINT32_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT32_WITHIN((delta), (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_UINT64_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT64_WITHIN((delta), (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_size_t_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_HEX_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_HEX8_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX8_WITHIN((delta), (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_HEX16_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX16_WITHIN((delta), (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_HEX32_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_HEX64_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_HEX64_WITHIN((delta), (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_CHAR_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_CHAR_WITHIN((delta), (expected), (actual), __LINE__, NULL)
/* Integer Array Ranges (of all sizes) */
#define TEST_ASSERT_INT_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
#define TEST_ASSERT_INT8_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
#define TEST_ASSERT_INT16_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
#define TEST_ASSERT_INT32_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
#define TEST_ASSERT_INT64_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_INT64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
#define TEST_ASSERT_UINT_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
#define TEST_ASSERT_UINT8_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
#define TEST_ASSERT_UINT16_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
#define TEST_ASSERT_UINT32_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
#define TEST_ASSERT_UINT64_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
#define TEST_ASSERT_size_t_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
#define TEST_ASSERT_HEX_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
#define TEST_ASSERT_HEX8_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
#define TEST_ASSERT_HEX16_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
#define TEST_ASSERT_HEX32_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
#define TEST_ASSERT_HEX64_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_HEX64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
#define TEST_ASSERT_CHAR_ARRAY_WITHIN(delta, expected, actual, num_elements) UNITY_TEST_ASSERT_CHAR_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, NULL)
/* Structs and Strings */
#define TEST_ASSERT_EQUAL_PTR(expected, actual) UNITY_TEST_ASSERT_EQUAL_PTR((expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_STRING(expected, actual) UNITY_TEST_ASSERT_EQUAL_STRING((expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_STRING_LEN(expected, actual, len) UNITY_TEST_ASSERT_EQUAL_STRING_LEN((expected), (actual), (len), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_MEMORY(expected, actual, len) UNITY_TEST_ASSERT_EQUAL_MEMORY((expected), (actual), (len), __LINE__, NULL)
/* Arrays */
#define TEST_ASSERT_EQUAL_INT_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_INT8_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT8_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_INT16_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT16_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_INT32_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_INT64_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_UINT_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_UINT8_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT8_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_UINT16_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT16_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_UINT32_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_UINT64_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_size_t_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_HEX_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_HEX8_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX8_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_HEX16_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX16_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_HEX32_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_HEX64_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_PTR_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_PTR_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_STRING_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_STRING_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_MEMORY_ARRAY(expected, actual, len, num_elements) UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY((expected), (actual), (len), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_CHAR_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_CHAR_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
/* Arrays Compared To Single Value */
#define TEST_ASSERT_EACH_EQUAL_INT(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EACH_EQUAL_INT8(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT8((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EACH_EQUAL_INT16(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT16((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EACH_EQUAL_INT32(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT32((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EACH_EQUAL_INT64(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_INT64((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EACH_EQUAL_UINT(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EACH_EQUAL_UINT8(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT8((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EACH_EQUAL_UINT16(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT16((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EACH_EQUAL_UINT32(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT32((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EACH_EQUAL_UINT64(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT64((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EACH_EQUAL_size_t(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EACH_EQUAL_HEX(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EACH_EQUAL_HEX8(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX8((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EACH_EQUAL_HEX16(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX16((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EACH_EQUAL_HEX32(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EACH_EQUAL_HEX64(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_HEX64((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EACH_EQUAL_PTR(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_PTR((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EACH_EQUAL_STRING(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_STRING((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EACH_EQUAL_MEMORY(expected, actual, len, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_MEMORY((expected), (actual), (len), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EACH_EQUAL_CHAR(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_CHAR((expected), (actual), (num_elements), __LINE__, NULL)
/* Floating Point (If Enabled) */
#define TEST_ASSERT_FLOAT_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_FLOAT_WITHIN((delta), (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_FLOAT(expected, actual) UNITY_TEST_ASSERT_EQUAL_FLOAT((expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_FLOAT_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EACH_EQUAL_FLOAT(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_FLOAT_IS_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_INF((actual), __LINE__, NULL)
#define TEST_ASSERT_FLOAT_IS_NEG_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF((actual), __LINE__, NULL)
#define TEST_ASSERT_FLOAT_IS_NAN(actual) UNITY_TEST_ASSERT_FLOAT_IS_NAN((actual), __LINE__, NULL)
#define TEST_ASSERT_FLOAT_IS_DETERMINATE(actual) UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE((actual), __LINE__, NULL)
#define TEST_ASSERT_FLOAT_IS_NOT_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF((actual), __LINE__, NULL)
#define TEST_ASSERT_FLOAT_IS_NOT_NEG_INF(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF((actual), __LINE__, NULL)
#define TEST_ASSERT_FLOAT_IS_NOT_NAN(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN((actual), __LINE__, NULL)
#define TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE(actual) UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE((actual), __LINE__, NULL)
/* Double (If Enabled) */
#define TEST_ASSERT_DOUBLE_WITHIN(delta, expected, actual) UNITY_TEST_ASSERT_DOUBLE_WITHIN((delta), (expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_DOUBLE(expected, actual) UNITY_TEST_ASSERT_EQUAL_DOUBLE((expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_EQUAL_DOUBLE_ARRAY(expected, actual, num_elements) UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_EACH_EQUAL_DOUBLE(expected, actual, num_elements) UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE((expected), (actual), (num_elements), __LINE__, NULL)
#define TEST_ASSERT_DOUBLE_IS_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_INF((actual), __LINE__, NULL)
#define TEST_ASSERT_DOUBLE_IS_NEG_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF((actual), __LINE__, NULL)
#define TEST_ASSERT_DOUBLE_IS_NAN(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NAN((actual), __LINE__, NULL)
#define TEST_ASSERT_DOUBLE_IS_DETERMINATE(actual) UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE((actual), __LINE__, NULL)
#define TEST_ASSERT_DOUBLE_IS_NOT_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF((actual), __LINE__, NULL)
#define TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF((actual), __LINE__, NULL)
#define TEST_ASSERT_DOUBLE_IS_NOT_NAN(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN((actual), __LINE__, NULL)
#define TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE(actual) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE((actual), __LINE__, NULL)
/* Shorthand */
#ifdef UNITY_SHORTHAND_AS_OLD
#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, " Expected Not-Equal")
#endif
#ifdef UNITY_SHORTHAND_AS_INT
#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, NULL)
#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand)
#endif
#ifdef UNITY_SHORTHAND_AS_MEM
#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_ASSERT_EQUAL_MEMORY((&expected), (&actual), sizeof(expected), __LINE__, NULL)
#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand)
#endif
#ifdef UNITY_SHORTHAND_AS_RAW
#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_ASSERT(((expected) == (actual)), __LINE__, " Expected Equal")
#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, " Expected Not-Equal")
#endif
#ifdef UNITY_SHORTHAND_AS_NONE
#define TEST_ASSERT_EQUAL(expected, actual) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand)
#define TEST_ASSERT_NOT_EQUAL(expected, actual) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand)
#endif
/*-------------------------------------------------------
* Test Asserts (with additional messages)
*-------------------------------------------------------*/
/* Boolean */
#define TEST_ASSERT_MESSAGE(condition, message) UNITY_TEST_ASSERT( (condition), __LINE__, (message))
#define TEST_ASSERT_TRUE_MESSAGE(condition, message) UNITY_TEST_ASSERT( (condition), __LINE__, (message))
#define TEST_ASSERT_UNLESS_MESSAGE(condition, message) UNITY_TEST_ASSERT( !(condition), __LINE__, (message))
#define TEST_ASSERT_FALSE_MESSAGE(condition, message) UNITY_TEST_ASSERT( !(condition), __LINE__, (message))
#define TEST_ASSERT_NULL_MESSAGE(pointer, message) UNITY_TEST_ASSERT_NULL( (pointer), __LINE__, (message))
#define TEST_ASSERT_NOT_NULL_MESSAGE(pointer, message) UNITY_TEST_ASSERT_NOT_NULL((pointer), __LINE__, (message))
#define TEST_ASSERT_EMPTY_MESSAGE(pointer, message) UNITY_TEST_ASSERT_EMPTY( (pointer), __LINE__, (message))
#define TEST_ASSERT_NOT_EMPTY_MESSAGE(pointer, message) UNITY_TEST_ASSERT_NOT_EMPTY((pointer), __LINE__, (message))
/* Integers (of all sizes) */
#define TEST_ASSERT_EQUAL_INT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, (message))
#define TEST_ASSERT_EQUAL_INT8_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT8((expected), (actual), __LINE__, (message))
#define TEST_ASSERT_EQUAL_INT16_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT16((expected), (actual), __LINE__, (message))
#define TEST_ASSERT_EQUAL_INT32_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT32((expected), (actual), __LINE__, (message))
#define TEST_ASSERT_EQUAL_INT64_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT64((expected), (actual), __LINE__, (message))
#define TEST_ASSERT_EQUAL_UINT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT( (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_EQUAL_UINT8_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT8( (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_EQUAL_UINT16_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT16( (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_EQUAL_UINT32_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT32( (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_EQUAL_UINT64_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT64( (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_EQUAL_size_t_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_UINT( (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_EQUAL_HEX_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, (message))
#define TEST_ASSERT_EQUAL_HEX8_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX8( (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_EQUAL_HEX16_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX16((expected), (actual), __LINE__, (message))
#define TEST_ASSERT_EQUAL_HEX32_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX32((expected), (actual), __LINE__, (message))
#define TEST_ASSERT_EQUAL_HEX64_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_HEX64((expected), (actual), __LINE__, (message))
#define TEST_ASSERT_BITS_MESSAGE(mask, expected, actual, message) UNITY_TEST_ASSERT_BITS((mask), (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_BITS_HIGH_MESSAGE(mask, actual, message) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT32)(-1), (actual), __LINE__, (message))
#define TEST_ASSERT_BITS_LOW_MESSAGE(mask, actual, message) UNITY_TEST_ASSERT_BITS((mask), (UNITY_UINT32)(0), (actual), __LINE__, (message))
#define TEST_ASSERT_BIT_HIGH_MESSAGE(bit, actual, message) UNITY_TEST_ASSERT_BITS(((UNITY_UINT32)1 << (bit)), (UNITY_UINT32)(-1), (actual), __LINE__, (message))
#define TEST_ASSERT_BIT_LOW_MESSAGE(bit, actual, message) UNITY_TEST_ASSERT_BITS(((UNITY_UINT32)1 << (bit)), (UNITY_UINT32)(0), (actual), __LINE__, (message))
#define TEST_ASSERT_EQUAL_CHAR_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_CHAR((expected), (actual), __LINE__, (message))
/* Integer Not Equal To (of all sizes) */
#define TEST_ASSERT_NOT_EQUAL_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_NOT_EQUAL_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT8((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_NOT_EQUAL_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT16((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_NOT_EQUAL_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT32((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_NOT_EQUAL_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_INT64((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_NOT_EQUAL_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_NOT_EQUAL_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT8((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_NOT_EQUAL_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT16((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_NOT_EQUAL_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT32((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_NOT_EQUAL_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT64((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_NOT_EQUAL_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_UINT((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_NOT_EQUAL_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_HEX8((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_NOT_EQUAL_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_HEX16((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_NOT_EQUAL_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_HEX32((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_NOT_EQUAL_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_HEX64((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_NOT_EQUAL_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_NOT_EQUAL_CHAR((threshold), (actual), __LINE__, (message))
/* Integer Greater Than/ Less Than (of all sizes) */
#define TEST_ASSERT_GREATER_THAN_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_THAN_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_THAN_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT8((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_THAN_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT16((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_THAN_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT32((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_THAN_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_INT64((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_THAN_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_THAN_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT8((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_THAN_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT16((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_THAN_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT32((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_THAN_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT64((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_THAN_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_UINT((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_THAN_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX8((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_THAN_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX16((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_THAN_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX32((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_THAN_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_HEX64((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_THAN_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_THAN_CHAR((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_THAN_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_THAN_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_THAN_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT8((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_THAN_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT16((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_THAN_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT32((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_THAN_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_INT64((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_THAN_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_THAN_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT8((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_THAN_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT16((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_THAN_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT32((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_THAN_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT64((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_THAN_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_UINT((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_THAN_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX8((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_THAN_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX16((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_THAN_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX32((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_THAN_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_HEX64((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_THAN_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_THAN_CHAR((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_OR_EQUAL_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_OR_EQUAL_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_OR_EQUAL_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT8((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_OR_EQUAL_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT16((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_OR_EQUAL_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT32((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_OR_EQUAL_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_INT64((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_OR_EQUAL_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_OR_EQUAL_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_OR_EQUAL_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_OR_EQUAL_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_OR_EQUAL_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_OR_EQUAL_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_OR_EQUAL_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_OR_EQUAL_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_OR_EQUAL_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_OR_EQUAL_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_GREATER_OR_EQUAL_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_GREATER_OR_EQUAL_CHAR((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_OR_EQUAL_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_OR_EQUAL_INT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_OR_EQUAL_INT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT8((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_OR_EQUAL_INT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT16((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_OR_EQUAL_INT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT32((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_OR_EQUAL_INT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_INT64((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_OR_EQUAL_UINT_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_OR_EQUAL_UINT8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT8((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_OR_EQUAL_UINT16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT16((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_OR_EQUAL_UINT32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT32((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_OR_EQUAL_UINT64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT64((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_OR_EQUAL_size_t_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_UINT((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_OR_EQUAL_HEX8_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX8((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_OR_EQUAL_HEX16_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX16((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_OR_EQUAL_HEX32_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX32((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_OR_EQUAL_HEX64_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_HEX64((threshold), (actual), __LINE__, (message))
#define TEST_ASSERT_LESS_OR_EQUAL_CHAR_MESSAGE(threshold, actual, message) UNITY_TEST_ASSERT_SMALLER_OR_EQUAL_CHAR((threshold), (actual), __LINE__, (message))
/* Integer Ranges (of all sizes) */
#define TEST_ASSERT_INT_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT_WITHIN((delta), (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_INT8_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT8_WITHIN((delta), (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_INT16_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT16_WITHIN((delta), (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_INT32_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT32_WITHIN((delta), (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_INT64_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_INT64_WITHIN((delta), (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_UINT_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_UINT8_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT8_WITHIN((delta), (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_UINT16_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT16_WITHIN((delta), (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_UINT32_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT32_WITHIN((delta), (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_UINT64_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT64_WITHIN((delta), (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_size_t_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_UINT_WITHIN((delta), (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_HEX_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_HEX8_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX8_WITHIN((delta), (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_HEX16_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX16_WITHIN((delta), (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_HEX32_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX32_WITHIN((delta), (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_HEX64_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_HEX64_WITHIN((delta), (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_CHAR_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_CHAR_WITHIN((delta), (expected), (actual), __LINE__, (message))
/* Integer Array Ranges (of all sizes) */
#define TEST_ASSERT_INT_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
#define TEST_ASSERT_INT8_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
#define TEST_ASSERT_INT16_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
#define TEST_ASSERT_INT32_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
#define TEST_ASSERT_INT64_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_INT64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
#define TEST_ASSERT_UINT_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
#define TEST_ASSERT_UINT8_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
#define TEST_ASSERT_UINT16_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
#define TEST_ASSERT_UINT32_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
#define TEST_ASSERT_UINT64_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
#define TEST_ASSERT_size_t_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_UINT_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
#define TEST_ASSERT_HEX_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
#define TEST_ASSERT_HEX8_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX8_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
#define TEST_ASSERT_HEX16_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX16_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
#define TEST_ASSERT_HEX32_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX32_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
#define TEST_ASSERT_HEX64_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_HEX64_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
#define TEST_ASSERT_CHAR_ARRAY_WITHIN_MESSAGE(delta, expected, actual, num_elements, message) UNITY_TEST_ASSERT_CHAR_ARRAY_WITHIN((delta), (expected), (actual), num_elements, __LINE__, (message))
/* Structs and Strings */
#define TEST_ASSERT_EQUAL_PTR_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_PTR((expected), (actual), __LINE__, (message))
#define TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_STRING((expected), (actual), __LINE__, (message))
#define TEST_ASSERT_EQUAL_STRING_LEN_MESSAGE(expected, actual, len, message) UNITY_TEST_ASSERT_EQUAL_STRING_LEN((expected), (actual), (len), __LINE__, (message))
#define TEST_ASSERT_EQUAL_MEMORY_MESSAGE(expected, actual, len, message) UNITY_TEST_ASSERT_EQUAL_MEMORY((expected), (actual), (len), __LINE__, (message))
/* Arrays */
#define TEST_ASSERT_EQUAL_INT_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EQUAL_INT8_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT8_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EQUAL_INT16_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT16_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EQUAL_INT32_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT32_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EQUAL_INT64_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_INT64_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EQUAL_UINT_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EQUAL_UINT8_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT8_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EQUAL_UINT16_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT16_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EQUAL_UINT32_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT32_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EQUAL_UINT64_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT64_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EQUAL_size_t_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_UINT_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EQUAL_HEX_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EQUAL_HEX8_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX8_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EQUAL_HEX16_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX16_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EQUAL_HEX32_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX32_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EQUAL_HEX64_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_HEX64_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EQUAL_PTR_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_PTR_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EQUAL_STRING_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_STRING_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EQUAL_MEMORY_ARRAY_MESSAGE(expected, actual, len, num_elements, message) UNITY_TEST_ASSERT_EQUAL_MEMORY_ARRAY((expected), (actual), (len), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EQUAL_CHAR_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_CHAR_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
/* Arrays Compared To Single Value*/
#define TEST_ASSERT_EACH_EQUAL_INT_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EACH_EQUAL_INT8_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT8((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EACH_EQUAL_INT16_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT16((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EACH_EQUAL_INT32_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT32((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EACH_EQUAL_INT64_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_INT64((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EACH_EQUAL_UINT_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EACH_EQUAL_UINT8_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT8((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EACH_EQUAL_UINT16_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT16((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EACH_EQUAL_UINT32_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT32((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EACH_EQUAL_UINT64_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT64((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EACH_EQUAL_size_t_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_UINT((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EACH_EQUAL_HEX_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EACH_EQUAL_HEX8_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX8((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EACH_EQUAL_HEX16_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX16((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EACH_EQUAL_HEX32_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX32((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EACH_EQUAL_HEX64_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_HEX64((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EACH_EQUAL_PTR_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_PTR((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EACH_EQUAL_STRING_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_STRING((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EACH_EQUAL_MEMORY_MESSAGE(expected, actual, len, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_MEMORY((expected), (actual), (len), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EACH_EQUAL_CHAR_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_CHAR((expected), (actual), (num_elements), __LINE__, (message))
/* Floating Point (If Enabled) */
#define TEST_ASSERT_FLOAT_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_FLOAT_WITHIN((delta), (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_EQUAL_FLOAT_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_FLOAT((expected), (actual), __LINE__, (message))
#define TEST_ASSERT_EQUAL_FLOAT_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_FLOAT_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EACH_EQUAL_FLOAT_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_FLOAT((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_FLOAT_IS_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_INF((actual), __LINE__, (message))
#define TEST_ASSERT_FLOAT_IS_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NEG_INF((actual), __LINE__, (message))
#define TEST_ASSERT_FLOAT_IS_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NAN((actual), __LINE__, (message))
#define TEST_ASSERT_FLOAT_IS_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_DETERMINATE((actual), __LINE__, (message))
#define TEST_ASSERT_FLOAT_IS_NOT_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_INF((actual), __LINE__, (message))
#define TEST_ASSERT_FLOAT_IS_NOT_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NEG_INF((actual), __LINE__, (message))
#define TEST_ASSERT_FLOAT_IS_NOT_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_NAN((actual), __LINE__, (message))
#define TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_FLOAT_IS_NOT_DETERMINATE((actual), __LINE__, (message))
/* Double (If Enabled) */
#define TEST_ASSERT_DOUBLE_WITHIN_MESSAGE(delta, expected, actual, message) UNITY_TEST_ASSERT_DOUBLE_WITHIN((delta), (expected), (actual), __LINE__, (message))
#define TEST_ASSERT_EQUAL_DOUBLE_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_DOUBLE((expected), (actual), __LINE__, (message))
#define TEST_ASSERT_EQUAL_DOUBLE_ARRAY_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EQUAL_DOUBLE_ARRAY((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_EACH_EQUAL_DOUBLE_MESSAGE(expected, actual, num_elements, message) UNITY_TEST_ASSERT_EACH_EQUAL_DOUBLE((expected), (actual), (num_elements), __LINE__, (message))
#define TEST_ASSERT_DOUBLE_IS_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_INF((actual), __LINE__, (message))
#define TEST_ASSERT_DOUBLE_IS_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NEG_INF((actual), __LINE__, (message))
#define TEST_ASSERT_DOUBLE_IS_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NAN((actual), __LINE__, (message))
#define TEST_ASSERT_DOUBLE_IS_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_DETERMINATE((actual), __LINE__, (message))
#define TEST_ASSERT_DOUBLE_IS_NOT_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_INF((actual), __LINE__, (message))
#define TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NEG_INF((actual), __LINE__, (message))
#define TEST_ASSERT_DOUBLE_IS_NOT_NAN_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_NAN((actual), __LINE__, (message))
#define TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE_MESSAGE(actual, message) UNITY_TEST_ASSERT_DOUBLE_IS_NOT_DETERMINATE((actual), __LINE__, (message))
/* Shorthand */
#ifdef UNITY_SHORTHAND_AS_OLD
#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, (message))
#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, (message))
#endif
#ifdef UNITY_SHORTHAND_AS_INT
#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_INT((expected), (actual), __LINE__, message)
#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand)
#endif
#ifdef UNITY_SHORTHAND_AS_MEM
#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT_EQUAL_MEMORY((&expected), (&actual), sizeof(expected), __LINE__, message)
#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand)
#endif
#ifdef UNITY_SHORTHAND_AS_RAW
#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT(((expected) == (actual)), __LINE__, message)
#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_ASSERT(((expected) != (actual)), __LINE__, message)
#endif
#ifdef UNITY_SHORTHAND_AS_NONE
#define TEST_ASSERT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand)
#define TEST_ASSERT_NOT_EQUAL_MESSAGE(expected, actual, message) UNITY_TEST_FAIL(__LINE__, UnityStrErrShorthand)
#endif
/* end of UNITY_FRAMEWORK_H */
#ifdef __cplusplus
}
#endif
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,72 @@
#include <string.h>
#include "unity.h"
#include "taskchampion.h"
// creating UUIDs does not crash
static void test_uuid_creation(void) {
tc_uuid_new_v4();
tc_uuid_nil();
}
// converting UUIDs to a buf works
static void test_uuid_to_buf(void) {
TEST_ASSERT_EQUAL(TC_UUID_STRING_BYTES, 36);
TCUuid u2 = tc_uuid_nil();
char u2str[TC_UUID_STRING_BYTES];
tc_uuid_to_buf(u2, u2str);
TEST_ASSERT_EQUAL_MEMORY("00000000-0000-0000-0000-000000000000", u2str, TC_UUID_STRING_BYTES);
}
// converting UUIDs to a buf works
static void test_uuid_to_str(void) {
TCUuid u = tc_uuid_nil();
TCString s = tc_uuid_to_str(u);
TEST_ASSERT_EQUAL_STRING(
"00000000-0000-0000-0000-000000000000",
tc_string_content(&s));
tc_string_free(&s);
}
// converting valid UUIDs from string works
static void test_uuid_valid_from_str(void) {
TCUuid u;
char *ustr = "23cb25e0-5d1a-4932-8131-594ac6d3a843";
TEST_ASSERT_EQUAL(TC_RESULT_OK, tc_uuid_from_str(tc_string_borrow(ustr), &u));
TEST_ASSERT_EQUAL(0x23, u.bytes[0]);
TEST_ASSERT_EQUAL(0x43, u.bytes[15]);
}
// converting invalid UUIDs from string fails as expected
static void test_uuid_invalid_string_fails(void) {
TCUuid u;
char *ustr = "not-a-valid-uuid";
TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_uuid_from_str(tc_string_borrow(ustr), &u));
}
// converting invalid UTF-8 UUIDs from string fails as expected
static void test_uuid_bad_utf8(void) {
TCUuid u;
char *ustr = "\xf0\x28\x8c\xbc";
TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_uuid_from_str(tc_string_borrow(ustr), &u));
}
// converting a string with embedded NUL fails as expected
static void test_uuid_embedded_nul(void) {
TCUuid u;
TEST_ASSERT_EQUAL(TC_RESULT_ERROR, tc_uuid_from_str(tc_string_clone_with_len("ab\0de", 5), &u));
}
int uuid_tests(void) {
UNITY_BEGIN();
// each test case above should be named here, in order.
RUN_TEST(test_uuid_creation);
RUN_TEST(test_uuid_valid_from_str);
RUN_TEST(test_uuid_to_buf);
RUN_TEST(test_uuid_to_str);
RUN_TEST(test_uuid_invalid_string_fails);
RUN_TEST(test_uuid_bad_utf8);
RUN_TEST(test_uuid_embedded_nul);
return UNITY_END();
}

View file

@ -0,0 +1 @@
pub mod bindings_tests;

View file

@ -0,0 +1,31 @@
use lazy_static::lazy_static;
use std::sync::Mutex;
use tempfile::TempDir;
lazy_static! {
// the C library running the tests is not reentrant, so we use a mutex to ensure that only one
// test runs at a time.
static ref MUTEX: Mutex<()> = Mutex::new(());
}
macro_rules! suite(
{ $s:ident } => {
#[test]
fn $s() {
let tmp_dir = TempDir::new().expect("TempDir failed");
let (res, output) = {
let _guard = MUTEX.lock().unwrap();
// run the tests in the temp dir (NOTE: this must be inside
// the mutex guard!)
std::env::set_current_dir(tmp_dir.as_ref()).unwrap();
integration_tests::bindings_tests::$s()
};
println!("{}", output);
if res != 0 {
assert!(false, "test failed");
}
}
};
);
include!(concat!(env!("OUT_DIR"), "/bindings_test_suites.rs"));

18
lib/Cargo.toml Normal file
View file

@ -0,0 +1,18 @@
[package]
name = "taskchampion-lib"
version = "0.1.0"
edition = "2018"
[lib]
name = "taskchampion"
crate-type = ["staticlib", "cdylib"]
[dependencies]
libc = "0.2.113"
chrono = "^0.4.10"
taskchampion = { path = "../taskchampion" }
uuid = { version = "^0.8.2", features = ["v4"] }
anyhow = "1.0"
[dev-dependencies]
pretty_assertions = "1"

2
lib/Makefile Normal file
View file

@ -0,0 +1,2 @@
taskchampion.h: cbindgen.toml ../target/debug/libtaskchampion.so
cbindgen --config cbindgen.toml --crate taskchampion-lib --output $@

76
lib/header-intro.h Normal file
View file

@ -0,0 +1,76 @@
/**
* TaskChampion
*
* This file defines the C interface to libtaskchampion. This is a thin
* wrapper around the Rust `taskchampion` crate. Refer to the documentation
* for that crate at https://docs.rs/taskchampion/latest/taskchampion/ for API
* details. The comments in this file focus mostly on the low-level details of
* passing values to and from TaskChampion.
*
* # Overview
*
* This library defines two major types used to interact with the API, that map directly
* to Rust types.
*
* * TCReplica - see https://docs.rs/taskchampion/latest/taskchampion/struct.Replica.html
* * TCTask - see https://docs.rs/taskchampion/latest/taskchampion/struct.Task.html
* * TCServer - see https://docs.rs/taskchampion/latest/taskchampion/trait.Server.html
* * TCWorkingSet - see https://docs.rs/taskchampion/latest/taskchampion/struct.WorkingSet.html
*
* It also defines a few utility types:
*
* * TCString - a wrapper around both C (NUL-terminated) and Rust (always utf-8) strings.
* * TCList - a list of objects represented as a C array
* * see below for the remainder
*
* # Safety
*
* Each type contains specific instructions to ensure memory safety.
* The general rules are as follows.
*
* No types in this library are threadsafe. All values should be used in only
* one thread for their entire lifetime. It is safe to use unrelated values in
* different threads (for example, different threads may use different
* TCReplica values concurrently).
*
* ## Pass by Pointer
*
* Several types such as TCReplica and TCString are "opaque" types and always
* handled as pointers in C. The bytes these pointers address are private to
* the Rust implemetation and must not be accessed from C.
*
* Pass-by-pointer values have exactly one owner, and that owner is responsible
* for freeing the value (using a `tc__free` function), or transferring
* ownership elsewhere. Except where documented otherwise, when a value is
* passed to C, ownership passes to C as well. When a value is passed to Rust,
* ownership stays with the C code. The exception is TCString, ownership of
* which passes to Rust when it is used as a function argument.
*
* The limited circumstances where one value must not outlive another, due to
* pointer references between them, are documented below.
*
* ## Pass by Value
*
* Types such as TCUuid and TCList are passed by value, and contain fields
* that are accessible from C. C code is free to access the content of these
* types in a _read_only_ fashion.
*
* Pass-by-value values that contain pointers also have exactly one owner,
* responsible for freeing the value or transferring ownership. The tc__free
* functions for these types will replace the pointers with NULL to guard
* against use-after-free errors. The interior pointers in such values should
* never be freed directly (for example, `tc_string_free(tcuda.value)` is an
* error).
*
* TCUuid is a special case, because it does not contain pointers. It can be
* freely copied and need not be freed.
*
* ## Lists
*
* Lists are a special kind of pass-by-value type. Each contains `len` and
* `items`, where `items` is an array of length `len`. Lists, and the values
* in the `items` array, must be treated as read-only. On return from an API
* function, a list's ownership is with the C caller, which must eventually
* free the list. List data must be freed with the `tc__list_free` function.
* It is an error to free any value in the `items` array of a list.
*/

143
lib/src/annotation.rs Normal file
View file

@ -0,0 +1,143 @@
use crate::traits::*;
use crate::types::*;
use chrono::prelude::*;
/// TCAnnotation contains the details of an annotation.
///
/// # Safety
///
/// An annotation must be initialized from a tc_.. function, and later freed
/// with `tc_annotation_free` or `tc_annotation_list_free`.
///
/// Any function taking a `*TCAnnotation` requires:
/// - the pointer must not be NUL;
/// - the pointer must be one previously returned from a tc_… function;
/// - the memory referenced by the pointer must never be modified by C code; and
/// - ownership transfers to the called function, and the value must not be used
/// after the call returns. In fact, the value will be zeroed out to ensure this.
///
/// TCAnnotations are not threadsafe.
#[repr(C)]
pub struct TCAnnotation {
/// Time the annotation was made. Must be nonzero.
pub entry: libc::time_t,
/// Content of the annotation. Must not be NULL.
pub description: TCString,
}
impl PassByValue for TCAnnotation {
// NOTE: we cannot use `RustType = Annotation` here because conversion of the
// Rust to a String can fail.
type RustType = (DateTime<Utc>, RustString<'static>);
unsafe fn from_ctype(mut self) -> Self::RustType {
// SAFETY:
// - any time_t value is valid
// - time_t is copy, so ownership is not important
let entry = unsafe { libc::time_t::val_from_arg(self.entry) }.unwrap();
// SAFETY:
// - self.description is valid (came from return_val in as_ctype)
// - self is owned, so we can take ownership of this TCString
let description =
unsafe { TCString::take_val_from_arg(&mut self.description, TCString::default()) };
(entry, description)
}
fn as_ctype((entry, description): Self::RustType) -> Self {
TCAnnotation {
entry: libc::time_t::as_ctype(Some(entry)),
// SAFETY:
// - ownership of the TCString tied to ownership of Self
description: unsafe { TCString::return_val(description) },
}
}
}
impl Default for TCAnnotation {
fn default() -> Self {
TCAnnotation {
entry: 0 as libc::time_t,
description: TCString::default(),
}
}
}
/// TCAnnotationList represents a list of annotations.
///
/// The content of this struct must be treated as read-only.
#[repr(C)]
pub struct TCAnnotationList {
/// number of annotations in items
len: libc::size_t,
/// total size of items (internal use only)
_capacity: libc::size_t,
/// array of annotations. these remain owned by the TCAnnotationList instance and will be freed by
/// tc_annotation_list_free. This pointer is never NULL for a valid TCAnnotationList.
items: *const TCAnnotation,
}
impl CList for TCAnnotationList {
type Element = TCAnnotation;
unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self {
TCAnnotationList {
len,
_capacity: cap,
items,
}
}
fn into_raw_parts(self) -> (*const Self::Element, usize, usize) {
(self.items, self.len, self._capacity)
}
}
/// Free a TCAnnotation instance. The instance, and the TCString it contains, must not be used
/// after this call.
#[no_mangle]
pub unsafe extern "C" fn tc_annotation_free(tcann: *mut TCAnnotation) {
debug_assert!(!tcann.is_null());
// SAFETY:
// - tcann is not NULL
// - *tcann is a valid TCAnnotation (caller promised to treat it as read-only)
let annotation = unsafe { TCAnnotation::take_val_from_arg(tcann, TCAnnotation::default()) };
drop(annotation);
}
/// Free a TCAnnotationList instance. The instance, and all TCAnnotations it contains, must not be used after
/// this call.
///
/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCAnnotationList.
#[no_mangle]
pub unsafe extern "C" fn tc_annotation_list_free(tcanns: *mut TCAnnotationList) {
// SAFETY:
// - tcanns is not NULL and points to a valid TCAnnotationList (caller is not allowed to
// modify the list)
// - caller promises not to use the value after return
unsafe { drop_value_list(tcanns) }
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn empty_list_has_non_null_pointer() {
let tcanns = unsafe { TCAnnotationList::return_val(Vec::new()) };
assert!(!tcanns.items.is_null());
assert_eq!(tcanns.len, 0);
assert_eq!(tcanns._capacity, 0);
}
#[test]
fn free_sets_null_pointer() {
let mut tcanns = unsafe { TCAnnotationList::return_val(Vec::new()) };
// SAFETY: testing expected behavior
unsafe { tc_annotation_list_free(&mut tcanns) };
assert!(tcanns.items.is_null());
assert_eq!(tcanns.len, 0);
assert_eq!(tcanns._capacity, 0);
}
}

34
lib/src/atomic.rs Normal file
View file

@ -0,0 +1,34 @@
//! Trait implementations for a few atomic types
use crate::traits::*;
use chrono::prelude::*;
impl PassByValue for usize {
type RustType = usize;
unsafe fn from_ctype(self) -> usize {
self
}
fn as_ctype(arg: usize) -> usize {
arg
}
}
/// Convert an Option<DateTime<Utc>> to a libc::time_t, or zero if not set.
impl PassByValue for libc::time_t {
type RustType = Option<DateTime<Utc>>;
unsafe fn from_ctype(self) -> Option<DateTime<Utc>> {
if self == 0 {
None
} else {
Some(Utc.timestamp(self as i64, 0))
}
}
fn as_ctype(arg: Option<DateTime<Utc>>) -> libc::time_t {
arg.map(|ts| ts.timestamp() as libc::time_t)
.unwrap_or(0 as libc::time_t)
}
}

106
lib/src/kv.rs Normal file
View file

@ -0,0 +1,106 @@
use crate::traits::*;
use crate::types::*;
/// TCKV contains a key/value pair that is part of a task.
///
/// Neither key nor value are ever NULL. They remain owned by the TCKV and
/// will be freed when it is freed with tc_kv_list_free.
#[repr(C)]
pub struct TCKV {
pub key: TCString,
pub value: TCString,
}
impl PassByValue for TCKV {
type RustType = (RustString<'static>, RustString<'static>);
unsafe fn from_ctype(self) -> Self::RustType {
// SAFETY:
// - self.key is not NULL (field docstring)
// - self.key came from return_ptr in as_ctype
// - self is owned, so we can take ownership of this TCString
let key = unsafe { TCString::val_from_arg(self.key) };
// SAFETY: (same)
let value = unsafe { TCString::val_from_arg(self.value) };
(key, value)
}
fn as_ctype((key, value): Self::RustType) -> Self {
TCKV {
// SAFETY:
// - ownership of the TCString tied to ownership of Self
key: unsafe { TCString::return_val(key) },
// SAFETY:
// - ownership of the TCString tied to ownership of Self
value: unsafe { TCString::return_val(value) },
}
}
}
/// TCKVList represents a list of key/value pairs.
///
/// The content of this struct must be treated as read-only.
#[repr(C)]
pub struct TCKVList {
/// number of key/value pairs in items
len: libc::size_t,
/// total size of items (internal use only)
_capacity: libc::size_t,
/// array of TCKV's. these remain owned by the TCKVList instance and will be freed by
/// tc_kv_list_free. This pointer is never NULL for a valid TCKVList.
items: *const TCKV,
}
impl CList for TCKVList {
type Element = TCKV;
unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self {
TCKVList {
len,
_capacity: cap,
items,
}
}
fn into_raw_parts(self) -> (*const Self::Element, usize, usize) {
(self.items, self.len, self._capacity)
}
}
/// Free a TCKVList instance. The instance, and all TCKVs it contains, must not be used after
/// this call.
///
/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCKVList.
#[no_mangle]
pub unsafe extern "C" fn tc_kv_list_free(tckvs: *mut TCKVList) {
// SAFETY:
// - tckvs is not NULL and points to a valid TCKVList (caller is not allowed to
// modify the list)
// - caller promises not to use the value after return
unsafe { drop_value_list(tckvs) }
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn empty_list_has_non_null_pointer() {
let tckvs = unsafe { TCKVList::return_val(Vec::new()) };
assert!(!tckvs.items.is_null());
assert_eq!(tckvs.len, 0);
assert_eq!(tckvs._capacity, 0);
}
#[test]
fn free_sets_null_pointer() {
let mut tckvs = unsafe { TCKVList::return_val(Vec::new()) };
// SAFETY: testing expected behavior
unsafe { tc_kv_list_free(&mut tckvs) };
assert!(tckvs.items.is_null());
assert_eq!(tckvs.len, 0);
assert_eq!(tckvs._capacity, 0);
}
}

39
lib/src/lib.rs Normal file
View file

@ -0,0 +1,39 @@
// Not compatible with the MSRV
// #![warn(unsafe_op_in_unsafe_fn)]
#![allow(unused_unsafe)]
// Not working yet in stable - https://github.com/rust-lang/rust-clippy/issues/8020
// #![warn(clippy::undocumented_unsafe_blocks)]
// docstrings for extern "C" functions are reflected into C, and do not benefit
// from safety docs.
#![allow(clippy::missing_safety_doc)]
mod traits;
mod util;
pub mod annotation;
pub mod atomic;
pub mod kv;
pub mod replica;
pub mod result;
pub mod server;
pub mod status;
pub mod string;
pub mod task;
pub mod uda;
pub mod uuid;
pub mod workingset;
pub(crate) mod types {
pub(crate) use crate::annotation::{TCAnnotation, TCAnnotationList};
pub(crate) use crate::kv::{TCKVList, TCKV};
pub(crate) use crate::replica::TCReplica;
pub(crate) use crate::result::TCResult;
pub(crate) use crate::server::TCServer;
pub(crate) use crate::status::TCStatus;
pub(crate) use crate::string::{RustString, TCString, TCStringList};
pub(crate) use crate::task::{TCTask, TCTaskList};
pub(crate) use crate::uda::{TCUda, TCUdaList, Uda};
pub(crate) use crate::uuid::{TCUuid, TCUuidList};
pub(crate) use crate::workingset::TCWorkingSet;
}

428
lib/src/replica.rs Normal file
View file

@ -0,0 +1,428 @@
use crate::traits::*;
use crate::types::*;
use crate::util::err_to_ruststring;
use std::ptr::NonNull;
use taskchampion::{Replica, StorageConfig};
/// A replica represents an instance of a user's task data, providing an easy interface
/// for querying and modifying that data.
///
/// # Error Handling
///
/// When a `tc_replica_..` function that returns a TCResult returns TC_RESULT_ERROR, then
/// `tc_replica_error` will return the error message.
///
/// # Safety
///
/// The `*TCReplica` returned from `tc_replica_new…` functions is owned by the caller and
/// must later be freed to avoid a memory leak.
///
/// Any function taking a `*TCReplica` requires:
/// - the pointer must not be NUL;
/// - the pointer must be one previously returned from a tc_… function;
/// - the memory referenced by the pointer must never be modified by C code; and
/// - except for `tc_replica_free`, ownership of a `*TCReplica` remains with the caller.
///
/// Once passed to `tc_replica_free`, a `*TCReplica` becomes invalid and must not be used again.
///
/// TCReplicas are not threadsafe.
pub struct TCReplica {
/// The wrapped Replica
inner: Replica,
/// If true, this replica has an outstanding &mut (for a TaskMut)
mut_borrowed: bool,
/// The error from the most recent operation, if any
error: Option<RustString<'static>>,
}
impl PassByPointer for TCReplica {}
impl TCReplica {
/// Mutably borrow the inner Replica
pub(crate) fn borrow_mut(&mut self) -> &mut Replica {
if self.mut_borrowed {
panic!("replica is already borrowed");
}
self.mut_borrowed = true;
&mut self.inner
}
/// Release the borrow made by [`borrow_mut`]
pub(crate) fn release_borrow(&mut self) {
if !self.mut_borrowed {
panic!("replica is not borrowed");
}
self.mut_borrowed = false;
}
}
impl From<Replica> for TCReplica {
fn from(rep: Replica) -> TCReplica {
TCReplica {
inner: rep,
mut_borrowed: false,
error: None,
}
}
}
/// Utility function to allow using `?` notation to return an error value. This makes
/// a mutable borrow, because most Replica methods require a `&mut`.
fn wrap<T, F>(rep: *mut TCReplica, f: F, err_value: T) -> T
where
F: FnOnce(&mut Replica) -> anyhow::Result<T>,
{
debug_assert!(!rep.is_null());
// SAFETY:
// - rep is not NULL (promised by caller)
// - *rep is a valid TCReplica (promised by caller)
// - rep is valid for the duration of this function
// - rep is not modified by anything else (not threadsafe)
let rep: &mut TCReplica = unsafe { TCReplica::from_ptr_arg_ref_mut(rep) };
if rep.mut_borrowed {
panic!("replica is borrowed and cannot be used");
}
rep.error = None;
match f(&mut rep.inner) {
Ok(v) => v,
Err(e) => {
rep.error = Some(err_to_ruststring(e));
err_value
}
}
}
/// Utility function to allow using `?` notation to return an error value in the constructor.
fn wrap_constructor<T, F>(f: F, error_out: *mut TCString, err_value: T) -> T
where
F: FnOnce() -> anyhow::Result<T>,
{
if !error_out.is_null() {
// SAFETY:
// - error_out is not NULL (just checked)
// - properly aligned and valid (promised by caller)
unsafe { *error_out = TCString::default() };
}
match f() {
Ok(v) => v,
Err(e) => {
if !error_out.is_null() {
// SAFETY:
// - error_out is not NULL (just checked)
// - properly aligned and valid (promised by caller)
unsafe {
TCString::val_to_arg_out(err_to_ruststring(e), error_out);
}
}
err_value
}
}
}
/// Create a new TCReplica with an in-memory database. The contents of the database will be
/// lost when it is freed with tc_replica_free.
#[no_mangle]
pub unsafe extern "C" fn tc_replica_new_in_memory() -> *mut TCReplica {
let storage = StorageConfig::InMemory
.into_storage()
.expect("in-memory always succeeds");
// SAFETY:
// - caller promises to free this value
unsafe { TCReplica::from(Replica::new(storage)).return_ptr() }
}
/// Create a new TCReplica with an on-disk database having the given filename. On error, a string
/// is written to the error_out parameter (if it is not NULL) and NULL is returned. The caller
/// must free this string.
#[no_mangle]
pub unsafe extern "C" fn tc_replica_new_on_disk(
path: TCString,
error_out: *mut TCString,
) -> *mut TCReplica {
wrap_constructor(
|| {
// SAFETY:
// - path is valid (promised by caller)
// - caller will not use path after this call (convention)
let mut path = unsafe { TCString::val_from_arg(path) };
let storage = StorageConfig::OnDisk {
taskdb_dir: path.to_path_buf()?,
}
.into_storage()?;
// SAFETY:
// - caller promises to free this value
Ok(unsafe { TCReplica::from(Replica::new(storage)).return_ptr() })
},
error_out,
std::ptr::null_mut(),
)
}
/// Get a list of all tasks in the replica.
///
/// Returns a TCTaskList with a NULL items field on error.
#[no_mangle]
pub unsafe extern "C" fn tc_replica_all_tasks(rep: *mut TCReplica) -> TCTaskList {
wrap(
rep,
|rep| {
// note that the Replica API returns a hashmap here, but we discard
// the keys and return a simple list. The task UUIDs are available
// from task.get_uuid(), so information is not lost.
let tasks: Vec<_> = rep
.all_tasks()?
.drain()
.map(|(_uuid, t)| {
NonNull::new(
// SAFETY:
// - caller promises to free this value (via freeing the list)
unsafe { TCTask::from(t).return_ptr() },
)
.expect("TCTask::return_ptr returned NULL")
})
.collect();
// SAFETY:
// - value is not allocated and need not be freed
Ok(unsafe { TCTaskList::return_val(tasks) })
},
TCTaskList::null_value(),
)
}
/// Get a list of all uuids for tasks in the replica.
///
/// Returns a TCUuidList with a NULL items field on error.
///
/// The caller must free the UUID list with `tc_uuid_list_free`.
#[no_mangle]
pub unsafe extern "C" fn tc_replica_all_task_uuids(rep: *mut TCReplica) -> TCUuidList {
wrap(
rep,
|rep| {
let uuids: Vec<_> = rep
.all_task_uuids()?
.drain(..)
// SAFETY:
// - value is not allocated and need not be freed
.map(|uuid| unsafe { TCUuid::return_val(uuid) })
.collect();
// SAFETY:
// - value will be freed (promised by caller)
Ok(unsafe { TCUuidList::return_val(uuids) })
},
TCUuidList::null_value(),
)
}
/// Get the current working set for this replica. The resulting value must be freed
/// with tc_working_set_free.
///
/// Returns NULL on error.
#[no_mangle]
pub unsafe extern "C" fn tc_replica_working_set(rep: *mut TCReplica) -> *mut TCWorkingSet {
wrap(
rep,
|rep| {
let ws = rep.working_set()?;
// SAFETY:
// - caller promises to free this value
Ok(unsafe { TCWorkingSet::return_ptr(ws.into()) })
},
std::ptr::null_mut(),
)
}
/// Get an existing task by its UUID.
///
/// Returns NULL when the task does not exist, and on error. Consult tc_replica_error
/// to distinguish the two conditions.
#[no_mangle]
pub unsafe extern "C" fn tc_replica_get_task(rep: *mut TCReplica, tcuuid: TCUuid) -> *mut TCTask {
wrap(
rep,
|rep| {
// SAFETY:
// - tcuuid is a valid TCUuid (all bytes are valid)
// - tcuuid is Copy so ownership doesn't matter
let uuid = unsafe { TCUuid::val_from_arg(tcuuid) };
if let Some(task) = rep.get_task(uuid)? {
// SAFETY:
// - caller promises to free this task
Ok(unsafe { TCTask::from(task).return_ptr() })
} else {
Ok(std::ptr::null_mut())
}
},
std::ptr::null_mut(),
)
}
/// Create a new task. The task must not already exist.
///
/// Returns the task, or NULL on error.
#[no_mangle]
pub unsafe extern "C" fn tc_replica_new_task(
rep: *mut TCReplica,
status: TCStatus,
description: TCString,
) -> *mut TCTask {
// SAFETY:
// - description is valid (promised by caller)
// - caller will not use description after this call (convention)
let mut description = unsafe { TCString::val_from_arg(description) };
wrap(
rep,
|rep| {
let task = rep.new_task(status.into(), description.as_str()?.to_string())?;
// SAFETY:
// - caller promises to free this task
Ok(unsafe { TCTask::from(task).return_ptr() })
},
std::ptr::null_mut(),
)
}
/// Create a new task. The task must not already exist.
///
/// Returns the task, or NULL on error.
#[no_mangle]
pub unsafe extern "C" fn tc_replica_import_task_with_uuid(
rep: *mut TCReplica,
tcuuid: TCUuid,
) -> *mut TCTask {
wrap(
rep,
|rep| {
// SAFETY:
// - tcuuid is a valid TCUuid (all bytes are valid)
// - tcuuid is Copy so ownership doesn't matter
let uuid = unsafe { TCUuid::val_from_arg(tcuuid) };
let task = rep.import_task_with_uuid(uuid)?;
// SAFETY:
// - caller promises to free this task
Ok(unsafe { TCTask::from(task).return_ptr() })
},
std::ptr::null_mut(),
)
}
/// Synchronize this replica with a server.
///
/// The `server` argument remains owned by the caller, and must be freed explicitly.
#[no_mangle]
pub unsafe extern "C" fn tc_replica_sync(
rep: *mut TCReplica,
server: *mut TCServer,
avoid_snapshots: bool,
) -> TCResult {
wrap(
rep,
|rep| {
debug_assert!(!server.is_null());
// SAFETY:
// - server is not NULL
// - *server is a valid TCServer (promised by caller)
// - server is valid for the lifetime of tc_replica_sync (not threadsafe)
// - server will not be accessed simultaneously (not threadsafe)
let server = unsafe { TCServer::from_ptr_arg_ref_mut(server) };
rep.sync(server.as_mut(), avoid_snapshots)?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Undo local operations until the most recent UndoPoint.
///
/// If undone_out is not NULL, then on success it is set to 1 if operations were undone, or 0 if
/// there are no operations that can be done.
#[no_mangle]
pub unsafe extern "C" fn tc_replica_undo(rep: *mut TCReplica, undone_out: *mut i32) -> TCResult {
wrap(
rep,
|rep| {
let undone = if rep.undo()? { 1 } else { 0 };
if !undone_out.is_null() {
// SAFETY:
// - undone_out is not NULL (just checked)
// - undone_out is properly aligned (implicitly promised by caller)
unsafe { *undone_out = undone };
}
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Add an UndoPoint, if one has not already been added by this Replica. This occurs automatically
/// when a change is made. The `force` flag allows forcing a new UndoPoint even if one has already
/// been created by this Replica, and may be useful when a Replica instance is held for a long time
/// and used to apply more than one user-visible change.
#[no_mangle]
pub unsafe extern "C" fn tc_replica_add_undo_point(rep: *mut TCReplica, force: bool) -> TCResult {
wrap(
rep,
|rep| {
rep.add_undo_point(force)?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Rebuild this replica's working set, based on whether tasks are pending or not. If `renumber`
/// is true, then existing tasks may be moved to new working-set indices; in any case, on
/// completion all pending tasks are in the working set and all non- pending tasks are not.
#[no_mangle]
pub unsafe extern "C" fn tc_replica_rebuild_working_set(
rep: *mut TCReplica,
renumber: bool,
) -> TCResult {
wrap(
rep,
|rep| {
rep.rebuild_working_set(renumber)?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Get the latest error for a replica, or a string with NULL ptr if no error exists. Subsequent
/// calls to this function will return NULL. The rep pointer must not be NULL. The caller must
/// free the returned string.
#[no_mangle]
pub unsafe extern "C" fn tc_replica_error(rep: *mut TCReplica) -> TCString {
// SAFETY:
// - rep is not NULL (promised by caller)
// - *rep is a valid TCReplica (promised by caller)
// - rep is valid for the duration of this function
// - rep is not modified by anything else (not threadsafe)
let rep: &mut TCReplica = unsafe { TCReplica::from_ptr_arg_ref_mut(rep) };
if let Some(rstring) = rep.error.take() {
// SAFETY:
// - caller promises to free this string
unsafe { TCString::return_val(rstring) }
} else {
TCString::default()
}
}
/// Free a replica. The replica may not be used after this function returns and must not be freed
/// more than once.
#[no_mangle]
pub unsafe extern "C" fn tc_replica_free(rep: *mut TCReplica) {
// SAFETY:
// - replica is not NULL (promised by caller)
// - replica is valid (promised by caller)
// - caller will not use description after this call (promised by caller)
let replica = unsafe { TCReplica::take_from_ptr_arg(rep) };
if replica.mut_borrowed {
panic!("replica is borrowed and cannot be freed");
}
drop(replica);
}

9
lib/src/result.rs Normal file
View file

@ -0,0 +1,9 @@
/// A result from a TC operation. Typically if this value is TC_RESULT_ERROR,
/// the associated object's `tc_.._error` method will return an error message.
/// cbindgen:prefix-with-name
/// cbindgen:rename-all=ScreamingSnakeCase
#[repr(i32)]
pub enum TCResult {
Error = -1,
Ok = 0,
}

143
lib/src/server.rs Normal file
View file

@ -0,0 +1,143 @@
use crate::traits::*;
use crate::types::*;
use crate::util::err_to_ruststring;
use taskchampion::{Server, ServerConfig};
/// TCServer represents an interface to a sync server. Aside from new and free, a server
/// has no C-accessible API, but is designed to be passed to `tc_replica_sync`.
///
/// ## Safety
///
/// TCServer are not threadsafe, and must not be used with multiple replicas simultaneously.
pub struct TCServer(Box<dyn Server>);
impl PassByPointer for TCServer {}
impl From<Box<dyn Server>> for TCServer {
fn from(server: Box<dyn Server>) -> TCServer {
TCServer(server)
}
}
impl AsMut<Box<dyn Server>> for TCServer {
fn as_mut(&mut self) -> &mut Box<dyn Server> {
&mut self.0
}
}
/// Utility function to allow using `?` notation to return an error value.
fn wrap<T, F>(f: F, error_out: *mut TCString, err_value: T) -> T
where
F: FnOnce() -> anyhow::Result<T>,
{
if !error_out.is_null() {
// SAFETY:
// - error_out is not NULL (just checked)
// - properly aligned and valid (promised by caller)
unsafe { *error_out = TCString::default() };
}
match f() {
Ok(v) => v,
Err(e) => {
if !error_out.is_null() {
// SAFETY:
// - error_out is not NULL (just checked)
// - properly aligned and valid (promised by caller)
unsafe {
TCString::val_to_arg_out(err_to_ruststring(e), error_out);
}
}
err_value
}
}
}
/// Create a new TCServer that operates locally (on-disk). See the TaskChampion docs for the
/// description of the arguments.
///
/// On error, a string is written to the error_out parameter (if it is not NULL) and NULL is
/// returned. The caller must free this string.
///
/// The server must be freed after it is used - tc_replica_sync does not automatically free it.
#[no_mangle]
pub unsafe extern "C" fn tc_server_new_local(
server_dir: TCString,
error_out: *mut TCString,
) -> *mut TCServer {
wrap(
|| {
// SAFETY:
// - server_dir is valid (promised by caller)
// - caller will not use server_dir after this call (convention)
let mut server_dir = unsafe { TCString::val_from_arg(server_dir) };
let server_config = ServerConfig::Local {
server_dir: server_dir.to_path_buf()?,
};
let server = server_config.into_server()?;
// SAFETY: caller promises to free this server.
Ok(unsafe { TCServer::return_ptr(server.into()) })
},
error_out,
std::ptr::null_mut(),
)
}
/// Create a new TCServer that connects to a remote server. See the TaskChampion docs for the
/// description of the arguments.
///
/// On error, a string is written to the error_out parameter (if it is not NULL) and NULL is
/// returned. The caller must free this string.
///
/// The server must be freed after it is used - tc_replica_sync does not automatically free it.
#[no_mangle]
pub unsafe extern "C" fn tc_server_new_remote(
origin: TCString,
client_key: TCUuid,
encryption_secret: TCString,
error_out: *mut TCString,
) -> *mut TCServer {
wrap(
|| {
// SAFETY:
// - origin is valid (promised by caller)
// - origin ownership is transferred to this function
let origin = unsafe { TCString::val_from_arg(origin) }.into_string()?;
// SAFETY:
// - client_key is a valid Uuid (any 8-byte sequence counts)
let client_key = unsafe { TCUuid::val_from_arg(client_key) };
// SAFETY:
// - encryption_secret is valid (promised by caller)
// - encryption_secret ownership is transferred to this function
let encryption_secret = unsafe { TCString::val_from_arg(encryption_secret) }
.as_bytes()
.to_vec();
let server_config = ServerConfig::Remote {
origin,
client_key,
encryption_secret,
};
let server = server_config.into_server()?;
// SAFETY: caller promises to free this server.
Ok(unsafe { TCServer::return_ptr(server.into()) })
},
error_out,
std::ptr::null_mut(),
)
}
/// Free a server. The server may not be used after this function returns and must not be freed
/// more than once.
#[no_mangle]
pub unsafe extern "C" fn tc_server_free(server: *mut TCServer) {
debug_assert!(!server.is_null());
// SAFETY:
// - server is not NULL
// - server came from tc_server_new_.., which used return_ptr
// - server will not be used after (promised by caller)
let server = unsafe { TCServer::take_from_ptr_arg(server) };
drop(server);
}

36
lib/src/status.rs Normal file
View file

@ -0,0 +1,36 @@
pub use taskchampion::Status;
/// The status of a task, as defined by the task data model.
/// cbindgen:prefix-with-name
/// cbindgen:rename-all=ScreamingSnakeCase
#[repr(C)]
pub enum TCStatus {
Pending,
Completed,
Deleted,
/// Unknown signifies a status in the task DB that was not
/// recognized.
Unknown,
}
impl From<TCStatus> for Status {
fn from(status: TCStatus) -> Status {
match status {
TCStatus::Pending => Status::Pending,
TCStatus::Completed => Status::Completed,
TCStatus::Deleted => Status::Deleted,
TCStatus::Unknown => Status::Unknown("unknown".to_string()),
}
}
}
impl From<Status> for TCStatus {
fn from(status: Status) -> TCStatus {
match status {
Status::Pending => TCStatus::Pending,
Status::Completed => TCStatus::Completed,
Status::Deleted => TCStatus::Deleted,
Status::Unknown(_) => TCStatus::Unknown,
}
}
}

703
lib/src/string.rs Normal file
View file

@ -0,0 +1,703 @@
use crate::traits::*;
use crate::util::{string_into_raw_parts, vec_into_raw_parts};
use std::ffi::{CStr, CString, OsString};
use std::os::raw::c_char;
use std::path::PathBuf;
/// TCString supports passing strings into and out of the TaskChampion API.
///
/// # Rust Strings and C Strings
///
/// A Rust string can contain embedded NUL characters, while C considers such a character to mark
/// the end of a string. Strings containing embedded NULs cannot be represented as a "C string"
/// and must be accessed using `tc_string_content_and_len` and `tc_string_clone_with_len`. In
/// general, these two functions should be used for handling arbitrary data, while more convenient
/// forms may be used where embedded NUL characters are impossible, such as in static strings.
///
/// # UTF-8
///
/// TaskChampion expects all strings to be valid UTF-8. `tc_string_…` functions will fail if given
/// a `*TCString` containing invalid UTF-8.
///
/// # Safety
///
/// The `ptr` field may be checked for NULL, where documentation indicates this is possible. All
/// other fields in a TCString are private and must not be used from C. They exist in the struct
/// to ensure proper allocation and alignment.
///
/// When a `TCString` appears as a return value or output argument, ownership is passed to the
/// caller. The caller must pass that ownership back to another function or free the string.
///
/// Any function taking a `TCString` requires:
/// - the pointer must not be NUL;
/// - the pointer must be one previously returned from a tc_… function; and
/// - the memory referenced by the pointer must never be modified by C code.
///
/// Unless specified otherwise, TaskChampion functions take ownership of a `TCString` when it is
/// given as a function argument, and the caller must not use or free TCStrings after passing them
/// to such API functions.
///
/// A TCString with a NULL `ptr` field need not be freed, although tc_free_string will not fail
/// for such a value.
///
/// TCString is not threadsafe.
/// cbindgen:field-names=[ptr, _u1, _u2, _u3]
#[repr(C)]
pub struct TCString {
// defined based on the type
ptr: *mut libc::c_void,
len: usize,
cap: usize,
// type of TCString this represents
ty: u8,
}
// TODO: figure out how to ignore this but still use it in TCString
/// A discriminator for TCString
#[repr(u8)]
enum TCStringType {
/// Null. Nothing is contained in this string.
///
/// * `ptr` is NULL.
/// * `len` and `cap` are zero.
Null = 0,
/// A CString.
///
/// * `ptr` is the result of CString::into_raw, containing a terminating NUL. It may not be
/// valid UTF-8.
/// * `len` and `cap` are zero.
CString,
/// A CStr, referencing memory borrowed from C
///
/// * `ptr` points to the string, containing a terminating NUL. It may not be valid UTF-8.
/// * `len` and `cap` are zero.
CStr,
/// A String.
///
/// * `ptr`, `len`, and `cap` are as would be returned from String::into_raw_parts.
String,
/// A byte sequence.
///
/// * `ptr`, `len`, and `cap` are as would be returned from Vec::into_raw_parts.
Bytes,
}
impl Default for TCString {
fn default() -> Self {
TCString {
ptr: std::ptr::null_mut(),
len: 0,
cap: 0,
ty: TCStringType::Null as u8,
}
}
}
impl TCString {
pub(crate) fn is_null(&self) -> bool {
self.ptr.is_null()
}
}
#[derive(PartialEq, Debug)]
pub enum RustString<'a> {
Null,
CString(CString),
CStr(&'a CStr),
String(String),
Bytes(Vec<u8>),
}
impl<'a> Default for RustString<'a> {
fn default() -> Self {
RustString::Null
}
}
impl PassByValue for TCString {
type RustType = RustString<'static>;
unsafe fn from_ctype(self) -> Self::RustType {
match self.ty {
ty if ty == TCStringType::CString as u8 => {
// SAFETY:
// - ptr was derived from CString::into_raw
// - data was not modified since that time (caller promises)
RustString::CString(unsafe { CString::from_raw(self.ptr as *mut c_char) })
}
ty if ty == TCStringType::CStr as u8 => {
// SAFETY:
// - ptr was created by CStr::as_ptr
// - data was not modified since that time (caller promises)
RustString::CStr(unsafe { CStr::from_ptr(self.ptr as *mut c_char) })
}
ty if ty == TCStringType::String as u8 => {
// SAFETY:
// - ptr was created by string_into_raw_parts
// - data was not modified since that time (caller promises)
RustString::String(unsafe {
String::from_raw_parts(self.ptr as *mut u8, self.len, self.cap)
})
}
ty if ty == TCStringType::Bytes as u8 => {
// SAFETY:
// - ptr was created by vec_into_raw_parts
// - data was not modified since that time (caller promises)
RustString::Bytes(unsafe {
Vec::from_raw_parts(self.ptr as *mut u8, self.len, self.cap)
})
}
_ => RustString::Null,
}
}
fn as_ctype(arg: Self::RustType) -> Self {
match arg {
RustString::Null => Self {
ty: TCStringType::Null as u8,
..Default::default()
},
RustString::CString(cstring) => Self {
ty: TCStringType::CString as u8,
ptr: cstring.into_raw() as *mut libc::c_void,
..Default::default()
},
RustString::CStr(cstr) => Self {
ty: TCStringType::CStr as u8,
ptr: cstr.as_ptr() as *mut libc::c_void,
..Default::default()
},
RustString::String(string) => {
let (ptr, len, cap) = string_into_raw_parts(string);
Self {
ty: TCStringType::String as u8,
ptr: ptr as *mut libc::c_void,
len,
cap,
}
}
RustString::Bytes(bytes) => {
let (ptr, len, cap) = vec_into_raw_parts(bytes);
Self {
ty: TCStringType::Bytes as u8,
ptr: ptr as *mut libc::c_void,
len,
cap,
}
}
}
}
}
impl<'a> RustString<'a> {
/// Get a regular Rust &str for this value.
pub(crate) fn as_str(&mut self) -> Result<&str, std::str::Utf8Error> {
match self {
RustString::CString(cstring) => cstring.as_c_str().to_str(),
RustString::CStr(cstr) => cstr.to_str(),
RustString::String(ref string) => Ok(string.as_ref()),
RustString::Bytes(_) => {
self.bytes_to_string()?;
self.as_str() // now the String variant, so won't recurse
}
RustString::Null => unreachable!(),
}
}
/// Consume this RustString and return an equivalent String, or an error if not
/// valid UTF-8. In the error condition, the original data is lost.
pub(crate) fn into_string(mut self) -> Result<String, std::str::Utf8Error> {
match self {
RustString::CString(cstring) => cstring.into_string().map_err(|e| e.utf8_error()),
RustString::CStr(cstr) => cstr.to_str().map(|s| s.to_string()),
RustString::String(string) => Ok(string),
RustString::Bytes(_) => {
self.bytes_to_string()?;
self.into_string() // now the String variant, so won't recurse
}
RustString::Null => unreachable!(),
}
}
pub(crate) fn as_bytes(&self) -> &[u8] {
match self {
RustString::CString(cstring) => cstring.as_bytes(),
RustString::CStr(cstr) => cstr.to_bytes(),
RustString::String(string) => string.as_bytes(),
RustString::Bytes(bytes) => bytes.as_ref(),
RustString::Null => unreachable!(),
}
}
/// Convert the RustString, in place, from the Bytes to String variant. On successful return,
/// the RustString has variant RustString::String.
fn bytes_to_string(&mut self) -> Result<(), std::str::Utf8Error> {
let mut owned = RustString::Null;
// temporarily swap a Null value into self; we'll swap that back
// shortly.
std::mem::swap(self, &mut owned);
match owned {
RustString::Bytes(bytes) => match String::from_utf8(bytes) {
Ok(string) => {
*self = RustString::String(string);
Ok(())
}
Err(e) => {
let (e, bytes) = (e.utf8_error(), e.into_bytes());
// put self back as we found it
*self = RustString::Bytes(bytes);
Err(e)
}
},
_ => {
// not bytes, so just swap back
std::mem::swap(self, &mut owned);
Ok(())
}
}
}
/// Convert the RustString, in place, into one of the C variants. If this is not
/// possible, such as if the string contains an embedded NUL, then the string
/// remains unchanged.
fn string_to_cstring(&mut self) {
let mut owned = RustString::Null;
// temporarily swap a Null value into self; we'll swap that back shortly
std::mem::swap(self, &mut owned);
match owned {
RustString::String(string) => {
match CString::new(string) {
Ok(cstring) => {
*self = RustString::CString(cstring);
}
Err(nul_err) => {
// recover the underlying String from the NulError and restore
// the RustString
let original_bytes = nul_err.into_vec();
// SAFETY: original_bytes came from a String moments ago, so still valid utf8
let string = unsafe { String::from_utf8_unchecked(original_bytes) };
*self = RustString::String(string);
}
}
}
_ => {
// not a CString, so just swap back
std::mem::swap(self, &mut owned);
}
}
}
pub(crate) fn to_path_buf(&mut self) -> Result<PathBuf, std::str::Utf8Error> {
#[cfg(unix)]
let path: OsString = {
// on UNIX, we can use the bytes directly, without requiring that they
// be valid UTF-8.
use std::ffi::OsStr;
use std::os::unix::ffi::OsStrExt;
OsStr::from_bytes(self.as_bytes()).to_os_string()
};
#[cfg(windows)]
let path: OsString = {
// on Windows, we assume the filename is valid Unicode, so it can be
// represented as UTF-8.
OsString::from(self.as_str()?.to_string())
};
Ok(path.into())
}
}
impl<'a> From<String> for RustString<'a> {
fn from(string: String) -> RustString<'a> {
RustString::String(string)
}
}
impl<'a> From<&str> for RustString<'static> {
fn from(string: &str) -> RustString<'static> {
RustString::String(string.to_string())
}
}
/// Utility function to borrow a TCString from a pointer arg, modify it,
/// and restore it.
///
/// This implements a kind of "interior mutability", relying on the
/// single-threaded use of all TC* types.
///
/// # SAFETY
///
/// - tcstring must not be NULL
/// - *tcstring must be a valid TCString
/// - *tcstring must not be accessed by anything else, despite the *const
unsafe fn wrap<T, F>(tcstring: *const TCString, f: F) -> T
where
F: FnOnce(&mut RustString) -> T,
{
debug_assert!(!tcstring.is_null());
// SAFETY:
// - we have exclusive to *tcstring (promised by caller)
let tcstring = tcstring as *mut TCString;
// SAFETY:
// - tcstring is not NULL
// - *tcstring is a valid string (promised by caller)
let mut rstring = unsafe { TCString::take_val_from_arg(tcstring, TCString::default()) };
let rv = f(&mut rstring);
// update the caller's TCString with the updated RustString
// SAFETY:
// - tcstring is not NULL (we just took from it)
// - tcstring points to valid memory (we just took from it)
unsafe { TCString::val_to_arg_out(rstring, tcstring) };
rv
}
/// TCStringList represents a list of strings.
///
/// The content of this struct must be treated as read-only.
#[repr(C)]
pub struct TCStringList {
/// number of strings in items
len: libc::size_t,
/// total size of items (internal use only)
_capacity: libc::size_t,
/// TCStringList representing each string. these remain owned by the TCStringList instance and will
/// be freed by tc_string_list_free. This pointer is never NULL for a valid TCStringList, and the
/// *TCStringList at indexes 0..len-1 are not NULL.
items: *const TCString,
}
impl CList for TCStringList {
type Element = TCString;
unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self {
TCStringList {
len,
_capacity: cap,
items,
}
}
fn into_raw_parts(self) -> (*const Self::Element, usize, usize) {
(self.items, self.len, self._capacity)
}
}
/// Create a new TCString referencing the given C string. The C string must remain valid and
/// unchanged until after the TCString is freed. It's typically easiest to ensure this by using a
/// static string.
///
/// NOTE: this function does _not_ take responsibility for freeing the given C string. The
/// given string can be freed once the TCString referencing it has been freed.
///
/// For example:
///
/// ```
/// char *url = get_item_url(..); // dynamically allocate C string
/// tc_task_annotate(task, tc_string_borrow(url)); // TCString created, passed, and freed
/// free(url); // string is no longer referenced and can be freed
/// ```
#[no_mangle]
pub unsafe extern "C" fn tc_string_borrow(cstr: *const libc::c_char) -> TCString {
debug_assert!(!cstr.is_null());
// SAFETY:
// - cstr is not NULL (promised by caller, verified by assertion)
// - cstr's lifetime exceeds that of the TCString (promised by caller)
// - cstr contains a valid NUL terminator (promised by caller)
// - cstr's content will not change before it is destroyed (promised by caller)
let cstr: &CStr = unsafe { CStr::from_ptr(cstr) };
// SAFETY:
// - caller promises to free this string
unsafe { TCString::return_val(RustString::CStr(cstr)) }
}
/// Create a new TCString by cloning the content of the given C string. The resulting TCString
/// is independent of the given string, which can be freed or overwritten immediately.
#[no_mangle]
pub unsafe extern "C" fn tc_string_clone(cstr: *const libc::c_char) -> TCString {
debug_assert!(!cstr.is_null());
// SAFETY:
// - cstr is not NULL (promised by caller, verified by assertion)
// - cstr's lifetime exceeds that of this function (by C convention)
// - cstr contains a valid NUL terminator (promised by caller)
// - cstr's content will not change before it is destroyed (by C convention)
let cstr: &CStr = unsafe { CStr::from_ptr(cstr) };
let cstring: CString = cstr.into();
// SAFETY:
// - caller promises to free this string
unsafe { TCString::return_val(RustString::CString(cstring)) }
}
/// Create a new TCString containing the given string with the given length. This allows creation
/// of strings containing embedded NUL characters. As with `tc_string_clone`, the resulting
/// TCString is independent of the passed buffer, which may be reused or freed immediately.
///
/// The length should _not_ include any trailing NUL.
///
/// The given length must be less than half the maximum value of usize.
#[no_mangle]
pub unsafe extern "C" fn tc_string_clone_with_len(
buf: *const libc::c_char,
len: usize,
) -> TCString {
debug_assert!(!buf.is_null());
debug_assert!(len < isize::MAX as usize);
// SAFETY:
// - buf is valid for len bytes (by C convention)
// - (no alignment requirements for a byte slice)
// - content of buf will not be mutated during the lifetime of this slice (lifetime
// does not outlive this function call)
// - the length of the buffer is less than isize::MAX (promised by caller)
let slice = unsafe { std::slice::from_raw_parts(buf as *const u8, len) };
// allocate and copy into Rust-controlled memory
let vec = slice.to_vec();
// SAFETY:
// - caller promises to free this string
unsafe { TCString::return_val(RustString::Bytes(vec)) }
}
/// Get the content of the string as a regular C string. The given string must be valid. The
/// returned value is NULL if the string contains NUL bytes or (in some cases) invalid UTF-8. The
/// returned C string is valid until the TCString is freed or passed to another TC API function.
///
/// In general, prefer [`tc_string_content_with_len`] except when it's certain that the string is
/// valid and NUL-free.
///
/// This function takes the TCString by pointer because it may be modified in-place to add a NUL
/// terminator. The pointer must not be NULL.
///
/// This function does _not_ take ownership of the TCString.
#[no_mangle]
pub unsafe extern "C" fn tc_string_content(tcstring: *const TCString) -> *const libc::c_char {
// SAFETY;
// - tcstring is not NULL (promised by caller)
// - *tcstring is valid (promised by caller)
// - *tcstring is not accessed concurrently (single-threaded)
unsafe {
wrap(tcstring, |rstring| {
// try to eliminate the Bytes variant. If this fails, we'll return NULL
// below, so the error is ignorable.
let _ = rstring.bytes_to_string();
// and eliminate the String variant
rstring.string_to_cstring();
match &rstring {
RustString::CString(cstring) => cstring.as_ptr(),
RustString::String(_) => std::ptr::null(), // string_to_cstring failed
RustString::CStr(cstr) => cstr.as_ptr(),
RustString::Bytes(_) => std::ptr::null(), // already returned above
RustString::Null => unreachable!(),
}
})
}
}
/// Get the content of the string as a pointer and length. The given string must not be NULL.
/// This function can return any string, even one including NUL bytes or invalid UTF-8. The
/// returned buffer is valid until the TCString is freed or passed to another TaskChampio
/// function.
///
/// This function takes the TCString by pointer because it may be modified in-place to add a NUL
/// terminator. The pointer must not be NULL.
///
/// This function does _not_ take ownership of the TCString.
#[no_mangle]
pub unsafe extern "C" fn tc_string_content_with_len(
tcstring: *const TCString,
len_out: *mut usize,
) -> *const libc::c_char {
// SAFETY;
// - tcstring is not NULL (promised by caller)
// - *tcstring is valid (promised by caller)
// - *tcstring is not accessed concurrently (single-threaded)
unsafe {
wrap(tcstring, |rstring| {
let bytes = rstring.as_bytes();
// SAFETY:
// - len_out is not NULL (promised by caller)
// - len_out points to valid memory (promised by caller)
// - len_out is properly aligned (C convention)
usize::val_to_arg_out(bytes.len(), len_out);
bytes.as_ptr() as *const libc::c_char
})
}
}
/// Free a TCString. The given string must not be NULL. The string must not be used
/// after this function returns, and must not be freed more than once.
#[no_mangle]
pub unsafe extern "C" fn tc_string_free(tcstring: *mut TCString) {
// SAFETY:
// - tcstring is not NULL (promised by caller)
// - caller is exclusive owner of tcstring (promised by caller)
drop(unsafe { TCString::take_val_from_arg(tcstring, TCString::default()) });
}
/// Free a TCStringList instance. The instance, and all TCStringList it contains, must not be used after
/// this call.
///
/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCStringList.
#[no_mangle]
pub unsafe extern "C" fn tc_string_list_free(tcstrings: *mut TCStringList) {
// SAFETY:
// - tcstrings is not NULL and points to a valid TCStringList (caller is not allowed to
// modify the list)
// - caller promises not to use the value after return
unsafe { drop_value_list(tcstrings) };
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn empty_list_has_non_null_pointer() {
let tcstrings = unsafe { TCStringList::return_val(Vec::new()) };
assert!(!tcstrings.items.is_null());
assert_eq!(tcstrings.len, 0);
assert_eq!(tcstrings._capacity, 0);
}
#[test]
fn free_sets_null_pointer() {
let mut tcstrings = unsafe { TCStringList::return_val(Vec::new()) };
// SAFETY: testing expected behavior
unsafe { tc_string_list_free(&mut tcstrings) };
assert!(tcstrings.items.is_null());
assert_eq!(tcstrings.len, 0);
assert_eq!(tcstrings._capacity, 0);
}
const INVALID_UTF8: &[u8] = b"abc\xf0\x28\x8c\x28";
fn make_cstring() -> RustString<'static> {
RustString::CString(CString::new("a string").unwrap())
}
fn make_cstr() -> RustString<'static> {
let cstr = CStr::from_bytes_with_nul(b"a string\0").unwrap();
RustString::CStr(&cstr)
}
fn make_string() -> RustString<'static> {
RustString::String("a string".into())
}
fn make_string_with_nul() -> RustString<'static> {
RustString::String("a \0 nul!".into())
}
fn make_invalid_bytes() -> RustString<'static> {
RustString::Bytes(INVALID_UTF8.to_vec())
}
fn make_bytes() -> RustString<'static> {
RustString::Bytes(b"bytes".to_vec())
}
#[test]
fn cstring_as_str() {
assert_eq!(make_cstring().as_str().unwrap(), "a string");
}
#[test]
fn cstr_as_str() {
assert_eq!(make_cstr().as_str().unwrap(), "a string");
}
#[test]
fn string_as_str() {
assert_eq!(make_string().as_str().unwrap(), "a string");
}
#[test]
fn string_with_nul_as_str() {
assert_eq!(make_string_with_nul().as_str().unwrap(), "a \0 nul!");
}
#[test]
fn invalid_bytes_as_str() {
let as_str_err = make_invalid_bytes().as_str().unwrap_err();
assert_eq!(as_str_err.valid_up_to(), 3); // "abc" is valid
}
#[test]
fn valid_bytes_as_str() {
assert_eq!(make_bytes().as_str().unwrap(), "bytes");
}
#[test]
fn cstring_as_bytes() {
assert_eq!(make_cstring().as_bytes(), b"a string");
}
#[test]
fn cstr_as_bytes() {
assert_eq!(make_cstr().as_bytes(), b"a string");
}
#[test]
fn string_as_bytes() {
assert_eq!(make_string().as_bytes(), b"a string");
}
#[test]
fn string_with_nul_as_bytes() {
assert_eq!(make_string_with_nul().as_bytes(), b"a \0 nul!");
}
#[test]
fn invalid_bytes_as_bytes() {
assert_eq!(make_invalid_bytes().as_bytes(), INVALID_UTF8);
}
#[test]
fn cstring_string_to_cstring() {
let mut tcstring = make_cstring();
tcstring.string_to_cstring();
assert_eq!(tcstring, make_cstring()); // unchanged
}
#[test]
fn cstr_string_to_cstring() {
let mut tcstring = make_cstr();
tcstring.string_to_cstring();
assert_eq!(tcstring, make_cstr()); // unchanged
}
#[test]
fn string_string_to_cstring() {
let mut tcstring = make_string();
tcstring.string_to_cstring();
assert_eq!(tcstring, make_cstring()); // converted to CString, same content
}
#[test]
fn string_with_nul_string_to_cstring() {
let mut tcstring = make_string_with_nul();
tcstring.string_to_cstring();
assert_eq!(tcstring, make_string_with_nul()); // unchanged
}
#[test]
fn bytes_string_to_cstring() {
let mut tcstring = make_bytes();
tcstring.string_to_cstring();
assert_eq!(tcstring, make_bytes()); // unchanged
}
}

854
lib/src/task.rs Normal file
View file

@ -0,0 +1,854 @@
use crate::traits::*;
use crate::types::*;
use crate::util::err_to_ruststring;
use chrono::{TimeZone, Utc};
use std::convert::TryFrom;
use std::ops::Deref;
use std::ptr::NonNull;
use std::str::FromStr;
use taskchampion::{Annotation, Tag, Task, TaskMut};
/// A task, as publicly exposed by this library.
///
/// A task begins in "immutable" mode. It must be converted to "mutable" mode
/// to make any changes, and doing so requires exclusive access to the replica
/// until the task is freed or converted back to immutable mode.
///
/// An immutable task carries no reference to the replica that created it, and can be used until it
/// is freed or converted to a TaskMut. A mutable task carries a reference to the replica and
/// must be freed or made immutable before the replica is freed.
///
/// All `tc_task_..` functions taking a task as an argument require that it not be NULL.
///
/// When a `tc_task_..` function that returns a TCResult returns TC_RESULT_ERROR, then
/// `tc_task_error` will return the error message.
///
/// # Safety
///
/// A task is an owned object, and must be freed with tc_task_free (or, if part of a list,
/// with tc_task_list_free).
///
/// Any function taking a `*TCTask` requires:
/// - the pointer must not be NUL;
/// - the pointer must be one previously returned from a tc_… function;
/// - the memory referenced by the pointer must never be modified by C code; and
/// - except for `tc_{task,task_list}_free`, ownership of a `*TCTask` remains with the caller.
///
/// Once passed to tc_task_free, a `*TCTask` becomes invalid and must not be used again.
///
/// TCTasks are not threadsafe.
pub struct TCTask {
/// The wrapped Task or TaskMut
inner: Inner,
/// The error from the most recent operation, if any
error: Option<RustString<'static>>,
}
enum Inner {
/// A regular, immutable task
Immutable(Task),
/// A mutable task, together with the replica to which it holds an exclusive
/// reference.
Mutable(TaskMut<'static>, *mut TCReplica),
/// A transitional state for a TCTask as it goes from mutable to immutable and back. A task
/// can only be in this state outside of [`to_mut`] and [`to_immut`] if a panic occurs during
/// one of those methods.
Invalid,
}
impl PassByPointer for TCTask {}
impl TCTask {
/// Make an immutable TCTask into a mutable TCTask. Does nothing if the task
/// is already mutable.
///
/// # Safety
///
/// The tcreplica pointer must not be NULL, and the replica it points to must not
/// be freed before TCTask.to_immut completes.
unsafe fn to_mut(&mut self, tcreplica: *mut TCReplica) {
self.inner = match std::mem::replace(&mut self.inner, Inner::Invalid) {
Inner::Immutable(task) => {
// SAFETY:
// - tcreplica is not null (promised by caller)
// - tcreplica outlives the pointer in this variant (promised by caller)
let tcreplica_ref: &mut TCReplica =
unsafe { TCReplica::from_ptr_arg_ref_mut(tcreplica) };
let rep_ref = tcreplica_ref.borrow_mut();
Inner::Mutable(task.into_mut(rep_ref), tcreplica)
}
Inner::Mutable(task, tcreplica) => Inner::Mutable(task, tcreplica),
Inner::Invalid => unreachable!(),
}
}
/// Make an mutable TCTask into a immutable TCTask. Does nothing if the task
/// is already immutable.
#[allow(clippy::wrong_self_convention)] // to_immut_mut is not better!
fn to_immut(&mut self) {
self.inner = match std::mem::replace(&mut self.inner, Inner::Invalid) {
Inner::Immutable(task) => Inner::Immutable(task),
Inner::Mutable(task, tcreplica) => {
// SAFETY:
// - tcreplica is not null (promised by caller of to_mut, which created this
// variant)
// - tcreplica is still alive (promised by caller of to_mut)
let tcreplica_ref: &mut TCReplica =
unsafe { TCReplica::from_ptr_arg_ref_mut(tcreplica) };
tcreplica_ref.release_borrow();
Inner::Immutable(task.into_immut())
}
Inner::Invalid => unreachable!(),
}
}
}
impl From<Task> for TCTask {
fn from(task: Task) -> TCTask {
TCTask {
inner: Inner::Immutable(task),
error: None,
}
}
}
/// Utility function to get a shared reference to the underlying Task. All Task getters
/// are error-free, so this does not handle errors.
fn wrap<T, F>(task: *mut TCTask, f: F) -> T
where
F: FnOnce(&Task) -> T,
{
// SAFETY:
// - task is not null (promised by caller)
// - task outlives this function (promised by caller)
let tctask: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) };
let task: &Task = match &tctask.inner {
Inner::Immutable(t) => t,
Inner::Mutable(t, _) => t.deref(),
Inner::Invalid => unreachable!(),
};
tctask.error = None;
f(task)
}
/// Utility function to get a mutable reference to the underlying Task. The
/// TCTask must be mutable. The inner function may use `?` syntax to return an
/// error, which will be represented with the `err_value` returned to C.
fn wrap_mut<T, F>(task: *mut TCTask, f: F, err_value: T) -> T
where
F: FnOnce(&mut TaskMut) -> anyhow::Result<T>,
{
// SAFETY:
// - task is not null (promised by caller)
// - task outlives this function (promised by caller)
let tctask: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) };
let task: &mut TaskMut = match tctask.inner {
Inner::Immutable(_) => panic!("Task is immutable"),
Inner::Mutable(ref mut t, _) => t,
Inner::Invalid => unreachable!(),
};
tctask.error = None;
match f(task) {
Ok(rv) => rv,
Err(e) => {
tctask.error = Some(err_to_ruststring(e));
err_value
}
}
}
impl TryFrom<RustString<'static>> for Tag {
type Error = anyhow::Error;
fn try_from(mut rstring: RustString) -> Result<Tag, anyhow::Error> {
let tagstr = rstring.as_str()?;
Tag::from_str(tagstr)
}
}
/// TCTaskList represents a list of tasks.
///
/// The content of this struct must be treated as read-only.
#[repr(C)]
pub struct TCTaskList {
/// number of tasks in items
len: libc::size_t,
/// total size of items (internal use only)
_capacity: libc::size_t,
/// array of pointers representing each task. these remain owned by the TCTaskList instance and
/// will be freed by tc_task_list_free. This pointer is never NULL for a valid TCTaskList,
/// and the *TCTaskList at indexes 0..len-1 are not NULL.
items: *const NonNull<TCTask>,
}
impl CList for TCTaskList {
type Element = NonNull<TCTask>;
unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self {
TCTaskList {
len,
_capacity: cap,
items,
}
}
fn into_raw_parts(self) -> (*const Self::Element, usize, usize) {
(self.items, self.len, self._capacity)
}
}
/// Convert an immutable task into a mutable task.
///
/// The task must not be NULL. It is modified in-place, and becomes mutable.
///
/// The replica must not be NULL. After this function returns, the replica _cannot be used at all_
/// until this task is made immutable again. This implies that it is not allowed for more than one
/// task associated with a replica to be mutable at any time.
///
/// Typical mutation of tasks is bracketed with `tc_task_to_mut` and `tc_task_to_immut`:
///
/// ```c
/// tc_task_to_mut(task, rep);
/// success = tc_task_done(task);
/// tc_task_to_immut(task, rep);
/// if (!success) { ... }
/// ```
#[no_mangle]
pub unsafe extern "C" fn tc_task_to_mut(task: *mut TCTask, tcreplica: *mut TCReplica) {
// SAFETY:
// - task is not null (promised by caller)
// - task outlives 'a (promised by caller)
let tctask: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) };
// SAFETY:
// - tcreplica is not NULL (promised by caller)
// - tcreplica lives until later call to to_immut via tc_task_to_immut (promised by caller,
// who cannot call tc_replica_free during this time)
unsafe { tctask.to_mut(tcreplica) };
}
/// Convert a mutable task into an immutable task.
///
/// The task must not be NULL. It is modified in-place, and becomes immutable.
///
/// The replica passed to `tc_task_to_mut` may be used freely after this call.
#[no_mangle]
pub unsafe extern "C" fn tc_task_to_immut(task: *mut TCTask) {
// SAFETY:
// - task is not null (promised by caller)
// - task outlives 'a (promised by caller)
let tctask: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) };
tctask.to_immut();
}
/// Get a task's UUID.
#[no_mangle]
pub unsafe extern "C" fn tc_task_get_uuid(task: *mut TCTask) -> TCUuid {
wrap(task, |task| {
// SAFETY:
// - value is not allocated and need not be freed
unsafe { TCUuid::return_val(task.get_uuid()) }
})
}
/// Get a task's status.
#[no_mangle]
pub unsafe extern "C" fn tc_task_get_status(task: *mut TCTask) -> TCStatus {
wrap(task, |task| task.get_status().into())
}
/// Get the underlying key/value pairs for this task. The returned TCKVList is
/// a "snapshot" of the task and will not be updated if the task is subsequently
/// modified. It is the caller's responsibility to free the TCKVList.
#[no_mangle]
pub unsafe extern "C" fn tc_task_get_taskmap(task: *mut TCTask) -> TCKVList {
wrap(task, |task| {
let vec: Vec<TCKV> = task
.get_taskmap()
.iter()
.map(|(k, v)| {
let key = RustString::from(k.as_ref());
let value = RustString::from(v.as_ref());
TCKV::as_ctype((key, value))
})
.collect();
// SAFETY:
// - caller will free this list
unsafe { TCKVList::return_val(vec) }
})
}
/// Get a task's description, or NULL if the task cannot be represented as a C string (e.g., if it
/// contains embedded NUL characters).
#[no_mangle]
pub unsafe extern "C" fn tc_task_get_description(task: *mut TCTask) -> TCString {
wrap(task, |task| {
let descr = task.get_description();
// SAFETY:
// - caller promises to free this string
unsafe { TCString::return_val(descr.into()) }
})
}
/// Get the entry timestamp for a task (when it was created), or 0 if not set.
#[no_mangle]
pub unsafe extern "C" fn tc_task_get_entry(task: *mut TCTask) -> libc::time_t {
wrap(task, |task| libc::time_t::as_ctype(task.get_entry()))
}
/// Get the wait timestamp for a task, or 0 if not set.
#[no_mangle]
pub unsafe extern "C" fn tc_task_get_wait(task: *mut TCTask) -> libc::time_t {
wrap(task, |task| libc::time_t::as_ctype(task.get_wait()))
}
/// Get the modified timestamp for a task, or 0 if not set.
#[no_mangle]
pub unsafe extern "C" fn tc_task_get_modified(task: *mut TCTask) -> libc::time_t {
wrap(task, |task| libc::time_t::as_ctype(task.get_modified()))
}
/// Check if a task is waiting.
#[no_mangle]
pub unsafe extern "C" fn tc_task_is_waiting(task: *mut TCTask) -> bool {
wrap(task, |task| task.is_waiting())
}
/// Check if a task is active (started and not stopped).
#[no_mangle]
pub unsafe extern "C" fn tc_task_is_active(task: *mut TCTask) -> bool {
wrap(task, |task| task.is_active())
}
/// Check if a task has the given tag. If the tag is invalid, this function will return false, as
/// that (invalid) tag is not present. No error will be reported via `tc_task_error`.
#[no_mangle]
pub unsafe extern "C" fn tc_task_has_tag(task: *mut TCTask, tag: TCString) -> bool {
// SAFETY:
// - tag is valid (promised by caller)
// - caller will not use tag after this call (convention)
let tcstring = unsafe { TCString::val_from_arg(tag) };
wrap(task, |task| {
if let Ok(tag) = Tag::try_from(tcstring) {
task.has_tag(&tag)
} else {
false
}
})
}
/// Get the tags for the task.
///
/// The caller must free the returned TCStringList instance. The TCStringList instance does not
/// reference the task and the two may be freed in any order.
#[no_mangle]
pub unsafe extern "C" fn tc_task_get_tags(task: *mut TCTask) -> TCStringList {
wrap(task, |task| {
let vec: Vec<TCString> = task
.get_tags()
.map(|t| {
// SAFETY:
// - this TCString will be freed via tc_string_list_free.
unsafe { TCString::return_val(t.as_ref().into()) }
})
.collect();
// SAFETY:
// - caller will free the list
unsafe { TCStringList::return_val(vec) }
})
}
/// Get the annotations for the task.
///
/// The caller must free the returned TCAnnotationList instance. The TCStringList instance does not
/// reference the task and the two may be freed in any order.
#[no_mangle]
pub unsafe extern "C" fn tc_task_get_annotations(task: *mut TCTask) -> TCAnnotationList {
wrap(task, |task| {
let vec: Vec<TCAnnotation> = task
.get_annotations()
.map(|a| {
let description = RustString::from(a.description);
TCAnnotation::as_ctype((a.entry, description))
})
.collect();
// SAFETY:
// - caller will free the list
unsafe { TCAnnotationList::return_val(vec) }
})
}
/// Get the named UDA from the task.
///
/// Returns a TCString with NULL ptr field if the UDA does not exist.
#[no_mangle]
pub unsafe extern "C" fn tc_task_get_uda<'a>(
task: *mut TCTask,
ns: TCString,
key: TCString,
) -> TCString {
wrap(task, |task| {
// SAFETY:
// - ns is valid (promised by caller)
// - caller will not use ns after this call (convention)
if let Ok(ns) = unsafe { TCString::val_from_arg(ns) }.as_str() {
// SAFETY: same
if let Ok(key) = unsafe { TCString::val_from_arg(key) }.as_str() {
if let Some(value) = task.get_uda(ns, key) {
// SAFETY:
// - caller will free this string (caller promises)
return unsafe { TCString::return_val(value.into()) };
}
}
}
TCString::default()
})
}
/// Get the named legacy UDA from the task.
///
/// Returns NULL if the UDA does not exist.
#[no_mangle]
pub unsafe extern "C" fn tc_task_get_legacy_uda<'a>(task: *mut TCTask, key: TCString) -> TCString {
wrap(task, |task| {
// SAFETY:
// - key is valid (promised by caller)
// - caller will not use key after this call (convention)
if let Ok(key) = unsafe { TCString::val_from_arg(key) }.as_str() {
if let Some(value) = task.get_legacy_uda(key) {
// SAFETY:
// - caller will free this string (caller promises)
return unsafe { TCString::return_val(value.into()) };
}
}
TCString::default()
})
}
/// Get all UDAs for this task.
///
/// Legacy UDAs are represented with an empty string in the ns field.
#[no_mangle]
pub unsafe extern "C" fn tc_task_get_udas(task: *mut TCTask) -> TCUdaList {
wrap(task, |task| {
let vec: Vec<TCUda> = task
.get_udas()
.map(|((ns, key), value)| {
// SAFETY:
// - will be freed by tc_uda_list_free
unsafe {
TCUda::return_val(Uda {
ns: Some(ns.into()),
key: key.into(),
value: value.into(),
})
}
})
.collect();
// SAFETY:
// - caller will free this list
unsafe { TCUdaList::return_val(vec) }
})
}
/// Get all UDAs for this task.
///
/// All TCUdas in this list have a NULL ns field. The entire UDA key is
/// included in the key field. The caller must free the returned list.
#[no_mangle]
pub unsafe extern "C" fn tc_task_get_legacy_udas(task: *mut TCTask) -> TCUdaList {
wrap(task, |task| {
let vec: Vec<TCUda> = task
.get_legacy_udas()
.map(|(key, value)| {
// SAFETY:
// - will be freed by tc_uda_list_free
unsafe {
TCUda::return_val(Uda {
ns: None,
key: key.into(),
value: value.into(),
})
}
})
.collect();
// SAFETY:
// - caller will free this list
unsafe { TCUdaList::return_val(vec) }
})
}
/// Set a mutable task's status.
#[no_mangle]
pub unsafe extern "C" fn tc_task_set_status(task: *mut TCTask, status: TCStatus) -> TCResult {
wrap_mut(
task,
|task| {
task.set_status(status.into())?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Set a mutable task's description.
#[no_mangle]
pub unsafe extern "C" fn tc_task_set_description(
task: *mut TCTask,
description: TCString,
) -> TCResult {
// SAFETY:
// - description is valid (promised by caller)
// - caller will not use description after this call (convention)
let mut description = unsafe { TCString::val_from_arg(description) };
wrap_mut(
task,
|task| {
task.set_description(description.as_str()?.to_string())?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Set a mutable task's entry (creation time). Pass entry=0 to unset
/// the entry field.
#[no_mangle]
pub unsafe extern "C" fn tc_task_set_entry(task: *mut TCTask, entry: libc::time_t) -> TCResult {
wrap_mut(
task,
|task| {
// SAFETY: any time_t value is a valid timestamp
task.set_entry(unsafe { entry.from_ctype() })?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Set a mutable task's wait timestamp. Pass wait=0 to unset the wait field.
#[no_mangle]
pub unsafe extern "C" fn tc_task_set_wait(task: *mut TCTask, wait: libc::time_t) -> TCResult {
wrap_mut(
task,
|task| {
// SAFETY: any time_t value is a valid timestamp
task.set_wait(unsafe { wait.from_ctype() })?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Set a mutable task's modified timestamp. The value cannot be zero.
#[no_mangle]
pub unsafe extern "C" fn tc_task_set_modified(
task: *mut TCTask,
modified: libc::time_t,
) -> TCResult {
wrap_mut(
task,
|task| {
task.set_modified(
// SAFETY: any time_t value is a valid timestamp
unsafe { modified.from_ctype() }
.ok_or_else(|| anyhow::anyhow!("modified cannot be zero"))?,
)?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Start a task.
#[no_mangle]
pub unsafe extern "C" fn tc_task_start(task: *mut TCTask) -> TCResult {
wrap_mut(
task,
|task| {
task.start()?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Stop a task.
#[no_mangle]
pub unsafe extern "C" fn tc_task_stop(task: *mut TCTask) -> TCResult {
wrap_mut(
task,
|task| {
task.stop()?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Mark a task as done.
#[no_mangle]
pub unsafe extern "C" fn tc_task_done(task: *mut TCTask) -> TCResult {
wrap_mut(
task,
|task| {
task.done()?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Mark a task as deleted.
#[no_mangle]
pub unsafe extern "C" fn tc_task_delete(task: *mut TCTask) -> TCResult {
wrap_mut(
task,
|task| {
task.delete()?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Add a tag to a mutable task.
#[no_mangle]
pub unsafe extern "C" fn tc_task_add_tag(task: *mut TCTask, tag: TCString) -> TCResult {
// SAFETY:
// - tag is valid (promised by caller)
// - caller will not use tag after this call (convention)
let tcstring = unsafe { TCString::val_from_arg(tag) };
wrap_mut(
task,
|task| {
let tag = Tag::try_from(tcstring)?;
task.add_tag(&tag)?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Remove a tag from a mutable task.
#[no_mangle]
pub unsafe extern "C" fn tc_task_remove_tag(task: *mut TCTask, tag: TCString) -> TCResult {
// SAFETY:
// - tag is valid (promised by caller)
// - caller will not use tag after this call (convention)
let tcstring = unsafe { TCString::val_from_arg(tag) };
wrap_mut(
task,
|task| {
let tag = Tag::try_from(tcstring)?;
task.remove_tag(&tag)?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Add an annotation to a mutable task. This call takes ownership of the
/// passed annotation, which must not be used after the call returns.
#[no_mangle]
pub unsafe extern "C" fn tc_task_add_annotation(
task: *mut TCTask,
annotation: *mut TCAnnotation,
) -> TCResult {
// SAFETY:
// - annotation is not NULL (promised by caller)
// - annotation is return from a tc_string_.. so is valid
// - caller will not use annotation after this call
let (entry, description) =
unsafe { TCAnnotation::take_val_from_arg(annotation, TCAnnotation::default()) };
wrap_mut(
task,
|task| {
let description = description.into_string()?;
task.add_annotation(Annotation { entry, description })?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Remove an annotation from a mutable task.
#[no_mangle]
pub unsafe extern "C" fn tc_task_remove_annotation(task: *mut TCTask, entry: i64) -> TCResult {
wrap_mut(
task,
|task| {
task.remove_annotation(Utc.timestamp(entry, 0))?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Set a UDA on a mutable task.
#[no_mangle]
pub unsafe extern "C" fn tc_task_set_uda(
task: *mut TCTask,
ns: TCString,
key: TCString,
value: TCString,
) -> TCResult {
// safety:
// - ns is valid (promised by caller)
// - caller will not use ns after this call (convention)
let mut ns = unsafe { TCString::val_from_arg(ns) };
// SAFETY: same
let mut key = unsafe { TCString::val_from_arg(key) };
// SAFETY: same
let mut value = unsafe { TCString::val_from_arg(value) };
wrap_mut(
task,
|task| {
task.set_uda(
ns.as_str()?.to_string(),
key.as_str()?.to_string(),
value.as_str()?.to_string(),
)?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Remove a UDA fraom a mutable task.
#[no_mangle]
pub unsafe extern "C" fn tc_task_remove_uda(
task: *mut TCTask,
ns: TCString,
key: TCString,
) -> TCResult {
// safety:
// - ns is valid (promised by caller)
// - caller will not use ns after this call (convention)
let mut ns = unsafe { TCString::val_from_arg(ns) };
// SAFETY: same
let mut key = unsafe { TCString::val_from_arg(key) };
wrap_mut(
task,
|task| {
task.remove_uda(ns.as_str()?.to_string(), key.as_str()?.to_string())?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Set a legacy UDA on a mutable task.
#[no_mangle]
pub unsafe extern "C" fn tc_task_set_legacy_uda(
task: *mut TCTask,
key: TCString,
value: TCString,
) -> TCResult {
// safety:
// - key is valid (promised by caller)
// - caller will not use key after this call (convention)
let mut key = unsafe { TCString::val_from_arg(key) };
// SAFETY: same
let mut value = unsafe { TCString::val_from_arg(value) };
wrap_mut(
task,
|task| {
task.set_legacy_uda(key.as_str()?.to_string(), value.as_str()?.to_string())?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Remove a UDA fraom a mutable task.
#[no_mangle]
pub unsafe extern "C" fn tc_task_remove_legacy_uda(task: *mut TCTask, key: TCString) -> TCResult {
// safety:
// - key is valid (promised by caller)
// - caller will not use key after this call (convention)
let mut key = unsafe { TCString::val_from_arg(key) };
wrap_mut(
task,
|task| {
task.remove_legacy_uda(key.as_str()?.to_string())?;
Ok(TCResult::Ok)
},
TCResult::Error,
)
}
/// Get the latest error for a task, or a string NULL ptr field if the last operation succeeded.
/// Subsequent calls to this function will return NULL. The task pointer must not be NULL. The
/// caller must free the returned string.
#[no_mangle]
pub unsafe extern "C" fn tc_task_error(task: *mut TCTask) -> TCString {
// SAFETY:
// - task is not null (promised by caller)
// - task outlives 'a (promised by caller)
let task: &mut TCTask = unsafe { TCTask::from_ptr_arg_ref_mut(task) };
if let Some(rstring) = task.error.take() {
// SAFETY:
// - caller promises to free this value
unsafe { TCString::return_val(rstring) }
} else {
TCString::default()
}
}
/// Free a task. The given task must not be NULL. The task must not be used after this function
/// returns, and must not be freed more than once.
///
/// If the task is currently mutable, it will first be made immutable.
#[no_mangle]
pub unsafe extern "C" fn tc_task_free(task: *mut TCTask) {
// SAFETY:
// - task is not NULL (promised by caller)
// - caller will not use the TCTask after this (promised by caller)
let mut tctask = unsafe { TCTask::take_from_ptr_arg(task) };
// convert to immut if it was mutable
tctask.to_immut();
drop(tctask);
}
/// Free a TCTaskList instance. The instance, and all TCTaskList it contains, must not be used after
/// this call.
///
/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCTaskList.
#[no_mangle]
pub unsafe extern "C" fn tc_task_list_free(tctasks: *mut TCTaskList) {
// SAFETY:
// - tctasks is not NULL and points to a valid TCTaskList (caller is not allowed to
// modify the list)
// - caller promises not to use the value after return
unsafe { drop_pointer_list(tctasks) };
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn empty_list_has_non_null_pointer() {
let tctasks = unsafe { TCTaskList::return_val(Vec::new()) };
assert!(!tctasks.items.is_null());
assert_eq!(tctasks.len, 0);
assert_eq!(tctasks._capacity, 0);
}
#[test]
fn free_sets_null_pointer() {
let mut tctasks = unsafe { TCTaskList::return_val(Vec::new()) };
// SAFETY: testing expected behavior
unsafe { tc_task_list_free(&mut tctasks) };
assert!(tctasks.items.is_null());
assert_eq!(tctasks.len, 0);
assert_eq!(tctasks._capacity, 0);
}
}

272
lib/src/traits.rs Normal file
View file

@ -0,0 +1,272 @@
use crate::util::vec_into_raw_parts;
use std::ptr::NonNull;
/// Support for values passed to Rust by value. These are represented as full structs in C. Such
/// values are implicitly copyable, via C's struct assignment.
///
/// The Rust and C types may differ, with from_ctype and as_ctype converting between them.
/// Implement this trait for the C type.
///
/// The RustType must be droppable (not containing raw pointers).
pub(crate) trait PassByValue: Sized {
type RustType;
/// Convert a C value to a Rust value.
///
/// # Safety
///
/// `self` must be a valid CType.
#[allow(clippy::wrong_self_convention)]
unsafe fn from_ctype(self) -> Self::RustType;
/// Convert a Rust value to a C value.
fn as_ctype(arg: Self::RustType) -> Self;
/// Take a value from C as an argument.
///
/// # Safety
///
/// - `self` must be a valid instance of the C type. This is typically ensured either by
/// requiring that C code not modify it, or by defining the valid values in C comments.
unsafe fn val_from_arg(arg: Self) -> Self::RustType {
// SAFETY:
// - arg is a valid CType (promised by caller)
unsafe { arg.from_ctype() }
}
/// Take a value from C as a pointer argument, replacing it with the given value. This is used
/// to invalidate the C value as an additional assurance against subsequent use of the value.
///
/// # Safety
///
/// - arg must not be NULL
/// - *arg must be a valid, properly aligned instance of the C type
unsafe fn take_val_from_arg(arg: *mut Self, mut replacement: Self) -> Self::RustType {
// SAFETY:
// - arg is valid (promised by caller)
// - replacement is valid and aligned (guaranteed by Rust)
unsafe { std::ptr::swap(arg, &mut replacement) };
// SAFETY:
// - replacement (formerly *arg) is a valid CType (promised by caller)
unsafe { PassByValue::val_from_arg(replacement) }
}
/// Return a value to C
///
/// # Safety
///
/// - if the value is allocated, the caller must ensure that the value is eventually freed
unsafe fn return_val(arg: Self::RustType) -> Self {
Self::as_ctype(arg)
}
/// Return a value to C, via an "output parameter"
///
/// # Safety
///
/// - `arg_out` must not be NULL and must be properly aligned and pointing to valid memory
/// of the size of CType.
unsafe fn val_to_arg_out(val: Self::RustType, arg_out: *mut Self) {
debug_assert!(!arg_out.is_null());
// SAFETY:
// - arg_out is not NULL (promised by caller, asserted)
// - arg_out is properly aligned and points to valid memory (promised by caller)
unsafe { *arg_out = Self::as_ctype(val) };
}
}
/// Support for values passed to Rust by pointer. These are represented as opaque structs in C,
/// and always handled as pointers.
pub(crate) trait PassByPointer: Sized {
/// Take a value from C as an argument.
///
/// # Safety
///
/// - arg must not be NULL
/// - arg must be a value returned from Box::into_raw (via return_ptr or ptr_to_arg_out)
/// - arg becomes invalid and must not be used after this call
unsafe fn take_from_ptr_arg(arg: *mut Self) -> Self {
debug_assert!(!arg.is_null());
// SAFETY: see docstring
unsafe { *(Box::from_raw(arg)) }
}
/// Borrow a value from C as an argument.
///
/// # Safety
///
/// - arg must not be NULL
/// - *arg must be a valid instance of Self
/// - arg must be valid for the lifetime assigned by the caller
/// - arg must not be modified by anything else during that lifetime
unsafe fn from_ptr_arg_ref<'a>(arg: *const Self) -> &'a Self {
debug_assert!(!arg.is_null());
// SAFETY: see docstring
unsafe { &*arg }
}
/// Mutably borrow a value from C as an argument.
///
/// # Safety
///
/// - arg must not be NULL
/// - *arg must be a valid instance of Self
/// - arg must be valid for the lifetime assigned by the caller
/// - arg must not be accessed by anything else during that lifetime
unsafe fn from_ptr_arg_ref_mut<'a>(arg: *mut Self) -> &'a mut Self {
debug_assert!(!arg.is_null());
// SAFETY: see docstring
unsafe { &mut *arg }
}
/// Return a value to C, transferring ownership
///
/// # Safety
///
/// - the caller must ensure that the value is eventually freed
unsafe fn return_ptr(self) -> *mut Self {
Box::into_raw(Box::new(self))
}
/// Return a value to C, transferring ownership, via an "output parameter".
///
/// # Safety
///
/// - the caller must ensure that the value is eventually freed
/// - arg_out must not be NULL
/// - arg_out must point to valid, properly aligned memory for a pointer value
unsafe fn ptr_to_arg_out(self, arg_out: *mut *mut Self) {
debug_assert!(!arg_out.is_null());
// SAFETY: see docstring
unsafe { *arg_out = self.return_ptr() };
}
}
/// Support for C lists of objects referenced by value.
///
/// The underlying C type should have three fields, containing items, length, and capacity. The
/// required trait functions just fetch and set these fields.
///
/// The PassByValue trait will be implemented automatically, converting between the C type and
/// `Vec<Element>`.
///
/// For most cases, it is only necessary to implement `tc_.._free` that calls either
/// drop_value_list (if Element is PassByValue) or drop_pointer_list (if element is PassByPointer).
///
/// # Safety
///
/// The C type must be documented as read-only. None of the fields may be modified, nor anything
/// accessible via the `items` array.
///
/// This class guarantees that the items pointer is non-NULL for any valid list (even when len=0).
pub(crate) trait CList: Sized {
type Element;
/// Create a new CList from the given items, len, and capacity.
///
/// # Safety
///
/// The arguments must either:
/// - be NULL, 0, and 0, respectively; or
/// - be valid for Vec::from_raw_parts
unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self;
/// Get the items, len, and capacity (in that order) for this instance. These must be
/// precisely the same values passed tearlier to `from_raw_parts`.
fn into_raw_parts(self) -> (*const Self::Element, usize, usize);
/// Generate a NULL value. By default this is a NULL items pointer with zero length and
/// capacity.
fn null_value() -> Self {
// SAFETY:
// - satisfies the first case in from_raw_parts' safety documentation
unsafe { Self::from_raw_parts(std::ptr::null(), 0, 0) }
}
}
/// Given a CList containing pass-by-value values, drop all of the values and
/// the list.
///
/// This is a convenience function for `tc_.._list_free` functions.
///
/// # Safety
///
/// - List must be non-NULL and point to a valid CL instance
/// - The caller must not use the value array points to after this function, as
/// it has been freed. It will be replaced with the null value.
pub(crate) unsafe fn drop_value_list<CL, T>(list: *mut CL)
where
CL: CList<Element = T>,
T: PassByValue,
{
debug_assert!(!list.is_null());
// SAFETY:
// - *list is a valid CL (promised by caller)
let mut vec = unsafe { CL::take_val_from_arg(list, CL::null_value()) };
// first, drop each of the elements in turn
for e in vec.drain(..) {
// SAFETY:
// - e is a valid Element (promised by caller)
// - e is owned
drop(unsafe { PassByValue::val_from_arg(e) });
}
// then drop the vector
drop(vec);
}
/// Given a CList containing NonNull pointers, drop all of the pointed-to values and the list.
///
/// This is a convenience function for `tc_.._list_free` functions.
///
/// # Safety
///
/// - List must be non-NULL and point to a valid CL instance
/// - The caller must not use the value array points to after this function, as
/// it has been freed. It will be replaced with the null value.
pub(crate) unsafe fn drop_pointer_list<CL, T>(list: *mut CL)
where
CL: CList<Element = NonNull<T>>,
T: PassByPointer,
{
debug_assert!(!list.is_null());
// SAFETY:
// - *list is a valid CL (promised by caller)
let mut vec = unsafe { CL::take_val_from_arg(list, CL::null_value()) };
// first, drop each of the elements in turn
for e in vec.drain(..) {
// SAFETY:
// - e is a valid Element (promised by caller)
// - e is owned
drop(unsafe { PassByPointer::take_from_ptr_arg(e.as_ptr()) });
}
// then drop the vector
drop(vec);
}
impl<A> PassByValue for A
where
A: CList,
{
type RustType = Vec<A::Element>;
unsafe fn from_ctype(self) -> Self::RustType {
let (items, len, cap) = self.into_raw_parts();
debug_assert!(!items.is_null());
// SAFETY:
// - CList::from_raw_parts requires that items, len, and cap be valid for
// Vec::from_raw_parts if not NULL, and they are not NULL (as promised by caller)
// - CList::into_raw_parts returns precisely the values passed to from_raw_parts.
// - those parts are passed to Vec::from_raw_parts here.
unsafe { Vec::from_raw_parts(items as *mut _, len, cap) }
}
fn as_ctype(arg: Self::RustType) -> Self {
let (items, len, cap) = vec_into_raw_parts(arg);
// SAFETY:
// - satisfies the second case in from_raw_parts' safety documentation
unsafe { Self::from_raw_parts(items, len, cap) }
}
}

148
lib/src/uda.rs Normal file
View file

@ -0,0 +1,148 @@
use crate::traits::*;
use crate::types::*;
/// TCUda contains the details of a UDA.
#[repr(C)]
pub struct TCUda {
/// Namespace of the UDA. For legacy UDAs, this may have a NULL ptr field.
pub ns: TCString,
/// UDA key. Must not be NULL.
pub key: TCString,
/// Content of the UDA. Must not be NULL.
pub value: TCString,
}
pub(crate) struct Uda {
pub ns: Option<RustString<'static>>,
pub key: RustString<'static>,
pub value: RustString<'static>,
}
impl PassByValue for TCUda {
type RustType = Uda;
unsafe fn from_ctype(self) -> Self::RustType {
Uda {
ns: if self.ns.is_null() {
None
} else {
// SAFETY:
// - self is owned, so we can take ownership of this TCString
// - self.ns is a valid, non-null TCString (NULL just checked)
Some(unsafe { TCString::val_from_arg(self.ns) })
},
// SAFETY:
// - self is owned, so we can take ownership of this TCString
// - self.key is a valid, non-null TCString (see type docstring)
key: unsafe { TCString::val_from_arg(self.key) },
// SAFETY:
// - self is owned, so we can take ownership of this TCString
// - self.value is a valid, non-null TCString (see type docstring)
value: unsafe { TCString::val_from_arg(self.value) },
}
}
fn as_ctype(uda: Uda) -> Self {
TCUda {
// SAFETY: caller assumes ownership of this value
ns: if let Some(ns) = uda.ns {
unsafe { TCString::return_val(ns) }
} else {
TCString::default()
},
// SAFETY: caller assumes ownership of this value
key: unsafe { TCString::return_val(uda.key) },
// SAFETY: caller assumes ownership of this value
value: unsafe { TCString::return_val(uda.value) },
}
}
}
impl Default for TCUda {
fn default() -> Self {
TCUda {
ns: TCString::default(),
key: TCString::default(),
value: TCString::default(),
}
}
}
/// TCUdaList represents a list of UDAs.
///
/// The content of this struct must be treated as read-only.
#[repr(C)]
pub struct TCUdaList {
/// number of UDAs in items
len: libc::size_t,
/// total size of items (internal use only)
_capacity: libc::size_t,
/// array of UDAs. These remain owned by the TCUdaList instance and will be freed by
/// tc_uda_list_free. This pointer is never NULL for a valid TCUdaList.
items: *const TCUda,
}
impl CList for TCUdaList {
type Element = TCUda;
unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self {
TCUdaList {
len,
_capacity: cap,
items,
}
}
fn into_raw_parts(self) -> (*const Self::Element, usize, usize) {
(self.items, self.len, self._capacity)
}
}
/// Free a TCUda instance. The instance, and the TCStrings it contains, must not be used
/// after this call.
#[no_mangle]
pub unsafe extern "C" fn tc_uda_free(tcuda: *mut TCUda) {
debug_assert!(!tcuda.is_null());
// SAFETY:
// - *tcuda is a valid TCUda (caller promises to treat it as read-only)
let uda = unsafe { TCUda::take_val_from_arg(tcuda, TCUda::default()) };
drop(uda);
}
/// Free a TCUdaList instance. The instance, and all TCUdas it contains, must not be used after
/// this call.
///
/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCUdaList.
#[no_mangle]
pub unsafe extern "C" fn tc_uda_list_free(tcudas: *mut TCUdaList) {
// SAFETY:
// - tcudas is not NULL and points to a valid TCUdaList (caller is not allowed to
// modify the list)
// - caller promises not to use the value after return
unsafe { drop_value_list(tcudas) }
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn empty_list_has_non_null_pointer() {
let tcudas = unsafe { TCUdaList::return_val(Vec::new()) };
assert!(!tcudas.items.is_null());
assert_eq!(tcudas.len, 0);
assert_eq!(tcudas._capacity, 0);
}
#[test]
fn free_sets_null_pointer() {
let mut tcudas = unsafe { TCUdaList::return_val(Vec::new()) };
// SAFETY: testing expected behavior
unsafe { tc_uda_list_free(&mut tcudas) };
assert!(tcudas.items.is_null());
assert_eq!(tcudas.len, 0);
assert_eq!(tcudas._capacity, 0);
}
}

23
lib/src/util.rs Normal file
View file

@ -0,0 +1,23 @@
use crate::string::RustString;
pub(crate) fn err_to_ruststring(e: impl std::string::ToString) -> RustString<'static> {
RustString::from(e.to_string())
}
/// An implementation of Vec::into_raw_parts, which is still unstable. Returns ptr, len, cap.
pub(crate) fn vec_into_raw_parts<T>(vec: Vec<T>) -> (*mut T, usize, usize) {
// emulate Vec::into_raw_parts():
// - disable dropping the Vec with ManuallyDrop
// - extract ptr, len, and capacity using those methods
let mut vec = std::mem::ManuallyDrop::new(vec);
(vec.as_mut_ptr(), vec.len(), vec.capacity())
}
/// An implementation of String::into_raw_parts, which is still unstable. Returns ptr, len, cap.
pub(crate) fn string_into_raw_parts(string: String) -> (*mut u8, usize, usize) {
// emulate String::into_raw_parts():
// - disable dropping the String with ManuallyDrop
// - extract ptr, len, and capacity using those methods
let mut string = std::mem::ManuallyDrop::new(string);
(string.as_mut_ptr(), string.len(), string.capacity())
}

168
lib/src/uuid.rs Normal file
View file

@ -0,0 +1,168 @@
use crate::traits::*;
use crate::types::*;
use libc;
use taskchampion::Uuid;
// NOTE: this must be a simple constant so that cbindgen can evaluate it
/// Length, in bytes, of the string representation of a UUID (without NUL terminator)
pub const TC_UUID_STRING_BYTES: usize = 36;
/// TCUuid is used as a task identifier. Uuids do not contain any pointers and need not be freed.
/// Uuids are typically treated as opaque, but the bytes are available in big-endian format.
///
/// cbindgen:field-names=[bytes]
#[repr(C)]
pub struct TCUuid([u8; 16]);
impl PassByValue for TCUuid {
type RustType = Uuid;
unsafe fn from_ctype(self) -> Self::RustType {
// SAFETY:
// - any 16-byte value is a valid Uuid
Uuid::from_bytes(self.0)
}
fn as_ctype(arg: Uuid) -> Self {
TCUuid(*arg.as_bytes())
}
}
/// Create a new, randomly-generated UUID.
#[no_mangle]
pub unsafe extern "C" fn tc_uuid_new_v4() -> TCUuid {
// SAFETY:
// - value is not allocated
unsafe { TCUuid::return_val(Uuid::new_v4()) }
}
/// Create a new UUID with the nil value.
#[no_mangle]
pub unsafe extern "C" fn tc_uuid_nil() -> TCUuid {
// SAFETY:
// - value is not allocated
unsafe { TCUuid::return_val(Uuid::nil()) }
}
/// TCUuidList represents a list of uuids.
///
/// The content of this struct must be treated as read-only.
#[repr(C)]
pub struct TCUuidList {
/// number of uuids in items
len: libc::size_t,
/// total size of items (internal use only)
_capacity: libc::size_t,
/// array of uuids. these remain owned by the TCUuidList instance and will be freed by
/// tc_uuid_list_free. This pointer is never NULL for a valid TCUuidList.
items: *const TCUuid,
}
impl CList for TCUuidList {
type Element = TCUuid;
unsafe fn from_raw_parts(items: *const Self::Element, len: usize, cap: usize) -> Self {
TCUuidList {
len,
_capacity: cap,
items,
}
}
fn into_raw_parts(self) -> (*const Self::Element, usize, usize) {
(self.items, self.len, self._capacity)
}
}
/// Write the string representation of a TCUuid into the given buffer, which must be
/// at least TC_UUID_STRING_BYTES long. No NUL terminator is added.
#[no_mangle]
pub unsafe extern "C" fn tc_uuid_to_buf(tcuuid: TCUuid, buf: *mut libc::c_char) {
debug_assert!(!buf.is_null());
// SAFETY:
// - buf is valid for len bytes (by C convention)
// - (no alignment requirements for a byte slice)
// - content of buf will not be mutated during the lifetime of this slice (lifetime
// does not outlive this function call)
// - the length of the buffer is less than isize::MAX (promised by caller)
let buf: &mut [u8] = unsafe {
std::slice::from_raw_parts_mut(buf as *mut u8, ::uuid::adapter::Hyphenated::LENGTH)
};
// SAFETY:
// - tcuuid is a valid TCUuid (all byte patterns are valid)
let uuid: Uuid = unsafe { TCUuid::val_from_arg(tcuuid) };
uuid.to_hyphenated().encode_lower(buf);
}
/// Return the hyphenated string representation of a TCUuid. The returned string
/// must be freed with tc_string_free.
#[no_mangle]
pub unsafe extern "C" fn tc_uuid_to_str(tcuuid: TCUuid) -> TCString {
// SAFETY:
// - tcuuid is a valid TCUuid (all byte patterns are valid)
let uuid: Uuid = unsafe { TCUuid::val_from_arg(tcuuid) };
let s = uuid.to_string();
// SAFETY:
// - caller promises to free this value.
unsafe { TCString::return_val(s.into()) }
}
/// Parse the given string as a UUID. Returns TC_RESULT_ERROR on parse failure or if the given
/// string is not valid.
#[no_mangle]
pub unsafe extern "C" fn tc_uuid_from_str(s: TCString, uuid_out: *mut TCUuid) -> TCResult {
debug_assert!(!s.is_null());
debug_assert!(!uuid_out.is_null());
// SAFETY:
// - s is valid (promised by caller)
// - caller will not use s after this call (convention)
let mut s = unsafe { TCString::val_from_arg(s) };
if let Ok(s) = s.as_str() {
if let Ok(u) = Uuid::parse_str(s) {
// SAFETY:
// - uuid_out is not NULL (promised by caller)
// - alignment is not required
unsafe { TCUuid::val_to_arg_out(u, uuid_out) };
return TCResult::Ok;
}
}
TCResult::Error
}
/// Free a TCUuidList instance. The instance, and all TCUuids it contains, must not be used after
/// this call.
///
/// When this call returns, the `items` pointer will be NULL, signalling an invalid TCUuidList.
#[no_mangle]
pub unsafe extern "C" fn tc_uuid_list_free(tcuuids: *mut TCUuidList) {
// SAFETY:
// - tcuuids is not NULL and points to a valid TCUuidList (caller is not allowed to
// modify the list)
// - caller promises not to use the value after return
unsafe { drop_value_list(tcuuids) };
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn empty_list_has_non_null_pointer() {
let tcuuids = unsafe { TCUuidList::return_val(Vec::new()) };
assert!(!tcuuids.items.is_null());
assert_eq!(tcuuids.len, 0);
assert_eq!(tcuuids._capacity, 0);
}
#[test]
fn free_sets_null_pointer() {
let mut tcuuids = unsafe { TCUuidList::return_val(Vec::new()) };
// SAFETY: testing expected behavior
unsafe { tc_uuid_list_free(&mut tcuuids) };
assert!(tcuuids.items.is_null());
assert_eq!(tcuuids.len, 0);
assert_eq!(tcuuids._capacity, 0);
}
}

103
lib/src/workingset.rs Normal file
View file

@ -0,0 +1,103 @@
use crate::traits::*;
use crate::types::*;
use taskchampion::{Uuid, WorkingSet};
/// A TCWorkingSet represents a snapshot of the working set for a replica. It is not automatically
/// updated based on changes in the replica. Its lifetime is independent of the replica and it can
/// be freed at any time.
///
/// To iterate over a working set, search indexes 1 through largest_index.
///
/// # Safety
///
/// The `*TCWorkingSet` returned from `tc_replica_working_set` is owned by the caller and
/// must later be freed to avoid a memory leak. Its lifetime is independent of the replica
/// from which it was generated.
///
/// Any function taking a `*TCWorkingSet` requires:
/// - the pointer must not be NUL;
/// - the pointer must be one previously returned from `tc_replica_working_set`
/// - the memory referenced by the pointer must never be accessed by C code; and
/// - except for `tc_replica_free`, ownership of a `*TCWorkingSet` remains with the caller.
///
/// Once passed to `tc_replica_free`, a `*TCWorkingSet` becomes invalid and must not be used again.
///
/// TCWorkingSet is not threadsafe.
pub struct TCWorkingSet(WorkingSet);
impl PassByPointer for TCWorkingSet {}
impl From<WorkingSet> for TCWorkingSet {
fn from(ws: WorkingSet) -> TCWorkingSet {
TCWorkingSet(ws)
}
}
/// Utility function to get a shared reference to the underlying WorkingSet.
fn wrap<T, F>(ws: *mut TCWorkingSet, f: F) -> T
where
F: FnOnce(&WorkingSet) -> T,
{
// SAFETY:
// - ws is not null (promised by caller)
// - ws outlives 'a (promised by caller)
let tcws: &TCWorkingSet = unsafe { TCWorkingSet::from_ptr_arg_ref(ws) };
f(&tcws.0)
}
/// Get the working set's length, or the number of UUIDs it contains.
#[no_mangle]
pub unsafe extern "C" fn tc_working_set_len(ws: *mut TCWorkingSet) -> usize {
wrap(ws, |ws| ws.len())
}
/// Get the working set's largest index.
#[no_mangle]
pub unsafe extern "C" fn tc_working_set_largest_index(ws: *mut TCWorkingSet) -> usize {
wrap(ws, |ws| ws.largest_index())
}
/// Get the UUID for the task at the given index. Returns true if the UUID exists in the working
/// set. If not, returns false and does not change uuid_out.
#[no_mangle]
pub unsafe extern "C" fn tc_working_set_by_index(
ws: *mut TCWorkingSet,
index: usize,
uuid_out: *mut TCUuid,
) -> bool {
debug_assert!(!uuid_out.is_null());
wrap(ws, |ws| {
if let Some(uuid) = ws.by_index(index) {
// SAFETY:
// - uuid_out is not NULL (promised by caller)
// - alignment is not required
unsafe { TCUuid::val_to_arg_out(uuid, uuid_out) };
true
} else {
false
}
})
}
/// Get the working set index for the task with the given UUID. Returns 0 if the task is not in
/// the working set.
#[no_mangle]
pub unsafe extern "C" fn tc_working_set_by_uuid(ws: *mut TCWorkingSet, uuid: TCUuid) -> usize {
wrap(ws, |ws| {
// SAFETY:
// - tcuuid is a valid TCUuid (all byte patterns are valid)
let uuid: Uuid = unsafe { TCUuid::val_from_arg(uuid) };
ws.by_uuid(uuid).unwrap_or(0)
})
}
/// Free a TCWorkingSet. The given value must not be NULL. The value must not be used after this
/// function returns, and must not be freed more than once.
#[no_mangle]
pub unsafe extern "C" fn tc_working_set_free(ws: *mut TCWorkingSet) {
// SAFETY:
// - rep is not NULL (promised by caller)
// - caller will not use the TCWorkingSet after this (promised by caller)
let ws = unsafe { TCWorkingSet::take_from_ptr_arg(ws) };
drop(ws);
}

1011
lib/taskchampion.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -1 +0,0 @@
// test-only crate

View file

@ -110,7 +110,7 @@ impl Replica {
let mut task = Task::new(uuid, taskmap).into_mut(self);
task.set_description(description)?;
task.set_status(status)?;
task.set_entry(Utc::now())?;
task.set_entry(Some(Utc::now()))?;
trace!("task {} created", uuid);
Ok(task.into_immut())
}
@ -177,7 +177,7 @@ impl Replica {
/// Add an UndoPoint, if one has not already been added by this Replica. This occurs
/// automatically when a change is made. The `force` flag allows forcing a new UndoPoint
/// even if one has laready been created by this Replica, and may be useful when a Replica
/// even if one has already been created by this Replica, and may be useful when a Replica
/// instance is held for a long time and used to apply more than one user-visible change.
pub fn add_undo_point(&mut self, force: bool) -> anyhow::Result<()> {
if force || !self.added_undo_point {

View file

@ -106,6 +106,7 @@ pub trait StorageTxn {
fn clear_working_set(&mut self) -> Result<()>;
/// Check whether this storage is entirely empty
#[allow(clippy::wrong_self_convention)] // mut is required here for storage access
fn is_empty(&mut self) -> Result<bool> {
let mut empty = true;
empty = empty && self.all_tasks()?.is_empty();

View file

@ -1,5 +1,6 @@
/// The status of a task, as defined by the task data model.
#[derive(Debug, PartialEq, Clone, strum_macros::Display)]
#[repr(C)]
pub enum Status {
Pending,
Completed,

View file

@ -120,6 +120,10 @@ impl Task {
.unwrap_or("")
}
pub fn get_entry(&self) -> Option<DateTime<Utc>> {
self.get_timestamp(Prop::Entry.as_ref())
}
pub fn get_priority(&self) -> Priority {
self.taskmap
.get(Prop::Status.as_ref())
@ -299,8 +303,8 @@ impl<'r> TaskMut<'r> {
self.set_string(Prop::Description.as_ref(), Some(description))
}
pub(crate) fn set_entry(&mut self, entry: DateTime<Utc>) -> anyhow::Result<()> {
self.set_timestamp(Prop::Entry.as_ref(), Some(entry))
pub fn set_entry(&mut self, entry: Option<DateTime<Utc>>) -> anyhow::Result<()> {
self.set_timestamp(Prop::Entry.as_ref(), entry)
}
pub fn set_wait(&mut self, wait: Option<DateTime<Utc>>) -> anyhow::Result<()> {
@ -526,6 +530,24 @@ mod test {
assert!(!task.is_active());
}
#[test]
fn test_entry_not_set() {
let task = Task::new(Uuid::new_v4(), TaskMap::new());
assert_eq!(task.get_entry(), None);
}
#[test]
fn test_entry_set() {
let ts = Utc.ymd(1980, 1, 1).and_hms(0, 0, 0);
let task = Task::new(
Uuid::new_v4(),
vec![(String::from("entry"), format!("{}", ts.timestamp()))]
.drain(..)
.collect(),
);
assert_eq!(task.get_entry(), Some(ts));
}
#[test]
fn test_wait_not_set() {
let task = Task::new(Uuid::new_v4(), TaskMap::new());

View file

@ -38,6 +38,11 @@ impl WorkingSet {
self.by_index.iter().filter(|e| e.is_some()).count()
}
/// Get the largest index in the working set, or zero if the set is empty.
pub fn largest_index(&self) -> usize {
self.by_index.len().saturating_sub(1)
}
/// True if the length is zero
pub fn is_empty(&self) -> bool {
self.by_index.iter().all(|e| e.is_none())
@ -103,6 +108,24 @@ mod test {
assert_eq!(ws.is_empty(), true);
}
#[test]
fn test_largest_index() {
let uuid1 = Uuid::new_v4();
let uuid2 = Uuid::new_v4();
let ws = WorkingSet::new(vec![]);
assert_eq!(ws.largest_index(), 0);
let ws = WorkingSet::new(vec![None, Some(uuid1)]);
assert_eq!(ws.largest_index(), 1);
let ws = WorkingSet::new(vec![None, Some(uuid1), None, Some(uuid2)]);
assert_eq!(ws.largest_index(), 3);
let ws = WorkingSet::new(vec![None, Some(uuid1), None, Some(uuid2), None]);
assert_eq!(ws.largest_index(), 4);
}
#[test]
fn test_by_index() {
let (uuid1, uuid2, ws) = make();

8
xtask/Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "xtask"
version = "0.4.1"
edition = "2018"
[dependencies]
anyhow = "1.0"
cbindgen = "0.20.0"

48
xtask/src/main.rs Normal file
View file

@ -0,0 +1,48 @@
//! This executable defines the `cargo xtask` subcommands.
//!
//! At the moment it is very simple, but if this grows more subcommands then
//! it will be sensible to use `clap` or another similar library.
use cbindgen::*;
use std::env;
use std::path::PathBuf;
pub fn main() -> anyhow::Result<()> {
let arg = env::args().nth(1);
match arg.as_ref().map(|arg| arg.as_str()) {
Some("codegen") => codegen(),
Some(arg) => anyhow::bail!("unknown xtask {}", arg),
_ => anyhow::bail!("unknown xtask"),
}
}
/// `cargo xtask codegen`
///
/// This uses cbindgen to generate `lib/taskchampion.h`.
fn codegen() -> anyhow::Result<()> {
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let workspace_dir = manifest_dir.parent().unwrap();
let lib_crate_dir = workspace_dir.join("lib");
Builder::new()
.with_crate(&lib_crate_dir)
.with_config(Config {
header: Some(include_str!("../../lib/header-intro.h").into()),
language: Language::C,
cpp_compat: true,
sys_includes: vec!["stdbool.h".into(), "stdint.h".into(), "time.h".into()],
usize_is_size_t: true,
no_includes: true,
enumeration: EnumConfig {
// this appears to still default to true for C
enum_class: false,
..Default::default()
},
..Default::default()
})
.generate()
.expect("Unable to generate bindings")
.write_to_file(lib_crate_dir.join("taskchampion.h"));
Ok(())
}