Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 167 additions & 9 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,25 @@ name: CI
# Triggers the workflow on push or pull request events but only for the master branch
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
# Job: Unit test suite
unit-tests:
name: "Unit Tests"
unit-tests-linux-gcc-ceedling:
name: "Unit Tests (Linux/GCC/Ceedling)"
runs-on: ubuntu-latest
steps:
# Checks out repository under $GITHUB_WORKSPACE
- name: Checkout Latest Repo
uses: actions/checkout@v4
uses: actions/checkout@v7
with:
submodules: recursive

# Set up Ruby
- name: Set Up Ruby

- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: '3.2'
ruby-version: "3.1"
bundler-cache: true

# Install Ceedling
- name: Install Ceedling
Expand All @@ -37,3 +35,163 @@ jobs:
- name: Run All Self Tests
run: |
ceedling clobber test:all

unit-tests-linux-gcc-bare:
name: "Unit Tests (Linux/GCC/Bare)"
runs-on: ubuntu-latest
steps:
- name: Checkout Latest Repo
uses: actions/checkout@v7

- name: Install Python 3
uses: actions/setup-python@v7
with:
python-version: "3.x"

- name: Cache pip packages
uses: actions/cache@v6
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-

- name: Create virtual environment
run: |
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt

- name: Run build and test
run: |
source .venv/bin/activate
python waf configure build -v

unit-tests-linux-clang-bare:
name: "Unit Tests (Linux/Clang/Bare)"
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v7

- name: Install Python 3
uses: actions/setup-python@v7
with:
python-version: "3.x"

- name: Cache pip packages
uses: actions/cache@v6
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-

- name: Create virtual environment
run: |
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt

- name: Install clang + llvm-cov
run: |
sudo apt-get update
sudo apt-get install -y clang llvm

- name: Check clang
run: clang --version

- name: Build with clang
run: |
source .venv/bin/activate
python waf configure build -v --check-c-compiler clang

unit-tests-windows-gcc-bare:
name: "Unit Tests (Windows/GCC/Bare)"
runs-on: windows-latest

steps:
- name: Checkout Latest Repo
uses: actions/checkout@v7

- name: Install MSYS2
uses: msys2/setup-msys2@v2
with:
update: true
install: >-
mingw-w64-x86_64-gcc
mingw-w64-x86_64-make
mingw-w64-x86_64-binutils

- name: Add MSYS2 MinGW64 to PATH
shell: pwsh
run: |
echo "C:\msys64\mingw64\bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append

- name: Check GCC
shell: pwsh
run: gcc --version

- name: Install Python 3 (native Windows)
uses: actions/setup-python@v7
with:
python-version: "3.x"

- name: Cache pip packages
uses: actions/cache@v6
with:
path: ~\AppData\Local\pip\Cache
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-

- name: Create virtual environment
shell: pwsh
run: |
python -m venv .venv
.\.venv\Scripts\activate
pip install --upgrade pip
pip install -r requirements.txt

- name: Run waf build with MSYS2 GCC
shell: pwsh
run: |
.\.venv\Scripts\activate
python waf configure build -v --check-c-compiler gcc


unit-tests-windows-msvc-bare:
name: "Unit Tests (Windows/MSVC/Bare)"
runs-on: windows-latest

steps:
- name: Checkout Latest Repo
uses: actions/checkout@v7

- name: Install Python 3
uses: actions/setup-python@v7
with:
python-version: "3.x"

- name: Cache pip packages
uses: actions/cache@v6
with:
path: ~\AppData\Local\pip\Cache
key: ${{ runner.os }}-pip-${{ hashFiles('requirements.txt') }}
restore-keys: |
${{ runner.os }}-pip-

- name: Create virtual environment
run: |
python3 -m venv .venv
.\.venv\Scripts\activate
pip install --upgrade pip
pip install -r requirements.txt

- name: Run build and test (MSVC)
run: |
.\.venv\Scripts\activate
python waf configure build -v --check-c-compiler msvc
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
build
*.sublime-*
*.sublime-*
waf*-*-*/
.lock-waf_*
22 changes: 14 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Mostly error handling. Passing errors down a long chain of function calls gets u
So what if you could just specify certain places where you want to handle errors, and all your errors were
transferred there? Let's try a lame example:

CException uses C standard library functions setjmp and longjmp to operate. As long as the target system
CException uses C standard library functions `setjmp` and `longjmp` to operate. As long as the target system
has these two functions defined, this library should be useable with very little configuration. It even
supports environments where multiple program flows are in use, such as real-time operating systems...
we started this project for use in embedded systems... but it obviously can be used for larger systems too.
Expand All @@ -42,7 +42,7 @@ void functionC(void) {
}
```

There are about a gajillion exception frameworks using a similar setjmp/longjmp method out there... and there
There are about a gajillion exception frameworks using a similar `setjmp`/`longjmp` method out there... and there
will probably be more in the future. Unfortunately, when we started our last embedded project, all those that
existed either (a) did not support multiple tasks (therefore multiple stacks) or (b) were way more complex
than we really wanted. CException was born.
Expand All @@ -63,7 +63,7 @@ in passing objects or structs or strings... just simple error codes. Fast. Easy
### Performance...

CException can be configured for single tasking or multitasking. In single tasking, there is
very little overhead past the setjmp/longjmp calls (which are already fast). In multitasking, your only additional
very little overhead past the `setjmp`/`longjmp` calls (which are already fast). In multitasking, your only additional
overhead is the time it takes you to determine a unique task id (0 to num_tasks).

How?
Expand All @@ -82,7 +82,7 @@ function calls (nested as deeply as you like). There can be as many Throws as yo
execution of the guts of your Try block ends as soon as the first Throw is triggered. Once you throw, you're
transferred to the Catch block. A silly example:

```
```c
void SillyExampleWhichPrintsZeroThroughFive(void) {
volatile CEXCEPTION_T e;
int i;
Expand All @@ -109,7 +109,7 @@ successfully utilize this library:

Do not directly `return` from within a `Try` block, nor `goto` into or out of a `Try` block.
The `Try` macro allocates some local memory and alters a global pointer. These are cleaned up at the
top of the `Catch` macro. Gotos and returns would bypass some of these steps, resulting in memory leaks
top of the `Catch` macro. `goto`s and returns would bypass some of these steps, resulting in memory leaks
or unpredictable behavior.

### Local Variables
Expand Down Expand Up @@ -174,7 +174,7 @@ Configuration
CException is a mostly portable library. It has one universal dependency, plus some macros which are required if
working in a multi-tasking environment.

The standard C library setjmp must be available. Since this is part of the standard library, it's all good.
The standard C library `setjmp` must be available. Since this is part of the standard library, it's all good.

If working in a multitasking environment, you need a stack frame for each task. Therefore, you must define
methods for obtaining an index into an array of frames and to get the overall number of id's are required. If
Expand Down Expand Up @@ -232,5 +232,11 @@ Testing
If you want to validate that CException works with your tools or that it works with your custom
configuration, you may want to run the included test suite. This is the test suite (along with real
projects we've used it on) that we use to make sure that things actually work the way we claim.
The test suite makes use of Ceedling, which uses the Unity Test Framework. It will require a native C compiler.
The example makefile and rakefile both use gcc.

There are two ways of testing CException, using the unit testing frame work `Ceedling` or bare metal.
- `Ceedling`: The test suite makes use of `Ceedling`, which uses the Unity Test Framework. It will require a native C compiler.
The example makefile and `rakefile` both use gcc.
- Bare metal: The test suite makes use of `waf`, which is used as build an test framework.
It will require a native C compiler (GCC/Clang on Linux or GCC/MSVC on Windows).
Usage: `python waf configure build`
A coverage report is created if GCC or Clang are used and GCOVR is available.
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
gcovr==8.6
75 changes: 75 additions & 0 deletions test/TestException.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,35 @@
SPDX-License-Identifier: MIT
========================================================================= */

#ifndef BARE_TEST
#include "unity.h"
#endif // BARE_TEST
#include "CException.h"

#ifdef BARE_TEST
#include <assert.h>
#include <stdio.h>
#include <stdbool.h>
#endif // BARE_TEST

#ifdef BARE_TEST

#ifndef TEST_FAIL_MESSAGE
#define TEST_FAIL_MESSAGE(x) fprintf(stderr, "%s", x)
#endif // TEST_FAIL_MESSAGE

// Due to 'test/support/CExceptionConfig.h' CEXCEPTION_T is defined as int.
void TEST_ASSERT_EQUAL(int a, CEXCEPTION_T b) {
assert(a == b);
}

void TEST_ASSERT_FALSE(unsigned int a) {
if (a != false) {
assert(false);
}
}
#endif // BARE_TEST

volatile int TestingTheFallback;
volatile int TestingTheFallbackId;

Expand Down Expand Up @@ -313,6 +339,9 @@ void test_CanHaveNestedTryBlocksInASingleFunction_ThrowOutside(void)
TEST_ASSERT_EQUAL(2, i);
}

#ifdef CEXCEPTION_USE_CONFIG_FILE
// This test only makes sense to run when CException was built using the
// customized configuration test/support/CExceptionConfig.h
void test_AThrowWithoutATryCatchWillUseDefaultHandlerIfSpecified(void)
{
//Let the fallback handler know we're expecting it to get called this time, so don't fail
Expand All @@ -324,7 +353,11 @@ void test_AThrowWithoutATryCatchWillUseDefaultHandlerIfSpecified(void)
TEST_ASSERT_FALSE(TestingTheFallback);
TEST_ASSERT_EQUAL(0xBE, TestingTheFallbackId);
}
#endif // CEXCEPTION_USE_CONFIG_FILE

#ifdef CEXCEPTION_USE_CONFIG_FILE
// This test only makes sense to run when CException was built using the
// customized configuration test/support/CExceptionConfig.h
void test_AThrowWithoutOutsideATryCatchWillUseDefaultHandlerEvenAfterTryCatch(void)
{
CEXCEPTION_T e;
Expand All @@ -347,6 +380,7 @@ void test_AThrowWithoutOutsideATryCatchWillUseDefaultHandlerEvenAfterTryCatch(vo
TEST_ASSERT_FALSE(TestingTheFallback);
TEST_ASSERT_EQUAL(0xBE, TestingTheFallbackId);
}
#endif // CEXCEPTION_USE_CONFIG_FILE

void test_AbilityToExitTryWithoutThrowingAnError(void)
{
Expand Down Expand Up @@ -396,3 +430,44 @@ void test_AbilityToExitTryWillOnlyExitOneLevel(void)
// verify that we picked up and ran after first Try
TEST_ASSERT_EQUAL(1, i);
}

#ifdef BARE_TEST
int main() {
printf("SetUp\n");
setUp();
printf("test_BasicTryDoesNothingIfNoThrow\n");
test_BasicTryDoesNothingIfNoThrow();
printf("test_BasicThrowAndCatch\n");
test_BasicThrowAndCatch();
printf("test_BasicThrowAndCatch_WithMiniSyntax\n");
test_BasicThrowAndCatch_WithMiniSyntax();
printf("test_VerifyVolatilesSurviveThrowAndCatch\n");
test_VerifyVolatilesSurviveThrowAndCatch();
printf("test_ThrowFromASubFunctionAndCatchInRootFunc\n");
test_ThrowFromASubFunctionAndCatchInRootFunc();
printf("test_ThrowAndCatchFromASubFunctionAndRethrowToCatchInRootFunc\n");
test_ThrowAndCatchFromASubFunctionAndRethrowToCatchInRootFunc();
printf("test_ThrowAndCatchFromASubFunctionAndNoRethrowToCatchInRootFunc\n");
test_ThrowAndCatchFromASubFunctionAndNoRethrowToCatchInRootFunc();
printf("test_ThrowAnErrorThenEnterATryBlockFromWithinCatch_VerifyThisDoesntCorruptExceptionId\n");
test_ThrowAnErrorThenEnterATryBlockFromWithinCatch_VerifyThisDoesntCorruptExceptionId();
printf("test_ThrowAnErrorThenEnterATryBlockFromWithinCatch_VerifyThatEachExceptionIdIndependent\n");
test_ThrowAnErrorThenEnterATryBlockFromWithinCatch_VerifyThatEachExceptionIdIndependent();
printf("test_CanHaveMultipleTryBlocksInASingleFunction\n");
test_CanHaveMultipleTryBlocksInASingleFunction();
printf("test_CanHaveNestedTryBlocksInASingleFunction_ThrowInside\n");
test_CanHaveNestedTryBlocksInASingleFunction_ThrowInside();
printf("test_CanHaveNestedTryBlocksInASingleFunction_ThrowOutside\n");
test_CanHaveNestedTryBlocksInASingleFunction_ThrowOutside();
printf("test_AThrowWithoutATryCatchWillUseDefaultHandlerIfSpecified\n");
test_AThrowWithoutATryCatchWillUseDefaultHandlerIfSpecified();
printf("test_AThrowWithoutOutsideATryCatchWillUseDefaultHandlerEvenAfterTryCatch\n");
test_AThrowWithoutOutsideATryCatchWillUseDefaultHandlerEvenAfterTryCatch();
printf("test_AbilityToExitTryWithoutThrowingAnError\n");
test_AbilityToExitTryWithoutThrowingAnError();
printf("test_AbilityToExitTryWillOnlyExitOneLevel\n");
test_AbilityToExitTryWillOnlyExitOneLevel();
printf("tearDown\n");
tearDown();
}
#endif // BARE_TEST
Loading