14 releases (4 breaking)
0.5.0 | Oct 22, 2024 |
---|---|
0.4.0 | Oct 21, 2024 |
0.3.1 | Oct 19, 2024 |
0.2.1 | Oct 17, 2024 |
0.1.7 | Oct 14, 2024 |
#149 in Debugging
49KB
817 lines
NoBug
provides GLARING! assertions and annotations for rust code. These have different
semantic than the stdlib assertions. The Idea behind NoBug
is to have active annotations
with semantic meaning. The annotations are not just comments but actively checked at runtime
or compile time. By using uppercase macros they are easily visible and searchable in source
code.
Overview
Unlike the stdlib assertions which panic on failure, the NoBug
assertions will abort the
program and thus are not catchable. Without the unwinding overhead they wont generate any
excess code. Only in debug build tests they will panic because the test harness running tests in
threads and catching panics. When aborting at runtime is not desired then the *_DBG!
variants
of the macros can be used. Those will only check the condition in debug builds. This can be
enforced by disabling the abort_on_fail
feature.
Assertions
Aside from a generic (and discouraged) ASSERT!
macro we provide:
REQUIRE!
-- checking preconditions
Asserts that input parameters for functions and methods are valid. This is important when a function or method takes inputs that have a wider range of valid ranges than the function or method expects. This can be numeric data or text for example.ENSURE!
-- checking postconditions
Asserts that the output of a code block is valid. To be used when a computation could produce a result that is not within a valid range.CHECK!
-- generic checks for testing and similar cases.
Substitute forstd::assert!
macros in tests.
Each of those has a correspondending ASSERT_DBG!
, REQUIRE_DBG!
and ENSURE_DBG!
variants that inserts checks only in debug builds.
REQUIRE!
and CHECK!
have somme specializations for const asserts and comparisons which
give more concise error messages.
Since some programs must not abort on assertion failure we provide a abort_on_failure
feature. When this is not enabled then any DIE!
and any other macro calling it will cause a
compile error in release, non-test builds. One can only use the *_DBG!
variants of the nobug
macros then.
Threre is a special ASSERT_ONCE!
macro that will only check the condition once and then
never again.
Annotations
NoBug
defines macros to actively annotate code. Actively means that these macros can do
different things in release or debug builds, depending on configuration:
- Be a [
compile_error!()
]
When things must be resolved before software is released and deployed. - Check that assertions are valid
- Abort the program with a message
When a code path can't make progress because of unfinished code. - Log a message each time the macro is encountered
Just to nag the programmer - Log a message once when the macro is encountered for the first time
Less nagging - Do nothing/optimize out
In release builds all annotations are either optimized out or compile errors. Thus they have zero cost in release builds.
We provide following annotation macros (with the noted defaults):
FIXME!
-- Bug that must be fixed.FIXED!
-- Annotate fixed bugs.TODO!
-- Incomplete but required code.DONE!
-- Completed code.WIP!
-- Work in progress.IDEA!
-- Not required improvement of existing code.PLANNED!
-- Potential future planned feature.
FIXME!
, FIXED!
, WIP!
and DONE!
can be augmented with optional code blocks that are
checked at runtime. Notifying about assertions still being valid and detecting regressions.
MOCK!
must have code blocks either a single one or conditional IF-THEN-ELSE blocks. This
can be used where WIP!
is not sufficient.
Message logging
Nobug logs messages to stderr (using the log or tracing crate is planned for the future). Messages can be of different severity levels.
CRITICAL!
-- Print a critical message.
This is used when the program is going to abort.NOTICE!
-- Print a notice to the user.
This is used for annotations.TRACE!
-- Print a trace message to the user.
This is used for debugging/progress.NOTICE_ONCE!
-- Print a notice to the user.
This is used for annotations.
Each of these has a *_DBG!
variant that only logs in debug builds. There is a
TRACE_DBG_IF!
macro that does conditional logging in debug builds.
Message Format
The messages are entries delimited by a colon followed by a space, they always start with the file and line number of the message.
<file>:<line>: <reason>: <message>
where <message>
can have multiple free-form parts each delimited by colon space as well
Tool Macros
The above uses some tool macros which may be useful on their own in some cases.
DIE!
-- Abort (or panic).compile_error!()
whenabort_on_fail
is not enabled.ONCE!
-- Execute a block only once.MOCK!
-- Trivial code mocking.
Feature Flags
-
abort_on_fail
This is enabled by default. When not enabled thenDIE!
and all macros using it will cause a compile error in release/non-test builds. This is useful when you want to make sure that nobug will never abort in release builds. One can only use the*_DBG!
variants of the nobug macros in release builds then. -
const_expr
This is enabled by default. When not enabled then theREQUIRE!
andCHECK!
macros will not useconst {}
expression specializations and fall back to a inferior implementation. Disabling this feature is required when using a compiler before v1.79.0. -
trace_nobug
Makesnobug
TRACE!
all calls to its own macros. This is useful for debugging nobug itself or to get some quick insight of the a program's progress.
Workflow
The idea is to use the annotations and assertions to actively document the code. Assertions
ideally stay in the code and are not removed. They are the contract of the code, only when
performance is critical they can be replaced by a the *_DBG!
variant. Keep in mind that in
some cases the compiler can optimize the checks away or do more aggressive optimizations when
it knows that certain conditions are always true.
Nobug assertions are not a substitution for the std::assert!
macros. When required they can
be mixed. For the cases where panics need to be caught and handled the std::assert!
macros
must be used. The NoBug
macros are for the cases where the program is in a undefined state
due to a programming error that can't be detected at compile time and should be aborted.
The annotations can be used to define a stateful workflow, for example:
FIXME!
->FIXED!
IDEA!
->WIP!
->DONE!
PLANNED!
->TODO!
->WIP!
->DONE!
This is not rigid, states can be left out and unused. Only keep whatever makes sense.
Especially the final FIXED!
and DONE!
states can eventually accumulate and clutter the
code. It may still be benefitical to keep them in the code for a while since they guard
against regressions.
Found bugs that are annotated with FIXME!
may as well transformed into a test case within
the unit or integration tests.
Incomplete code can be mocked with MOCK!
and then replaced with the real implementation when
it is done. In some cases this might be preferable to or complement WIP!
since it allows to
run some tests with the mocked code.
Testsuites may prefer to use the CHECK!
macro if appropriate.
History
In 2006 I started a C Debug library of the same name (https://git.pipapo.org/cehteh/nobug). This implementation will carry some of its ideas over to rust.
Roadmap
Currently we only implemented most basic functionally. In future more features will be added.
- Eventually some of the macros will become proc macros
- Support for invariants on objects (needs proc macros)
- With proc macros we can have some nicer syntax, for example:
#[nobug] fn example(i: i32 where i > 0) -> i32 where result < 100 { ... }
- Add support for logging and tracing