diff --git a/sys-utils/libcmt/Makefile b/sys-utils/libcmt/Makefile index 8f439071..0bb47721 100644 --- a/sys-utils/libcmt/Makefile +++ b/sys-utils/libcmt/Makefile @@ -27,7 +27,7 @@ TOOLCHAIN_PREFIX ?= riscv64-linux-gnu- TARGET_CC := $(TOOLCHAIN_PREFIX)gcc TARGET_AR := $(TOOLCHAIN_PREFIX)ar TARGET_CFLAGS := -Wvla -O2 -g -Wall -pedantic -Wextra -Isrc -CFLAGS := -Wvla -O2 -g -Wall -pedantic -Wextra -Isrc +CFLAGS := -fsanitize=address,undefined -pedantic -fstrict-aliasing -Wstrict-aliasing=3 -Wvla -O2 -g -Wall -pedantic -Wextra -Isrc CC := gcc all: libcmt host @@ -142,12 +142,15 @@ $(mock_OBJDIR)/keccak: tests/keccak.c $(mock_LIB) $(mock_OBJDIR)/merkle: tests/merkle.c $(mock_LIB) $(CC) $(CFLAGS) -o $@ $^ -$(mock_OBJDIR)/rollup: tests/rollup.c $(mock_LIB) - $(CC) $(CFLAGS) -o $@ $^ +$(mock_OBJDIR)/rollup: tests/rollup.c tests/data.h $(mock_LIB) + $(CC) -Itests $(CFLAGS) -o $@ $^ test: $(unittests_BINS) $(foreach test,$(unittests_BINS),$(test) &&) true +tests/data.h: tests/create-data.sh + $< > $@ + #------------------------------------------------------------------------------- tools_OBJDIR := build/tools tools_BINS := \ diff --git a/sys-utils/libcmt/src/io-mock.c b/sys-utils/libcmt/src/io-mock.c index 0e41a598..5fdca984 100644 --- a/sys-utils/libcmt/src/io-mock.c +++ b/sys-utils/libcmt/src/io-mock.c @@ -22,6 +22,9 @@ #include #include +/** track the number of open "devices". Mimic the kernel driver behavior by limiting it to 1 */ +static int open_count = 0; + static int read_whole_file(const char *name, size_t max, void *data, size_t *length); static int write_whole_file(const char *name, size_t length, const void *data); @@ -29,11 +32,13 @@ int cmt_io_init(cmt_io_driver_t *_me) { if (!_me) { return -EINVAL; } - cmt_io_driver_mock_t *me = &_me->mock; - - if (me->tx->begin || me->rx->begin) { + if (open_count) { return -EBUSY; } + + open_count++; + cmt_io_driver_mock_t *me = &_me->mock; + size_t tx_length = 2U << 20; // 2MB size_t rx_length = 2U << 20; // 2MB cmt_buf_init(me->tx, tx_length, malloc(tx_length)); @@ -68,10 +73,18 @@ void cmt_io_fini(cmt_io_driver_t *_me) { if (!_me) { return; } + + if (open_count == 0) { + return; + } + + open_count--; cmt_io_driver_mock_t *me = &_me->mock; free(me->tx->begin); free(me->rx->begin); + + memset(_me, 0, sizeof(*_me)); } cmt_buf_t cmt_io_get_tx(cmt_io_driver_t *me) { @@ -104,12 +117,16 @@ static int load_next_input(cmt_io_driver_mock_t *me, struct cmt_io_yield *rr) { size_t file_length = 0; int rc = read_whole_file(filepath, cmt_buf_length(me->rx), me->rx->begin, &file_length); if (rc) { - (void) fprintf(stderr, "failed to load \"%s\". %s\n", filepath, strerror(-rc)); + if (cmt_util_debug_enabled()) { + (void) fprintf(stderr, "failed to load \"%s\". %s\n", filepath, strerror(-rc)); + } return rc; } // NOLINTNEXTLINE(clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) if (sscanf(filepath, " %127[^.]%15s", me->input_filename, me->input_fileext) != 2) { - (void) fprintf(stderr, "failed to parse filename: \"%s\"\n", filepath); + if (cmt_util_debug_enabled()) { + (void) fprintf(stderr, "failed to parse filename: \"%s\"\n", filepath); + } return -EINVAL; } @@ -146,7 +163,7 @@ static int store_output(cmt_io_driver_mock_t *me, const char *filepath, struct c static int store_next_output(cmt_io_driver_mock_t *me, char *ns, int *seq, struct cmt_io_yield *rr) { char filepath[128 + 32 + 8 + 16]; // NOLINTNEXTLINE(cert-err33-c, clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling) - snprintf(filepath, sizeof filepath, "%s.%s%d%s", me->input_filename, ns, *seq++, me->input_fileext); + snprintf(filepath, sizeof filepath, "%s.%s%d%s", me->input_filename, ns, (*seq)++, me->input_fileext); return store_output(me, filepath, rr); } diff --git a/sys-utils/libcmt/src/rollup.c b/sys-utils/libcmt/src/rollup.c index d7693ebf..41f76a3d 100644 --- a/sys-utils/libcmt/src/rollup.c +++ b/sys-utils/libcmt/src/rollup.c @@ -83,9 +83,12 @@ int cmt_rollup_emit_voucher(cmt_rollup_t *me, uint32_t address_length, const voi cmt_buf_t of[1]; void *params_base = tx->begin + 4; // after funsel + if (address_length != CMT_ADDRESS_LENGTH) + return -EINVAL; + // clang-format off if (DBG(cmt_abi_put_funsel(wr, VOUCHER)) - || DBG(cmt_abi_put_uint_be(wr, address_length, address_data)) + || DBG(cmt_abi_put_address(wr, address_data)) || DBG(cmt_abi_put_uint_be(wr, value_length, value_data)) || DBG(cmt_abi_put_bytes_s(wr, of)) || DBG(cmt_abi_put_bytes_d(wr, of, length, data, params_base))) { @@ -367,11 +370,6 @@ int cmt_gio_request(cmt_rollup_t *me, cmt_gio_t *req) { return -ENOBUFS; } - size_t rd_length = cmt_buf_length(rd); - if (rd_length != rr->data) { - return -EINVAL; - } - req->response_data = rd->begin; req->response_code = rr->reason; req->response_data_length = rr->data; diff --git a/sys-utils/libcmt/tests/buf.c b/sys-utils/libcmt/tests/buf.c index 037e93de..50d0670f 100644 --- a/sys-utils/libcmt/tests/buf.c +++ b/sys-utils/libcmt/tests/buf.c @@ -13,13 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "buf.h" #include -#include #include -#include "buf.h" +#include -void split_in_bounds_must_succeed() -{ +void split_in_bounds_must_succeed() { uint8_t _[8]; cmt_buf_t b; cmt_buf_init(&b, sizeof _, _); @@ -63,8 +62,7 @@ void split_in_bounds_must_succeed() printf("test_buf_split_in_bounds_must_succeed passed\n"); } -void split_out_of_bounds_must_fail(void) -{ +void split_out_of_bounds_must_fail(void) { uint8_t _[8]; cmt_buf_t b; cmt_buf_t lhs; @@ -74,13 +72,10 @@ void split_out_of_bounds_must_fail(void) assert(cmt_buf_split(&b, 9, &lhs, &rhs) == -ENOBUFS); assert(cmt_buf_split(&b, SIZE_MAX, &lhs, &rhs) == -ENOBUFS); printf("test_buf_split_out_of_bounds_must_fail passed\n"); - } -int main(void) -{ +int main(void) { split_in_bounds_must_succeed(); split_out_of_bounds_must_fail(); return 0; } - diff --git a/sys-utils/libcmt/tests/create-data.sh b/sys-utils/libcmt/tests/create-data.sh new file mode 100755 index 00000000..6d792f30 --- /dev/null +++ b/sys-utils/libcmt/tests/create-data.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +[ ! -d build/data/ ] && mkdir build/data/ + +echo "#ifndef DATA_H" +echo "#define DATA_H" +echo "#include " +echo "uint8_t valid_advance_0[] = {" +cast calldata "EvmAdvance(uint256,address,address,uint256,uint256,uint256,bytes)" \ + 0x0000000000000000000000000000000000000001 \ + 0x0000000000000000000000000000000000000002 \ + 0x0000000000000000000000000000000000000003 \ + 0x0000000000000000000000000000000000000004 \ + 0x0000000000000000000000000000000000000005 \ + 0x0000000000000000000000000000000000000006 \ + 0x`echo -en "advance-0" | xxd -p -c0` | xxd -r -p | xxd -i +echo "};" + +echo "uint8_t valid_inspect_0[] = {" +echo -en "inspect-0" | xxd -i +echo "};" + +echo "uint8_t valid_report_0[] = {" +echo -en "report-0" | xxd -i +echo "};" + +echo "uint8_t valid_exception_0[] = {" +echo -en "exception-0" | xxd -i +echo "};" + +echo "uint8_t valid_voucher_0[] = {" +cast calldata "Voucher(address,uint256,bytes)" \ + 0x0000000000000000000000000000000000000001 \ + 0x00000000000000000000000000000000deadbeef \ + 0x`echo -en "voucher-0" | xxd -p -c0` | xxd -r -p | xxd -i +echo "};" + +echo "uint8_t valid_notice_0[] = {" +cast calldata "Notice(bytes)" \ + 0x`echo -en "notice-0" | xxd -p -c0` | xxd -r -p | xxd -i +echo "};" +echo "#endif /* DATA_H */" diff --git a/sys-utils/libcmt/tests/data.h b/sys-utils/libcmt/tests/data.h new file mode 100644 index 00000000..999efa79 --- /dev/null +++ b/sys-utils/libcmt/tests/data.h @@ -0,0 +1,67 @@ +#ifndef DATA_H +#define DATA_H +#include +uint8_t valid_advance_0[] = { + 0xcc, 0x7d, 0xee, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x61, 0x64, 0x76, 0x61, + 0x6e, 0x63, 0x65, 0x2d, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; +uint8_t valid_inspect_0[] = { + 0x69, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x2d, 0x30 +}; +uint8_t valid_report_0[] = { + 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2d, 0x30 +}; +uint8_t valid_exception_0[] = { + 0x65, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x30 +}; +uint8_t valid_voucher_0[] = { + 0x23, 0x7a, 0x81, 0x6f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, + 0x76, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x72, 0x2d, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +}; +uint8_t valid_notice_0[] = { + 0xc2, 0x58, 0xd6, 0xe5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x6e, 0x6f, 0x74, 0x69, + 0x63, 0x65, 0x2d, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; +#endif /* DATA_H */ diff --git a/sys-utils/libcmt/tests/rollup.c b/sys-utils/libcmt/tests/rollup.c new file mode 100644 index 00000000..aab89979 --- /dev/null +++ b/sys-utils/libcmt/tests/rollup.c @@ -0,0 +1,210 @@ +/* Copyright Cartesi and individual authors (see AUTHORS) + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "rollup.h" +#include "data.h" +#include +#include +#include +#include +#include +#include + +static int read_whole_file(const char *name, size_t max, void *data, size_t *length) { + int rc = 0; + + FILE *file = fopen(name, "rb"); + if (!file) { + return -errno; + } + + *length = fread(data, 1, max, file); + if (!feof(file)) { + rc = -ENOBUFS; + } + if (fclose(file) != 0) { + perror("fclose failed"); + } + return rc; +} + +static int write_whole_file(const char *name, size_t length, const void *data) { + int rc = 0; + + FILE *file = fopen(name, "wb"); + if (!file) { + return -errno; + } + + if (fwrite(data, 1, length, file) != length) { + rc = -errno; + } + if (fclose(file) != 0) { + perror("fclose failed"); + } + return rc; +} +void test_rollup_init_and_fini(void) { + cmt_rollup_t rollup; + + // init twice returns failure, same as when using the kernel driver + assert(cmt_rollup_init(&rollup) == 0); + assert(cmt_rollup_init(&rollup) == -EBUSY); + cmt_rollup_fini(&rollup); + + // and should reset when closed + assert(cmt_rollup_init(&rollup) == 0); + cmt_rollup_fini(&rollup); + + // double free is a bug, but try to avoid crashing + cmt_rollup_fini(&rollup); + + // fail to initialize with NULL + assert(cmt_rollup_init(NULL) == -EINVAL); + cmt_rollup_fini(NULL); + + printf("test_rollup_init_and_fini passed!\n"); +} + +static void check_first_input(cmt_rollup_t *rollup) { + cmt_rollup_advance_t advance; + + assert(cmt_rollup_read_advance_state(rollup, &advance) == 0); + assert(advance.chain_id == 1); + // clang-format off + uint8_t expected_app_contract[CMT_ADDRESS_LENGTH] = { + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x02, + }; + uint8_t expected_msg_sender[CMT_ADDRESS_LENGTH] = { + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x03, + }; + char expected_payload[] = "advance-0"; + // clang-format on + + // verify the parsed data + assert(memcmp(advance.app_contract, expected_app_contract, CMT_ADDRESS_LENGTH) == 0); + assert(memcmp(advance.msg_sender, expected_msg_sender, CMT_ADDRESS_LENGTH) == 0); + assert(advance.block_number == 4); + assert(advance.block_timestamp == 5); + assert(advance.index == 6); + assert(advance.payload_length == strlen(expected_payload)); + assert(memcmp(advance.payload, expected_payload, strlen(expected_payload)) == 0); +} + +static void check_second_input(cmt_rollup_t *rollup) { + cmt_rollup_inspect_t inspect; + + char expected_payload[] = "inspect-0"; + assert(cmt_rollup_read_inspect_state(rollup, &inspect) == 0); + assert(inspect.payload_length == strlen(expected_payload)); + assert(memcmp(inspect.payload, expected_payload, strlen(expected_payload)) == 0); +} + +void test_rollup_parse_inputs(void) { + cmt_rollup_t rollup; + cmt_rollup_finish_t finish = {.accept_previous_request = true}; + + uint8_t data[] = {0}; + + // synthesize inputs and feed them to io-mock via CMT_INPUTS env. + assert(write_whole_file("build/0.bin", sizeof valid_advance_0, valid_advance_0) == 0); + assert(write_whole_file("build/1.bin", sizeof valid_inspect_0, valid_inspect_0) == 0); + assert(write_whole_file("build/2.bin", sizeof data, data) == 0); + assert(truncate("build/2.bin", 3 << 20) == 0); + assert(setenv("CMT_INPUTS", "0:build/0.bin,1:build/1.bin,0:build/2.bin", 1) == 0); + + assert(cmt_rollup_init(&rollup) == 0); + assert(cmt_rollup_finish(&rollup, &finish) == 0); + check_first_input(&rollup); + + assert(cmt_rollup_finish(&rollup, &finish) == 0); + check_second_input(&rollup); + + assert(cmt_rollup_finish(&rollup, &finish) == -ENODATA); + + cmt_rollup_fini(&rollup); + printf("test_rollup_parse_inputs passed!\n"); +} + +void test_rollup_outputs_reports_and_exceptions(void) { + cmt_rollup_t rollup; + uint64_t index = 0; + + // test data must fit + uint8_t buffer[1024]; + size_t read_size = 0; + + // clang-format off + uint8_t address[CMT_ADDRESS_LENGTH] = { + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01 + }; + // clang-format on + + assert(cmt_rollup_init(&rollup) == 0); + + // voucher + uint8_t value[] = {0xde, 0xad, 0xbe, 0xef}; + char voucher_data[] = "voucher-0"; + assert(cmt_rollup_emit_voucher(&rollup, sizeof address, address, sizeof value, value, strlen(voucher_data), + voucher_data, &index) == 0); + assert(index == 0); + assert(read_whole_file("none.output-0.bin", sizeof buffer, buffer, &read_size) == 0); + assert(sizeof valid_voucher_0 == read_size); + assert(memcmp(valid_voucher_0, buffer, sizeof valid_voucher_0) == 0); + + // notice + char notice_data[] = "notice-0"; + assert(cmt_rollup_emit_notice(&rollup, strlen(notice_data), notice_data, &index) == 0); + assert(read_whole_file("none.output-1.bin", sizeof buffer, buffer, &read_size) == 0); + assert(sizeof valid_notice_0 == read_size); + assert(memcmp(valid_notice_0, buffer, sizeof valid_notice_0) == 0); + assert(index == 1); + + // report + char report_data[] = "report-0"; + assert(cmt_rollup_emit_report(&rollup, strlen(report_data), report_data) == 0); + assert(read_whole_file("none.report-0.bin", sizeof buffer, buffer, &read_size) == 0); + assert(sizeof valid_report_0 == read_size); + assert(memcmp(valid_report_0, buffer, sizeof valid_report_0) == 0); + + // exception + char exception_data[] = "exception-0"; + assert(cmt_rollup_emit_exception(&rollup, strlen(exception_data), exception_data) == 0); + assert(read_whole_file("none.exception-0.bin", sizeof buffer, buffer, &read_size) == 0); + assert(sizeof valid_exception_0 == read_size); + assert(memcmp(valid_exception_0, buffer, sizeof valid_exception_0) == 0); + + cmt_rollup_fini(&rollup); + printf("test_rollup_outputs_reports_and_exceptions passed!\n"); +} + +int main(void) { + test_rollup_init_and_fini(); + test_rollup_parse_inputs(); + test_rollup_outputs_reports_and_exceptions(); + return 0; +}