mirror of
https://github.com/GothenburgBitFactory/taskwarrior.git
synced 2025-06-26 10:54:26 +02:00
Merge pull request #332 from djmitche/cdylib
build a C interface to taskchampion
This commit is contained in:
commit
a7f353bd6e
50 changed files with 9683 additions and 34 deletions
2
.cargo/config
Normal file
2
.cargo/config
Normal file
|
@ -0,0 +1,2 @@
|
|||
[alias]
|
||||
xtask = "run --package xtask --"
|
82
Cargo.lock
generated
82
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -4,5 +4,7 @@ members = [
|
|||
"taskchampion",
|
||||
"cli",
|
||||
"sync-server",
|
||||
"replica-server-tests"
|
||||
"lib",
|
||||
"integration-tests",
|
||||
"xtask",
|
||||
]
|
||||
|
|
24
README.md
24
README.md
|
@ -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
2
integration-tests/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
test-db
|
||||
test-sync-server
|
|
@ -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" }
|
30
integration-tests/README.md
Normal file
30
integration-tests/README.md
Normal 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`.
|
73
integration-tests/build.rs
Normal file
73
integration-tests/build.rs
Normal 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);
|
||||
}
|
30
integration-tests/src/bindings_tests/mod.rs
Normal file
30
integration-tests/src/bindings_tests/mod.rs
Normal 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"));
|
328
integration-tests/src/bindings_tests/replica.c
Normal file
328
integration-tests/src/bindings_tests/replica.c
Normal 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();
|
||||
}
|
125
integration-tests/src/bindings_tests/string.c
Normal file
125
integration-tests/src/bindings_tests/string.c
Normal 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();
|
||||
}
|
582
integration-tests/src/bindings_tests/task.c
Normal file
582
integration-tests/src/bindings_tests/task.c
Normal 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();
|
||||
}
|
30
integration-tests/src/bindings_tests/test.c
Normal file
30
integration-tests/src/bindings_tests/test.c
Normal 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);
|
||||
}
|
21
integration-tests/src/bindings_tests/unity/LICENSE.txt
Normal file
21
integration-tests/src/bindings_tests/unity/LICENSE.txt
Normal 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.
|
3
integration-tests/src/bindings_tests/unity/README.md
Normal file
3
integration-tests/src/bindings_tests/unity/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# Unity
|
||||
|
||||
This directory contains the src from https://github.com/ThrowTheSwitch/Unity, revision 8ba01386008196a92ef4fdbdb0b00f2434c79563.
|
2119
integration-tests/src/bindings_tests/unity/unity.c
Normal file
2119
integration-tests/src/bindings_tests/unity/unity.c
Normal file
File diff suppressed because it is too large
Load diff
661
integration-tests/src/bindings_tests/unity/unity.h
Normal file
661
integration-tests/src/bindings_tests/unity/unity.h
Normal 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
|
1053
integration-tests/src/bindings_tests/unity/unity_internals.h
Normal file
1053
integration-tests/src/bindings_tests/unity/unity_internals.h
Normal file
File diff suppressed because it is too large
Load diff
72
integration-tests/src/bindings_tests/uuid.c
Normal file
72
integration-tests/src/bindings_tests/uuid.c
Normal 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();
|
||||
}
|
1
integration-tests/src/lib.rs
Normal file
1
integration-tests/src/lib.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod bindings_tests;
|
31
integration-tests/tests/bindings.rs
Normal file
31
integration-tests/tests/bindings.rs
Normal 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
18
lib/Cargo.toml
Normal 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
2
lib/Makefile
Normal 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
76
lib/header-intro.h
Normal 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.
|
||||
* * TC…List - 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 TC…List 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
143
lib/src/annotation.rs
Normal 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
34
lib/src/atomic.rs
Normal 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
106
lib/src/kv.rs
Normal 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
39
lib/src/lib.rs
Normal 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
428
lib/src/replica.rs
Normal 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
9
lib/src/result.rs
Normal 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
143
lib/src/server.rs
Normal 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
36
lib/src/status.rs
Normal 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
703
lib/src/string.rs
Normal 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
854
lib/src/task.rs
Normal 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
272
lib/src/traits.rs
Normal 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
148
lib/src/uda.rs
Normal 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
23
lib/src/util.rs
Normal 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
168
lib/src/uuid.rs
Normal 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
103
lib/src/workingset.rs
Normal 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
1011
lib/taskchampion.h
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1 +0,0 @@
|
|||
// test-only crate
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
8
xtask/Cargo.toml
Normal 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
48
xtask/src/main.rs
Normal 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(())
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue