adding new ceedling test project

This commit is contained in:
hathach
2019-06-10 16:18:27 +07:00
parent 579f468d38
commit 97c5c7a937
199 changed files with 23201 additions and 0 deletions

View File

@@ -0,0 +1,250 @@
# 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 allows for more loosely-coupled tests.
And, when tests fail -- since you get the actual line number of the failure -- it's a lot easier to figure out what went wrong.
## Installing the plug-in
To use the plugin you need to 1) get the contents of this repo and 2) configure your project to use it.
### Get the source
The easiest way to get the source is to just clone this repo into the Ceedling plugin folder for your existing Ceedling project.
(Don't have a Ceedling project already? [Here are instructions to create one.](http://www.electronvector.com/blog/try-embedded-test-driven-development-right-now-with-ceedling))
From within `<your-project>/vendor/ceedling/plugins`, run:
`git clone https://github.com/ElectronVector/fake_function_framework.git`
This will create a new folder named `fake_function_framework` in the plugins folder.
### Enable the plug-in.
The plug-in is enabled from within your project.yml file.
In the `:plugins` configuration, add `fake_function_framework` to the list of enabled plugins:
```yaml
:plugins:
:load_paths:
- vendor/ceedling/plugins
:enabled:
- stdout_pretty_tests_report
- module_generator
- fake_function_framework
```
*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.
Modules can still be generated with the default module generator: `rake module:create[my_module]`.
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.

View File

@@ -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 "rake clobber"
sh "rake test:all"
end
end
task :default => [:spec, :integration_test]

View File

@@ -0,0 +1,71 @@
---
# Notes:
# Sample project C code is not presently written to produce a release artifact.
# As such, release build options are disabled.
# This sample, therefore, only demonstrates running a collection of unit tests.
:project:
:use_exceptions: FALSE
:use_test_preprocessor: TRUE
:use_auxiliary_dependencies: TRUE
:build_root: build
# :release_build: TRUE
:test_file_prefix: test_
#:release_build:
# :output: MyApp.out
# :use_assembly: FALSE
:environment:
:extension:
:executable: .out
:paths:
:test:
- +:test/**
:source:
- src/**
:support:
:defines:
# in order to add common defines:
# 1) remove the trailing [] from the :common: section
# 2) add entries to the :common: section (e.g. :test: has TEST defined)
:commmon: &common_defines []
:test:
- *common_defines
- TEST
:test_preprocess:
- *common_defines
- TEST
:cmock:
:mock_prefix: mock_
:when_no_prototypes: :warn
:enforce_strict_ordering: TRUE
:plugins:
- :ignore
- :callback
:treat_as:
uint8: HEX8
uint16: HEX16
uint32: UINT32
int8: INT8
bool: UINT8
#:tools:
# Ceedling defaults to using gcc for compiling, linking, etc.
# As [:tools] is blank, gcc will be used (so long as it's in your system path)
# See documentation to configure a given toolchain for use
:plugins:
:load_paths:
# This change from the default is for running Ceedling out of another folder.
- ../../../../plugins
:enabled:
- stdout_pretty_tests_report
- module_generator
- fake_function_framework
...

View File

@@ -0,0 +1,7 @@
# This change from the default is for running Ceedling out of another folder.
PROJECT_CEEDLING_ROOT = "../../../.."
load "#{PROJECT_CEEDLING_ROOT}/lib/ceedling.rb"
Ceedling.load_project
task :default => %w[ test:all release ]

View File

@@ -0,0 +1 @@
#include "bar.h"

View File

@@ -0,0 +1,14 @@
#ifndef bar_H
#define bar_H
#include "custom_types.h"
void bar_turn_on(void);
void bar_print_message(const char * message);
void bar_print_message_formatted(const char * format, ...);
void bar_numbers(int one, int two, char three);
void bar_const_test(const char * a, char * const b, const int c);
custom_t bar_needs_custom_type(void);
const char * bar_return_const_ptr(int one);
#endif // bar_H

View File

@@ -0,0 +1,6 @@
#ifndef custom_types_H
#define custom_types_H
typedef int custom_t;
#endif

View File

@@ -0,0 +1,7 @@
#include <stdio.h>
#include "display.h"
void display_turnOffStatusLed(void)
{
printf("Display: Status LED off");
}

View File

@@ -0,0 +1,16 @@
#include <stdbool.h>
void display_turnOffStatusLed(void);
void display_turnOnStatusLed(void);
void display_setVolume(int level);
void display_setModeToMinimum(void);
void display_setModeToMaximum(void);
void display_setModeToAverage(void);
bool display_isError(void);
void display_powerDown(void);
void display_updateData(int data, void(*updateCompleteCallback)(void));
/*
The entry is returned (up to `length` bytes) in the provided `entry` buffer.
*/
void display_getKeyboardEntry(char * entry, int length);

View File

@@ -0,0 +1,93 @@
/*
This module implements some business logic to test.
Signal events by calling the functions on the module.
*/
#include <stdio.h>
#include <string.h>
#include "event_processor.h"
#include "display.h"
void event_deviceReset(void)
{
//printf ("Device reset\n");
display_turnOffStatusLed();
}
void event_volumeKnobMaxed(void)
{
display_setVolume(11);
}
void event_powerReadingUpdate(int powerReading)
{
if (powerReading >= 5)
{
display_turnOnStatusLed();
}
}
void event_modeSelectButtonPressed(void)
{
static int mode = 0;
if (mode == 0)
{
display_setModeToMinimum();
mode++;
}
else if (mode == 1)
{
display_setModeToMaximum();
mode++;
}
else if (mode == 2)
{
display_setModeToAverage();
mode++;
}
else
{
mode = 0;
}
}
void event_devicePoweredOn(void)
{
if (display_isError())
{
display_powerDown();
}
}
void event_keyboardCheckTimerExpired(void)
{
char userEntry[100];
display_getKeyboardEntry(userEntry, 100);
if (strcmp(userEntry, "sleep") == 0)
{
display_powerDown();
}
}
static bool event_lastComplete = false;
/* Function called when the display update is complete. */
static void displayUpdateComplete(void)
{
event_lastComplete = true;
}
void event_newDataAvailable(int data)
{
event_lastComplete = false;
display_updateData(data, displayUpdateComplete);
}
bool eventProcessor_isLastEventComplete(void)
{
return event_lastComplete;
}

View File

@@ -0,0 +1,11 @@
#include <stdbool.h>
void event_deviceReset(void);
void event_volumeKnobMaxed(void);
void event_powerReadingUpdate(int powerReading);
void event_modeSelectButtonPressed(void);
void event_devicePoweredOn(void);
void event_keyboardCheckTimerExpired(void);
void event_newDataAvailable(int data);
bool eventProcessor_isLastEventComplete(void);

View File

@@ -0,0 +1,16 @@
#include "foo.h"
#include "bar.h"
#include "subfolder/zzz.h"
void foo_turn_on(void) {
bar_turn_on();
zzz_sleep(1, "sleepy");
}
void foo_print_message(const char * message) {
bar_print_message(message);
}
void foo_print_special_message(void) {
bar_print_message_formatted("The numbers are %d, %d and %d", 1, 2, 3);
}

View File

@@ -0,0 +1,8 @@
#ifndef foo_H
#define foo_H
void foo_turn_on(void);
void foo_print_message(const char * message);
void foo_print_special_message(void);
#endif // foo_H

View File

@@ -0,0 +1 @@
#include "zzz.h"

View File

@@ -0,0 +1,6 @@
#ifndef zzz_H
#define zzz_H
int zzz_sleep(int time, char * name);
#endif // zzz_H

View File

@@ -0,0 +1,155 @@
#include "unity.h"
#include "event_processor.h"
#include "mock_display.h"
#include <string.h>
void setUp (void)
{
}
void tearDown (void)
{
}
/*
Test that a single function was called.
*/
void
test_whenTheDeviceIsReset_thenTheStatusLedIsTurnedOff()
{
// When
event_deviceReset();
// Then
TEST_ASSERT_EQUAL(1, display_turnOffStatusLed_fake.call_count);
// or use the helper macro...
TEST_ASSERT_CALLED(display_turnOffStatusLed);
}
/*
Test that a single function is NOT called.
*/
void
test_whenThePowerReadingIsLessThan5_thenTheStatusLedIsNotTurnedOn(void)
{
// When
event_powerReadingUpdate(4);
// Then
TEST_ASSERT_EQUAL(0, display_turnOnStatusLed_fake.call_count);
// or use the helper macro...
TEST_ASSERT_NOT_CALLED(display_turnOffStatusLed);
}
/*
Test that a single function was called with the correct arugment.
*/
void
test_whenTheVolumeKnobIsMaxed_thenVolumeDisplayIsSetTo11(void)
{
// When
event_volumeKnobMaxed();
// Then
TEST_ASSERT_EQUAL(1, display_setVolume_fake.call_count);
// or use the helper macro...
TEST_ASSERT_CALLED(display_setVolume);
TEST_ASSERT_EQUAL(11, display_setVolume_fake.arg0_val);
}
/*
Test a sequence of calls.
*/
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]);
// or use the helper macros...
TEST_ASSERT_CALLED_IN_ORDER(0, display_setModeToMinimum);
TEST_ASSERT_CALLED_IN_ORDER(1, display_setModeToMaximum);
TEST_ASSERT_CALLED_IN_ORDER(2, display_setModeToAverage);
}
/*
Mock a return value from a function.
*/
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);
// or use the helper macro...
TEST_ASSERT_CALLED(display_powerDown);
}
/*
Mock a sequence of calls with return values.
*/
/*
Mocking a function with a value returned by reference.
*/
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);
// or use the helper macro...
TEST_ASSERT_CALLED(display_powerDown);
}
/*
Mock 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());
}

View File

@@ -0,0 +1,47 @@
#include "unity.h"
#include "foo.h"
#include "mock_bar.h"
#include "mock_zzz.h"
void setUp(void)
{
}
void tearDown(void)
{
}
void test_foo(void)
{
//When
foo_turn_on();
//Then
TEST_ASSERT_EQUAL(1, bar_turn_on_fake.call_count);
TEST_ASSERT_EQUAL(1, zzz_sleep_fake.call_count);
TEST_ASSERT_EQUAL_STRING("sleepy", zzz_sleep_fake.arg1_val);
}
void test_foo_again(void)
{
//When
foo_turn_on();
//Then
TEST_ASSERT_EQUAL(1, bar_turn_on_fake.call_count);
}
void test_foo_mock_with_const(void)
{
foo_print_message("123");
TEST_ASSERT_EQUAL(1, bar_print_message_fake.call_count);
TEST_ASSERT_EQUAL_STRING("123", bar_print_message_fake.arg0_val);
}
void test_foo_mock_with_variable_args(void)
{
foo_print_special_message();
TEST_ASSERT_EQUAL(1, bar_print_message_formatted_fake.call_count);
TEST_ASSERT_EQUAL_STRING("The numbers are %d, %d and %d", bar_print_message_formatted_fake.arg0_val);
}

View File

@@ -0,0 +1,87 @@
require 'ceedling/plugin'
require 'fff_mock_generator'
class FakeFunctionFramework < Plugin
# Set up Ceedling to use this plugin.
def setup
# Get the location of this plugin.
@plugin_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
puts "Using fake function framework (fff)..."
# Switch out the cmock_builder with our own.
@ceedling[:cmock_builder].cmock = FffMockGeneratorForCMock.new(@ceedling[:setupinator].config_hash[:cmock])
# Add the path to fff.h to the include paths.
COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR << "#{@plugin_root}/vendor/fff"
COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR << "#{@plugin_root}/src"
end
def post_runner_generate(arg_hash)
# After the test runner file has been created, append the FFF globals
# definition to the end of the test runner. These globals will be shared by
# all mocks linked into the test.
File.open(arg_hash[:runner_file], 'a') do |f|
f.puts
f.puts "//=======Defintions of FFF variables====="
f.puts %{#include "fff.h"}
f.puts "DEFINE_FFF_GLOBALS;"
end
end
end # class FakeFunctionFramework
class FffMockGeneratorForCMock
def initialize(options=nil)
@cm_config = CMockConfig.new(options)
@cm_parser = CMockHeaderParser.new(@cm_config)
@silent = (@cm_config.verbosity < 2)
# These are the additional files to include in the mock files.
@includes_h_pre_orig_header = (@cm_config.includes || @cm_config.includes_h_pre_orig_header || []).map{|h| h =~ /</ ? h : "\"#{h}\""}
@includes_h_post_orig_header = (@cm_config.includes_h_post_orig_header || []).map{|h| h =~ /</ ? h : "\"#{h}\""}
@includes_c_pre_header = (@cm_config.includes_c_pre_header || []).map{|h| h =~ /</ ? h : "\"#{h}\""}
@includes_c_post_header = (@cm_config.includes_c_post_header || []).map{|h| h =~ /</ ? h : "\"#{h}\""}
end
def setup_mocks(files)
[files].flatten.each do |src|
generate_mock (src)
end
end
def generate_mock (header_file_to_mock)
module_name = File.basename(header_file_to_mock, '.h')
puts "Creating mock for #{module_name}..." unless @silent
mock_name = @cm_config.mock_prefix + module_name + @cm_config.mock_suffix
mock_path = @cm_config.mock_path
if @cm_config.subdir
# If a subdirectory has been configured, append it to the mock path.
mock_path = "#{mock_path}/#{@cm_config.subdir}"
end
full_path_for_mock = "#{mock_path}/#{mock_name}"
# Parse the header file so we know what to mock.
parsed_header = @cm_parser.parse(module_name, File.read(header_file_to_mock))
# Create the directory if it doesn't exist.
mkdir_p full_path_for_mock.pathmap("%d")
# Generate the mock header file.
puts "Creating mock: #{full_path_for_mock}.h"
# Create the mock header.
File.open("#{full_path_for_mock}.h", 'w') do |f|
f.write(FffMockGenerator.create_mock_header(module_name, mock_name, parsed_header,
@includes_h_pre_orig_header, @includes_h_post_orig_header))
end
# Create the mock source file.
File.open("#{full_path_for_mock}.c", 'w') do |f|
f.write(FffMockGenerator.create_mock_source(mock_name, parsed_header,
@includes_c_pre_orig_header, @includes_c_post_orig_header))
end
end
end

View File

@@ -0,0 +1,163 @@
# Creates mock files from parsed header files that can be linked into applications.
# The mocks created are compatible with CMock for use with Ceedling.
class FffMockGenerator
def self.create_mock_header(module_name, mock_name, parsed_header, pre_includes=nil,
post_includes=nil)
output = StringIO.new
write_opening_include_guard(mock_name, output)
output.puts
write_extra_includes(pre_includes, output)
write_header_includes(module_name, output)
write_extra_includes(post_includes, output)
output.puts
write_typedefs(parsed_header, output)
output.puts
write_function_declarations(parsed_header, output)
output.puts
write_control_function_prototypes(mock_name, output)
output.puts
write_closing_include_guard(mock_name, output)
output.string
end
def self.create_mock_source (mock_name, parsed_header, pre_includes=nil,
post_includes=nil)
output = StringIO.new
write_extra_includes(pre_includes, output)
write_source_includes(mock_name, output)
write_extra_includes(post_includes, output)
output.puts
write_function_definitions(parsed_header, output)
output.puts
write_control_function_definitions(mock_name, parsed_header, output)
output.string
end
private
# Header file generation functions.
def self.write_opening_include_guard(mock_name, output)
output.puts "#ifndef #{mock_name}_H"
output.puts "#define #{mock_name}_H"
end
def self.write_header_includes(module_name, output)
output.puts %{#include "fff.h"}
output.puts %{#include "fff_unity_helper.h"}
output.puts %{#include "#{module_name}.h"}
end
def self.write_typedefs(parsed_header, output)
return unless parsed_header.key?(:typedefs)
parsed_header[:typedefs].each do |typedef|
output.puts typedef
end
end
def self.write_function_declarations(parsed_header, output)
write_function_macros("DECLARE", parsed_header, output)
end
def self.write_control_function_prototypes(mock_name, output)
output.puts "void #{mock_name}_Init(void);"
output.puts "void #{mock_name}_Verify(void);"
output.puts "void #{mock_name}_Destroy(void);"
end
def self.write_closing_include_guard(mock_name, output)
output.puts "#endif // #{mock_name}_H"
end
# Source file generation functions.
def self.write_source_includes (mock_name, output)
output.puts "#include <string.h>"
output.puts %{#include "fff.h"}
output.puts %{#include "#{mock_name}.h"}
end
def self.write_function_definitions(parsed_header, output)
write_function_macros("DEFINE", parsed_header, output)
end
def self.write_control_function_definitions(mock_name, parsed_header, output)
output.puts "void #{mock_name}_Init(void)"
output.puts "{"
# In the init function, reset the FFF globals. These are used for things
# like the call history.
output.puts " FFF_RESET_HISTORY();"
# Also, reset all of the fakes.
if parsed_header[:functions]
parsed_header[:functions].each do |function|
output.puts " RESET_FAKE(#{function[:name]})"
end
end
output.puts "}"
output.puts "void #{mock_name}_Verify(void)"
output.puts "{"
output.puts "}"
output.puts "void #{mock_name}_Destroy(void)"
output.puts "{"
output.puts "}"
end
# Shared functions.
def self.write_extra_includes(includes, output)
if includes
includes.each {|inc| output.puts "#include #{inc}\n"}
end
end
def self.write_function_macros(macro_type, parsed_header, output)
return unless parsed_header.key?(:functions)
parsed_header[:functions].each do |function|
name = function[:name]
return_type = function[:return][:type]
if function.has_key? :modifier
# Prepend any modifier. If there isn't one, trim any leading whitespace.
return_type = "#{function[:modifier]} #{return_type}".lstrip
end
arg_count = function[:args].size
# Check for variable arguments.
var_arg_suffix = ""
if function[:var_arg]
# If there are are variable arguments, then we need to add this argument
# to the count, update the suffix that will get added to the macro.
arg_count += 1
var_arg_suffix = "_VARARG"
end
# Generate the correct macro.
if return_type == 'void'
output.print "#{macro_type}_FAKE_VOID_FUNC#{arg_count}#{var_arg_suffix}(#{name}"
else
output.print "#{macro_type}_FAKE_VALUE_FUNC#{arg_count}#{var_arg_suffix}(#{return_type}, #{name}"
end
# Append each argument type.
function[:args].each do |arg|
output.print ", "
if arg[:const?]
output.print "const "
end
output.print "#{arg[:type]}"
end
# If this argument list ends with a variable argument, add it here at the end.
if function[:var_arg]
output.print ", ..."
end
# Close the declaration.
output.puts ");"
end
end
end

View File

@@ -0,0 +1,304 @@
require 'stringio'
require 'fff_mock_generator.rb'
require 'header_generator.rb'
# Test the contents of the .h file created for the mock.
describe "FffMockGenerator.create_mock_header" do
context "when there is nothing to mock," do
let(:mock_header) {
parsed_header = {}
FffMockGenerator.create_mock_header("display", "mock_display", parsed_header)
}
it "then the generated header file starts with an opening include guard" do
expect(mock_header).to start_with(
"#ifndef mock_display_H\n" +
"#define mock_display_H")
end
it "then the generated file ends with a closing include guard" do
expect(mock_header).to end_with(
"#endif // mock_display_H\n")
end
it "then the generated file includes the fff header" do
expect(mock_header).to include(
%{#include "fff.h"\n})
end
it "then the generated file has a prototype for the init function" do
expect(mock_header).to include(
"void mock_display_Init(void);")
end
it "then the generated file has a prototype for the verify function" do
expect(mock_header).to include(
"void mock_display_Verify(void);")
end
it "then the generated file has a prototype for the destroy function" do
expect(mock_header).to include(
"void mock_display_Destroy(void);")
end
end
context "when there is a function with no args and a void return," do
let(:mock_header) {
parsed_header = create_cmock_style_parsed_header(
[{:name => 'display_turnOffStatusLed', :return_type => 'void'}])
FffMockGenerator.create_mock_header("display", "mock_display", parsed_header)
}
it "then the generated header file starts with an opening include guard" do
expect(mock_header).to start_with(
"#ifndef mock_display_H\n" +
"#define mock_display_H")
end
it "then the generated header file contains a fake function declaration" do
expect(mock_header).to include(
"DECLARE_FAKE_VOID_FUNC0(display_turnOffStatusLed);"
)
end
it "then the generated file ends with a closing include guard" do
expect(mock_header).to end_with(
"#endif // mock_display_H\n")
end
end
context "when there is a function with no args and a bool return," do
let(:mock_header) {
parsed_header = create_cmock_style_parsed_header(
[{:name => 'display_isError', :return_type => 'bool'}])
FffMockGenerator.create_mock_header("display", "mock_display", parsed_header)
}
it "then the generated file contains the fake function declaration" do
expect(mock_header).to include(
"DECLARE_FAKE_VALUE_FUNC0(bool, display_isError);"
)
end
end
context "when there is a function with no args and an int return," do
let(:mock_header) {
parsed_header = create_cmock_style_parsed_header(
[{:name => 'display_isError', :return_type => 'int'}])
FffMockGenerator.create_mock_header("display", "mock_display", parsed_header)
}
it "then the generated file contains the fake function declaration" do
expect(mock_header).to include(
"DECLARE_FAKE_VALUE_FUNC0(int, display_isError);"
)
end
end
context "when there is a function with args and a void return," do
let(:mock_header) {
parsed_header = create_cmock_style_parsed_header(
[{:name => 'display_setVolume', :return_type => 'void', :args => ['int']}])
FffMockGenerator.create_mock_header("display", "mock_display", parsed_header)
}
it "then the generated file contains the fake function declaration" do
expect(mock_header).to include(
"DECLARE_FAKE_VOID_FUNC1(display_setVolume, int);"
)
end
end
context "when there is a function with args and a value return," do
let(:mock_header) {
parsed_header = create_cmock_style_parsed_header(
[{:name => 'a_function', :return_type => 'int', :args => ['char *']}])
FffMockGenerator.create_mock_header("display", "mock_display", parsed_header)
}
it "then the generated file contains the fake function declaration" do
expect(mock_header).to include(
"FAKE_VALUE_FUNC1(int, a_function, char *);"
)
end
end
context "when there is a function with many args and a void return," do
let(:mock_header) {
parsed_header = create_cmock_style_parsed_header(
[{:name => 'a_function', :return_type => 'void',
:args => ['int', 'char *', 'int', 'int', 'bool', 'applesauce']}])
FffMockGenerator.create_mock_header("display", "mock_display", parsed_header)
}
it "then the generated file contains the fake function declaration" do
expect(mock_header).to include(
"DECLARE_FAKE_VOID_FUNC6(a_function, int, char *, int, int, bool, applesauce);"
)
end
end
context "when there are multiple functions," do
let(:mock_header) {
parsed_header = create_cmock_style_parsed_header(
[ {:name => 'a_function', :return_type => 'int', :args => ['char *']},
{:name => 'another_function', :return_type => 'void'},
{:name => 'three', :return_type => 'bool', :args => ['float', 'int']}
])
FffMockGenerator.create_mock_header("display", "mock_display", parsed_header)
}
it "then the generated file contains the first fake function declaration" do
expect(mock_header).to include(
"DECLARE_FAKE_VALUE_FUNC1(int, a_function, char *);"
)
end
it "then the generated file contains the second fake function declaration" do
expect(mock_header).to include(
"DECLARE_FAKE_VOID_FUNC0(another_function);"
)
end
it "then the generated file contains the third fake function declaration" do
expect(mock_header).to include(
"DECLARE_FAKE_VALUE_FUNC2(bool, three, float, int);"
)
end
end
context "when there is a typedef," do
let(:mock_header) {
parsed_header = create_cmock_style_parsed_header(
nil, ["typedef void (*displayCompleteCallback) (void);"])
FffMockGenerator.create_mock_header("display", "mock_display", parsed_header)
}
it "then the generated file contains the typedef" do
expect(mock_header).to include(
"typedef void (*displayCompleteCallback) (void);"
)
end
end
context "when there is a void function with variable arguments" do
let(:mock_header){
parsed_header = {}
parsed_header[:functions] = [{
:name => "function_with_var_args",
:return => {:type => "void"},
:var_arg => "...",
:args => [{:type => 'char *'}]
}]
FffMockGenerator.create_mock_header("display", "mock_display", parsed_header)
}
it "then the generated file contains the vararg declaration" do
expect(mock_header).to include(
"DECLARE_FAKE_VOID_FUNC2_VARARG(function_with_var_args, char *, ...)"
)
end
end
context "when there is a function with a return value and variable arguments" do
let(:mock_header){
parsed_header = {}
parsed_header[:functions] = [{
:name => "function_with_var_args",
:return => {:type => "int"},
:var_arg => "...",
:args => [{:type => 'char *'}]
}]
FffMockGenerator.create_mock_header("display", "mock_display", parsed_header)
}
it "then the generated file contains the vararg declaration" do
expect(mock_header).to include(
"DECLARE_FAKE_VALUE_FUNC2_VARARG(int, function_with_var_args, char *, ...)"
)
end
end
context "when there is a void function with variable arguments and " +
"additional arguments" do
let(:mock_header){
parsed_header = {}
parsed_header[:functions] = [{
:name => "function_with_var_args",
:return => {:type => "void"},
:var_arg => "...",
:args => [{:type => 'char *'}, {:type => 'int'}]
}]
FffMockGenerator.create_mock_header("display", "mock_display", parsed_header)
}
it "then the generated file contains the vararg declaration" do
expect(mock_header).to include(
"DECLARE_FAKE_VOID_FUNC3_VARARG(function_with_var_args, char *, int, ...)"
)
end
end
context "when there is a function with a pointer to a const value" do
let(:mock_header){
parsed_header = {}
parsed_header[:functions] = [{
:name => "const_test_function",
:return => {:type => "void"},
:args => [{:type => "char *", :name => "a", :ptr? => false, :const? => true},
{:type => "char *", :name => "b", :ptr? => false, :const? => false}]
}]
FffMockGenerator.create_mock_header("display", "mock_display", parsed_header)
}
it "then the generated file contains the correct const argument in the declaration" do
expect(mock_header).to include(
"DECLARE_FAKE_VOID_FUNC2(const_test_function, const char *, char *)"
)
end
end
context "when there is a function that returns a const pointer" do
let(:mock_header){
parsed_header = {}
parsed_header[:functions] = [{
:name => "return_const_pointer_test_function",
:modifier => "const",
:return => {:type => "char *" },
:args => [{:type => "int", :name => "a"}]
}]
FffMockGenerator.create_mock_header("display", "mock_display", parsed_header)
}
it "then the generated file contains the correct const return value in the declaration" do
expect(mock_header).to include(
"DECLARE_FAKE_VALUE_FUNC1(const char *, return_const_pointer_test_function, int)"
)
end
end
context "when there is a function that returns a const int" do
let(:mock_header){
parsed_header = {}
parsed_header[:functions] = [{
:name => "return_const_int_test_function",
:modifier => "const",
:return => {:type => "int" },
:args => []
}]
FffMockGenerator.create_mock_header("display", "mock_display", parsed_header)
}
it "then the generated file contains the correct const return value in the declaration" do
expect(mock_header).to include(
"DECLARE_FAKE_VALUE_FUNC0(const int, return_const_int_test_function)"
)
end
end
context "when there are pre-includes" do
let(:mock_header) {
parsed_header = {}
FffMockGenerator.create_mock_header("display", "mock_display", parsed_header,
[%{"another_header.h"}])
}
it "then they are included before the other files" do
expect(mock_header).to include(
%{#include "another_header.h"\n} +
%{#include "fff.h"}
)
end
end
context "when there are post-includes" do
let(:mock_header) {
parsed_header = {}
FffMockGenerator.create_mock_header("display", "mock_display", parsed_header,
nil, [%{"another_header.h"}])
}
it "then they are included after the other files" do
expect(mock_header).to include(
%{#include "display.h"\n} +
%{#include "another_header.h"\n}
)
end
end
end

View File

@@ -0,0 +1,149 @@
require 'stringio'
require 'fff_mock_generator.rb'
# Test the contents of the .c file created for the mock.
describe "FffMockGenerator.create_mock_source" do
context "when there is nothing to mock," do
let(:mock_source) {
parsed_header = {}
FffMockGenerator.create_mock_source("mock_my_module", parsed_header)
}
it "then the generated file includes the fff header" do
expect(mock_source).to include(
# fff.h also requires including string.h
%{#include <string.h>\n} +
%{#include "fff.h"}
)
end
it "then the generated file includes the mock header" do
expect(mock_source).to include(
%{#include "mock_my_module.h"\n}
)
end
it "then the generated file defines the init function" do
expect(mock_source).to include(
"void mock_my_module_Init(void)\n" +
"{\n" +
" FFF_RESET_HISTORY();\n" +
"}"
)
end
it "then the generated file defines the verify function" do
expect(mock_source).to include(
"void mock_my_module_Verify(void)\n" +
"{\n" +
"}"
)
end
it "then the generated file defines the destroy function" do
expect(mock_source).to include(
"void mock_my_module_Destroy(void)\n" +
"{\n" +
"}"
)
end
end
context "when there are multiple functions," do
let(:mock_source) {
parsed_header = create_cmock_style_parsed_header(
[ {:name => 'a_function', :return_type => 'int', :args => ['char *']},
{:name => 'another_function', :return_type => 'void'},
{:name => 'three', :return_type => 'bool', :args => ['float', 'int']}
])
FffMockGenerator.create_mock_source("mock_display", parsed_header)
}
it "then the generated file contains the first fake function definition" do
expect(mock_source).to include(
"DEFINE_FAKE_VALUE_FUNC1(int, a_function, char *);"
)
end
it "then the generated file contains the second fake function definition" do
expect(mock_source).to include(
"DEFINE_FAKE_VOID_FUNC0(another_function);"
)
end
it "then the generated file contains the third fake function definition" do
expect(mock_source).to include(
"DEFINE_FAKE_VALUE_FUNC2(bool, three, float, int);"
)
end
it "then the init function resets all of the fakes" do
expect(mock_source).to include(
"void mock_display_Init(void)\n" +
"{\n" +
" FFF_RESET_HISTORY();\n" +
" RESET_FAKE(a_function)\n" +
" RESET_FAKE(another_function)\n" +
" RESET_FAKE(three)\n" +
"}"
)
end
end
context "when there is a void function with variable arguments and " +
"additional arguments" do
let(:mock_source){
parsed_header = {}
parsed_header[:functions] = [{
:name => "function_with_var_args",
:return => {:type => "void"},
:var_arg => "...",
:args => [{:type => 'char *'}, {:type => 'int'}]
}]
FffMockGenerator.create_mock_source("mock_display", parsed_header)
}
it "then the generated file contains the vararg definition" do
expect(mock_source).to include(
"DEFINE_FAKE_VOID_FUNC3_VARARG(function_with_var_args, char *, int, ...)"
)
end
end
context "when there is a function with a pointer to a const value" do
let(:mock_source){
parsed_header = {}
parsed_header[:functions] = [{
:name => "const_test_function",
:return => {:type => "void"},
:args => [{:type => "char *", :name => "a", :ptr? => false, :const? => true},
{:type => "char *", :name => "b", :ptr? => false, :const? => false}]
}]
FffMockGenerator.create_mock_source("mock_display", parsed_header)
}
it "then the generated file contains the correct const argument in the declaration" do
expect(mock_source).to include(
"DEFINE_FAKE_VOID_FUNC2(const_test_function, const char *, char *)"
)
end
end
context "when there are pre-includes" do
let(:mock_source) {
parsed_source = {}
FffMockGenerator.create_mock_source("mock_display", parsed_source,
[%{"another_header.h"}])
}
it "then they are included before the other files" do
expect(mock_source).to include(
%{#include "another_header.h"\n} +
%{#include <string.h>}
)
end
end
context "when there are post-includes" do
let(:mock_source) {
parsed_source = {}
FffMockGenerator.create_mock_source("mock_display", parsed_source,
nil, [%{"another_header.h"}])
}
it "then they are included before the other files" do
expect(mock_source).to include(
%{#include "mock_display.h"\n} +
%{#include "another_header.h"\n}
)
end
end
end

View File

@@ -0,0 +1,51 @@
# Create a CMock-style parsed header hash. This the type of hash created by
# CMock when parsing header files for automock generation. It contains all of
# includes, typedefs and functions (with return types and arguments) parsed from
# the header file.
def create_cmock_style_parsed_header(functions, typedefs = nil)
parsed_header = {
:includes => nil,
:functions => [],
:typedefs => []
}
# Add the typedefs.
if typedefs
typedefs.each do |typedef|
parsed_header[:typedefs] << typedef
end
end
# Add the functions.
if functions
functions.each do |function|
# Build the array of arguments.
args = []
if function.key?(:args)
function[:args].each do |arg|
args << {
:type => arg
}
end
end
parsed_header[:functions] << {
:name => function[:name],
:modifier => "",
:return => {
:type => function[:return_type],
:name => "cmock_to_return",
:ptr? => false,
:const? => false,
:str => "void cmock_to_return",
:void? => true
},
:var_arg => nil,
:args_string => "void",
:args => args,
:args_call => "",
:contains_ptr? => false
}
end
end
parsed_header
end

View File

@@ -0,0 +1,96 @@
# This file was generated by the `rspec --init` command. Conventionally, all
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
# The generated `.rspec` file contains `--require spec_helper` which will cause
# this file to always be loaded, without a need to explicitly require it in any
# files.
#
# Given that it is always loaded, you are encouraged to keep this file as
# light-weight as possible. Requiring heavyweight dependencies from this file
# will add to the boot time of your test suite on EVERY test run, even for an
# individual file that may not need all of that loaded. Instead, consider making
# a separate helper file that requires the additional dependencies and performs
# the additional setup, and require it from the spec files that actually need
# it.
#
# The `.rspec` file also contains a few flags that are not defaults but that
# users commonly want.
#
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
RSpec.configure do |config|
# rspec-expectations config goes here. You can use an alternate
# assertion/expectation library such as wrong or the stdlib/minitest
# assertions if you prefer.
config.expect_with :rspec do |expectations|
# This option will default to `true` in RSpec 4. It makes the `description`
# and `failure_message` of custom matchers include text for helper methods
# defined using `chain`, e.g.:
# be_bigger_than(2).and_smaller_than(4).description
# # => "be bigger than 2 and smaller than 4"
# ...rather than:
# # => "be bigger than 2"
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
# rspec-mocks config goes here. You can use an alternate test double
# library (such as bogus or mocha) by changing the `mock_with` option here.
config.mock_with :rspec do |mocks|
# Prevents you from mocking or stubbing a method that does not exist on
# a real object. This is generally recommended, and will default to
# `true` in RSpec 4.
mocks.verify_partial_doubles = true
end
# The settings below are suggested to provide a good initial experience
# with RSpec, but feel free to customize to your heart's content.
=begin
# These two settings work together to allow you to limit a spec run
# to individual examples or groups you care about by tagging them with
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
# get run.
config.filter_run :focus
config.run_all_when_everything_filtered = true
# Allows RSpec to persist some state between runs in order to support
# the `--only-failures` and `--next-failure` CLI options. We recommend
# you configure your source control system to ignore this file.
config.example_status_persistence_file_path = "spec/examples.txt"
# Limits the available syntax to the non-monkey patched syntax that is
# recommended. For more details, see:
# - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
# - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
config.disable_monkey_patching!
# This setting enables warnings. It's recommended, but in some cases may
# be too noisy due to issues in dependencies.
config.warnings = true
# Many RSpec users commonly either run the entire suite or an individual
# file, and it's useful to allow more verbose output when running an
# individual spec file.
if config.files_to_run.one?
# Use the documentation formatter for detailed output,
# unless a formatter has already been configured
# (e.g. via a command-line flag).
config.default_formatter = 'doc'
end
# Print the 10 slowest examples and example groups at the
# end of the spec run, to help surface which specs are running
# particularly slow.
config.profile_examples = 10
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
# --seed 1234
config.order = :random
# Seed global randomization in this process using the `--seed` CLI option.
# Setting this allows you to use `--seed` to deterministically reproduce
# test failures related to randomization by passing the same `--seed` value
# as the one that triggered the failure.
Kernel.srand config.seed
=end
end

View File

@@ -0,0 +1,33 @@
#ifndef fff_unity_helper_H
#define fff_unity_helper_H
/*
FFF helper macros for Unity.
*/
/*
Fail if the function was not called the expected number of times.
*/
#define TEST_ASSERT_CALLED_TIMES(times_, function_) \
TEST_ASSERT_EQUAL_MESSAGE(times_, \
function_ ## _fake.call_count, \
"Function " #function_ " called the incorrect number of times.")
/*
Fail if the function was not called exactly once.
*/
#define TEST_ASSERT_CALLED(function_) TEST_ASSERT_CALLED_TIMES(1, function_)
/*
Fail if the function was called 1 or more times.
*/
#define TEST_ASSERT_NOT_CALLED(function_) TEST_ASSERT_CALLED_TIMES(0, function_)
/*
Fail if the function was not called in this particular order.
*/
#define TEST_ASSERT_CALLED_IN_ORDER(order_, function_) \
TEST_ASSERT_EQUAL_PTR_MESSAGE((void *) function_, \
fff.call_history[order_], \
"Function " #function_ " not called in order " #order_ )
#endif