Skip to content

Commit

Permalink
Merge pull request #840 from ThrowTheSwitch/dev/0_32/fff_updates
Browse files Browse the repository at this point in the history
Dev/0 32/fff updates
  • Loading branch information
mvandervoord authored Jan 29, 2024
2 parents 4f58d78 + f245cf4 commit 90b52d3
Show file tree
Hide file tree
Showing 31 changed files with 1,694 additions and 10 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ jobs:
ceedling module:create[someNewModule] module:destroy[someNewModule] test:all
cd ../..
# Run FFF Example
- name: Run Tests On FFF Plugin
run: |
cd plugins/fff
rake
cd ../..
# Job: Automatic Minor Releases
auto-release:
name: "Automatic Minor Releases"
Expand Down
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,3 @@
path = vendor/cmock
url = https://github.com/ThrowTheSwitch/CMock.git
branch = master
[submodule "plugins/fake_function_framework"]
path = plugins/fake_function_framework
url = https://github.com/ElectronVector/fake_function_framework.git
4 changes: 4 additions & 0 deletions docs/BreakingChanges.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,9 @@ Similarly, various global constant project file accessors have changed, specific

See the [official documentation](CeedlingPacket.md) on global constants & accessors for updated lists and information.

# Plugin Name Changes

The following plugin names will need to be updated in the `:plugins` section of your `project.yml` file.

- The plugin previously called `fake_function_framework` is now simply called `fff`.

7 changes: 1 addition & 6 deletions docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ There's more to be done, but Ceedling's documentation is more complete and accur
- The historically unwieldy `verbosity` command line task now comes in two flavors. The original recipe numeric parameterized version (e.g. `[4]`) exist as is. The new extra crispy recipe includes — funny enough — verbose task names `verbosity:silent`, `verbosity:errors`, `verbosity:complain`, `verbosity:normal`, `verbosity:obnoxious`, `verbosity:debug`.
- This release marks the beginning of the end for Rake as a backbone of Ceedling. Over many years it has become clear that Rake's design assumptions hamper building the sorts of features Ceedling's users want, Rake's command line structure creates a messy user experience for a full application built around it, and Rake's quirks cause maintenance challenges. Particularly for test suites, much of Ceedling's (invisible) dependence on Rake has been removed in this release. Much more remains to be done, including replicating some of the abilities Rake offers.
- This is the first ever release of Ceedling with proper release notes. Hello, there! Release notes will be a regular part of future Ceedling updates. If you haven't noticed already, this edition of the notes are detailed and quite lengthy. This is entirely due to how extensive the changes are in the 0.32 release. Future releases will have far shorter notes.
- The `fake_function_framework` plugin has been renamed simply `fff`

### Important Changes in Behavior to Be Aware Of 🚨

Expand Down Expand Up @@ -119,12 +120,6 @@ Colored build output and test results in your terminal is glorious. Long ago the

Ceedling's logging will eventually be updated to rely on a proper logging library. This will provide a number of important features along with greater speed and stability for the tool as a whole. This will also be the opportunity to add robust terminal text coloring support.

#### Fake Function Framework (FFF) temporarily disabled

Fake Function Framework (FFF) support in place of CMock mock generation is currently broken and the plugin has been disabled.

The FFF plugin is deeply dependent on the previous build pipeline and Ceedling's dependence on Rake. Without an all-new plugin structure and Rake fully removed, FFF cannot be made to work in Ceedling's current transitional state.

#### Bullseye Plugin temporarily disabled

The gcov plugin has been updated and improved, but its proprietary counterpart, the [Bullseye](https://www.bullseye.com) plugin, is not presently functional. The needed fixes and updates require a software license that we do not (yet) have.
Expand Down
9 changes: 9 additions & 0 deletions lib/ceedling/configurator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,15 @@ def find_and_merge_plugins(config)

config_plugins.each do |plugin|
plugin_config = @yaml_wrapper.load(plugin)

#special handling for plugin paths
if (plugin_config.include? :paths)
plugin_config[:paths].update(plugin_config[:paths]) do |k,v|
plugin_path = plugin.match(/(.*)[\/]config[\/]\w+\.yml/)[1]
v.map {|vv| vv.gsub!(/\$PLUGIN_PATH/,plugin_path) }
end
end

config.deep_merge(plugin_config)
end

Expand Down
1 change: 0 additions & 1 deletion plugins/fake_function_framework
Submodule fake_function_framework deleted from d3914e
240 changes: 240 additions & 0 deletions plugins/fff/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
# A Fake Function Framework Plug-in for Ceedling

This is a plug-in for [Ceedling](https://github.com/ThrowTheSwitch/Ceedling) to use the [Fake Function Framework](https://github.com/meekrosoft/fff) for mocking instead of CMock.

Using fff provides less strict mocking than CMock, and can allow for more loosely-coupled tests.

### Thanks

A special thanks to [Matt Chernosky](http://www.electronvector.com) for developing this plugin originally. It's a well-loved piece of the Ceedling
ecosystem and we really appreciate his support through the years.

### Enable the plug-in.

The plug-in is enabled from within your project.yml file.

In the `:plugins` configuration, add `fff` to the list of enabled plugins:

```yaml
:plugins:
:load_paths:
- vendor/ceedling/plugins
:enabled:
- stdout_pretty_tests_report
- module_generator
- fff
```
*Note that you could put the plugin source in some other loaction.
In that case you'd need to add a new path the `:load_paths`.*

## How to use it

You use fff with Ceedling the same way you used to use CMock.

If you want to "mock" `some_module.h` in your tests, just `#include "mock_some_module.h"`.
This creates a fake function for each of the functions defined in `some_module.h`.

The name of each fake is the original function name with an appended `_fake`.
For example, if we're generating fakes for a stack module with `push` and `pop` functions, we would have the fakes `push_fake` and `pop_fake`.
These fakes are linked into our test executable so that any time our unit under test calls `push` or `pop` our fakes are called instead.

Each of these fakes is actually a structure containing information about how the function was called, and what it might return.
We can use Unity to inspect these fakes in our tests, and verify the interactions of our units.
There is also a global structure named `fff` which we can use to check the sequence of calls.

The fakes can also be configured to return particular values, so you can exercise the unit under test however you want.

The examples below explain how to use fff to test a variety of module interactions.
Each example uses fakes for a "display" module, created from a display.h file with `#include "mock_display.h"`. The `display.h` file must exist and must contain the prototypes for the functions to be faked.

### Test that a function was called once

```c
void
test_whenTheDeviceIsReset_thenTheStatusLedIsTurnedOff()
{
// When
event_deviceReset();
// Then
TEST_ASSERT_EQUAL(1, display_turnOffStatusLed_fake.call_count);
}
```

### Test that a function was NOT called

```c
void
test_whenThePowerReadingIsLessThan5_thenTheStatusLedIsNotTurnedOn(void)
{
// When
event_powerReadingUpdate(4);
// Then
TEST_ASSERT_EQUAL(0, display_turnOnStatusLed_fake.call_count);
}
```

## Test that a single function was called with the correct argument

```c
void
test_whenTheVolumeKnobIsMaxed_thenVolumeDisplayIsSetTo11(void)
{
// When
event_volumeKnobMaxed();
// Then
TEST_ASSERT_EQUAL(1, display_setVolume_fake.call_count);
TEST_ASSERT_EQUAL(11, display_setVolume_fake.arg0_val);
}
```

## Test that calls are made in a particular sequence

```c
void
test_whenTheModeSelectButtonIsPressed_thenTheDisplayModeIsCycled(void)
{
// When
event_modeSelectButtonPressed();
event_modeSelectButtonPressed();
event_modeSelectButtonPressed();
// Then
TEST_ASSERT_EQUAL_PTR((void*)display_setModeToMinimum, fff.call_history[0]);
TEST_ASSERT_EQUAL_PTR((void*)display_setModeToMaximum, fff.call_history[1]);
TEST_ASSERT_EQUAL_PTR((void*)display_setModeToAverage, fff.call_history[2]);
}
```

## Fake a return value from a function

```c
void
test_givenTheDisplayHasAnError_whenTheDeviceIsPoweredOn_thenTheDisplayIsPoweredDown(void)
{
// Given
display_isError_fake.return_val = true;
// When
event_devicePoweredOn();
// Then
TEST_ASSERT_EQUAL(1, display_powerDown_fake.call_count);
}
```

## Fake a function with a value returned by reference

```c
void
test_givenTheUserHasTypedSleep_whenItIsTimeToCheckTheKeyboard_theDisplayIsPoweredDown(void)
{
// Given
char mockedEntry[] = "sleep";
void return_mock_value(char * entry, int length)
{
if (length > strlen(mockedEntry))
{
strncpy(entry, mockedEntry, length);
}
}
display_getKeyboardEntry_fake.custom_fake = return_mock_value;
// When
event_keyboardCheckTimerExpired();
// Then
TEST_ASSERT_EQUAL(1, display_powerDown_fake.call_count);
}
```

## Fake a function with a function pointer parameter

```
void
test_givenNewDataIsAvailable_whenTheDisplayHasUpdated_thenTheEventIsComplete(void)
{
// A mock function for capturing the callback handler function pointer.
void(*registeredCallback)(void) = 0;
void mock_display_updateData(int data, void(*callback)(void))
{
//Save the callback function.
registeredCallback = callback;
}
display_updateData_fake.custom_fake = mock_display_updateData;

// Given
event_newDataAvailable(10);

// When
if (registeredCallback != 0)
{
registeredCallback();
}

// Then
TEST_ASSERT_EQUAL(true, eventProcessor_isLastEventComplete());
}
```
## Helper macros
For convenience, there are also some helper macros that create new Unity-style asserts:
- `TEST_ASSERT_CALLED(function)`: Asserts that a function was called once.
- `TEST_ASSERT_NOT_CALLED(function)`: Asserts that a function was never called.
- `TEST_ASSERT_CALLED_TIMES(times, function)`: Asserts that a function was called a particular number of times.
- `TEST_ASSERT_CALLED_IN_ORDER(order, function)`: Asserts that a function was called in a particular order.
Here's how you might use one of these instead of simply checking the call_count value:
```c
void
test_whenTheDeviceIsReset_thenTheStatusLedIsTurnedOff()
{
// When
event_deviceReset();
// Then
// This how to directly use fff...
TEST_ASSERT_EQUAL(1, display_turnOffStatusLed_fake.call_count);
// ...and this is how to use the helper macro.
TEST_ASSERT_CALLED(display_turnOffStatusLed);
}
```

## Test setup

All of the fake functions, and any fff global state are all reset automatically between each test.

## CMock configuration

Use still use some of the CMock configuration options for setting things like the mock prefix, and for including additional header files in the mock files.

```yaml
:cmock:
:mock_prefix: mock_
:includes:
-
:includes_h_pre_orig_header:
-
:includes_h_post_orig_header:
-
:includes_c_pre_header:
-
:includes_c_post_header:
```
## Running the tests
There are unit and integration tests for the plug-in itself.
These are run with the default `rake` task.
The integration test runs the tests for the example project in examples/fff_example.
For the integration tests to succeed, this repository must be placed in a Ceedling tree in the plugins folder.

## More examples

There is an example project in examples/fff_example.
It shows how to use the plug-in with some full-size examples.
19 changes: 19 additions & 0 deletions plugins/fff/Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
require 'rake'
require 'rspec/core/rake_task'

desc "Run all rspecs"
RSpec::Core::RakeTask.new(:spec) do |t|
t.pattern = Dir.glob('spec/**/*_spec.rb')
t.rspec_opts = '--format documentation'
# t.rspec_opts << ' more options'
end

desc "Run integration test on example"
task :integration_test do
chdir("./examples/fff_example") do
sh "ceedling clobber"
sh "ceedling test:all"
end
end

task :default => [:spec, :integration_test]
6 changes: 6 additions & 0 deletions plugins/fff/config/fff.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
:paths:
:support:
- $PLUGIN_PATH/src
- $PLUGIN_PATH/vendor/fff
...
2 changes: 2 additions & 0 deletions plugins/fff/examples/fff_example/build/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
Loading

0 comments on commit 90b52d3

Please sign in to comment.