Git Product home page Git Product logo

autofff's Introduction

AutoFFF

pre-commit.ci status build PyPI version License: MIT

Auto-generate FFF fake definitions for C API header files.

Incorporate this script into your normal build environment (like make) and automatically generate test-headers with faked function definitions ready-to-use with FFF's own gtest or some other unit-testing-framework.

The Idea Behind Faking

Especially in the embedded world, compiling your (unit-)tests against the actual target platform isn't feasible, as the architecture you're executing the test on and your target platform you're writing the code for are generally not the same.

This is where faking a specific platform API might help you. Instead of calling the actual target platform API, you link your code-under-test (CuT) against a faked version of said API. Now, whenever your CuT tries to access the platform API,it is instead calling the fake-implementation which you can easily configure in your test-cases' setup phase.

FFF (fake-functions-framework) is a framework designed to easily create faked definitions of your API function-declarations, allowing you to configure return values and inspect call and argument histories that were called during the tests' runtime. Check out their awesome project on Github. AutoFFF utilizes the ability to easily create fake definitions provided by FFF, but could also be adapted to other mocking frameworks.

The problem with faking an API in embedded C is usually the infeasibility of using dynamic linking and C's lack of techniques like 'reflection' to manipulate your CuT during runtime. This makes the process of writing fake definitions a tedious, labor intensive and error prone matter.

Introducing AutoFFF, an attempt at automating the process of writing so called test-headers (headers which include the faked definitions).

Two Philosophies of Faking

When writing fakes you will notice that there are two approaches of laying out your fake.

  1. Banning the original API header
    This strategy bans the original header by defining the API headers include guard, making it impossible to include the original function, variable and type declarations. This gives you ultimate freedom in the test-header, but also means that you will have to manually declare any types, functions and variables the API-user might expect. It also allows you to control the include hierarchy and maybe skip some headers which aren't compatible with your test-runner's architecture. In general this approach usually involves a lot of copy&pasting and is therefore more prone to "code rot". You also need to deep-dive any header you want to fake, understand its structure and inspect all the declarations and defines very closely. Not the optimal strategy if you're looking for an easy-to-maintain way of managing test-headers.
  2. Wrapping the original API header
    Conversely to the banning method, the wrapping strategy directly includes the original API header, and thereby imports any type, variable and function declarations. Also the include hierarchy is taken over from the original. The only thing to add into the test-header are the fake definitions. This method evidently grants you less freedom in the test-header, but is usually much shorter and slightly less prone to "rot" over time.

It should become obvious which method is better suited for automation. AutoFFF follows the wrapping approach of writing test-headers, which for most cases should be good enough.

Finally it must be stated, that these two philosophies seldomly mix well!

Installation

Use pip to download and install AutoFFF from the PyPi repositories:

py -3.6 -m pip install autofff

Or install from source:

py -3.6 -m pip install .

Note that you'll most likely require the pycparser fake_libc_includes header collection for AutoFFF to work. The pip package does not ship with this external code. You may download the faked libc headers from pycparsers Github, or check out the project as a submodule (when installing from source run git submodule update --init).

Usage

As a Module

py -3.6 -m autofff \
    ./examples/driver.h \
    -O ./output/driver_th.h \
    -I ./examples \
    -F ./dependencies/pycparser/utils/fake_libc_include

Using the provided Makefile

To run build and run the tests, simply execute:

make run_tests

You can also let the makefile do the installation of AutoFFF for you.

make install_autofff

Running the 'Generate Fakes' Example

make -f examples/generate-via-makefile/generate_fakes.mk CRAWL_PATHS=examples

As a Python Package

import autofff

import os.path

targetHeader = "examples/simple-headers/driver.h"
outputHeader = "output/driver_th.h"
fakes = './autofff/dependencies/pycparser/utils/fake_libc_include'

scnr = autofff.scanner.GCCScanner(targetHeader, fakes) # Create GCC code scanner
result = scnr.scan() # Scan for function declarations and definitions

gen = autofff.generator.SimpleFakeGenerator(os.path.splitext(os.path.basename(outputHeader))[0], targetHeader) # Create new generator with name output-header and path to target-header

if not os.path.exists(os.path.dirname(outputHeader)):
    dirname = os.path.dirname(outputHeader)
    os.makedirs(dirname)

with open(outputHeader, "w") as fs:
    gen.generate(result, fs) # Generate fff-fakes from scanner-result

How Fakes Are Generated

The format of the generated test-header obviously depends on the specifics of the FakeGenerator being used.

  1. The BareFakeGenerator will only generate the FAKE_VALUE_- and FAKE_VOID_FUNC macros without any decorations, like include guards or header includes. Use this generator if you want to add your own (shell-based-)processing on top.
  2. The SimpleFakeGenerator will generate a "minimum viable test header", meaning the result should be compilable without too much effort.

In-Header Defined Functions

In some API headers functions may be defined within the header. This will cause issues when trying to fake this function, because by including the header the function definition is copied into each translation unit. If we try to apply a fake definition the usual way, we will end up with a "redefinition of function x" error.

AutoFFF implements a workaround to avoid this redefinition error and allowing to fake the original function. This workaround simply consists of some defines which will re-route any call to the original in-header definition to our faked one. For this to work it is required that the test-header is included (and thereby pre-processed) before any function call to the function under consideration is instructed, i.e. the test-header must be included before the CuT. Any function call that is processed before the workaround is being pre-processed will leave this function call targeted towards the original in-header definition.

In practice the workaround looks like this:

/* api.h */
#ifndef API_HEADER_H_
#define API_HEADER_H_

const char* foo(void)
{
    return "Definitions inside headers are great!";
}

#endif
/* api_th.h */
#ifndef TEST_HEADER_H_
#define TEST_HEADER_H_

#include "fff.h"
#include "api.h"

/* Re-route any call to 'foo' to 'foo_fff' (our fake definition). */
#define foo foo_fff
/* By re-routing the '_fake' and '_reset' type and function the workaround becomes invisible in the test-case. */
#define foo_fake Foo_fff_fake
#define foo_reset Foo_fff_reset
/* Create the fake definition using the now re-routed 'foo'-symbol. */
FAKE_VALUE_FUNC(const char *, foo);

#endif
/* cut.c - code-under-test */
#include "api.h"
#include <stdio.h>

const char* bar(void)
{
    const char* str = foo();
    return str;
}
/* test.c */
#include "fff.h"
#include "api_th.h" /* fakes + workaround */
#include "cut.c"

setup(void)
{
    RESET_FAKE(foo);
}

TEST_F(foo, ReturnBar_Success)
{
    const char* expected_retval = "Definitions inside headers make faking difficult!";
    foo_fake.return_val = expected_retval

    const char* str = bar();

    ASSERT_STREQ(expected_retval, str);
}

Working with obscure include policies

Some libraries like FreeRTOS or CMSIS require you to include their API headers in a very specific way. AutoFFF can't guess these policies (yet! ;P) from source-code alone. For cases where the include policy of your vendor lib does not allow each header to be preprocessed individually check out the -D (--define) and -i (--includefile) command line options. They may allow you to fix/trick the broken include chain. As an example, for FreeRTOS' list.h run:

py -3.6 -m autofff
    [...]/include/list.h
    -o [...]
    -i [...]/include/FreeRTOS.h <<< inject FreeRTOS.h before preprocessing list.h
    -F [...]

autofff's People

Contributors

chiefgokhlayeh avatar dependabot[bot] avatar pre-commit-ci[bot] avatar sangmo-kang avatar sebirdman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

autofff's Issues

Implement clang-based scanner

As discussed in #3 (comment), explore possibilities of using libclang's C API. Fortunately, libclang provides Python bindings for their C API.

Experimenting with it demonstrated, that libclang provides deeper lexical fidelity when parsing a C source/header file (i.e. it provides preprocessor tokens before they're expanded). Perhaps this can be used to more effectively work around __asm__(...)-statements, which make direct inclusion of that particular file impossible.

Passing Defines during generation

Hello,

First off, thanks so much for this! this is a fantastic project!

I'm running into an issue with some includes where generation fails becuase something is not defined.

for example. from list.h in FreeRTOS:

#ifndef INC_FREERTOS_H
	#error FreeRTOS.h must be included before list.h
#endif

Is there any way i can pass defines into the generator?

Outdated README

I think the instructions in the README regarding how to use autofff as a python package are no longer correct. You mention you can do the following:

import autofff

import os.path

targetHeader = input("Enter the path of the header you would like to scan: ")
outputHeader = input("Enter the path of the target header file which shall be generated: ")
fakes = './autofff/dependencies/pycparser/utils/fake_libc_include'

scnr = autofff.GCCScanner(targetHeader, fakes) # Create GCC code scanner

However, I don't think this works after this commit: 4ba015b. When you no longer do from scanner import * in the __init__.py file, you can't just use autofff.GCCScanner() after doing an import autofff.

I'm no python expert, but I was able to get the following to work:

from autofff import scanner

targetHeader = input("Enter the path of the header you would like to scan: ")
outputHeader = input("Enter the path of the target header file which shall be generated: ")
fakes = './autofff/dependencies/pycparser/utils/fake_libc_include'

scnr = autofff.scanner.GCCScanner(targetHeader, fakes) # Create GCC code scanner

Perhaps there is a better way to do it. All I know is what is listed in the README didn't work for me

pip download error

Hi Andreas,

seems like the pip install does not work as expected:

python -m pip install autofff
Collecting autofff
  Using cached https://files.pythonhosted.org/packages/fa/93/699704c1a277bd51e6f9c0577fa6ed585ec674f72a6533aa6ff61675ec9f/autofff-0.3-py3-none-any.whl
Collecting overrides>=1.9 (from autofff)
  Downloading https://files.pythonhosted.org/packages/de/55/3100c6d14c1ed177492fcf8f07c4a7d2d6c996c0a7fc6a9a0a41308e7eec/overrides-1.9.tar.gz
Collecting validator>=2.0.6 (from autofff)
  Using cached https://files.pythonhosted.org/packages/09/42/00e6244cdc62bbef3dcdfdbe8232e6960fcca2e7a501f0d74e5c6258b06e/validator-2.0.6.tar.gz
    Complete output from command python setup.py egg_info:
    Download error on https://pypi.python.org/simple/yanc/: [Errno 11001] getaddrinfo failed -- Some packages may not be found!
    Couldn't find index page for 'yanc' (maybe misspelled?)
    Download error on https://pypi.python.org/simple/: [Errno 11001] getaddrinfo failed -- Some packages may not be found!
    No local packages or download links found for yanc==0.2.4
    Traceback (most recent call last):
      File "<string>", line 20, in <module>
      File "C:\Users\fsc6lr\AppData\Local\Temp\pip-build-r9e4eyy_\validator\setup.py", line 21, in <module>
        , 'yanc==0.2.4'
      File "C:\CDDK_Example\Tools\Python\v3.4.4-1.1.0\lib\distutils\core.py", line 108, in setup
        _setup_distribution = dist = klass(attrs)
      File "C:\CDDK_Example\Tools\Python\v3.4.4-1.1.0\lib\site-packages\setuptools\dist.py", line 268, in __init__
        self.fetch_build_eggs(attrs['setup_requires'])
      File "C:\CDDK_Example\Tools\Python\v3.4.4-1.1.0\lib\site-packages\setuptools\dist.py", line 313, in fetch_build_eggs
        replace_conflicting=True,
      File "C:\CDDK_Example\Tools\Python\v3.4.4-1.1.0\lib\site-packages\pkg_resources\__init__.py", line 836, in resolve
        dist = best[req.key] = env.best_match(req, ws, installer)
      File "C:\CDDK_Example\Tools\Python\v3.4.4-1.1.0\lib\site-packages\pkg_resources\__init__.py", line 1081, in best_match
        return self.obtain(req, installer)
      File "C:\CDDK_Example\Tools\Python\v3.4.4-1.1.0\lib\site-packages\pkg_resources\__init__.py", line 1093, in obtain
        return installer(requirement)
      File "C:\CDDK_Example\Tools\Python\v3.4.4-1.1.0\lib\site-packages\setuptools\dist.py", line 380, in fetch_build_egg
        return cmd.easy_install(req)
      File "C:\CDDK_Example\Tools\Python\v3.4.4-1.1.0\lib\site-packages\setuptools\command\easy_install.py", line 623, in easy_install
        raise DistutilsError(msg)
    distutils.errors.DistutilsError: Could not find suitable distribution for Requirement.parse('yanc==0.2.4')

    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in C:\Users\fsc6lr\AppData\Local\Temp\pip-build-r9e4eyy_\validator

It also does not work with a manual installation "python -m pip install ."

I tried with anaconda3 with python 3.6 and a standalone python with python 3.4, both are not working...

Hope you can fix it ;P

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.