diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0b724accc..894cd1c24 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,9 +1,15 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + --- -# Continuous Integration Workflow: Test case suite run + validation build check +# Continuous Integration Workflow name: CI -# Controls when the action will run. -# Triggers the workflow on push or pull request events but only for the master branch +# Triggers the workflow on push or pull request events for master & test branches on: push: branches: @@ -13,69 +19,269 @@ on: branches: [ master ] workflow_dispatch: + +# Needed by softprops/action-gh-release +permissions: + # Allow built gem file push to Github release + contents: write + + jobs: - # Job: Unit test suite - unit-tests: - name: "Unit Tests" + # Job: Linux unit test suite + unit-tests-linux: + name: "Linux Test Suite" runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - ruby: ['2.7', '3.0', '3.1'] + ruby: ['3.0', '3.1', '3.2', '3.3'] steps: - # Install Binutils, Multilib, etc - - name: Install C dev Tools + # Use a cache for our tools to speed up testing + - uses: actions/cache@v4 + with: + path: vendor/bundle + key: bundle-use-ruby-${{ matrix.os }}-${{ matrix.ruby-version }}-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + bundle-use-ruby-${{ matrix.os }}-${{ matrix.ruby-version }}- + + # Checks out repository under $GITHUB_WORKSPACE + - name: Checkout Latest Repo + uses: actions/checkout@v4 + with: + submodules: recursive + + # Setup Ruby to run test & build steps on multiple ruby versions + - name: Setup Ruby Version Matrix + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + + # Install Gem Depdencies (Bundler version should match the one in Gemfile.lock) + - name: Install Gem Dependencies for Testing and Ceedling Gem Builds + run: | + gem install rubocop -v 0.57.2 + gem install bundler -v "$(grep -A 1 "BUNDLED WITH" Gemfile.lock | tail -n 1)" + bundle update + bundle install + + # Install gdb for backtrace feature testing + - name: Install gdb for Backtrace Feature Testing run: | sudo apt-get update -qq - sudo apt-get install --assume-yes --quiet gcc-multilib - sudo apt-get install -qq gcc-avr binutils-avr avr-libc gdb + sudo apt-get install --assume-yes --quiet gdb - # Install GCovr - - name: Install GCovr + # Install GCovr for Gcov plugin + - name: Install GCovr for Gcov Plugin Tests run: | sudo pip install gcovr + # Install ReportGenerator for Gcov plugin + # Fix PATH before tool installation + # https://stackoverflow.com/questions/59010890/github-action-how-to-restart-the-session + - name: Install ReportGenerator for Gcov Plugin Tests + run: | + mkdir --parents $HOME/.dotnet/tools + echo "$HOME/.dotnet/tools" >> $GITHUB_PATH + dotnet tool install --global dotnet-reportgenerator-globaltool + + # Run Tests + - name: Run All Self Tests + run: | + rake ci + + # Build & Install Ceedling Gem + - name: Build and Install Ceedling Gem + run: | + gem build ceedling.gemspec + gem install --local ceedling-*.gem + + # Run temp_sensor + - name: Run Tests on temp_sensor Project + run: | + cd examples/temp_sensor + ceedling test:all + cd ../.. + + # Run FFF Plugin Tests + - name: "Run Tests on Ceedling Plugin: FFF" + run: | + cd plugins/fff + rake + cd ../.. + + # Run Module Generator Plugin Tests + - name: "Run Tests on Ceedling Plugin: Module Generator" + run: | + cd plugins/module_generator + rake + cd ../.. + + # Run Dependencies Plugin Tests + - name: "Run Tests on Ceedling Plugin: Dependencies" + run: | + cd plugins/dependencies + rake + cd ../.. + + # Job: Windows unit test suite + unit-tests-windows: + name: "Windows Test Suite" + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + ruby: ['3.0', '3.1', '3.2', '3.3'] + steps: + # Use a cache for our tools to speed up testing + - uses: actions/cache@v4 + with: + path: vendor/bundle + key: bundle-use-ruby-${{ matrix.os }}-${{ matrix.ruby-version }}-${{ hashFiles('**/Gemfile.lock') }} + restore-keys: | + bundle-use-ruby-${{ matrix.os }}-${{ matrix.ruby-version }}- + # Checks out repository under $GITHUB_WORKSPACE - name: Checkout Latest Repo - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: submodules: recursive - # Setup Ruby Testing Tools to do tests on multiple ruby version - - name: Setup Ruby Testing Tools + # Setup Ruby to run test & build steps on multiple ruby versions + - name: Setup Ruby Version Matrix uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - # Install Ruby Testing Tools (Bundler version should match the one in Gemfile.lock) - - name: Install Ruby Testing Tools + + # Install Gem Depdencies (Bundler version should match the one in Gemfile.lock) + - name: Install Gem Dependencies for Testing and Ceedling Gem Builds + shell: bash run: | - gem install rspec gem install rubocop -v 0.57.2 gem install bundler -v "$(grep -A 1 "BUNDLED WITH" Gemfile.lock | tail -n 1)" bundle update bundle install + # Install GCovr for Gcov plugin test + - name: "Install GCovr for Tests of Ceedling Plugin: Gcov" + run: | + pip install gcovr + + # Install ReportGenerator for Gcov plugin test + - name: "Install ReportGenerator for Tests of Ceedling Plugin: Gcov" + run: | + dotnet tool install --global dotnet-reportgenerator-globaltool + # Run Tests - name: Run All Self Tests run: | - bundle exec rake ci + rake ci # Build & Install Gem - - name: Build and Install Gem + - name: Build and Install Ceedling Gem run: | gem build ceedling.gemspec gem install --local ceedling-*.gem - # Run Blinky - # Disabled because it's set up for avr-gcc - #- name: Run Tests On Blinky Project - # run: | - # cd examples/blinky - # ceedling module:create[someNewModule] module:destroy[someNewModule] test:all - # cd ../.. - - # Run Temp Sensor - - name: Run Tests On Temp Sensor Project + # Run temp_sensor example project + - name: Run Tests on temp_sensor Project run: | cd examples/temp_sensor - ceedling module:create[someNewModule] module:destroy[someNewModule] test:all + ceedling test:all + cd ../.. + + # Run FFF Plugin Tests + - name: "Run Tests on Ceedling Plugin: FFF" + run: | + cd plugins/fff + rake cd ../.. + + # Run Module Generator Plugin Tests + - name: "Run Tests on Ceedling Plugin: Module Generator" + run: | + cd plugins/module_generator + rake + cd ../.. + + # Run Dependencies Plugin Tests + - name: "Run Tests on Ceedling Plugin: Dependencies" + run: | + cd plugins/dependencies + rake + cd ../.. + + # Job: Automatic Minor Release + auto-release: + name: "Automatic Minor Release" + needs: + - unit-tests-linux + - unit-tests-windows + runs-on: ubuntu-latest + strategy: + matrix: + ruby: [3.2] + + steps: + # Checks out repository under $GITHUB_WORKSPACE + - name: Checkout Latest Repo + uses: actions/checkout@v4 + with: + submodules: recursive + + # Set Up Ruby Tools + - name: Set Up Ruby Tools + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby }} + + # Capture the SHA string + - name: Git commit short SHA as environment variable + shell: bash + run: | + echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_ENV + + - name: Ceedling tag as environment variable + shell: bash + run: | + echo "ceedling_tag=$(ruby ./lib/version.rb)" >> $GITHUB_ENV + + - name: Ceedling build string as environment variable + shell: bash + run: | + echo "ceedling_build=${{ env.ceedling_tag }}-${{ env.sha_short }}" >> $GITHUB_ENV + + # Create Git Commit SHA file in root of checkout + - name: Git Commit SHA file + shell: bash + run: | + echo "${{ env.sha_short }}" > ${{ github.workspace }}/GIT_COMMIT_SHA + + # Build Gem + - name: Build Gem + run: | + gem build ceedling.gemspec + + # Create Unofficial Release + - name: Create Pre-Release + uses: actions/create-release@v1 + id: create_release + with: + draft: false + prerelease: true + release_name: ${{ env.ceedling_build }} + tag_name: ${{ env.ceedling_build }} + body: "Automatic pre-release for ${{ env.ceedling_build }}" + env: + GITHUB_TOKEN: ${{ github.token }} + + # Post Gem to Unofficial Release + - name: Upload Pre-Release Gem + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ github.token }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./ceedling-${{ env.ceedling_tag }}.gem + asset_name: ceedling-${{ env.ceedling_build }}.gem + asset_content_type: test/x-gemfile + diff --git a/.gitignore b/.gitignore index 7224d1d97..b3cb00bbe 100644 --- a/.gitignore +++ b/.gitignore @@ -7,10 +7,13 @@ out.fail tags *.taghl -examples/blinky/build/ -examples/blinky/vendor/ examples/temp_sensor/vendor/ examples/temp_sensor/build/ +plugins/fff/examples/fff_example/build/ +plugins/module_generator/example/build/ +plugins/dependencies/example/boss/build/ +plugins/dependencies/example/boss/third_party/ +plugins/dependencies/example/supervisor/build/ ceedling.sublime-project ceedling.sublime-workspace @@ -19,3 +22,4 @@ ceedling-*.gem .ruby-version /.idea /.vscode +/GIT_COMMIT_SHA diff --git a/.gitmodules b/.gitmodules index 128aadf4d..e00535031 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/.rubocop.yml b/.rubocop.yml index 2d00ac807..59b714774 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,3 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + # This is the configuration used to check the rubocop source code. #inherit_from: .rubocop_todo.yml diff --git a/Gemfile b/Gemfile index a00f5e7f5..7a7d1d5c4 100644 --- a/Gemfile +++ b/Gemfile @@ -1,14 +1,26 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + source "http://rubygems.org/" -gem "bundler" -gem "rake" +gem "bundler", "~> 2.5" + +# Testing tools gem "rspec", "~> 3.8" -gem "require_all" -gem "constructor" -gem "diy" +gem "rake", ">= 12", "< 14" gem "rr" -gem "thor" -gem "deep_merge" +gem "require_all" + +# Ceedling dependencies +gem "diy", "~> 1.1" +gem "constructor", "~> 2" +gem "thor", "~> 1.3" +gem "deep_merge", "~> 1.2" +gem "unicode-display_width", "~> 3.1" #these will be used if present, but ignored otherwise #gem "curses" diff --git a/Gemfile.lock b/Gemfile.lock index 647a9fece..8133ca0c3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,41 +3,48 @@ GEM specs: constructor (2.0.0) deep_merge (1.2.2) - diff-lcs (1.3) + diff-lcs (1.5.1) diy (1.1.2) constructor (>= 1.0.0) - rake (13.0.6) - require_all (1.3.3) - rr (1.1.2) - rspec (3.8.0) - rspec-core (~> 3.8.0) - rspec-expectations (~> 3.8.0) - rspec-mocks (~> 3.8.0) - rspec-core (3.8.0) - rspec-support (~> 3.8.0) - rspec-expectations (3.8.2) + rake (13.2.1) + require_all (3.0.0) + rr (3.1.1) + rspec (3.13.0) + rspec-core (~> 3.13.0) + rspec-expectations (~> 3.13.0) + rspec-mocks (~> 3.13.0) + rspec-core (3.13.2) + rspec-support (~> 3.13.0) + rspec-expectations (3.13.3) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-mocks (3.8.0) + rspec-support (~> 3.13.0) + rspec-mocks (3.13.2) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.8.0) - rspec-support (3.8.0) - thor (0.20.3) + rspec-support (~> 3.13.0) + rspec-support (3.13.1) + thor (1.3.2) + unicode-display_width (3.1.2) + unicode-emoji (~> 4.0, >= 4.0.4) + unicode-emoji (4.0.4) PLATFORMS ruby + x64-mingw-ucrt x64-mingw32 + x86_64-darwin-22 + x86_64-linux DEPENDENCIES - bundler - constructor - deep_merge - diy - rake + bundler (~> 2.5) + constructor (~> 2) + deep_merge (~> 1.2) + diy (~> 1.1) + rake (>= 12, < 14) require_all rr rspec (~> 3.8) - thor + thor (~> 1.3) + unicode-display_width (~> 3.1) BUNDLED WITH - 2.4.3 + 2.5.23 diff --git a/README.md b/README.md index b74de9965..52fc92489 100644 --- a/README.md +++ b/README.md @@ -1,120 +1,741 @@ Ceedling ![CI](https://github.com/ThrowTheSwitch/Ceedling/workflows/CI/badge.svg) ======== -_October 26, 2024_ 🚚 **Ceedling 1.0.0** will be shipping very soon. -[Prerelease versions are available now](https://github.com/ThrowTheSwitch/Ceedling/releases). -See the [all new README](https://github.com/ThrowTheSwitch/Ceedling/blob/test/ceedling_0_32_rc/README.md) -for a reintroduction to Ceedling and links to a variety of complementary resources. See the -[Release Notes](https://github.com/ThrowTheSwitch/Ceedling/blob/test/ceedling_0_32_rc/docs/ReleaseNotes.md) -for an overview of all that’s new since 0.31.1 plus links to a detailed Changelog and list of +_November 28, 2024_ 🚚 **Ceedling 1.0.0** is a release candidate and will be +shipping very soon. See the [Release Notes](docs/ReleaseNotes.md) for an overview +of all that’s new since 0.31.1 plus links to the detailed Changelog and list of Breaking Changes. -Ceedling is a build system for C projects that is something of an extension -around Ruby’s Rake (make-ish) build system. Ceedling also makes TDD (Test-Driven Development) -in C a breeze by integrating [CMock](https://github.com/throwtheswitch/cmock), -[Unity](https://github.com/throwtheswitch/unity), and -[CException](https://github.com/throwtheswitch/cexception) -- -three other awesome open-source projects you can’t live without if you're creating awesomeness -in the C language. Ceedling is also extensible with a handy plugin mechanism. +# đŸŒ± Ceedling is a handy-dandy build system for C projects -Usage Documentation -=================== +## Developer-friendly release _and_ test builds -Documentation and license info exists [in the repo in docs/](docs/CeedlingPacket.md) +Ceedling can build your release artifact but is especially adept at building +unit test suites for your C projects — even in tricky embedded systems. -Getting Started -=============== +Ceedling and its complementary pieces and parts are and always will be freely +available and open source. **_[Ceedling Pro][ceedling-pro]_** is a growing list +of paid products and services to help you do even more with these tools. -First make sure Ruby is installed on your system (if it's not already). Then, from a command prompt: +[ceedling-pro]: https://thingamabyte.com/ceedlingpro - > gem install ceedling +⭐ **Eager to just get going? Jump to +[📚 Documentation & Learning](#-documentation--learning) and +[🚀 Getting Started](#-getting-started).** -(Alternate Installation for Those Planning to Be Ceedling Developers) -====================================================================== +Ceedling works the way developers want to work. It is flexible and entirely +command-line driven. It drives code generation and command line tools for you. +All generated and framework code is easy to see and understand. - > git clone --recursive https://github.com/throwtheswitch/ceedling.git - > cd ceedling - > bundle install # Ensures you have all RubyGems needed - > git submodule update --init --recursive # Updates all submodules - > bundle exec rake # Run all Ceedling library tests +Ceedling’s features support all types of C development from low-level embedded +to enterprise systems. No tool is perfect, but Ceedling can do a whole lot to +help you and your team produce quality software. -If bundler isn't installed on your system or you run into problems, you might have to install it: +## Ceedling is a suite of tools - > sudo gem install bundler +Ceedling is also a suite of tools. It is the glue for bringing together three +other awesome open-source projects you can’t live without if you‘re creating +awesomeness in the C language. -If you run into trouble running bundler and get messages like this `can't find gem -bundler (>= 0.a) with executable bundle (Gem::GemNotFoundException)`, you may -need to install a different version of bundler. For this please reference the -version in the Gemfile.lock. An example based on the current Gemfile.lock is as -followed: +1. **[Unity]**, an [xUnit]-style test framework. +1. **[CMock]**†, a code generating, + [function mocking & stubbing][test-doubles] kit for interaction-based testing. +1. **[CException]**, a framework for adding simple exception handling to C projects + in the style of higher-order programming languages. - > sudo gem install bundler -v 1.16.2 +† Through a [plugin][FFF-plugin], Ceedling also supports [FFF] for +[fake functions][test-doubles] as an alternative to CMock’s mocks and stubs. -Creating A Project -================== +## But, wait. There’s more. -Creating a project with Ceedling is easy. Simply tell ceedling the -name of the project, and it will create a subdirectory called that -name and fill it with a default directory structure and configuration. +For simple project structures, Ceedling can build and test an entire project +from just a few lines in its project configuration file. - ceedling new YourNewProjectName +Because it handles all the nitty-gritty of rebuilds and becuase of Unity and +CMock, Ceedling makes [Test-Driven Development][TDD] in C a breeze. It even +provides handy backtrace debugging options for finding the source of crashing +code exercised by your unit tests. -You can add files to your src and test directories and they will -instantly become part of your test build. Need a different structure? -You can start to tweak the `project.yml` file immediately with your new -path or tool requirements. +Ceedling is extensible with a simple plugin mechanism. It comes with a +number of [built-in plugins][ceedling-plugins] for code coverage, test suite +report generation, Continuous Integration features, IDE integration, release +library builds & dependency management, and more. -You can upgrade to the latest version of Ceedling at any time, -automatically gaining access to the packaged Unity and CMock that -come with it. +[Unity]: https://github.com/throwtheswitch/unity +[xUnit]: https://en.wikipedia.org/wiki/XUnit +[CMock]: https://github.com/throwtheswitch/cmock +[CException]: https://github.com/throwtheswitch/cexception +[TDD]: http://en.wikipedia.org/wiki/Test-driven_development +[test-doubles]: https://blog.pragmatists.com/test-doubles-fakes-mocks-and-stubs-1a7491dfa3da +[FFF]: https://github.com/meekrosoft/fff +[FFF-plugin]: plugins/fff +[ceedling-plugins]: docs/CeedlingPacket.md#ceedling-plugins - gem update ceedling +
-Documentation -============= +# đŸ™‹â€â™€ïž Need Help? Want to Help? -Are you just getting started with Ceedling? Maybe you'd like your -project to be installed with some of its handy documentation? No problem! -You can do this when you create a new project. +* Found a bug or want to suggest a feature? + **[Submit an issue][ceedling-issues]** at this repo. +* Trying to understand features or solve a testing problem? Hit the + **[discussion forums][forums]**. +* Paid training, customizations, and support contracts are available through + **[Ceedling Pro][ceedling-pro]**. - ceedling new --docs MyAwesomeProject +The ThrowTheSwitch community follows a **[code of conduct](docs/CODE_OF_CONDUCT.md)**. -Bonding Your Tools And Project -============================== +Please familiarize yourself with our guidelines for **[contributing](docs/CONTRIBUTING.md)** to this project, be it code, reviews, documentation, or reports. -Ceedling can deploy all of its guts into the project as well. This -allows it to be used without having to worry about external dependencies. -You don't have to worry about Ceedling changing for this particular -project just because you updated your gems... no need to worry about -changes in Unity or CMock breaking your build in the future. If you'd like -to use Ceedling this way, tell it you want a local copy when you create +Yes, work has begun on certified versions of the Ceedling suite of tools to be available through **[Ceedling Pro][ceedling-pro]**. [Reach out to ThingamaByte][thingama-contact] for more. + +[ceedling-issues]: https://github.com/ThrowTheSwitch/Ceedling/issues +[forums]: https://www.throwtheswitch.org/forums +[thingama-contact]: https://www.thingamabyte.com/contact + +
+ +# 🧑‍🍳 Sample Unit Testing Code + +While Ceedling can build your release artifact, its claim to fame is building and running test suites. + +There’s a good chance you’re looking at Ceedling because of its test suite abilities. And, you’d probably like to see what that looks like, huh? Well, let’s cook you up some realistic examples of tested code and running Ceedling with that code. + +(A sample Ceedling project configuration file and links to documentation for it are a bit further down in _[🚀 Getting Started](#-getting-started)_.) + +## First, we start with servings of source code to be tested
 + +### Recipe.c + +```c +#include "Recipe.h" +#include "Kitchen.h" +#include + +#define MAX_SPICE_COUNT (4) +#define MAX_SPICE_AMOUNT_TSP (8.0f) + +static float spice_amount = 0; +static uint8_t spice_count = 0; + +void Recipe_Reset(char* recipe, size_t size) { + memset(recipe, 0, size); + spice_amount = 0; + spice_count = 0; +} + +// Add ingredients to a spice list string with amounts (tsp.) +bool_t Recipe_BuildSpiceListTsp(char* list, size_t maxLen, SpiceId spice, float amount) { + if ((++spice_count > MAX_SPICE_COUNT) || ((spice_amount += amount) > MAX_SPICE_AMOUNT_TSP)) { + snprintf( list, maxLen, "Too spicy!" ); + return FALSE; + } + + // Kitchen_Ingredient() not shown + snprintf( list + strlen(list), maxLen, "%s\n", Kitchen_Ingredient( spice, amount, TEASPOON ) ); + return TRUE; +} +``` + +### Baking.c + +```c +#include "Oven.h" +#include "Time.h" +#include "Baking.h" + +bool_t Baking_PreheatOven(float setTempF, duration_t timeout) { + float temperature = 0.0; + Timer* timer = Time_StartTimer( timeout ); + + Oven_SetTemperatureF( setTempF ); + + while (temperature < setTempF) { + Time_SleepMs( 250 ); + if (Time_IsTimerExpired( timer )) break; + temperature = Oven_GetTemperatureReadingF(); + } + + return (temperature >= setTempF); +} + +``` + +## Next, a sprinkle of unit test code
 + +Some of what Ceedling does is by naming conventions. See Ceedling’s [documentation](#-documentation--learning) for much more on this. + +### TestRecipe.c + +```c +#include "unity.h" // Unity, unit test framework +#include "Recipe.h" // By convention, Recipe.c is part of TestRecipe executable build +#include "Kitchen.h" // By convention, Kitchen.c (not shown) is part of TestRecipe executable build + +char recipe[100]; + +void setUp(void) { + // Execute reset before each test case + Recipe_Reset( recipe, sizeof(recipe) ); +} + +void test_Recipe_BuildSpiceListTsp_shouldBuildSpiceList(void) { + TEST_ASSERT_TRUE( Recipe_BuildSpiceListTsp( recipe, sizeof(recipe), OREGANO, 0.5 ) ); + TEST_ASSERT_TRUE( Recipe_BuildSpiceListTsp( recipe, sizeof(recipe), ROSEMARY, 1.0 ) ); + TEST_ASSERT_TRUE( Recipe_BuildSpiceListTsp( recipe, sizeof(recipe), THYME, 0.33 ) ); + TEST_ASSERT_EQUAL_STRING( "1/2 tsp. Oregano\n1 tsp. Rosemary\n1/3 tsp. Thyme\n", recipe ); +} + +void test_Recipe_BuildSpiceListTsp_shouldFailIfTooMuchSpice(void) { + TEST_ASSERT_TRUE ( Recipe_BuildSpiceListTsp( recipe, sizeof(recipe), CORIANDER, 4.0 ) ); + TEST_ASSERT_TRUE ( Recipe_BuildSpiceListTsp( recipe, sizeof(recipe), BLACK_PEPPER, 4.0 ) ); + // Total spice = 8.0 + 0.1 tsp. + TEST_ASSERT_FALSE( Recipe_BuildSpiceListTsp( recipe, sizeof(recipe), BASIL, 0.1 ) ); + TEST_ASSERT_EQUAL_STRING( "Too spicy!", recipe ); +} + +void test_Recipe_BuildSpiceListTsp_shouldFailIfTooManySpices(void) { + TEST_ASSERT_TRUE ( Recipe_BuildSpiceListTsp( recipe, sizeof(recipe), OREGANO, 1.0 ) ); + TEST_ASSERT_TRUE ( Recipe_BuildSpiceListTsp( recipe, sizeof(recipe), CORIANDER, 1.0 ) ); + TEST_ASSERT_TRUE ( Recipe_BuildSpiceListTsp( recipe, sizeof(recipe), BLACK_PEPPER, 1.0 ) ); + TEST_ASSERT_TRUE ( Recipe_BuildSpiceListTsp( recipe, sizeof(recipe), THYME, 1.0 ) ); + // Attempt to add 5th spice + TEST_ASSERT_FALSE( Recipe_BuildSpiceListTsp( recipe, sizeof(recipe), BASIL, 1.0 ) ); + TEST_ASSERT_EQUAL_STRING( "Too spicy!", recipe ); +} +``` + +### TestBaking.c + +Let’s flavor our test code with a dash of mocks as well
 + +```c +#include "unity.h" // Unity, unit test framework +#include "Baking.h" // By convention, Baking.c is part of TestBaking executable build +#include "MockOven.h" // By convention, mock .h/.c code generated from Oven.h by CMock +#include "MockTime.h" // By convention, mock .h/.c code generated from Time.h by CMock + +/* + * đŸš« This test will fail! Find the missing logic in `Baking_PreheatOven()`. + * (`Oven_SetTemperatureF()` returns success / failure.) + */ +void test_Baking_PreheatOven_shouldFailIfSettingOvenTemperatureFails(void) { + Timer timer; // Uninitialized struct + + Time_StartTimer_ExpectAndReturn( TWENTY_MIN, &timer ); + + // Tell source code that setting the oven temperature did not work + Oven_SetTemperatureF_ExpectAndReturn( 350.0, FALSE ); + + TEST_ASSERT_FALSE( Baking_PreheatOven( 350.0, TWENTY_MIN ) ); +} + +void test_Baking_PreheatOven_shouldFailIfTimeoutExpires(void) { + Timer timer; // Uninitialized struct + + Time_StartTimer_ExpectAndReturn( TEN_MIN, &timer ); + + Oven_SetTemperatureF_ExpectAndReturn( 200.0, TRUE ); + + // We only care that `sleep()` is called, not necessarily every call to it + Time_SleepMs_Ignore(); + + // Unrolled loop of timeout and temperature checks + Time_IsTimerExpired_ExpectAndReturn( &timer, FALSE ); + Oven_GetTemperatureReadingF_ExpectAndReturn( 100.0 ); + Time_IsTimerExpired_ExpectAndReturn( &timer, FALSE ); + Oven_GetTemperatureReadingF_ExpectAndReturn( 105.0 ); + Time_IsTimerExpired_ExpectAndReturn( &timer, FALSE ); + Oven_GetTemperatureReadingF_ExpectAndReturn( 110.0 ); + Time_IsTimerExpired_ExpectAndReturn( &timer, TRUE ); + + TEST_ASSERT_FALSE( Baking_PreheatOven( 200.0, TEN_MIN ) ); +} + +void test_Baking_PreheatOven_shouldSucceedAfterAWhile(void) { + Timer timer; // Uninitialized struct + + Time_StartTimer_ExpectAndReturn( TEN_MIN, &timer ); + + Oven_SetTemperatureF_ExpectAndReturn( 400.0, TRUE ); + + // We only care that `sleep()` is called, not necessarily every call to it + Time_SleepMs_Ignore(); + + // Unrolled loop of timeout and temperature checks + Time_IsTimerExpired_ExpectAndReturn( &timer, FALSE ); + Oven_GetTemperatureReadingF_ExpectAndReturn( 390.0 ); + Time_IsTimerExpired_ExpectAndReturn( &timer, FALSE ); + Oven_GetTemperatureReadingF_ExpectAndReturn( 395.0 ); + Time_IsTimerExpired_ExpectAndReturn( &timer, FALSE ); + Oven_GetTemperatureReadingF_ExpectAndReturn( 399.0 ); + Time_IsTimerExpired_ExpectAndReturn( &timer, FALSE ); + Oven_GetTemperatureReadingF_ExpectAndReturn( 401.0 ); + + TEST_ASSERT_TRUE( Baking_PreheatOven( 400.0, TEN_MIN ) ); +} +``` + +## Add a pinch of command line
 + +See Ceedling’s [documentation](#-documentation--learning) for examples and everything you need to know about Ceedling’s configuration file options (not shown here). + +The super duper short version is that your project configuration file tells Ceedling where to find test and source files, what testing options you’re using, sets compilation symbols and build tool flags, enables your plugins, and configures your build tool command lines (Ceedling defaults to using the GNU compiler collection — which must be installed, if used). + +```shell + > ceedling test:all +``` + +## VoilĂ ! Test results. `#ChefsKiss` + +The test results below are one of the last bits of logging Ceedling produces for a test suite build. Not shown here are all the steps for extracting build details, C code generation, and compilation and linking. + +``` +------------------- +FAILED TEST SUMMARY +------------------- +[test/TestBaking.c] + Test: test_Baking_PreheatOven_shouldFailIfSettingOvenTemperatureFails + At line (7): "Function Time_SleepMs() called more times than expected." + +----------------------- +❌ OVERALL TEST SUMMARY +----------------------- +TESTED: 6 +PASSED: 5 +FAILED: 1 +IGNORED: 0 +``` + +## Ceedling also supports various side dishes in your delicious test suite + +The Unity project supports parameterized test cases like this: + +```C +TEST_RANGE([5, 100, 5]) +void test_should_handle_divisible_by_5_for_parameterized_test_range(int num) { + TEST_ASSERT_EQUAL(0, (num % 5)); +} +``` + +Ceedling can do all the magic to build and run this test code simply by enabling parameterized test cases in its project configuration. Keep reading for more on how to configure a Ceedling build. + +```yaml +:unity: + :use_param_tests: TRUE +``` + +
+ +# 📚 Documentation & Learning + +A variety of options for [community-based support][TTS-help] exist. + +Training and support contracts are available through **_[Ceedling Pro][ceedling-pro]_** + +[TTS-help]: https://www.throwtheswitch.org/#help-section + +The [Agile Embedded Podcast][ae-podcast] includes an [episode on Ceedling][ceedling-episode]! + +[ae-podcast]: https://agileembeddedpodcast.com/ +[ceedling-episode]: https://agileembeddedpodcast.com/episodes/ceedling + +## Ceedling docs + +* **_[Ceedling Packet][ceedling-packet]_** is Ceedling’s user manual. It also references and links to the documentation of the projects, _Unity_, _CMock_, and _CException_, that it weaves together into your test and release builds. +* **[Release Notes][release-notes]**, **[Breaking Changes][breaking-changes]**, and **[Changelog][changelog]** can be found in the **[docs/](docs/)** directory along with a variety of guides and much more. +* The **[Plugins section](https://github.com/ThrowTheSwitch/Ceedling/blob/test/ceedling_0_32_rc/docs/CeedlingPacket.md#ceedling-plugins)** within _Ceedling Packet_ lists all of Ceedling’s built-in plugins providing overviews and links to their documentation. + +## Library and courses + +[ThrowTheSwitch.org][TTS]: + +* Provides a small but useful **[library of resources and guides][library]** on testing and using the Ceedling suite of tools. +* Discusses your **[options for running a test suite][running-options]**, particularly in the context of embedded systems. +* Links to paid courses, **_[Dr. Surly’s School for Mad Scientists][courses]_**, that provide in-depth training on creating C unit tests and using Unity, CMock, and Ceedling to do so. + +## Online tutorial + +Matt Chernosky’s **[detailed tutorial][tutorial]** demonstrates using Ceedling to build a C project with test suite. As the tutorial is a number of years old, the content is a bit out of date. That said, it provides an excellent overview of a real project. Matt is the author of [FFF] and the [FFF plugin][FFF-plugin] for Ceedling. + +[ceedling-packet]: docs/CeedlingPacket.md +[release-notes]: docs/ReleaseNotes.md +[breaking-changes]: docs/BreakingChanges.md +[changelog]: docs/Changelog.md +[TTS]: https://throwtheswitch.org +[library]: http://www.throwtheswitch.org/library +[running-options]: http://www.throwtheswitch.org/build/which +[courses]: http://www.throwtheswitch.org/dr-surlys-school +[tutorial]: http://www.electronvector.com/blog/add-unit-tests-to-your-current-project-with-ceedling + +
+ +# 🚀 Getting Started + +👀 See the **_[Quick Start](docs/CeedlingPacket.md#quick-start)_** section in Ceedling’s user manual, _Ceedling Packet_. + +## The basics + +### Local installation + +1. Install [Ruby]. (Only Ruby 3+ supported.) +1. Install Ceedling. All supporting frameworks are included. + ```shell + > gem install ceedling + ``` +1. Begin crafting your project: + 1. Create an empty Ceedling project. + ```shell + > ceedling new [] + ``` + 1. Or, add a Ceedling project file to the root of an existing code project. +1. Run tasks like so: + ```shell + > ceedling test:all release + ``` + +[Ruby]: https://www.ruby-lang.org/ + +### _MadScienceLab_ Docker Images + +As an alternative to local installation, fully packaged Docker images containing Ruby, Ceedling, the GCC toolchain, and more are also available. [Docker][docker-overview] is a virtualization technology that provides self-contained software bundles that are a portable, well-managed alternative to local installation of tools like Ceedling. + +Four Docker image variants containing Ceedling and supporting tools exist. These four images are available for both Intel and ARM host platforms (Docker does the right thing based on your host environment). The latter includes ARM Linux and Apple’s M-series macOS devices. + +1. **_[MadScienceLab][docker-image-base]_**. This image contains Ruby, Ceedling, CMock, Unity, CException, the GNU Compiler Collection (gcc), and a handful of essential C libraries and command line utilities. +1. **_[MadScienceLab Plugins][docker-image-plugins]_**. This image contains all of the above plus the command line tools that Ceedling’s built-in plugins rely on. Naturally, it is quite a bit larger than option (1) because of the additional tools and dependencies. +1. **_[MadScienceLab ARM][docker-image-arm]_**. This image mirrors (1) with the compiler toolchain replaced with the GNU `arm-none-eabi` variant. +1. **_[MadScienceLab ARM + Plugins][docker-image-arm-plugins]_**. This image is (3) with the addition of all the complementary plugin tooling just like (2) provides. + +See the Docker Hub pages linked above for more documentation on these images. + +Just to be clear here, most users of the _MadScienceLab_ Docker images will probably care about the ability to run unit tests on your own host. If you are one of those users, no matter what host platform you are on — Intel or ARM — you’ll want to go with (1) or (2) above. The tools within the image will automatically do the right thing within your environment. Options (3) and (4) are most useful for specialized cross-compilation scenarios. + +#### _MadScienceLab_ Docker Image usage basics + +To use a _MadScienceLab_ image from your local terminal: + +1. [Install Docker][docker-install] +1. Determine: + 1. The local path of your Ceedling project + 1. The variant and revision of the Docker image you’ll be using +1. Run the container with: + 1. The Docker `run` command and `-it --rm` command line options + 1. A Docker volume mapping from the root of your project to the default project path inside the container (_/home/dev/project_) + +See the command line examples in the following two sections. + +Note that all of these somewhat lengthy command lines lend themselves well to being wrapped up in simple helper scripts specific to your project and directory structure. + +#### Run a _MadScienceLab_ Docker Image as an interactive terminal + +When the container launches as shown below, it will drop you into a Z-shell command line that has access to all the tools and utilities available within the container. In this usage, the Docker container becomes just another terminal, including ending its execution with `exit`. + +```shell + > docker run -it --rm -v /my/local/project/path:/home/dev/project throwtheswitch/madsciencelab-plugins:1.0.0 +``` + +Once the _MadScienceLab_ container’s command line is available, to run Ceedling, execute it just as you would after installing Ceedling locally: + +```shell + ~/project > ceedling help +``` + +```shell + ~/project > ceedling new ... +``` + +```shell + ~/project > ceedling test:all +``` + +#### Run a _MadScienceLab_ Docker Image as a command line utility + +Alternatively, you can run Ceedling through the _MadScienceLab_ Docker container directly from the command line as a command line utility. The general pattern is immediately below. + +```shell + > docker run -it --rm -v /my/local/project/path:/home/dev/project throwtheswitch/madsciencelab-plugins:1.0.0 +``` + +As a specific example, to run all tests in a suite, the command line would be this: + +```shell + > docker run -it --rm -v /my/local/project/path:/home/dev/project throwtheswitch/madsciencelab-plugins:1.0.0 ceedling test:all +``` + +In this usage, the container starts, executes Ceedling, and then ends. + +[docker-overview]: https://www.ibm.com/topics/docker +[docker-install]: https://www.docker.com/products/docker-desktop/ + +[docker-image-base]: https://hub.docker.com/repository/docker/throwtheswitch/madsciencelab +[docker-image-plugins]: https://hub.docker.com/repository/docker/throwtheswitch/madsciencelab-plugins +[docker-image-arm]: https://hub.docker.com/repository/docker/throwtheswitch/madsciencelab-arm-none-eabi +[docker-image-arm-plugins]: https://hub.docker.com/repository/docker/throwtheswitch/madsciencelab-arm-none-eabi-plugins + +### Example super-duper simple Ceedling configuration file + +```yaml +:project: + :build_root: project/build/ + :release_build: TRUE + +:paths: + :test: + - tests/** + :source: + - source/** + :include: + - inc/** +``` + +See this [commented project configuration file][example-config-file] for a much more complete and sophisticated example of a project configuration. + +Or, use Ceedling’s built-in `examples` & `example` commands to extract a sample project and reference its project file. + +See the [configuration section][ceedling-packet-config] in _Ceedling Packet_ for way more details on your project configuration options than we can provide here. + +[example-config-file]: assets/project_as_gem.yml +[ceedling-packet-config]: docs/CeedlingPacket.md#the-almighty-ceedling-project-configuration-file-in-glorious-yaml + +## Using Ceedling’s command line (and related) + +### Command line help + +For an overview of all commands, it’s as easy as
 + +```sh + > ceedling help +``` + +For a detailed explanation of a single command
 + +```sh + > ceedling help +``` + +### Creating a project + +Creating a project with Ceedling is easy. Simply tell Ceedling the name of the +project, and it will create a directory with that name and fill it with a +default subdirectory structure and configuration file. An optional destination +path is also possible. + +```shell + > ceedling new YourNewProjectName +``` + +You can add files to your `src/` and `test/` directories, and they will +instantly become part of your test and/or release build. Need a different +structure? You can modify the `project.yml` file with your new path or +tooling setup. + +#### Installing local documentation + +Are you just getting started with Ceedling? Maybe you’d like your +project to be installed with some of its handy [documentation](docs/)? +No problem! You can do this when you create a new project
 + +```shell + > ceedling new --docs MyAwesomeProject +``` + +#### Attaching a Ceedling version to your project + +Ceedling can be installed as a globally available Ruby gem. Ceedling can +also deploy all its guts into your project instead. This allows it to +be used without worrying about external dependencies. More importantly, +you don’t have to worry about Ceedling changing outside of your project +just because you updated your gems. No need to worry about changes in +Unity or CMock breaking your build in the future. + +To use Ceedling this way, tell it you want a local copy when you create your project: - ceedling new --local YourNewProjectName +```shell + > ceedling new --local YourNewProjectName +``` + +This will install all of Unity, CMock, CException, and Ceedling itself +into a new folder `vendor/` inside your project `YourNewProjectName/`. +It will create the same simple empty directory structure for you with +`src/` and `test/` folders as the standard `new` command. + +### Running build & plugin tasks + +You can view all the build and plugin tasks available to you thanks to your +Ceedling project file with `ceedling help`. Ceedling’s command line help +provides a summary list from your project configuration if Ceedling is +able to find your project file (`ceedling help help` for more on this). + +Running Ceedling build tasks tends to look like this
 + +```shell + > ceedling test:all release +``` + +```shell + > ceedling gcov:all --verbosity=obnoxious --test-case=boot --mixin=code_cruncher_toolchain +``` + +### Upgrading / updating Ceedling + +You can upgrade to the latest version of Ceedling at any time, automatically +gaining access to any accompanying updates to Unity, CMock, and CException. + +To update a locally installed gem
 + +```shell + > gem update ceedling +``` + +Otherwise, if you are using the Docker image, you may upgrade by pulling +a newer version of the image
 + +```shell + > docker pull throwtheswitch/madsciencelab: +``` + +If you want to force a vendored version of Ceedling inside your project to +upgrade to match your latest gem, no problem. Just do the following
 + +```shell + > ceedling upgrade --local YourNewProjectName +``` + +Just like with the `new` command, an `upgrade` should be executed from +within the root directory of your project. + +### Git integration + +Are you using Git? You might want Ceedling to create a `.gitignore` +that ignores the build folder while retaining control of the artifacts +folder. This will also add a `.gitkeep` file to your `test/support` folder. +You can enable this by adding `--gitsupport` to your `new` call. + +```shell + > ceedling new --gitsupport YourNewProjectName +``` +
+ +# đŸ’» Contributing to Ceedling Development + +## Alternate installation options for Ceedling development + +### Alternate local installation for development + +After installing Ruby
 + +```shell + > git clone --recursive https://github.com/throwtheswitch/ceedling.git + > cd ceedling + > git submodule update --init --recursive + > bundle install +``` + +The Ceedling repository incorporates its supporting frameworks and some +plugins via Git submodules. A simple clone may not pull in the latest +and greatest. + +The `bundle` tool ensures you have all needed Ruby gems installed. If +Bundler isn’t installed on your system or you run into problems, you +might have to install it: + +```shell + > sudo gem install bundler +``` + +If you run into trouble running bundler and get messages like _can’t +find gem bundler (>= 0.a) with executable bundle +(Gem::GemNotFoundException)_, you may need to install a different +version of Bundler. For this please reference the version in the +Gemfile.lock. + +```shell + > sudo gem install bundler -v +``` + +### Alternate Docker image usage for development + +As an alternative to local installation of Ceedling, nearly all development +tasks can be accomplished with the _MadScienceLab_ Docker images. + +When running an existing image as a development container, one merely needs +to map a volume from your local Ceedling code repository to Ceedling’s +installation location within the container. With that accomplished, +experimenting with project builds and running self-tests is simple. + +1. Start your target Docker container from your host system terminal: + + ```shell + > docker run -it --rm throwtheswitch/: + ``` +1. Look up and note Ceedling’s installation path (listed in `version` output) from within the container command line: + + ```shell + ~/project > ceedling version + + + ``` +1. Exit the container. +1. Restart the container from your host system with the Ceedling installation + volume mapping from (2) and any other command line options you need: + + ```shell + > docker run -it --rm -v /my/local/ceedling/repo: -v /my/local/experiment/path:/home/dev/project throwtheswitch/: + ``` + +For development tasks, from the container shell you can: + +1. Run experiment projects you map into the container (e.g. at _/home/dev/project_). +1. Run the self-test suite. Navigate to the gem installation path discovered in (2) above. From this location, follow the instructions in the section that immediately follows. + +## Running Ceedling’s self-tests + +Ceedling uses [RSpec] for its tests. + +To execute tests you may run the following from the root of your local +Ceedling repository. This test suite build option balances test coverage +with suite execution time. + +```shell + > rake spec +``` + +To run individual test files (Ceedling’s Ruby-based tests, that is) and +perform other tasks, use the available Rake tasks. From the root of your +local Ceedling repo, list those task like this: + +```shell + > rake -T +``` -This will install all of Unity, CMock, and Ceedling into a new folder -named `vendor` inside your project `YourNewProjectName`. It will still create -the simple directory structure for you with `src` and `test` folders. +[RSpec]: https://rspec.info -SCORE! +## Working in `bin/` vs. `lib/` -If you want to force a locally installed version of Ceedling to upgrade -to match your latest gem later, it's easy! Just issue the following command: +Most of Ceedling’s functionality is contained in the application code residing +in `lib/`. Ceedling’s command line handling, startup configuration, project +file loading, and mixin handling are contained in a “bootloader” in `bin/`. +The code in `bin/` is the source of the `ceedling` command line tool and +launches the application from `lib/`. - ceedling upgrade --local YourNewProjectName +Depending on what you’re working on you may need to run Ceedling using +a specialized approach. -Just like the `new` command, it's called from the parent directory of your -project. +If you are only working in `lib/`, you can: -Are you afraid of losing all your local changes when this happens? You can keep -Ceedling from updating your project file by issuing `no_configs`. +1. Run Ceedling using the `ceedling` command line utility you already have + installed. The code in `bin/` will run from your locally installed gem or + from within your Docker container and launch the Ceedling application for + you. +1. Modify a project file by setting a path value for `:project` ↳ `:which_ceedling` + that points to the local copy of Ceedling you cloned from the Git repository. + See _CeedlingPacket_ for details. - ceedling upgrade --local --no_configs TheProject +If you are working in `bin/`, running `ceedling` at the command line will not +call your modified code. Instead, you must execute the path to the executable +`ceedling` in the `bin/` folder of the local Ceedling repository you are +working on. -Git Integration -=============== -Are you using Git? You might want to automatically have Ceedling create a -`gitignore` file for you by adding `--gitignore` to your `new` call. -*HAPPY TESTING!* diff --git a/Rakefile b/Rakefile index bcdcdf2a9..6dfe1dc83 100644 --- a/Rakefile +++ b/Rakefile @@ -1,4 +1,11 @@ #!/usr/bin/env rake +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'bundler' require 'rspec/core/rake_task' @@ -7,5 +14,13 @@ RSpec::Core::RakeTask.new(:spec) do |t| t.pattern = 'spec/**/*_spec.rb' end +Dir['spec/**/*_spec.rb'].each do |p| + base = File.basename(p,'.*').gsub('_spec','') + desc "rspec #{base}" + RSpec::Core::RakeTask.new("spec:#{base}") do |t| + t.pattern = p + end +end + task :default => [:spec] task :ci => [:spec] diff --git a/assets/auto_link_deep_dependencies/src/a.c b/assets/auto_link_deep_dependencies/src/a.c deleted file mode 100644 index 78b18e8ff..000000000 --- a/assets/auto_link_deep_dependencies/src/a.c +++ /dev/null @@ -1,7 +0,0 @@ -#include "a.h" -#include "b.h" - -int function_from_a(int a) -{ - return 2 * function_from_b(a); -} diff --git a/assets/auto_link_deep_dependencies/src/b.c b/assets/auto_link_deep_dependencies/src/b.c deleted file mode 100644 index f40980081..000000000 --- a/assets/auto_link_deep_dependencies/src/b.c +++ /dev/null @@ -1,7 +0,0 @@ -#include "b.h" -#include "c.h" - -int function_from_b(int b) -{ - return 2 * function_from_c(b); -} diff --git a/assets/auto_link_deep_dependencies/src/c.c b/assets/auto_link_deep_dependencies/src/c.c deleted file mode 100644 index 810aed80d..000000000 --- a/assets/auto_link_deep_dependencies/src/c.c +++ /dev/null @@ -1,8 +0,0 @@ -#include "c.h" -#include "never_compiled.h" - -int function_from_c(int c) -{ - function_never_compiled(2); - return 2 * c; -} diff --git a/assets/auto_link_deep_dependencies/src/internal_inc/a.h b/assets/auto_link_deep_dependencies/src/internal_inc/a.h deleted file mode 100644 index 8cde06168..000000000 --- a/assets/auto_link_deep_dependencies/src/internal_inc/a.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef A_H -#define A_H - -int function_from_a(int a); - -#endif /* A_H */ \ No newline at end of file diff --git a/assets/auto_link_deep_dependencies/src/internal_inc/b.h b/assets/auto_link_deep_dependencies/src/internal_inc/b.h deleted file mode 100644 index 50035e04d..000000000 --- a/assets/auto_link_deep_dependencies/src/internal_inc/b.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef B_H -#define B_H - -int function_from_b(int b); - -#endif /* B_H */ \ No newline at end of file diff --git a/assets/auto_link_deep_dependencies/src/internal_inc/c.h b/assets/auto_link_deep_dependencies/src/internal_inc/c.h deleted file mode 100644 index e481ee9bf..000000000 --- a/assets/auto_link_deep_dependencies/src/internal_inc/c.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef C_H -#define C_H - -int function_from_c(int c); - -#endif /* C_H */ \ No newline at end of file diff --git a/assets/auto_link_deep_dependencies/src/internal_inc/never_compiled.h b/assets/auto_link_deep_dependencies/src/internal_inc/never_compiled.h deleted file mode 100644 index 85ab1e889..000000000 --- a/assets/auto_link_deep_dependencies/src/internal_inc/never_compiled.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef NEVER_COMPILED_H -#define NEVER_COMPILED_H - -int function_never_compiled(int x); - -#endif /* NEVER_COMPILED_H */ \ No newline at end of file diff --git a/assets/auto_link_deep_dependencies/src/never_compiled.c b/assets/auto_link_deep_dependencies/src/never_compiled.c deleted file mode 100644 index 2e282bca0..000000000 --- a/assets/auto_link_deep_dependencies/src/never_compiled.c +++ /dev/null @@ -1,6 +0,0 @@ -#include "never_compiled.h" - -int function_never_compiled(int x) -{ - never runed function -} \ No newline at end of file diff --git a/assets/auto_link_deep_dependencies/test/test_a.c b/assets/auto_link_deep_dependencies/test/test_a.c deleted file mode 100644 index 2fc6afbeb..000000000 --- a/assets/auto_link_deep_dependencies/test/test_a.c +++ /dev/null @@ -1,11 +0,0 @@ -#include "unity.h" -#include "a.h" -#include "mock_never_compiled.h" - -void setUp(void) {} -void tearDown(void) {} - -void test_function_from_a_should_return_16(void) { - function_never_compiled_ExpectAndReturn(2, 2); - TEST_ASSERT_EQUAL(16, function_from_a(2)); -} diff --git a/assets/auto_link_deep_dependencies/test/test_b.c b/assets/auto_link_deep_dependencies/test/test_b.c deleted file mode 100644 index 82a36555a..000000000 --- a/assets/auto_link_deep_dependencies/test/test_b.c +++ /dev/null @@ -1,11 +0,0 @@ -#include "unity.h" -#include "b.h" -#include "mock_never_compiled.h" - -void setUp(void) {} -void tearDown(void) {} - -void test_function_from_b_should_return_8(void) { - function_never_compiled_ExpectAndReturn(2, 2); - TEST_ASSERT_EQUAL(8, function_from_b(2)); -} diff --git a/assets/auto_link_deep_dependencies/test/test_c.c b/assets/auto_link_deep_dependencies/test/test_c.c deleted file mode 100644 index ac8b2d651..000000000 --- a/assets/auto_link_deep_dependencies/test/test_c.c +++ /dev/null @@ -1,11 +0,0 @@ -#include "unity.h" -#include "c.h" -#include "mock_never_compiled.h" - -void setUp(void) {} -void tearDown(void) {} - -void test_function_from_c_should_return_4(void) { - function_never_compiled_ExpectAndReturn(2, 2); - TEST_ASSERT_EQUAL(4, function_from_c(2)); -} diff --git a/assets/ceedling b/assets/ceedling index 539308570..40cea2cd3 100755 --- a/assets/ceedling +++ b/assets/ceedling @@ -1,3 +1,9 @@ #!/bin/bash +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= ruby vendor/ceedling/bin/ceedling $* diff --git a/assets/default_gitignore b/assets/default_gitignore index a9ebca2cf..492412d9f 100644 --- a/assets/default_gitignore +++ b/assets/default_gitignore @@ -1,5 +1,7 @@ -build/artifacts -build/gcov -build/logs -build/temp -build/test +# Ignore the build/ directory in the root of the project. +# Generally speaking, best practice is to omit generated files from revision control. +/build/ + +# But reserve the artifacts/ subdirectory for revision control. +# Ceedling's notion of artifacts includes reports or release binaries you *may* want to revision. +!/build/artifacts/ diff --git a/assets/example_file.c b/assets/example_file.c index a50ae4bbe..631d5fefa 100644 --- a/assets/example_file.c +++ b/assets/example_file.c @@ -1,5 +1,16 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "example_file.h" int add_numbers(int a, int b) { return a + b; } + +int difference_between_numbers(int a, int b) { + return a - b; +} diff --git a/assets/example_file.h b/assets/example_file.h index dab6ee8bf..2e52d9827 100644 --- a/assets/example_file.h +++ b/assets/example_file.h @@ -1,6 +1,15 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef EXAMPLE_FILE_H #define EXAMPLE_FILE_H int add_numbers(int a, int b); +int difference_between_numbers(int a, int b); + #endif /* EXAMPLE_FILE_H */ diff --git a/assets/example_file_call.c b/assets/example_file_call.c index 9c3583798..00da661b8 100644 --- a/assets/example_file_call.c +++ b/assets/example_file_call.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "example_file_call.h" #include "example_file.h" diff --git a/assets/example_file_call.h b/assets/example_file_call.h index a56815131..b1781f2e2 100644 --- a/assets/example_file_call.h +++ b/assets/example_file_call.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef EXAMPLE_FILE_CALL_H #define EXAMPLE_FILE_CALL_H diff --git a/assets/project_as_gem.yml b/assets/project_as_gem.yml index 0442db2f0..98b4d47cb 100644 --- a/assets/project_as_gem.yml +++ b/assets/project_as_gem.yml @@ -1,84 +1,216 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + --- +:project: + # how to use ceedling. If you're not sure, leave this as `gem` and `?` + :which_ceedling: gem + :ceedling_version: '?' -# 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. + # optional features. If you don't need them, keep them turned off for performance + :use_mocks: TRUE + :use_test_preprocessor: :none + :use_backtrace: :simple + :use_decorators: :auto #Decorate Ceedling's output text. Your options are :auto, :all, or :none -:project: - :use_exceptions: FALSE - :use_test_preprocessor: TRUE - :use_auxiliary_dependencies: TRUE + # tweak the way ceedling handles automatic tasks :build_root: build -# :release_build: TRUE :test_file_prefix: test_ - :which_ceedling: gem - :ceedling_version: '?' :default_tasks: - test:all -#:test_build: -# :use_assembly: TRUE + # performance options. If your tools start giving mysterious errors, consider + # dropping this to 1 to force single-tasking + :test_threads: 8 + :compile_threads: 8 + + # enable release build (more details in release_build section below) + :release_build: FALSE + +# Specify where to find mixins and any that should be enabled automatically +:mixins: + :enabled: [] + :load_paths: [] + +# further details to configure the way Ceedling handles test code +:test_build: + :use_assembly: FALSE + +# further details to configure the way Ceedling handles release code +:release_build: + :output: MyApp.out + :use_assembly: FALSE + :artifacts: [] + +# Plugins are optional Ceedling features which can be enabled. Ceedling supports +# a variety of plugins which may effect the way things are compiled, reported, +# or may provide new command options. Refer to the readme in each plugin for +# details on how to use it. +:plugins: + :load_paths: [] + :enabled: + #- beep # beeps when finished, so you don't waste time waiting for ceedling + - module_generator # handy for quickly creating source, header, and test templates + #- gcov # test coverage using gcov. Requires gcc, gcov, and a coverage analyzer like gcovr + #- bullseye # test coverage using bullseye. Requires bullseye for your platform + #- command_hooks # write custom actions to be called at different points during the build process + #- compile_commands_json_db # generate a compile_commands.json file + #- dependencies # automatically fetch 3rd party libraries, etc. + #- subprojects # managing builds and test for static libraries + #- fake_function_framework # use FFF instead of CMock -#:release_build: -# :output: MyApp.out -# :use_assembly: FALSE + # Report options (You'll want to choose one stdout option, but may choose multiple stored options if desired) + #- report_build_warnings_log + #- report_tests_gtestlike_stdout + #- report_tests_ide_stdout + #- report_tests_log_factory + - report_tests_pretty_stdout + #- report_tests_raw_output_log + #- report_tests_teamcity_stdout -:environment: +# Specify which reports you'd like from the log factory +:report_tests_log_factory: + :reports: + - json + - junit + - cppunit + - html +# override the default extensions for your system and toolchain :extension: + #:header: .h + #:source: .c + #:assembly: .s + #:dependencies: .d + #:object: .o :executable: .out + #:testpass: .pass + #:testfail: .fail + #:subprojects: .a +# This is where Ceedling should look for your source and test files. +# see documentation for the many options for specifying this. :paths: :test: - +:test/** - -:test/support :source: - src/** + :include: + - src/** # In simple projects, this entry often duplicates :source :support: - test/support :libraries: [] +# You can even specify specific files to add or remove from your test +# and release collections. Usually it's better to use paths and let +# Ceedling do the work for you! +:files: + :test: [] + :source: [] + +# Compilation symbols to be injected into builds +# See documentation for advanced options: +# - Test name matchers for different symbols per test executable build +# - Referencing symbols in multiple lists using advanced YAML +# - Specifiying symbols used during test preprocessing :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) - :common: &common_defines [] :test: - - *common_defines - - TEST - :test_preprocess: - - *common_defines - - TEST + - TEST # Simple list option to add symbol 'TEST' to compilation of all files in all test executables + :release: [] + # Enable to inject name of a test as a unique compilation symbol into its respective executable build. + :use_test_definition: FALSE + +# Configure additional command line flags provided to tools used in each build step +# :flags: +# :release: +# :compile: # Add '-Wall' and '--02' to compilation of all files in release target +# - -Wall +# - --O2 +# :test: +# :compile: +# '(_|-)special': # Add '-pedantic' to compilation of all files in all test executables with '_special' or '-special' in their names +# - -pedantic +# '*': # Add '-foo' to compilation of all files in all test executables +# - -foo + +# Configuration Options specific to CMock. See CMock docs for details :cmock: - :mock_prefix: mock_ - :when_no_prototypes: :warn - :enforce_strict_ordering: TRUE - :plugins: + # Core conffiguration + :plugins: # What plugins should be used by CMock? - :ignore - :callback - :treat_as: + :verbosity: 2 # the options being 0 errors only, 1 warnings and errors, 2 normal info, 3 verbose + :when_no_prototypes: :warn # the options being :ignore, :warn, or :erro + + # File configuration + :skeleton_path: '' # Subdirectory to store stubs when generated (default: '') + :mock_prefix: 'mock_' # Prefix to append to filenames for mocks + :mock_suffix: '' # Suffix to append to filenames for mocks + + # Parser configuration + :strippables: ['(?:__attribute__\s*\([ (]*.*?[ )]*\)+)'] + :attributes: + - __ramfunc + - __irq + - __fiq + - register + - extern + :c_calling_conventions: + - __stdcall + - __cdecl + - __fastcall + :treat_externs: :exclude # the options being :include or :exclud + :treat_inlines: :exclude # the options being :include or :exclud + + # Type handling configuration + #:unity_helper_path: '' # specify a string of where to find a unity_helper.h file to discover custom type assertions + :treat_as: # optionally add additional types to map custom types uint8: HEX8 uint16: HEX16 uint32: UINT32 int8: INT8 bool: UINT8 + #:treat_as_array: {} # hint to cmock that these types are pointers to something + #:treat_as_void: [] # hint to cmock that these types are actually aliases of void + :memcmp_if_unknown: true # allow cmock to use the memory comparison assertions for unknown types + :when_ptr: :compare_data # hint to cmock how to handle pointers in general, the options being :compare_ptr, :compare_data, or :smart -# Add -gcov to the plugins list to make sure of the gcov plugin -# You will need to have gcov and gcovr both installed to make it work. -# For more information on these options, see docs in plugins/gcov -:gcov: - :reports: - - HtmlDetailed - :gcovr: - :html_medium_threshold: 75 - :html_high_threshold: 90 + # Mock generation configuration + :weak: '' # Symbol to use to declare weak functions + :enforce_strict_ordering: true # Do we want cmock to enforce ordering of all function calls? + :fail_on_unexpected_calls: true # Do we want cmock to fail when it encounters a function call that wasn't expected? + :callback_include_count: true # Do we want cmock to include the number of calls to this callback, when using callbacks? + :callback_after_arg_check: false # Do we want cmock to enforce an argument check first when using a callback? + #:includes: [] # You can add additional includes here, or specify the location with the options below + #:includes_h_pre_orig_header: [] + #:includes_h_post_orig_header: [] + #:includes_c_pre_header: [] + #:includes_c_post_header: [] + #:array_size_type: [] # Specify a type or types that should be used for array lengths + #:array_size_name: 'size|len' # Specify a name or names that CMock might automatically recognize as the length of an array + :exclude_setjmp_h: false # Don't use setjmp when running CMock. Note that this might result in late reporting or out-of-order failures. -#: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 +# Configuration options specific to Unity. +:unity: + :defines: + - UNITY_EXCLUDE_FLOAT + +# You can optionally have ceedling create environment variables for you before +# performing the rest of its tasks. +:environment: [] +# :environment: +# # List enforces order allowing later to reference earlier with inline Ruby substitution +# - :var1: value +# - :var2: another value +# - :path: # Special PATH handling with platform-specific path separators +# - #{ENV['PATH']} # Environment variables can use inline Ruby substitution +# - /another/path/to/include # LIBRARIES # These libraries are automatically injected into the build process. Those specified as @@ -92,10 +224,187 @@ :test: [] :release: [] -:plugins: - :load_paths: - - "#{Ceedling.load_path}" - :enabled: - - stdout_pretty_tests_report - - module_generator +################################################################ +# PLUGIN CONFIGURATION +################################################################ + +# Add -gcov to the plugins list to make sure of the gcov plugin +# You will need to have gcov and gcovr both installed to make it work. +# For more information on these options, see docs in plugins/gcov +:gcov: + :summaries: TRUE # Enable simple coverage summaries to console after tests + :report_task: FALSE # Disabled dedicated report generation task (this enables automatic report generation) + :utilities: + - gcovr # Use gcovr to create the specified reports (default). + #- ReportGenerator # Use ReportGenerator to create the specified reports. + :reports: # Specify one or more reports to generate. + # Make an HTML summary report. + - HtmlBasic + # - HtmlDetailed + # - Text + # - Cobertura + # - SonarQube + # - JSON + # - HtmlInline + # - HtmlInlineAzure + # - HtmlInlineAzureDark + # - HtmlChart + # - MHtml + # - Badges + # - CsvSummary + # - Latex + # - LatexSummary + # - PngChart + # - TeamCitySummary + # - lcov + # - Xml + # - XmlSummary + :gcovr: + # :html_artifact_filename: TestCoverageReport.html + # :html_title: Test Coverage Report + :html_medium_threshold: 75 + :html_high_threshold: 90 + # :html_absolute_paths: TRUE + # :html_encoding: UTF-8 + +# :module_generator: +# :project_root: ./ +# :source_root: source/ +# :inc_root: includes/ +# :test_root: tests/ +# :naming: :snake #options: :bumpy, :camel, :caps, or :snake +# :includes: +# :tst: [] +# :src: [] +# :boilerplates: +# :src: "" +# :inc: "" +# :tst: "" + +# :dependencies: +# :libraries: +# - :name: WolfSSL +# :source_path: third_party/wolfssl/source +# :build_path: third_party/wolfssl/build +# :artifact_path: third_party/wolfssl/install +# :fetch: +# :method: :zip +# :source: \\shared_drive\third_party_libs\wolfssl\wolfssl-4.2.0.zip +# :environment: +# - CFLAGS+=-DWOLFSSL_DTLS_ALLOW_FUTURE +# :build: +# - "autoreconf -i" +# - "./configure --enable-tls13 --enable-singlethreaded" +# - make +# - make install +# :artifacts: +# :static_libraries: +# - lib/wolfssl.a +# :dynamic_libraries: +# - lib/wolfssl.so +# :includes: +# - include/** + +# :subprojects: +# :paths: +# - :name: libprojectA +# :source: +# - ./subprojectA/source +# :include: +# - ./subprojectA/include +# :build_root: ./subprojectA/build +# :defines: [] + +# :command_hooks: +# :pre_mock_preprocess: +# :post_mock_preprocess: +# :pre_test_preprocess: +# :post_test_preprocess: +# :pre_mock_generate: +# :post_mock_generate: +# :pre_runner_generate: +# :post_runner_generate: +# :pre_compile_execute: +# :post_compile_execute: +# :pre_link_execute: +# :post_link_execute: +# :pre_test_fixture_execute: +# :post_test_fixture_execute: +# :pre_test: +# :post_test: +# :pre_release: +# :post_release: +# :pre_build: +# :post_build: +# :post_error: + +################################################################ +# TOOLCHAIN CONFIGURATION +################################################################ + +#: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 +# :tools: +# :test_compiler: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :test_linker: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :test_assembler: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :test_fixture: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :test_includes_preprocessor: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :test_file_preprocessor: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :test_file_preprocessor_directives: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :test_dependencies_generator: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :release_compiler: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :release_linker: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :release_assembler: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :release_dependencies_generator: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE ... diff --git a/assets/project_with_guts.yml b/assets/project_with_guts.yml index cb1086fb3..19309b8fe 100644 --- a/assets/project_with_guts.yml +++ b/assets/project_with_guts.yml @@ -1,84 +1,216 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + --- +:project: + # how to use ceedling. If you're not sure, leave this as `gem` and `?` + :which_ceedling: vendor/ceedling + :ceedling_version: '?' -# 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. + # optional features. If you don't need them, keep them turned off for performance + :use_mocks: TRUE + :use_test_preprocessor: :none + :use_backtrace: :simple + :use_decorators: :auto #Decorate Ceedling's output text. Your options are :auto, :all, or :none -:project: - :use_exceptions: FALSE - :use_test_preprocessor: TRUE - :use_auxiliary_dependencies: TRUE + # tweak the way ceedling handles automatic tasks :build_root: build -# :release_build: TRUE :test_file_prefix: test_ - :which_ceedling: vendor/ceedling - :ceedling_version: '?' :default_tasks: - test:all -#:test_build: -# :use_assembly: TRUE + # performance options. If your tools start giving mysterious errors, consider + # dropping this to 1 to force single-tasking + :test_threads: 8 + :compile_threads: 8 + + # enable release build (more details in release_build section below) + :release_build: FALSE -#:release_build: -# :output: MyApp.out -# :use_assembly: FALSE +# Specify where to find mixins and any that should be enabled automatically +:mixins: + :enabled: [] + :load_paths: [] -:environment: +# further details to configure the way Ceedling handles test code +:test_build: + :use_assembly: FALSE + +# further details to configure the way Ceedling handles release code +:release_build: + :output: MyApp.out + :use_assembly: FALSE + :artifacts: [] + +# Plugins are optional Ceedling features which can be enabled. Ceedling supports +# a variety of plugins which may effect the way things are compiled, reported, +# or may provide new command options. Refer to the readme in each plugin for +# details on how to use it. +:plugins: + :load_paths: [] + :enabled: + #- beep # beeps when finished, so you don't waste time waiting for ceedling + - module_generator # handy for quickly creating source, header, and test templates + #- gcov # test coverage using gcov. Requires gcc, gcov, and a coverage analyzer like gcovr + #- bullseye # test coverage using bullseye. Requires bullseye for your platform + #- command_hooks # write custom actions to be called at different points during the build process + #- compile_commands_json_db # generate a compile_commands.json file + #- dependencies # automatically fetch 3rd party libraries, etc. + #- subprojects # managing builds and test for static libraries + #- fake_function_framework # use FFF instead of CMock + + # Report options (You'll want to choose one stdout option, but may choose multiple stored options if desired) + #- report_build_warnings_log + #- report_tests_gtestlike_stdout + #- report_tests_ide_stdout + #- report_tests_log_factory + - report_tests_pretty_stdout + #- report_tests_raw_output_log + #- report_tests_teamcity_stdout + +# Specify which reports you'd like from the log factory +:report_tests_log_factory: + :reports: + - json + - junit + - cppunit + - html +# override the default extensions for your system and toolchain :extension: + #:header: .h + #:source: .c + #:assembly: .s + #:dependencies: .d + #:object: .o :executable: .out + #:testpass: .pass + #:testfail: .fail + #:subprojects: .a +# This is where Ceedling should look for your source and test files. +# see documentation for the many options for specifying this. :paths: :test: - +:test/** - -:test/support :source: - src/** + :include: + - src/** # In simple projects, this entry often duplicates :source :support: - test/support :libraries: [] +# You can even specify specific files to add or remove from your test +# and release collections. Usually it's better to use paths and let +# Ceedling do the work for you! +:files: + :test: [] + :source: [] + +# Compilation symbols to be injected into builds +# See documentation for advanced options: +# - Test name matchers for different symbols per test executable build +# - Referencing symbols in multiple lists using advanced YAML +# - Specifiying symbols used during test preprocessing :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) - :common: &common_defines [] :test: - - *common_defines - - TEST - :test_preprocess: - - *common_defines - - TEST + - TEST # Simple list option to add symbol 'TEST' to compilation of all files in all test executables + :release: [] + + # Enable to inject name of a test as a unique compilation symbol into its respective executable build. + :use_test_definition: FALSE +# Configure additional command line flags provided to tools used in each build step +# :flags: +# :release: +# :compile: # Add '-Wall' and '--02' to compilation of all files in release target +# - -Wall +# - --O2 +# :test: +# :compile: +# '(_|-)special': # Add '-pedantic' to compilation of all files in all test executables with '_special' or '-special' in their names +# - -pedantic +# '*': # Add '-foo' to compilation of all files in all test executables +# - -foo + +# Configuration Options specific to CMock. See CMock docs for details :cmock: - :mock_prefix: mock_ - :when_no_prototypes: :warn - :enforce_strict_ordering: TRUE - :plugins: + # Core conffiguration + :plugins: # What plugins should be used by CMock? - :ignore - :callback - :treat_as: + :verbosity: 2 # the options being 0 errors only, 1 warnings and errors, 2 normal info, 3 verbose + :when_no_prototypes: :warn # the options being :ignore, :warn, or :erro + + # File configuration + :skeleton_path: '' # Subdirectory to store stubs when generated (default: '') + :mock_prefix: 'mock_' # Prefix to append to filenames for mocks + :mock_suffix: '' # Suffix to append to filenames for mocks + + # Parser configuration + :strippables: ['(?:__attribute__\s*\([ (]*.*?[ )]*\)+)'] + :attributes: + - __ramfunc + - __irq + - __fiq + - register + - extern + :c_calling_conventions: + - __stdcall + - __cdecl + - __fastcall + :treat_externs: :exclude # the options being :include or :exclud + :treat_inlines: :exclude # the options being :include or :exclud + + # Type handling configuration + #:unity_helper_path: '' # specify a string of where to find a unity_helper.h file to discover custom type assertions + :treat_as: # optionally add additional types to map custom types uint8: HEX8 uint16: HEX16 uint32: UINT32 int8: INT8 bool: UINT8 + #:treat_as_array: {} # hint to cmock that these types are pointers to something + #:treat_as_void: [] # hint to cmock that these types are actually aliases of void + :memcmp_if_unknown: true # allow cmock to use the memory comparison assertions for unknown types + :when_ptr: :compare_data # hint to cmock how to handle pointers in general, the options being :compare_ptr, :compare_data, or :smart -# Add -gcov to the plugins list to make sure of the gcov plugin -# You will need to have gcov and gcovr both installed to make it work. -# For more information on these options, see docs in plugins/gcov -:gcov: - :reports: - - HtmlDetailed - :gcovr: - :html_medium_threshold: 75 - :html_high_threshold: 90 + # Mock generation configuration + :weak: '' # Symbol to use to declare weak functions + :enforce_strict_ordering: true # Do we want cmock to enforce ordering of all function calls? + :fail_on_unexpected_calls: true # Do we want cmock to fail when it encounters a function call that wasn't expected? + :callback_include_count: true # Do we want cmock to include the number of calls to this callback, when using callbacks? + :callback_after_arg_check: false # Do we want cmock to enforce an argument check first when using a callback? + #:includes: [] # You can add additional includes here, or specify the location with the options below + #:includes_h_pre_orig_header: [] + #:includes_h_post_orig_header: [] + #:includes_c_pre_header: [] + #:includes_c_post_header: [] + #:array_size_type: [] # Specify a type or types that should be used for array lengths + #:array_size_name: 'size|len' # Specify a name or names that CMock might automatically recognize as the length of an array + :exclude_setjmp_h: false # Don't use setjmp when running CMock. Note that this might result in late reporting or out-of-order failures. -#: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 +# Configuration options specific to Unity. +:unity: + :defines: + - UNITY_EXCLUDE_FLOAT + +# You can optionally have ceedling create environment variables for you before +# performing the rest of its tasks. +:environment: [] +# :environment: +# # List enforces order allowing later to reference earlier with inline Ruby substitution +# - :var1: value +# - :var2: another value +# - :path: # Special PATH handling with platform-specific path separators +# - #{ENV['PATH']} # Environment variables can use inline Ruby substitution +# - /another/path/to/include # LIBRARIES # These libraries are automatically injected into the build process. Those specified as @@ -92,11 +224,188 @@ :test: [] :release: [] -:plugins: - :load_paths: - - vendor/ceedling/plugins - :enabled: - - stdout_pretty_tests_report - - module_generator - - raw_output_report +################################################################ +# PLUGIN CONFIGURATION +################################################################ + +# Add -gcov to the plugins list to make sure of the gcov plugin +# You will need to have gcov and gcovr both installed to make it work. +# For more information on these options, see docs in plugins/gcov +:gcov: + :summaries: TRUE # Enable simple coverage summaries to console after tests + :report_task: FALSE # Disabled dedicated report generation task (this enables automatic report generation) + :utilities: + - gcovr # Use gcovr to create the specified reports (default). + #- ReportGenerator # Use ReportGenerator to create the specified reports. + :reports: # Specify one or more reports to generate. + # Make an HTML summary report. + - HtmlBasic + # - HtmlDetailed + # - Text + # - Cobertura + # - SonarQube + # - JSON + # - HtmlInline + # - HtmlInlineAzure + # - HtmlInlineAzureDark + # - HtmlChart + # - MHtml + # - Badges + # - CsvSummary + # - Latex + # - LatexSummary + # - PngChart + # - TeamCitySummary + # - lcov + # - Xml + # - XmlSummary + :gcovr: + # :html_artifact_filename: TestCoverageReport.html + # :html_title: Test Coverage Report + :html_medium_threshold: 75 + :html_high_threshold: 90 + # :html_absolute_paths: TRUE + # :html_encoding: UTF-8 + +# :module_generator: +# :project_root: ./ +# :source_root: source/ +# :inc_root: includes/ +# :test_root: tests/ +# :naming: :snake #options: :bumpy, :camel, :caps, or :snake +# :includes: +# :tst: [] +# :src: [] +# :boilerplates: +# :src: "" +# :inc: "" +# :tst: "" + +# :dependencies: +# :libraries: +# - :name: WolfSSL +# :source_path: third_party/wolfssl/source +# :build_path: third_party/wolfssl/build +# :artifact_path: third_party/wolfssl/install +# :fetch: +# :method: :zip +# :source: \\shared_drive\third_party_libs\wolfssl\wolfssl-4.2.0.zip +# :environment: +# - CFLAGS+=-DWOLFSSL_DTLS_ALLOW_FUTURE +# :build: +# - "autoreconf -i" +# - "./configure --enable-tls13 --enable-singlethreaded" +# - make +# - make install +# :artifacts: +# :static_libraries: +# - lib/wolfssl.a +# :dynamic_libraries: +# - lib/wolfssl.so +# :includes: +# - include/** + +# :subprojects: +# :paths: +# - :name: libprojectA +# :source: +# - ./subprojectA/source +# :include: +# - ./subprojectA/include +# :build_root: ./subprojectA/build +# :defines: [] + +# :command_hooks: +# :pre_mock_preprocess: +# :post_mock_preprocess: +# :pre_test_preprocess: +# :post_test_preprocess: +# :pre_mock_generate: +# :post_mock_generate: +# :pre_runner_generate: +# :post_runner_generate: +# :pre_compile_execute: +# :post_compile_execute: +# :pre_link_execute: +# :post_link_execute: +# :pre_test_fixture_execute: +# :post_test_fixture_execute: +# :pre_test: +# :post_test: +# :pre_release: +# :post_release: +# :pre_build: +# :post_build: +# :post_error: + +################################################################ +# TOOLCHAIN CONFIGURATION +################################################################ + + +#: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 +# :tools: +# :test_compiler: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :test_linker: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :test_assembler: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :test_fixture: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :test_includes_preprocessor: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :test_file_preprocessor: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :test_file_preprocessor_directives: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :test_dependencies_generator: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :release_compiler: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :release_linker: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :release_assembler: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :release_dependencies_generator: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE ... diff --git a/assets/project_with_guts_gcov.yml b/assets/project_with_guts_gcov.yml deleted file mode 100644 index 29418a1cd..000000000 --- a/assets/project_with_guts_gcov.yml +++ /dev/null @@ -1,102 +0,0 @@ ---- - -# 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_ - :which_ceedling: vendor/ceedling - :ceedling_version: '?' - :default_tasks: - - test:all - -#:test_build: -# :use_assembly: TRUE - -#:release_build: -# :output: MyApp.out -# :use_assembly: FALSE - -:environment: - -:extension: - :executable: .out - -:paths: - :test: - - +:test/** - - -:test/support - :source: - - src/** - :support: - - test/support - :libraries: [] - -: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) - :common: &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 - -# Add -gcov to the plugins list to make sure of the gcov plugin -# You will need to have gcov and gcovr both installed to make it work. -# For more information on these options, see docs in plugins/gcov -:gcov: - :reports: - - HtmlDetailed - :gcovr: - :html_medium_threshold: 75 - :html_high_threshold: 90 - -#: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 - -# LIBRARIES -# These libraries are automatically injected into the build process. Those specified as -# common will be used in all types of builds. Otherwise, libraries can be injected in just -# tests or releases. These options are MERGED with the options in supplemental yaml files. -:libraries: - :placement: :end - :flag: "-l${1}" - :path_flag: "-L ${1}" - :system: [] # for example, you might list 'm' to grab the math library - :test: [] - :release: [] - -:plugins: - :load_paths: - - vendor/ceedling/plugins - :enabled: - - stdout_pretty_tests_report - - module_generator - - gcov -... diff --git a/assets/test_example_file.c b/assets/test_example_file.c index e8378aa54..7a43a7a96 100644 --- a/assets/test_example_file.c +++ b/assets/test_example_file.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "example_file.h" @@ -5,9 +12,9 @@ void setUp(void) {} void tearDown(void) {} void test_add_numbers_adds_numbers(void) { - TEST_ASSERT_EQUAL(2, add_numbers(1,1)); + TEST_ASSERT_EQUAL_INT(2, add_numbers(1,1)); } void test_add_numbers_will_fail(void) { - TEST_ASSERT_EQUAL(2, add_numbers(2,2)); + TEST_ASSERT_EQUAL_INT(2, add_numbers(2,2)); } diff --git a/assets/test_example_file_boom.c b/assets/test_example_file_boom.c index 1365296b9..fd2b64bc4 100644 --- a/assets/test_example_file_boom.c +++ b/assets/test_example_file_boom.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "example_file.h" @@ -5,9 +12,9 @@ void setUp(void) {} void tearDown(void) {} void test_add_numbers_adds_numbers(void) { - TEST_ASSERT_EQUAL(2, add_numbers(1,1) //Removed semicolon & parenthesis to make a compile error. + TEST_ASSERT_EQUAL_INT(2, add_numbers(1,1) //Removed semicolon & parenthesis to make a compile error. } void test_add_numbers_will_fail(void) { - TEST_ASSERT_EQUAL(2, add_numbers(2,2)); + TEST_ASSERT_EQUAL_INT(2, add_numbers(2,2)); } diff --git a/assets/test_example_file_crash.c b/assets/test_example_file_crash.c new file mode 100644 index 000000000..dcc86066a --- /dev/null +++ b/assets/test_example_file_crash.c @@ -0,0 +1,25 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#include +#include "unity.h" +#include "example_file.h" + + +void setUp(void) {} +void tearDown(void) {} + +void test_add_numbers_adds_numbers(void) { + TEST_ASSERT_EQUAL_INT(2, add_numbers(1,1)); +} + +void test_add_numbers_will_fail(void) { + // Platform-independent way of forcing a crash + uint32_t* nullptr = (void*) 0; + uint32_t i = *nullptr; + TEST_ASSERT_EQUAL_INT(2, add_numbers(i,2)); +} diff --git a/assets/test_example_file_sigsegv.c b/assets/test_example_file_sigsegv.c deleted file mode 100644 index 8ac1aef8d..000000000 --- a/assets/test_example_file_sigsegv.c +++ /dev/null @@ -1,16 +0,0 @@ -#include -#include "unity.h" -#include "example_file.h" - - -void setUp(void) {} -void tearDown(void) {} - -void test_add_numbers_adds_numbers(void) { - TEST_ASSERT_EQUAL(2, add_numbers(1,1)); -} - -void test_add_numbers_will_fail(void) { - raise(SIGSEGV); - TEST_ASSERT_EQUAL(2, add_numbers(2,2)); -} diff --git a/assets/test_example_file_success.c b/assets/test_example_file_success.c index 4bc326492..6800dfd8d 100644 --- a/assets/test_example_file_success.c +++ b/assets/test_example_file_success.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "example_file.h" @@ -5,10 +12,10 @@ void setUp(void) {} void tearDown(void) {} void test_add_numbers_adds_numbers(void) { - TEST_ASSERT_EQUAL(2, add_numbers(1,1)); + TEST_ASSERT_EQUAL_INT(2, add_numbers(1,1)); } void test_add_numbers_will_fail_but_is_ignored_for_now(void) { TEST_IGNORE(); - TEST_ASSERT_EQUAL(2, add_numbers(2,2)); + TEST_ASSERT_EQUAL_INT(2, add_numbers(2,2)); } diff --git a/assets/test_example_file_unity_printf.c b/assets/test_example_file_unity_printf.c index 0aee87345..b87da5434 100644 --- a/assets/test_example_file_unity_printf.c +++ b/assets/test_example_file_unity_printf.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "example_file.h" #include @@ -7,6 +14,6 @@ void tearDown(void) {} void test_add_numbers_adds_numbers(void) { TEST_PRINTF("1 + 1 =%d", 1 + 1); - TEST_ASSERT_EQUAL(2, add_numbers(1,1)); + TEST_ASSERT_EQUAL_INT(2, add_numbers(1,1)); } diff --git a/assets/test_example_file_verbose.c b/assets/test_example_file_verbose.c index 9e50ea622..63d2f3819 100644 --- a/assets/test_example_file_verbose.c +++ b/assets/test_example_file_verbose.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "example_file.h" #include @@ -7,6 +14,6 @@ void tearDown(void) {} void test_add_numbers_adds_numbers(void) { printf("1 + 1 = 2\n"); - TEST_ASSERT_EQUAL(2, add_numbers(1,1)); + TEST_ASSERT_EQUAL_INT(2, add_numbers(1,1)); } diff --git a/assets/test_example_file_with_mock.c b/assets/test_example_file_with_mock.c index f9e270be1..c3748ccf0 100644 --- a/assets/test_example_file_with_mock.c +++ b/assets/test_example_file_with_mock.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "example_file_call.h" // mock header should have higher priority than real file @@ -9,5 +16,5 @@ void tearDown(void) {} void test_add_numbers_adds_numbers(void) { add_numbers_ExpectAndReturn(1, 1, 2); - TEST_ASSERT_EQUAL(2, call_add_numbers(1, 1)); + TEST_ASSERT_EQUAL_INT(2, call_add_numbers(1, 1)); } diff --git a/assets/test_example_with_parameterized_tests.c b/assets/test_example_with_parameterized_tests.c index 3b511db62..9912bc3c6 100644 --- a/assets/test_example_with_parameterized_tests.c +++ b/assets/test_example_with_parameterized_tests.c @@ -1,7 +1,11 @@ -#include "unity.h" +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ -#define TEST_CASE(...) -#define TEST_RANGE(...) +#include "unity.h" void setUp(void) {} void tearDown(void) {} @@ -13,6 +17,11 @@ void test_should_handle_divisible_by_5_for_parameterized_test_case(int num) { TEST_ASSERT_EQUAL_MESSAGE(0, (num % 5), "All The Values Are Divisible By 5"); } +TEST_RANGE([5, 100, 5]) +void test_should_handle_divisible_by_5_for_parameterized_test_range(int num) { + TEST_ASSERT_EQUAL_MESSAGE(0, (num % 5), "All The Values Are Divisible By 5"); +} + TEST_RANGE([10, 100, 10], [5, 10, 5]) void test_should_handle_a_divisible_by_b_for_parameterized_test_range(int a, int b) { TEST_ASSERT_EQUAL_MESSAGE(0, (a % b), "All The a Values Are Divisible By b"); diff --git a/assets/tests_with_defines/src/adc_hardware.c b/assets/tests_with_defines/src/adc_hardware.c index 244dc6606..e316b9fb9 100644 --- a/assets/tests_with_defines/src/adc_hardware.c +++ b/assets/tests_with_defines/src/adc_hardware.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "adc_hardware.h" #include "adc_hardware_configurator.h" @@ -5,7 +12,7 @@ void AdcHardware_Init(void) { #ifdef SPECIFIC_CONFIG Adc_ResetSpec(); - #elif STANDARD_CONFIG + #elif defined(STANDARD_CONFIG) Adc_Reset(); #endif } diff --git a/assets/tests_with_defines/src/adc_hardware.h b/assets/tests_with_defines/src/adc_hardware.h index aee79daf7..7483ac99a 100644 --- a/assets/tests_with_defines/src/adc_hardware.h +++ b/assets/tests_with_defines/src/adc_hardware.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _ADCHARDWARE_H #define _ADCHARDWARE_H diff --git a/assets/tests_with_defines/src/adc_hardware_configurator.c b/assets/tests_with_defines/src/adc_hardware_configurator.c index 0ea263c58..bd90c6397 100644 --- a/assets/tests_with_defines/src/adc_hardware_configurator.c +++ b/assets/tests_with_defines/src/adc_hardware_configurator.c @@ -1,10 +1,17 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "adc_hardware_configurator.h" #ifdef SPECIFIC_CONFIG void Adc_ResetSpec(void) { } -#elif STANDARD_CONFIG +#elif defined(STANDARD_CONFIG) void Adc_Reset(void) { } diff --git a/assets/tests_with_defines/src/adc_hardware_configurator.h b/assets/tests_with_defines/src/adc_hardware_configurator.h index 699eced8e..8a8fe8153 100644 --- a/assets/tests_with_defines/src/adc_hardware_configurator.h +++ b/assets/tests_with_defines/src/adc_hardware_configurator.h @@ -1,9 +1,16 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _ADCHARDWARECONFIGURATOR_H #define _ADCHARDWARECONFIGURATOR_H #ifdef SPECIFIC_CONFIG void Adc_ResetSpec(void); -#elif STANDARD_CONFIG +#elif defined(STANDARD_CONFIG) void Adc_Reset(void); #endif diff --git a/assets/tests_with_defines/test/test_adc_hardware.c b/assets/tests_with_defines/test/test_adc_hardware.c index fa424dac4..260098f1d 100644 --- a/assets/tests_with_defines/test/test_adc_hardware.c +++ b/assets/tests_with_defines/test/test_adc_hardware.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "adc_hardware.h" #include "mock_adc_hardware_configurator.h" diff --git a/assets/tests_with_defines/test/test_adc_hardware_special.c b/assets/tests_with_defines/test/test_adc_hardware_special.c index ef215d49d..fcc3e1f0c 100644 --- a/assets/tests_with_defines/test/test_adc_hardware_special.c +++ b/assets/tests_with_defines/test/test_adc_hardware_special.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "adc_hardware.h" #include "mock_adc_hardware_configurator.h" diff --git a/assets/tests_with_preprocessing/src/adc_hardwareA.c b/assets/tests_with_preprocessing/src/adc_hardwareA.c new file mode 100644 index 000000000..4a28bbe49 --- /dev/null +++ b/assets/tests_with_preprocessing/src/adc_hardwareA.c @@ -0,0 +1,17 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#include "adc_hardwareA.h" +#include "adc_hardware_configuratorA.h" + +void AdcHardware_Init(void) +{ + // Reusing outer test file preprocessing symbol to prevent linking failure + #ifdef PREPROCESSING_TESTS + Adc_Reset(); + #endif +} diff --git a/assets/tests_with_preprocessing/src/adc_hardwareA.h b/assets/tests_with_preprocessing/src/adc_hardwareA.h new file mode 100644 index 000000000..7483ac99a --- /dev/null +++ b/assets/tests_with_preprocessing/src/adc_hardwareA.h @@ -0,0 +1,13 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#ifndef _ADCHARDWARE_H +#define _ADCHARDWARE_H + +void AdcHardware_Init(void); + +#endif // _ADCHARDWARE_H diff --git a/assets/tests_with_preprocessing/src/adc_hardwareB.c b/assets/tests_with_preprocessing/src/adc_hardwareB.c new file mode 100644 index 000000000..0d3e1b2d3 --- /dev/null +++ b/assets/tests_with_preprocessing/src/adc_hardwareB.c @@ -0,0 +1,14 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#include "adc_hardwareB.h" +#include "adc_hardware_configuratorB.h" + +void AdcHardware_Init(void) +{ + Adc_Reset(); +} diff --git a/assets/tests_with_preprocessing/src/adc_hardwareB.h b/assets/tests_with_preprocessing/src/adc_hardwareB.h new file mode 100644 index 000000000..7483ac99a --- /dev/null +++ b/assets/tests_with_preprocessing/src/adc_hardwareB.h @@ -0,0 +1,13 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#ifndef _ADCHARDWARE_H +#define _ADCHARDWARE_H + +void AdcHardware_Init(void); + +#endif // _ADCHARDWARE_H diff --git a/assets/tests_with_preprocessing/src/adc_hardwareC.c b/assets/tests_with_preprocessing/src/adc_hardwareC.c new file mode 100644 index 000000000..66f811595 --- /dev/null +++ b/assets/tests_with_preprocessing/src/adc_hardwareC.c @@ -0,0 +1,14 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#include "adc_hardwareC.h" +#include "adc_hardware_configuratorC.h" + +void AdcHardware_Init(void) +{ + Adc_Reset(); +} diff --git a/assets/tests_with_preprocessing/src/adc_hardwareC.h b/assets/tests_with_preprocessing/src/adc_hardwareC.h new file mode 100644 index 000000000..7483ac99a --- /dev/null +++ b/assets/tests_with_preprocessing/src/adc_hardwareC.h @@ -0,0 +1,13 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#ifndef _ADCHARDWARE_H +#define _ADCHARDWARE_H + +void AdcHardware_Init(void); + +#endif // _ADCHARDWARE_H diff --git a/assets/tests_with_preprocessing/src/adc_hardware_configuratorA.h b/assets/tests_with_preprocessing/src/adc_hardware_configuratorA.h new file mode 100644 index 000000000..d474d0799 --- /dev/null +++ b/assets/tests_with_preprocessing/src/adc_hardware_configuratorA.h @@ -0,0 +1,13 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#ifndef _ADCHARDWARECONFIGURATOR_H +#define _ADCHARDWARECONFIGURATOR_H + +void Adc_Reset(void); + +#endif // _ADCHARDWARECONFIGURATOR_H diff --git a/assets/tests_with_preprocessing/src/adc_hardware_configuratorB.h b/assets/tests_with_preprocessing/src/adc_hardware_configuratorB.h new file mode 100644 index 000000000..4b3ef81a3 --- /dev/null +++ b/assets/tests_with_preprocessing/src/adc_hardware_configuratorB.h @@ -0,0 +1,15 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#ifndef _ADCHARDWARECONFIGURATOR_H +#define _ADCHARDWARECONFIGURATOR_H + +#ifdef PREPROCESSING_MOCKS +void Adc_Reset(void); +#endif + +#endif // _ADCHARDWARECONFIGURATOR_H diff --git a/assets/tests_with_preprocessing/src/adc_hardware_configuratorC.h b/assets/tests_with_preprocessing/src/adc_hardware_configuratorC.h new file mode 100644 index 000000000..4b3ef81a3 --- /dev/null +++ b/assets/tests_with_preprocessing/src/adc_hardware_configuratorC.h @@ -0,0 +1,15 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#ifndef _ADCHARDWARECONFIGURATOR_H +#define _ADCHARDWARECONFIGURATOR_H + +#ifdef PREPROCESSING_MOCKS +void Adc_Reset(void); +#endif + +#endif // _ADCHARDWARECONFIGURATOR_H diff --git a/assets/tests_with_preprocessing/test/test_adc_hardwareA.c b/assets/tests_with_preprocessing/test/test_adc_hardwareA.c new file mode 100644 index 000000000..0d2cac8c7 --- /dev/null +++ b/assets/tests_with_preprocessing/test/test_adc_hardwareA.c @@ -0,0 +1,41 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#include "unity.h" +#include "adc_hardwareA.h" +#ifdef PREPROCESSING_TESTS +#include "mock_adc_hardware_configuratorA.h" +#endif + +void setUp(void) +{ +} + +void tearDown(void) +{ +} + +#ifdef PREPROCESSING_TESTS +void test_init_should_call_adc_reset(void) +{ + Adc_Reset_Expect(); + + AdcHardware_Init(); +} +#endif + +#ifndef PREPROCESSING_TESTS +void test_caseA_should_fail(void) +{ + TEST_FAIL_MESSAGE("Intentional failure"); +} + +void test_caseB_should_fail(void) +{ + TEST_FAIL_MESSAGE("Intentional failure"); +} +#endif \ No newline at end of file diff --git a/assets/tests_with_preprocessing/test/test_adc_hardwareB.c b/assets/tests_with_preprocessing/test/test_adc_hardwareB.c new file mode 100644 index 000000000..fcd4183a7 --- /dev/null +++ b/assets/tests_with_preprocessing/test/test_adc_hardwareB.c @@ -0,0 +1,25 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#include "unity.h" +#include "adc_hardwareB.h" +#include "mock_adc_hardware_configuratorB.h" + +void setUp(void) +{ +} + +void tearDown(void) +{ +} + +void test_init_should_call_adc_reset(void) +{ + Adc_Reset_Expect(); + + AdcHardware_Init(); +} diff --git a/assets/tests_with_preprocessing/test/test_adc_hardwareC.c b/assets/tests_with_preprocessing/test/test_adc_hardwareC.c new file mode 100644 index 000000000..e38dab62b --- /dev/null +++ b/assets/tests_with_preprocessing/test/test_adc_hardwareC.c @@ -0,0 +1,29 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#include "unity.h" +#include "adc_hardwareC.h" +#ifdef PREPROCESSING_TESTS +#include "mock_adc_hardware_configuratorC.h" +#endif + +void setUp(void) +{ +} + +void tearDown(void) +{ +} + +#ifdef PREPROCESSING_TESTS +void test_init_should_call_adc_reset(void) +{ + Adc_Reset_Expect(); + + AdcHardware_Init(); +} +#endif \ No newline at end of file diff --git a/assets/uncovered_example_file.c b/assets/uncovered_example_file.c index 830847a50..dc389359e 100644 --- a/assets/uncovered_example_file.c +++ b/assets/uncovered_example_file.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + // This file is to test abort on uncovered files feature int multiply_numbers(int a, int b) { diff --git a/bin/actions_wrapper.rb b/bin/actions_wrapper.rb new file mode 100644 index 000000000..43d02f9d6 --- /dev/null +++ b/bin/actions_wrapper.rb @@ -0,0 +1,50 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'thor' +require 'fileutils' + +# Wrapper for handy Thor Actions +class ActionsWrapper + include Thor::Base + include Thor::Actions + + JUNK_FILE_EXCLUDE_REGEX = + + # Most important mixin method is Thor::Actions class method `source_root()` we call externally + + def _directory(src, *args) + # Insert exclusion of macOS and Windows preview junk files if an exclude pattern is not present + # Thor's use of args is an array of call arguments, some of which can be single key/value hash options + if !args.any? {|h| h.class != Hash ? false : !h[:exclude_pattern].nil?} + args << {:exclude_pattern => /(\.DS_Store)|(thumbs\.db)/} + end + + directory( src, *args ) + end + + def _copy_file(src, *args) + copy_file( src, *args ) + end + + def _touch_file(src) + FileUtils.touch(src) + end + + def _chmod(src, mode, *args) + chmod( src, mode, *args ) + end + + def _empty_directory(dest, *args) + empty_directory( dest, *args ) + end + + def _gsub_file(path, flag, *args, &block) + gsub_file( path, flag, *args, &block ) + end + +end diff --git a/bin/app_cfg.rb b/bin/app_cfg.rb new file mode 100644 index 000000000..d04a28c25 --- /dev/null +++ b/bin/app_cfg.rb @@ -0,0 +1,134 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require "io/console" + +# Create our global application configuration option set +# This approach bridges clean Ruby and Rake + +class CeedlingAppConfig + + def initialize() + # Default installation location determined from the location of this file + ceedling_root_path = File.join( File.dirname( __FILE__ ), '..' ) + + # Create internal hash of needed values + @app_cfg = { + # Base path of any Ceedling installation + :ceedling_root_path => '', + + # Ceedling installation base path + /lib + :ceedling_lib_base_path => '', + + # Ceedling installation base path + /lib/ceedling + :ceedling_lib_path => '', + + # Ceedling installation base path + /plugins + :ceedling_plugins_path => '', + + # Ceedling installation base path + /vendor + :ceedling_vendor_path => '', + + # Ceedling installation base path + /examples + :ceedling_examples_path => '', + + # Ceedling lib path + lib/ceedling/rakefile.rb + :ceedling_rakefile_filepath => '', + + # Blank initial value for completeness + :project_config => {}, + + # Default path (in build directory) to hold logs + :logging_path => '', + + # If logging enabled, the filepath for Ceedling's log (may be explicitly set to be outside :logging_path) + :log_filepath => '', + + # Only specified in project config (no command line or environment variable) + :default_tasks => ['test:all'], + + # Default, blank test case filters + :include_test_case => '', + :exclude_test_case => '', + + # Default to task categry other than build/plugin tasks + :build_tasks? => false, + + # Default to `exit(1)` upon failing test cases + :tests_graceful_fail? => false, + + # Set terminal width (in columns) to a default + :terminal_width => 120, + } + + set_paths( ceedling_root_path ) + + # Try to query terminal width (not always available on all platforms) + begin + @app_cfg[:terminal_width] = (IO.console.winsize)[1] + rescue + # Do nothing; allow value already set to stand as default + end + end + + def set_project_config(config) + @app_cfg[:project_config] = config + end + + def set_logging_path(path) + @app_cfg[:logging_path] = path + end + + def set_log_filepath(filepath) + @app_cfg[:log_filepath] = filepath + end + + def set_include_test_case(matcher) + @app_cfg[:include_test_case] = matcher + end + + def set_exclude_test_case(matcher) + @app_cfg[:exclude_test_case] = matcher + end + + def set_build_tasks(enable) + @app_cfg[:build_tasks?] = enable + end + + def set_tests_graceful_fail(enable) + @app_cfg[:tests_graceful_fail?] = enable + end + + def set_paths(root_path) + _root_path = File.expand_path( root_path ) + lib_base_path = File.join( _root_path, 'lib' ) + lib_path = File.join( lib_base_path, 'ceedling' ) + + @app_cfg[:ceedling_root_path] = _root_path + @app_cfg[:ceedling_lib_base_path] = lib_base_path + @app_cfg[:ceedling_lib_path] = lib_path + @app_cfg[:ceedling_vendor_path] = File.join( _root_path, 'vendor' ) + @app_cfg[:ceedling_plugins_path] = File.join( _root_path, 'plugins' ) + @app_cfg[:ceedling_examples_path] = File.join( _root_path, 'examples' ) + + @app_cfg[:ceedling_rakefile_filepath] = File.join( lib_path, 'rakefile.rb' ) + end + + def build_tasks?() + return @app_cfg[:build_tasks?] + end + + def tests_graceful_fail?() + return @app_cfg[:tests_graceful_fail?] + end + + # External accessor to preserve hash-like read accesses + def [](key) + return @app_cfg[key] + end + +end diff --git a/bin/ceedling b/bin/ceedling index 5a1d49584..ad5ea9a62 100755 --- a/bin/ceedling +++ b/bin/ceedling @@ -1,364 +1,160 @@ #!/usr/bin/env ruby +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= -#these are always used require 'rubygems' -require 'fileutils' -# Check for the main project file (either the one defined in the ENV or the default) -main_filepath = ENV['CEEDLING_MAIN_PROJECT_FILE'] -project_found = (!main_filepath.nil? && File.exist?(main_filepath)) -if (!project_found) - main_filepath = "project.yml" - project_found = File.exist?(main_filepath) -end - -def is_windows? - return ((RbConfig::CONFIG['host_os'] =~ /mswin|mingw/) ? true : false) if defined?(RbConfig) - return ((Config::CONFIG['host_os'] =~ /mswin|mingw/) ? true : false) -end - -def here - File.join(File.expand_path(File.dirname(__FILE__)),"/..") -end - -unless (project_found) -#===================================== We Do Not Have A Project ================================================ - - puts "Welcome to Ceedling!" - require 'thor' - - class CeedlingTasks < Thor - include Thor::Actions - - desc "new PROJECT_NAME", "create a new ceedling project" - method_option :docs, :type => :boolean, :default => false, :desc => "Add docs in project vendor directory" - method_option :local, :type => :boolean, :default => false, :desc => "Create a copy of Ceedling in the project vendor directory" - method_option :gitignore, :type => :boolean, :default => false, :desc => "Create a gitignore file for ignoring ceedling generated files" - method_option :no_configs, :type => :boolean, :default => false, :desc => "Don't install starter configuration files" - method_option :noconfigs, :type => :boolean, :default => false - - #deprecated: - method_option :no_docs, :type => :boolean, :default => false - method_option :nodocs, :type => :boolean, :default => false - method_option :as_gem, :type => :boolean, :default => false - method_option :asgem, :type => :boolean, :default => false - method_option :with_ignore, :type => :boolean, :default => false - method_option :withignore, :type => :boolean, :default => false - def new(name, silent = false) - copy_assets_and_create_structure(name, silent, false, options) - end - - desc "upgrade PROJECT_NAME", "upgrade ceedling for a project (not req'd if gem used)" - def upgrade(name, silent = false) - as_local = true - yaml_path = File.join(name, "project.yml") - begin - require File.join(here,"lib","ceedling","yaml_wrapper.rb") - as_local = (YamlWrapper.new.load(yaml_path)[:project][:which_ceedling] != 'gem') - rescue - raise "ERROR: Could not find valid project file '#{yaml_path}'" - end - found_docs = File.exist?( File.join(name, "docs", "CeedlingPacket.md") ) - copy_assets_and_create_structure(name, silent, true, {:upgrade => true, :no_configs => true, :local => as_local, :docs => found_docs}) - end - - no_commands do - def copy_assets_and_create_structure(name, silent=false, force=false, options = {}) - - puts "WARNING: --no_docs deprecated. It is now the default. Specify -docs if you want docs installed." if (options[:no_docs] || options[:nodocs]) - puts "WARNING: --as_gem deprecated. It is now the default. Specify -local if you want ceedling installed to this project." if (options[:as_gem] || options[:asgem]) - puts "WARNING: --with_ignore deprecated. It is now called -gitignore" if (options[:with_ignore] || options[:withignore]) - - use_docs = options[:docs] || false - use_configs = !(options[:no_configs] || options[:noconfigs] || false) - use_gem = !(options[:local]) - use_ignore = options[:gitignore] || false - is_upgrade = options[:upgrade] || false - - ceedling_path = File.join(name, 'vendor', 'ceedling') - source_path = File.join(name, 'src') - test_path = File.join(name, 'test') - test_support_path = File.join(name, 'test/support') - - # If it's not an upgrade, make sure we have the paths we expect - if (!is_upgrade) - [source_path, test_path, test_support_path].each do |d| - FileUtils.mkdir_p d - end - else - prj_yaml = YamlWrapper.new.load(File.join(name, 'project.yml')) - test_support_path = if prj_yaml.key?(:path) && \ - prj_yaml[:path].key?(:support) - prj_yaml.key?[:path][:support] - else - '' - end - end - - # Genarate gitkeep in test support path - FileUtils.touch(File.join(test_support_path, '.gitkeep')) unless \ - test_support_path.empty? - - # If documentation requested, create a place to dump them and do so - doc_path = '' - if use_docs - doc_path = use_gem ? File.join(name, 'docs') : File.join(ceedling_path, 'docs') - FileUtils.mkdir_p doc_path - - in_doc_path = lambda {|f| File.join(doc_path, f)} - - # Add documentation from main projects to list - doc_files = {} - ['docs','vendor/unity/docs','vendor/cmock/docs','vendor/cexception/docs'].each do |p| - Dir[ File.expand_path(File.join(here, p, '*.md')) ].each do |f| - doc_files[ File.basename(f) ] = f unless(doc_files.include? f) - end - end - - # Add documentation from plugins to list - Dir[ File.join(here, 'plugins/**/README.md') ].each do |plugin_path| - k = "plugin_" + plugin_path.split(/\\|\//)[-2] + ".md" - doc_files[ k ] = File.expand_path(plugin_path) - end - - # Copy all documentation - doc_files.each_pair do |k, v| - copy_file(v, in_doc_path.call(k), :force => force) - end - end - - # If installed locally to project, copy ceedling, unity, cmock, & supports to vendor - unless use_gem - FileUtils.mkdir_p ceedling_path - - #copy full folders from ceedling gem into project - %w{plugins lib bin}.map do |f| - {:src => f, :dst => File.join(ceedling_path, f)} - end.each do |f| - directory(f[:src], f[:dst], :force => force) - end +# Get the path for our current code directory +ceedling_bin_path = File.expand_path( File.join( File.dirname( __FILE__ ), '..', 'bin' ) ) - # mark ceedling as an executable - File.chmod(0755, File.join(ceedling_path, 'bin', 'ceedling')) unless is_windows? +# Add load path so we can `require` files in bin/ +$LOAD_PATH.unshift( ceedling_bin_path ) - #copy necessary subcomponents from ceedling gem into project - sub_components = [ - {:src => 'vendor/c_exception/lib/', :dst => 'vendor/c_exception/lib'}, - {:src => 'vendor/cmock/config/', :dst => 'vendor/cmock/config'}, - {:src => 'vendor/cmock/lib/', :dst => 'vendor/cmock/lib'}, - {:src => 'vendor/cmock/src/', :dst => 'vendor/cmock/src'}, - {:src => 'vendor/diy/lib', :dst => 'vendor/diy/lib'}, - {:src => 'vendor/unity/auto/', :dst => 'vendor/unity/auto'}, - {:src => 'vendor/unity/src/', :dst => 'vendor/unity/src'}, - ] +# Pull in our startup configuration code in bin/ +require 'app_cfg' +CEEDLING_APPCFG = CeedlingAppConfig.new() - sub_components.each do |c| - directory(c[:src], File.join(ceedling_path, c[:dst]), :force => force) - end - end +# Add load paths for `require 'ceedling/*'` statements in bin/ code +$LOAD_PATH.unshift( CEEDLING_APPCFG[:ceedling_lib_base_path] ) - # We're copying in a configuration file if we haven't said not to - if (use_configs) - dst_yaml = File.join(name, 'project.yml') - src_yaml = if use_gem - File.join(here, 'assets', 'project_as_gem.yml') - else - if is_windows? - copy_file(File.join('assets', 'ceedling.cmd'), File.join(name, 'ceedling.cmd'), :force => force) - else - copy_file(File.join('assets', 'ceedling'), File.join(name, 'ceedling'), :force => force) - File.chmod(0755, File.join(name, 'ceedling')) - end - File.join(here, 'assets', 'project_with_guts.yml') - end +require 'constructor' # Assumed installed via Ceedling gem dependencies +require 'ceedling/constants' - # Perform the actual clone of the config file, while updating the version - File.open(dst_yaml,'w') do |dst| - require File.expand_path(File.join(File.dirname(__FILE__),"..","lib","ceedling","version.rb")) - dst << File.read(src_yaml).gsub(":ceedling_version: '?'",":ceedling_version: #{Ceedling::Version::CEEDLING}") - puts " create #{dst_yaml}" - end - end +# Centralized exception handler for: +# 1. Bootloader (bin/) +# 2. Application (lib/) last resort / outer exception handling +def boom_handler(loginator, exception) + $stderr.puts( "\n" ) - # Copy the gitignore file if requested - if (use_ignore) - copy_file(File.join('assets', 'default_gitignore'), File.join(name, '.gitignore'), :force => force) - end + if !loginator.nil? + loginator.log( exception.message, Verbosity::ERRORS, LogLabels::EXCEPTION ) - unless silent - puts "\n" - puts "Project '#{name}' #{force ? "upgraded" : "created"}!" - puts " - Tool documentation is located in #{doc_path}" if use_docs - puts " - Execute 'ceedling help' from #{name} to view available test & build tasks" - puts '' - end - end - end - - desc "examples", "list available example projects" - def examples() - puts "Available sample projects:" - FileUtils.cd(File.join(here, "examples")) do - Dir["*"].each {|proj| puts " #{proj}"} - end - end - - desc "example PROJ_NAME [DEST]", "new specified example project (in DEST, if specified)" - def example(proj_name, dest=nil) - if dest.nil? then dest = proj_name end - - copy_assets_and_create_structure(dest, true, false, {:local=>true, :docs=>true}) - - dest_src = File.join(dest,'src') - dest_test = File.join(dest,'test') - dest_project = File.join(dest,'project.yml') - - directory "examples/#{proj_name}/src", dest_src - directory "examples/#{proj_name}/test", dest_test - remove_file dest_project - copy_file "examples/#{proj_name}/project.yml", dest_project - - puts "\n" - puts "Example project '#{proj_name}' created!" - puts " - Tool documentation is located in vendor/ceedling/docs" - puts " - Execute 'ceedling help' to view available test & build tasks" - puts '' - end - - desc "version", "return the version of the tools installed" - def version() - require File.expand_path(File.join(File.dirname(__FILE__),"..","lib","ceedling","version.rb")) - puts " Ceedling:: #{Ceedling::Version::CEEDLING}" - puts " CMock:: #{Ceedling::Version::CMOCK}" - puts " Unity:: #{Ceedling::Version::UNITY}" - puts " CException:: #{Ceedling::Version::CEXCEPTION}" - end - end - - if (ARGV[0] =~ /^\-T$/) - puts "\n(No Project Detected, Therefore Showing Options to Create Projects)" - CeedlingTasks.tasks.each_pair do |k,v| - puts v.usage.ljust(25,' ') + v.description - end - puts "\n" + # Debug backtrace (only if debug verbosity) + loginator.log_debug_backtrace( exception ) + + # Something went really wrong... logging isn't even up and running yet else - CeedlingTasks.source_root here - CeedlingTasks.start - end - -#===================================== We Have A Project Already ================================================ -else - require File.join(here,"lib","ceedling","yaml_wrapper.rb") - require 'rbconfig' - - #determine platform - platform = begin - case(RbConfig::CONFIG['host_os']) - when /mswin|mingw|cygwin/i - :mswin - when /darwin/ - :osx - else - :linux - end - rescue - :linux + $stderr.puts( exception.message ) + $stderr.puts( "Backtrace ==>" ) + $stderr.puts( exception.backtrace ) end +end - #create our default meta-runner option set - options = { - :pretest => nil, - :args => [], - :add_path => [], - :path_connector => (platform == :mswin) ? ";" : ":", - :graceful_fail => false, - :which_ceedling => (Dir.exist?("vendor/ceedling") ? "vendor/ceedling" : 'gem'), - :default_tasks => [ 'test:all' ], - :list_tasks => false - } - - #guess that we need a special script file first if it exists - if (platform == :mswin) - options[:pretest] = File.exist?("#{ platform }_setup.bat") ? "#{ platform }_setup.bat" : nil - else - options[:pretest] = File.exist?("#{ platform }_setup.sh") ? "source #{ platform }_setup.sh" : nil - end - #merge in project settings if they can be found here - yaml_options = YamlWrapper.new.load(main_filepath) - if (yaml_options[:paths]) - options[:add_path] = yaml_options[:paths][:tools] || [] +# Entry point +begin + diy_vendor_path = File.join( CEEDLING_APPCFG[:ceedling_vendor_path], 'diy/lib' ) + + # Construct all bootloader objects + # 1. Add full path to $LOAD_PATH to simplify objects.yml + # 2. Add vendored DIY to $LOAD_PATH so we can use it + # 3. Require DIY (used by Ceedling application too) + # 4. Perform object construction + dependency injection from bin/objects.yml + # 5. Remove all paths added to $LOAD_PATH + # (Main application will restore certain paths -- possibly updated by :which_ceedling) + $LOAD_PATH.unshift( + CEEDLING_APPCFG[:ceedling_lib_path], + diy_vendor_path + ) + + require 'diy' + bin_objects_filepath = File.join( ceedling_bin_path, 'objects.yml' ) + objects = {} # Empty initial hash to be redefined (fingers crossed) + objects = DIY::Context.from_yaml( File.read( bin_objects_filepath ) ) + objects.build_everything() + + # Extract objects shared between bootloader and application + # This prevents double instantiation and preserves object state in handoff + handoff_objects = {} + handoff = [ + :loginator, + :file_wrapper, + :yaml_wrapper, + :config_walkinator, + :system_wrapper, + :verbosinator + ] + CEEDLING_HANDOFF_OBJECTS = handoff_objects + handoff.each {|name| handoff_objects[name] = objects[name] } + + # Load Thor-based top-level CLI after: + # 1. CEEDLING_BIN load path set + # 2. `objects` hash filled with DIY output + # 3. CEEDLING_HANDOFF_OBJECTS global is set + require 'cli' + + # Remove all load paths we've relied on (main application will set up load paths again) + $LOAD_PATH.delete( ceedling_bin_path ) + $LOAD_PATH.delete( CEEDLING_APPCFG[:ceedling_lib_path] ) + $LOAD_PATH.delete( diy_vendor_path ) + + # Keep a copy of the command line for edge case CLI hacking (Thor consumes ARGV) + _ARGV = ARGV.clone + + # Especially on Windows, tell Thor & Rake how wide the terminal is + ENV['THOR_COLUMNS'] = CEEDLING_APPCFG[:terminal_width].to_s() + ENV['RAKE_COLUMNS'] = ENV['THOR_COLUMNS'] + + # + # NOTE: See comment block in cli.rb to understand CLI handling + # ------------------------------------------------------------ + # + + # Command line filtering hacks + # - Backwards compatibility to silently preserve Rake `-T` CLI handling + # - Add in common `--version` or `-v` version alias handling + # Note: This `if` logic is necessary to ensure any other argument lists of size 1 end up with Thor + if (ARGV.size() == 1) and (ARGV[0] == '-T' or ARGV[0] == '--version' or ARGV[0] == '-v') + case ARGV[0] + when '-T' + # Call Rake task listing handler w/ default handling of project file and mixins + objects[:cli_handler].rake_help( env:ENV, app_cfg:CEEDLING_APPCFG ) + + when '--version', '-v' + # Call Ceedling's version handler directly + objects[:cli_handler].version( ENV, CEEDLING_APPCFG ) + end + + # Run command line args through Thor (including "naked" Rake tasks) else - options[:add_path] = [] - end - options[:graceful_fail] = yaml_options[:graceful_fail] if yaml_options[:graceful_fail] - options[:which_ceedling] = yaml_options[:project][:which_ceedling] if (yaml_options[:project] && yaml_options[:project][:which_ceedling]) - options[:default_tasks] = yaml_options[:default_tasks] if yaml_options[:default_tasks] - - #sort through command line options - ARGV.each do |v| - case(v) - when /^(?:new|examples?|templates?)$/ - puts "\nOops. You called ceedling with argument '#{v}'.\n" + - " This is an operation that will create a new project... \n" + - " but it looks like you're already in a project. If you really \n" + - " want to do this, try moving to an empty folder.\n\n" - abort - when /^help$/ - options[:list_tasks] = true - when /^-T$/ - options[:list_tasks] = true - when /^--tasks$/ - options[:list_tasks] = true - when /^project:(\w+)/ - ENV['CEEDLING_USER_PROJECT_FILE'] = "#{$1}.yml" - when /^--test_case=(\w+)/ - ENV['CEEDLING_INCLUDE_TEST_CASE_NAME'] = $1 - when /^--exclude_test_case=(\w+)/ - ENV['CEEDLING_EXCLUDE_TEST_CASE_NAME'] = $1 - else - options[:args].push(v) - end + CeedlingTasks::CLI.start( ARGV, + { + :app_cfg => CEEDLING_APPCFG, + :objects => objects, + } + ) end - #add to the path - if (options[:add_path] && !options[:add_path].empty?) - path = ENV["PATH"] - options[:add_path].each do |p| - f = File.expand_path(File.dirname(__FILE__),p) - path = (f + options[:path_connector] + path) unless path.include? f - end - ENV["PATH"] = path +# Handle case of Thor application CLI failing to handle command line arguments. +rescue Thor::UndefinedCommandError + # Marrying Thor & Rake command line handling creates a gap (see comments in CLI handling). + # If a user enters only Rake build tasks at the command line followed by Thor flags, + # our Thor configuration doesn't see those flags. + # We catch the exception of unrecognized Thor commands here (i.e. any "naked" Rake tasks), + # and try again by forcing the Thor `build` command at the beginning of the command line. + # This way, our Thor handling will process option flags and properly pass the Rake tasks + # along as well. + + # Necessary nested exception handling + begin + CeedlingTasks::CLI.start( _ARGV.unshift( 'build' ), + { + :app_cfg => CEEDLING_APPCFG, + :objects => objects, + } + ) + rescue StandardError => ex + boom_handler( objects[:loginator], ex ) + exit(1) end - # Load Ceedling (either through the rakefile OR directly) - if (File.exist?("rakefile.rb")) - load 'rakefile.rb' - else - if (options[:which_ceedling] == 'gem') - require 'ceedling' - else - load "#{options[:which_ceedling]}/lib/ceedling.rb" - end - Ceedling.load_project - end - - Rake.application.standard_exception_handling do - if options[:list_tasks] - # Display helpful task list when requested. This required us to dig into Rake internals a bit - Rake.application.define_singleton_method(:name=) {|n| @name = n} - Rake.application.name = 'ceedling' - Rake.application.options.show_tasks = :tasks - Rake.application.options.show_task_pattern = /^(?!.*build).*$/ - Rake.application.display_tasks_and_comments() - else - task :default => options[:default_tasks] - - # Run our Tasks! - Rake.application.collect_command_line_tasks(options[:args]) - Rake.application.top_level - end - end - true -#=================================================================================================================== +# Bootloader boom handling +rescue StandardError => ex + boom_handler( objects[:loginator], ex ) + exit(1) end + diff --git a/bin/cli.rb b/bin/cli.rb new file mode 100644 index 000000000..af42ae6c3 --- /dev/null +++ b/bin/cli.rb @@ -0,0 +1,542 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'thor' +require 'ceedling/constants' # From Ceedling application + +## +## Command Line Handling +## ===================== +## +## OVERVIEW +## -------- +## Ceedling's command line handling marries Thor and Rake. Thor does not call +## Rake. Rather, a handful of command line conventions, edge case handling, +## and Thor features are stitched together to ensure a given command line is +## processed by Thor and/or Rake. +## +## Ceedling's command line is processed with these mechanisms: +## 1. Special / edge case hacking of ARGV directly. +## 2. Thor for all application commands and flags. +## 3. Handing off to Rake from either (1) or (2) for task listing or running +## build tasks. +## +## EDGE CASE HACKING +## ----------------- +## Special / edge cases: +## 1. Silent backwards compatibility support for Rake's `-T`. +## 2. Thor does not recognize "naked" Rake build tasks as application commands +## (`ceedling test:all` instead of `ceedling build test:all`). So, we catch +## this exception and then provide the command line back to Thor as a `build` +## command line. This also allows us to ensure Thor processes `build` option +## flags following naked build tasks that would otherwise be ignored if +## we simply passed a failing command line to Rake. +## +## THOR +## ---- +## Thor is configured or overridden with these special attributes: +## * The default task is `build`. This means that if the `build` keyword is +## omitted but Thor otherwise recognizes the command line (a `build` flag is +## the first item on the command line), it will process it as the `build` +## command. The build command takes flags and tasks. Tasks are handed off to +## Rake to process. If no `build` keyword is present and `build` flags come +## after tasks, Thor sees the command line as unhandled commands. +## * The PermissiveCLI code handles unrecognized command exception so as to +## eat the Thor complaint and re-throw the exception for edge case handling. +## +## NOTES +## ----- +## * Ultimately, any unrecognized command or task is processed by Rake, and +## Rake makes the complaint. +## + + +## +## This Class +## ========== +## +## The nature of Thor more-or-less requires this class to be used as a class +## and not as an insantiated object. This shows up in a variety of ways: +## * The calling convention is `CeedlingTasks::CLI.start( ARGV )` +## * Many of the methods necessary to configure the CLI class are class +## methods in Thor and are called that way. +## +## The nature of Thor both requires and allows for some slightly ugly or +## brittle code -- relying on globals, etc. +## +## Because of this, care has been taken that this class contains as little +## logic as possible and is the funnel for any and all necessary global +## references and other little oddball needs. +## + + +# Special handling to prevent Thor from barfing on unrecognized CLI arguments +# (i.e. Rake tasks) +module PermissiveCLI + def self.extended(base) + super + base.check_unknown_options! + end + + # Redefine the Thor CLI entrypoint and exception handling + def start(args, config={}) + begin + # Copy args as Thor changes them within the call chain of dispatch() + _args = args.clone() + + # Call Thor's handlers as it does in start() + config[:shell] ||= Thor::Base.shell.new + dispatch(nil, args, nil, config) + + # Handle undefined commands at top-level and for `help ` + rescue Thor::UndefinedCommandError => ex + # Handle `help` for an argument that is not an application command such as `new` or `build` + if _args[0].downcase() == 'help' + + # Raise fatal StandardError to differentiate from UndefinedCommandError + msg = "Argument '#{_args[1]}' is not a recognized application command with detailed help. " + + "It may be a build / plugin task without detailed help or simply a goof." + raise( msg ) + + # Otherwise, eat unhandled command errors + else + # - No error message + # - No `exit()` + # - Re-raise to allow special, external CLI handling logic + raise ex + end + end + end +end + +module CeedlingTasks + + VERBOSITY_NORMAL = 'normal' + VERBOSITY_DEBUG = 'debug' + + DOC_LOCAL_FLAG = "Install Ceedling plus supporting tools to vendor/" + + DOC_DOCS_FLAG = "Copy all documentation to docs/ subdirectory of project" + + DOC_PROJECT_FLAG = "Loads the filepath as your base project configuration" + + DOC_MIXIN_FLAG = "Merges the configuration mixin by name or filepath." + + LONGDOC_LOCAL_FLAG = "`--local` copies Ceedling and its dependencies to a vendor/ + subdirectory in the root of the project. It also installs a + platform-appropriate executable script `ceedling` at the root of the + project." + + LONGDOC_MIXIN_FLAG = "`--mixin` merges the specified configuration mixin. This + flag may be repeated for multiple mixins. A simple mixin name initiates a + lookup from within mixin load paths in your project file and among built-in + mixins. A filepath and/or filename (with extension) will instead merge the + specified YAML file. See documentation for complete details. + \x5> --mixin my_compiler --mixin my/path/mixin.yml" + + # Intentionally disallowed Linux/Unix/Windows filename characters to minimize the chance + # of mistakenly filtering various string-base flags missing a parmeter + CLI_MISSING_PARAMETER_DEFAULT = "/<>\\||*" + + class CLI < Thor + include Thor::Actions + extend PermissiveCLI + + # Ensure we bail out with non-zero exit code if the command line is wrong + def self.exit_on_failure?() true end + + # Allow `build` to be omitted in command line + default_command( :build ) + + # Intercept construction to extract configuration and injected dependencies + def initialize(args, config, options) + super(args, config, options) + + @app_cfg = options[:app_cfg] + @handler = options[:objects][:cli_handler] + + @loginator = options[:objects][:loginator] + + # Set the name for labelling CLI interactions + CLI::package_name( @loginator.decorate( 'Ceedling application', LogLabels::TITLE ) ) + end + + + # Override Thor help to list Rake tasks as well + desc "help [COMMAND]", "Describe available commands and list build operations" + method_option :project, :type => :string, :default => nil, :lazy_default => CLI_MISSING_PARAMETER_DEFAULT, :aliases => ['-p'], :desc => DOC_PROJECT_FLAG + method_option :mixin, :type => :string, :default => [], :repeatable => true, :aliases => ['-m'], :desc => DOC_MIXIN_FLAG + method_option :debug, :type => :boolean, :default => false, :hide => true + long_desc( CEEDLING_HANDOFF_OBJECTS[:loginator].sanitize( + <<-LONGDESC + `ceedling help` provides summary help for all available application commands + and build tasks. + + COMMAND is optional and will produce detailed help for a specific application command -- + not a build or plugin task, however. + + `ceedling help` also lists the available build operations from loading your + project configuration. Optionally, a project filepath and/or mixins may be + provided to load a different project configuration than the default. + + Notes on Optional Flags: + + ‱ #{LONGDOC_MIXIN_FLAG} + LONGDESC + ) ) + def help(command=nil) + @handler.validate_string_param( + options[:project], + CLI_MISSING_PARAMETER_DEFAULT, + "--project is missing a required filepath parameter" + ) + + # Get unfrozen copies so we can add / modify + _options = options.dup() + _options[:project] = options[:project].dup() if !options[:project].nil? + _options[:mixin] = [] + options[:mixin].each {|mixin| _options[:mixin] << mixin.dup() } + + _options[:verbosity] = options[:debug] ? VERBOSITY_DEBUG : nil + + # Call application help with block to execute Thor's built-in help in the help logic + @handler.app_help( ENV, @app_cfg, _options, command ) { |command| super(command) } + end + + + desc "new NAME [DEST]", "Create a new project structure at optional DEST path" + method_option :local, :type => :boolean, :default => false, :desc => DOC_LOCAL_FLAG + method_option :docs, :type => :boolean, :default => false, :desc => DOC_DOCS_FLAG + method_option :configs, :type => :boolean, :default => true, :desc => "Install starter project file in project root" + method_option :force, :type => :boolean, :default => false, :desc => "Ignore any existing project and recreate destination" + method_option :debug, :type => :boolean, :default => false, :hide => true + method_option :gitsupport, :type => :boolean, :default => false, :desc => "Create .gitignore / .gitkeep files for convenience" + long_desc( CEEDLING_HANDOFF_OBJECTS[:loginator].sanitize( + <<-LONGDESC + `ceedling new` creates a new project structure. + + NAME is required and will be the containing directory for the new project. + + DEST is an optional directory path for the new project (e.g. /). + The default is your working directory. Nonexistent paths will be created. + + Notes on Optional Flags: + + ‱ #{LONGDOC_LOCAL_FLAG} + + ‱ `--force` completely destroys anything found in the target path for the + new project. + LONGDESC + ) ) + def new(name, dest=nil) + require 'version' # lib/version.rb for TAG constant + + # Get unfrozen copies so we can add / modify + _options = options.dup() + _dest = dest.dup() if !dest.nil? + + _options[:verbosity] = options[:debug] ? VERBOSITY_DEBUG : nil + + @handler.new_project( ENV, @app_cfg, Ceedling::Version::TAG, _options, name, _dest ) + end + + desc "upgrade PATH", "Upgrade vendored installation of Ceedling for a project at PATH" + method_option :project, :type => :string, :default => DEFAULT_PROJECT_FILENAME, :lazy_default => CLI_MISSING_PARAMETER_DEFAULT, :desc => "Project filename" + method_option :debug, :type => :boolean, :default => false, :hide => true + long_desc( CEEDLING_HANDOFF_OBJECTS[:loginator].sanitize( + <<-LONGDESC + `ceedling upgrade` updates an existing project. + + PATH is required and should be the root of the project to upgrade. + + This command only meaningfully operates on projects wth a local vendored copy + of Ceedling (in /vendor/) and optional documentation (in + /docs/). + + Running this command replaces vendored Ceedling with the version running + this command. If docs are found, they will be replaced. + + A basic check for project existence looks for vendored ceedlng and a project + configuration file. + + Notes on Optional Flags: + + ‱ `--project` specifies a filename (optionally with leading path) for the + project configuration file used in the project existence check. Otherwise, + the default ./#{DEFAULT_PROJECT_FILENAME} at the root of the project is + checked. + LONGDESC + ) ) + def upgrade(path) + @handler.validate_string_param( + options[:project], + CLI_MISSING_PARAMETER_DEFAULT, + "--project is missing a required filename parameter" + ) + + # Get unfrozen copies so we can add / modify + _options = options.dup() + _options[:project] = options[:project].dup() + _path = path.dup() + + _options[:verbosity] = options[:debug] ? VERBOSITY_DEBUG : nil + + @handler.upgrade_project( ENV, @app_cfg, _options, _path ) + end + + + desc "build [TASKS...]", "Run build tasks (`build` keyword not required)" + method_option :project, :type => :string, :default => nil, :lazy_default => CLI_MISSING_PARAMETER_DEFAULT, :aliases => ['-p'], :desc => DOC_PROJECT_FLAG + method_option :mixin, :type => :string, :default => [], :repeatable => true, :aliases => ['-m'], :desc => DOC_MIXIN_FLAG + method_option :verbosity, :type => :string, :default => VERBOSITY_NORMAL, :lazy_default => CLI_MISSING_PARAMETER_DEFAULT, :aliases => ['-v'], + :desc => "Sets logging level" + method_option :log, :type => :boolean, :default => nil, + :desc => "Enable logging to /#{DEFAULT_BUILD_LOGS_PATH}/#{DEFAULT_CEEDLING_LOGFILE}" + # :lazy_default allows us to check for missing parameters (if no filepath given Thor unhelpfully provides the flag name as its value) + method_option :logfile, :type => :string, :aliases => ['-l'], :default => '', :lazy_default => CLI_MISSING_PARAMETER_DEFAULT, + :desc => "Enables logging to specified filepath" + method_option :graceful_fail, :type => :boolean, :default => nil, :desc => "Force exit code of 0 for unit test failures" + method_option :test_case, :type => :string, :default => '', :lazy_default => CLI_MISSING_PARAMETER_DEFAULT, + :desc => "Filter for individual unit test names" + method_option :exclude_test_case, :type => :string, :default => '', :lazy_default => CLI_MISSING_PARAMETER_DEFAULT, + :desc => "Prevent matched unit test names from running" + # Include for consistency with other commands (override --verbosity) + method_option :debug, :type => :boolean, :default => false, :hide => true + long_desc( CEEDLING_HANDOFF_OBJECTS[:loginator].sanitize( + <<-LONGDESC + `ceedling build` executes build tasks created from your project configuration. + + NOTE: `build` is not required to run tasks. The following are equivalent: + \x5 > ceedling test:all + \x5 > ceedling build test:all + + TASKS are zero or more build operations created from your project configuration. + If no tasks are provided, built-in default tasks or your :project ↳ + :default_tasks will be executed. + + Notes on Optional Flags: + + ‱ #{LONGDOC_MIXIN_FLAG} + + ‱ `--test-case` and its inverse `--exclude-test-case` set test case name + matchers to run only a subset of the unit test suite. See docs for full details. + + ‱ `If --log and --logfile are both specified, --logfile will set the log file path. + If --no-log and --logfile are both specified, no logging will occur. + LONGDESC + ) ) + def build(*tasks) + @handler.validate_string_param( + options[:project], + CLI_MISSING_PARAMETER_DEFAULT, + "--project is missing a required filepath parameter" + ) + + @handler.validate_string_param( + options[:verbosity], + CLI_MISSING_PARAMETER_DEFAULT, + "--verbosity is missing a required parameter" + ) + + @handler.validate_string_param( + options[:logfile], + CLI_MISSING_PARAMETER_DEFAULT, + "--logfile is missing a required filepath parameter" + ) + + @handler.validate_string_param( + options[:test_case], + CLI_MISSING_PARAMETER_DEFAULT, + "--test-case is missing a required test case name parameter" + ) + + @handler.validate_string_param( + options[:exclude_test_case], + CLI_MISSING_PARAMETER_DEFAULT, + "--exclude-test-case is missing a required test case name parameter" + ) + + # Get unfrozen copies so we can add / modify + _options = options.dup() + _options[:project] = options[:project].dup() if !options[:project].nil? + _options[:mixin] = [] + options[:mixin].each {|mixin| _options[:mixin] << mixin.dup() } + _options[:verbosity] = VERBOSITY_DEBUG if options[:debug] + _options[:logfile] = options[:logfile].dup() + + @handler.build( env:ENV, app_cfg:@app_cfg, options:_options, tasks:tasks ) + end + + + desc "dumpconfig FILEPATH [SECTIONS...]", "Process project configuration and write final config to a YAML file" + method_option :project, :type => :string, :default => nil, :lazy_default => CLI_MISSING_PARAMETER_DEFAULT, :aliases => ['-p'], + :desc => DOC_PROJECT_FLAG + method_option :mixin, :type => :string, :default => [], :repeatable => true, :aliases => ['-m'], :desc => DOC_MIXIN_FLAG + method_option :app, :type => :boolean, :default => true, :desc => "Runs Ceedling application and its config manipulations" + method_option :debug, :type => :boolean, :default => false, :hide => true + long_desc( CEEDLING_HANDOFF_OBJECTS[:loginator].sanitize( + <<-LONGDESC + `ceedling dumpconfig` loads your project configuration, including all manipulations & merges, + and writes the final config to a YAML file. + + FILEPATH is a required path to a destination YAML file. A nonexistent path will be created. + + SECTIONS is an optional config “path” that extracts a portion of a configuration. The + top-level YAML container will be the path’s last element. + The following example will produce config.yml containing ':test_compiler: {...}'. + \x5> ceedling dumpconfig my/path/config.yml tools test_compiler + + Notes on Optional Flags: + + ‱ #{LONGDOC_MIXIN_FLAG} + + ‱ `--app` loads various settings, merges defaults, loads plugin config changes, and validates + the configuration. Disabling it dumps project config after any mixins but before any + application manipulations. + LONGDESC + ) ) + def dumpconfig(filepath, *sections) + @handler.validate_string_param( + options[:project], + CLI_MISSING_PARAMETER_DEFAULT, + "--project is missing a required filepath parameter" + ) + + # Get unfrozen copies so we can add / modify + _options = options.dup() + _options[:project] = options[:project].dup() if !options[:project].nil? + _options[:mixin] = [] + options[:mixin].each {|mixin| _options[:mixin] << mixin.dup() } + _filepath = filepath.dup() + + _options[:verbosity] = options[:debug] ? VERBOSITY_DEBUG : nil + + @handler.dumpconfig( ENV, @app_cfg, _options, _filepath, sections ) + end + + + desc "environment", "List all configured environment variable names with values." + method_option :project, :type => :string, :default => nil, :lazy_default => CLI_MISSING_PARAMETER_DEFAULT, :aliases => ['-p'], + :desc => DOC_PROJECT_FLAG + method_option :mixin, :type => :string, :default => [], :repeatable => true, :aliases => ['-m'], :desc => DOC_MIXIN_FLAG + method_option :debug, :type => :boolean, :default => false, :hide => true + long_desc( CEEDLING_HANDOFF_OBJECTS[:loginator].sanitize( + <<-LONGDESC + `ceedling environment` displays all environment variables that have been set for project use. + + Notes on Optional Flags: + + ‱ #{LONGDOC_MIXIN_FLAG} + LONGDESC + ) ) + def environment() + @handler.validate_string_param( + options[:project], + CLI_MISSING_PARAMETER_DEFAULT, + "--project is missing a required filepath parameter" + ) + + # Get unfrozen copies so we can add / modify + _options = options.dup() + _options[:project] = options[:project].dup() if !options[:project].nil? + _options[:mixin] = [] + options[:mixin].each {|mixin| _options[:mixin] << mixin.dup() } + + _options[:verbosity] = options[:debug] ? VERBOSITY_DEBUG : nil + + @handler.environment( ENV, @app_cfg, _options ) + end + + + desc "examples", "List available example projects" + method_option :debug, :type => :boolean, :default => false, :hide => true + long_desc( CEEDLING_HANDOFF_OBJECTS[:loginator].sanitize( + <<-LONGDESC + `ceedling examples` lists the names of the example projects that come packaged with Ceedling. + + The output of this list is most useful when used by the `ceedling example` (no ‘s’) command + to extract an example project to your filesystem. + LONGDESC + ) ) + def examples() + # Get unfrozen copies so we can add / modify + _options = options.dup() + + _options[:verbosity] = options[:debug] ? VERBOSITY_DEBUG : nil + + @handler.list_examples( ENV, @app_cfg, _options ) + end + + + desc "example NAME [DEST]", "Create named example project in optional DEST path" + method_option :local, :type => :boolean, :default => false, :desc => DOC_LOCAL_FLAG + method_option :docs, :type => :boolean, :default => false, :desc => DOC_DOCS_FLAG + method_option :debug, :type => :boolean, :default => false, :hide => true + long_desc( CEEDLING_HANDOFF_OBJECTS[:loginator].sanitize( + <<-LONGDESC + `ceedling example` extracts the named example project from within Ceedling to + your filesystem. + + NAME is required to specify the example to extract. A list of example projects + is available with the `examples` command. NAME will be the containing directory + for the extracted project. + + DEST is an optional containing directory path (ex: /). The default + is your working directory. A nonexistent path will be created. + + Notes on Optional Flags: + + ‱ #{LONGDOC_LOCAL_FLAG} + + NOTE: `example` is destructive. If the destination path is a previoulsy created + example project, `ceedling example` will overwrite the contents. + LONGDESC + ) ) + def example(name, dest=nil) + # Get unfrozen copies so we can add / modify + _options = options.dup() + _dest = dest.dup() if !dest.nil? + + _options[:verbosity] = options[:debug] ? VERBOSITY_DEBUG : nil + + @handler.create_example( ENV, @app_cfg, _options, name, _dest ) + end + + + desc "version", "Display version details of Ceedling components" + long_desc( CEEDLING_HANDOFF_OBJECTS[:loginator].sanitize( + <<-LONGDESC + `ceedling version` displays the version details of Ceedling and its supporting + frameworks along with Ceedling’s installation paths. + + Ceedling contains launcher and application components. The launcher + handles set up, loading your project configuration, and processing your + command line. The application runs your build and plugin tasks. The + launcher hands off to the application. The two components are not + necessarily from the same installation or of the same version. Local + vendoring options, the WHICH_CEEDLING environment variable, and more can + cause the Ceedling launcher to load a Ceedling application that is run + from a different path than the launcher. + + If the launcher and application are from different locations, the version + output lists details for both. If they are from the same location, only a + single Ceedling version is provided. + + NOTES: + + ‱ `version` does not load your project file. + + ‱ The build frameworks Unity, CMock, and CException are always sourced from + the Ceedling application. + LONGDESC + ) ) + def version() + @handler.version( ENV, @app_cfg ) + end + + end +end diff --git a/bin/cli_handler.rb b/bin/cli_handler.rb new file mode 100644 index 000000000..7b2d4a58d --- /dev/null +++ b/bin/cli_handler.rb @@ -0,0 +1,480 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'thor' +require 'mixins' # Built-in Mixins +require 'ceedling/constants' # From Ceedling application +require 'versionator' # Outisde DIY context + +class CliHandler + + constructor :configinator, :projectinator, :cli_helper, :path_validator, :actions_wrapper, :loginator + + # Override to prevent exception handling from walking & stringifying the object variables. + # Object variables are lengthy and produce a flood of output. + def inspect + return this.class.name + end + + + def setup() + # Aliases + @helper = @cli_helper + @actions = @actions_wrapper + end + + + def validate_string_param( param, missing, message ) + if param == missing + raise Thor::Error.new( message ) + end + end + + + # Thor application help + Rake help (if available) + def app_help(env, app_cfg, options, command, &thor_help) + verbosity = @helper.set_verbosity( options[:verbosity] ) + + # If help requested for a command, show it and skip listing build tasks + if !command.nil? + # Block handler + thor_help.call( command ) if block_given? + return + end + + # Display Thor-generated help listing + thor_help.call( command ) if block_given? + + # If it was help for a specific command, we're done + return if !command.nil? + + # If project configuration is available, also display Rake tasks + @path_validator.standardize_paths( options[:project], *options[:mixin], ) + return if !@projectinator.config_available?( filepath:options[:project], env:env ) + + list_rake_tasks( + env:env, + app_cfg: app_cfg, + filepath: options[:project], + mixins: options[:mixin], + # Silent Ceedling loading unless debug verbosity + silent: !(verbosity == Verbosity::DEBUG) + ) + end + + + # Public to be used by `-T` ARGV hack handling + def rake_help(env:, app_cfg:) + @helper.set_verbosity() # Default to normal + + list_rake_tasks( env:env, app_cfg:app_cfg ) + end + + + def new_project(env, app_cfg, ceedling_tag, options, name, dest) + @helper.set_verbosity( options[:verbosity] ) + + @path_validator.standardize_paths( dest ) + + # If destination is nil, reassign it to name + # Otherwise, join the destination and name into a new path + dest = dest.nil? ? ('./' + name) : File.join( dest, name ) + + # Check for existing project (unless --force) + if @helper.project_exists?( dest, :|, DEFAULT_PROJECT_FILENAME, 'src', 'test' ) + msg = "It appears a project already exists at #{dest}/. Use --force to destroy it and create a new project." + raise msg + end unless options[:force] + + # Update app_cfg paths (ignore return values) + @helper.which_ceedling?( env:env, app_cfg:app_cfg ) + + # Thor Actions for project tasks use paths in relation to this path + ActionsWrapper.source_root( app_cfg[:ceedling_root_path] ) + + # Blow away any existing directories and contents if --force + @actions.remove_dir( dest ) if options[:force] + + # Create blank directory structure + ['.', 'src', 'test', 'test/support'].each do |path| + @actions._empty_directory( File.join( dest, path) ) + end + + # Vendor the tools and install command line helper scripts + @helper.vendor_tools( app_cfg[:ceedling_root_path], dest ) if options[:local] + + # Copy in documentation + @helper.copy_docs( app_cfg[:ceedling_root_path], dest ) if options[:docs] + + # Copy / set up project file + @helper.create_project_file( dest, options[:local], ceedling_tag ) if options[:configs] + + # Copy Git Ignore file + if options[:gitsupport] + @actions._copy_file( + File.join( 'assets', 'default_gitignore' ), + File.join( dest, '.gitignore' ), + :force => true + ) + @actions._touch_file( File.join( dest, 'test/support', '.gitkeep') ) + end + + @loginator.log() # Blank line + @loginator.log( "New project '#{name}' created at #{dest}/\n", Verbosity::NORMAL, LogLabels::TITLE ) + end + + + def upgrade_project(env, app_cfg, options, path) + @helper.set_verbosity( options[:verbosity] ) + + @path_validator.standardize_paths( path, options[:project] ) + + # Check for existing project + if !@helper.project_exists?( path, :&, options[:project], 'vendor/ceedling/lib/version.rb' ) + msg = "Could not find an existing project at #{path}/." + raise msg + end + + which, _ = @helper.which_ceedling?( env:env, app_cfg:app_cfg ) + if (which == :gem) + msg = "Project configuration specifies the Ceedling gem, not vendored Ceedling" + @loginator.log( msg, Verbosity::NORMAL, LogLabels::NOTICE ) + end + + # Thor Actions for project tasks use paths in relation to this path + ActionsWrapper.source_root( app_cfg[:ceedling_root_path] ) + + # Recreate vendored tools + vendor_path = File.join( path, 'vendor', 'ceedling' ) + @actions.remove_dir( vendor_path ) + @helper.vendor_tools( app_cfg[:ceedling_root_path], path ) + + # Recreate documentation if we find docs/ subdirectory + docs_path = File.join( path, 'docs' ) + founds_docs = @helper.project_exists?( path, :&, File.join( 'docs', 'CeedlingPacket.md' ) ) + if founds_docs + @actions.remove_dir( docs_path ) + @helper.copy_docs( app_cfg[:ceedling_root_path], path ) + end + + @loginator.log() # Blank line + @loginator.log( "Upgraded project at #{path}/\n", Verbosity::NORMAL, LogLabels::TITLE ) + end + + + def build(env:, app_cfg:, options:{}, tasks:) + @helper.set_verbosity( options[:verbosity] ) + + @path_validator.standardize_paths( options[:project], options[:logfile], *options[:mixin] ) + + _, config = @configinator.loadinate( builtin_mixins:BUILTIN_MIXINS, filepath:options[:project], mixins:options[:mixin], env:env ) + + default_tasks = @configinator.default_tasks( config:config, default_tasks:app_cfg[:default_tasks] ) + + @helper.process_testcase_filters( + config: config, + include: options[:test_case], + exclude: options[:exclude_test_case], + tasks: tasks, + default_tasks: default_tasks + ) + + logging_path = @helper.process_logging_path( config ) + log_filepath = @helper.process_log_filepath( logging_path, options[:log], options[:logfile] ) + + @loginator.log( " > Logfile: #{log_filepath}" ) if !log_filepath.empty? + + # Save references + app_cfg.set_project_config( config ) + app_cfg.set_logging_path( logging_path ) + app_cfg.set_log_filepath( log_filepath ) + app_cfg.set_include_test_case( options[:test_case] ) + app_cfg.set_exclude_test_case( options[:exclude_test_case] ) + + # Set graceful_exit from command line & configuration options + app_cfg.set_tests_graceful_fail( + @helper.process_graceful_fail( + config: config, + cmdline_graceful_fail: options[:graceful_fail], + tasks: tasks, + default_tasks: default_tasks + ) + ) + + # Enable setup / operations duration logging in Rake context + app_cfg.set_build_tasks( @helper.build_or_plugin_task?( tasks:tasks, default_tasks:default_tasks ) ) + + _, path = @helper.which_ceedling?( env:env, config:config, app_cfg:app_cfg ) + + # Log Ceedling Application version information + _version = Versionator.new( + app_cfg[:ceedling_root_path], + app_cfg[:ceedling_vendor_path] + ) + + version = <<~VERSION + Application & Build Frameworks + Ceedling => #{_version.ceedling_build} + CMock => #{_version.cmock_tag} + Unity => #{_version.unity_tag} + CException => #{_version.cexception_tag} + VERSION + + @loginator.log( '', Verbosity::OBNOXIOUS ) + @loginator.log( version, Verbosity::OBNOXIOUS, LogLabels::CONSTRUCT ) + + @helper.load_ceedling( + config: config, + rakefile_path: path, + default_tasks: default_tasks + ) + + # Hand Rake tasks off to be executed + @helper.run_rake_tasks( tasks ) + end + + + def dumpconfig(env, app_cfg, options, filepath, sections) + @helper.set_verbosity( options[:verbosity] ) + + @path_validator.standardize_paths( filepath, options[:project], *options[:mixin] ) + + _, config = @configinator.loadinate( builtin_mixins:BUILTIN_MIXINS, filepath:options[:project], mixins:options[:mixin], env:env ) + + # Exception handling to ensure we dump the configuration regardless of config validation errors + begin + # If enabled, process the configuration through Ceedling automatic settings, defaults, plugins, etc. + if options[:app] + default_tasks = @configinator.default_tasks( config:config, default_tasks:app_cfg[:default_tasks] ) + + # Save references + app_cfg.set_project_config( config ) + app_cfg.set_logging_path( @helper.process_logging_path( config ) ) + + _, path = @helper.which_ceedling?( env:env, config:config, app_cfg:app_cfg ) + + config = @helper.load_ceedling( + config: config, + rakefile_path: path, + default_tasks: default_tasks + ) + else + @loginator.log( " > Skipped loading Ceedling application", Verbosity::OBNOXIOUS ) + end + ensure + @helper.dump_yaml( config, filepath, sections ) + + @loginator.log() # Blank line + @loginator.log( "Dumped project configuration to #{filepath}\n", Verbosity::NORMAL, LogLabels::TITLE ) + end + end + + + def environment(env, app_cfg, options) + @helper.set_verbosity( options[:verbosity] ) + + @path_validator.standardize_paths( options[:project], *options[:mixin] ) + + _, config = @configinator.loadinate( builtin_mixins:BUILTIN_MIXINS, filepath:options[:project], mixins:options[:mixin], env:env ) + + # Save references + app_cfg.set_project_config( config ) + app_cfg.set_logging_path( @helper.process_logging_path( config ) ) + + _, path = @helper.which_ceedling?( env:env, config:config, app_cfg:app_cfg ) + + config = @helper.load_ceedling( + config: config, + rakefile_path: path + ) + + env_list = [] + + # Process external environment -- filter for Ceedling variables + env.each do |var, value| + next if !(var =~ /ceedling/i) + name = var.to_s + env_list << "#{name}: \"#{value}\"" + end + + # Process environment created by configuration + config[:environment].each do |env| + env.each_key do |key| + name = key.to_s.upcase + env_list << "#{name}: \"#{env[key]}\"" + end + end + + output = "Environment variables:\n" + + env_list.sort.each do |line| + output << " ‱ #{line}\n" + end + + @loginator.log() # Blank line + @loginator.log( output + "\n", Verbosity::NORMAL, LogLabels::TITLE ) + end + + + def list_examples(env, app_cfg, options) + @helper.set_verbosity( options[:verbosity] ) + + # Process which_ceedling for app_cfg modifications but ignore return values + @helper.which_ceedling?( env:env, app_cfg:app_cfg ) + + examples = @helper.lookup_example_projects( app_cfg[:ceedling_examples_path] ) + + raise( "No examples projects found") if examples.empty? + + output = "Available example projects:\n" + + examples.each {|example| output << " ‱ #{example}\n" } + + @loginator.log() # Blank line + @loginator.log( output + "\n", Verbosity::NORMAL, LogLabels::TITLE ) + end + + + def create_example(env, app_cfg, options, name, dest) + @helper.set_verbosity( options[:verbosity] ) + + @path_validator.standardize_paths( dest ) + + # Process which_ceedling for app_cfg modifications but ignore return values + @helper.which_ceedling?( env:env, app_cfg:app_cfg ) + + examples = @helper.lookup_example_projects( app_cfg[:ceedling_examples_path] ) + + if !examples.include?( name ) + raise( "No example project '#{name}' could be found" ) + end + + # If destination is nil, reassign it to name + # Otherwise, join the destination and name into a new path + dest = dest.nil? ? ('./' + name) : File.join( dest, name ) + + dest_src = File.join( dest, 'src' ) + dest_test = File.join( dest, 'test' ) + dest_mixin = File.join( dest, 'mixin' ) + dest_project = File.join( dest, DEFAULT_PROJECT_FILENAME ) + dest_readme = File.join( dest, 'README.md' ) + + # Thor Actions for project tasks use paths in relation to this path + ActionsWrapper.source_root( app_cfg[:ceedling_root_path] ) + + @actions._directory( "examples/#{name}/src", dest_src, :force => true ) + @actions._directory( "examples/#{name}/test", dest_test, :force => true ) + @actions._directory( "examples/#{name}/mixin", dest_mixin, :force => true ) + @actions._copy_file( "examples/#{name}/#{DEFAULT_PROJECT_FILENAME}", dest_project, :force => true ) + @actions._copy_file( "examples/#{name}/README.md", dest_readme, :force => true ) + + # Vendor the tools and install command line helper scripts + @helper.vendor_tools( app_cfg[:ceedling_root_path], dest ) if options[:local] + + # Copy in documentation + @helper.copy_docs( app_cfg[:ceedling_root_path], dest ) if options[:docs] + + @loginator.log() # Blank line + @loginator.log( "Example project '#{name}' created at #{dest}/\n", Verbosity::NORMAL, LogLabels::TITLE ) + end + + + def version(env, app_cfg) + # Versionator is not needed to persist. So, it's not built in the DIY collection. + + @helper.set_verbosity() # Default to normal + + # Ceedling bootloader + launcher = Versionator.new( app_cfg[:ceedling_root_path] ) + + # This call updates Ceedling paths in app_cfg if which Ceedling has been modified + @helper.which_ceedling?( env:env, app_cfg:app_cfg ) + + # Ceedling application + application = Versionator.new( + app_cfg[:ceedling_root_path], + app_cfg[:ceedling_vendor_path] + ) + + # Blank Ceedling version block to be built out conditionally below + ceedling = nil + + # A simple Ceedling version block because launcher and application are the same + if launcher.ceedling_install_path == application.ceedling_install_path + ceedling = <<~CEEDLING + Ceedling => #{application.ceedling_build} + ---------------------- + #{application.ceedling_install_path + '/'} + CEEDLING + + # Full Ceedling version block because launcher and application are not the same + else + ceedling = <<~CEEDLING + Ceedling Launcher => #{launcher.ceedling_build} + ---------------------- + #{launcher.ceedling_install_path + '/'} + + Ceedling App => #{application.ceedling_build} + ---------------------- + #{application.ceedling_install_path + '/'} + CEEDLING + end + + build_frameworks = <<~BUILD_FRAMEWORKS + Build Frameworks + ---------------------- + CMock => #{application.cmock_tag} + Unity => #{application.unity_tag} + CException => #{application.cexception_tag} + BUILD_FRAMEWORKS + + # Assemble version details + version = ceedling + "\n" + build_frameworks + + # Add some indent + version = version.split( "\n" ).map {|line| ' ' + line}.join( "\n" ) + + # Add a header + version = "Welcome to Ceedling!\n\n" + version + + @loginator.log( version, Verbosity::NORMAL, LogLabels::TITLE ) + end + + + ### Private ### + + private + + def list_rake_tasks(env:, app_cfg:, filepath:nil, mixins:[], silent:false) + _, config = + @configinator.loadinate( + builtin_mixins:BUILTIN_MIXINS, + filepath: filepath, + mixins: mixins, + env: env, + silent: silent + ) + + # Save reference to loaded configuration + app_cfg.set_project_config( config ) + app_cfg.set_logging_path( @helper.process_logging_path( config ) ) + + _, path = @helper.which_ceedling?( env:env, config:config, app_cfg:app_cfg ) + + @helper.load_ceedling( + config: config, + rakefile_path: path, + default_tasks: app_cfg[:default_tasks] + ) + + msg = "Ceedling build & plugin tasks:\n(Parameterized tasks tend to need enclosing quotes or escape sequences in most shells)" + @loginator.log( msg, Verbosity::NORMAL, LogLabels::TITLE ) + + @helper.print_rake_tasks() + end + +end diff --git a/bin/cli_helper.rb b/bin/cli_helper.rb new file mode 100644 index 000000000..25c50b50f --- /dev/null +++ b/bin/cli_helper.rb @@ -0,0 +1,507 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'app_cfg' + +# From Ceedling application +require 'ceedling/constants' +require 'ceedling/exceptions' + +class CliHelper + + constructor :file_wrapper, :actions_wrapper, :config_walkinator, :path_validator, :loginator, :system_wrapper + + def setup + # Aliases + @actions = @actions_wrapper + end + + + def project_exists?( path, op, *components ) + exists = [] + + components.each do |f| + _path = File.join( path, f ) + exists << (@file_wrapper.exist?( _path ) or @file_wrapper.directory?( _path )) + end + + return exists.reduce(op) + end + + + def create_project_file(dest, local, ceedling_tag) + project_filepath = File.join( dest, DEFAULT_PROJECT_FILENAME ) + source_filepath = '' + + if local + source_filepath = File.join( 'assets', 'project_with_guts.yml' ) + else + source_filepath = File.join( 'assets', 'project_as_gem.yml' ) + end + + # Clone the project file + @actions._copy_file( source_filepath, project_filepath, :force => true) + # Silently update internal version + @actions._gsub_file( + project_filepath, + /:ceedling_version:\s+'\?'/, + ":ceedling_version: #{ceedling_tag}", + :verbose => false + ) + end + + + # Returns two value: (1) symbol :gem or :path and (2) path for Ceedling installation + def which_ceedling?(env:, config:{}, app_cfg:) + # Determine which Ceedling we're running (in priority) + # 1. If there's an environment variable set, validate it, and return :gem or a path + # 2. If :project ↳ :which_ceedling exists in the config, validate it, and return :gem or a path + # 3. If there's a vendor/ceedling/ path in our working directory, return it as a path + # 4. If nothing is set, default to :gem and return it + # 5. Update app_cfg paths if not the gem + + # Nil for prioritized case checking logic blocks that follow + which_ceedling = nil + + # Environment variable + if !env['WHICH_CEEDLING'].nil? + @loginator.log( " > Set which Ceedling using environment variable WHICH_CEEDLING", Verbosity::OBNOXIOUS ) + which_ceedling = env['WHICH_CEEDLING'].strip() + which_ceedling = :gem if (which_ceedling.casecmp( 'gem' ) == 0) + end + + # Configuration file + if which_ceedling.nil? + value, _ = @config_walkinator.fetch_value( :project, :which_ceedling, hash:config ) + if !value.nil? + which_ceedling = value.strip() + @loginator.log( " > Set which Ceedling from config :project ↳ :which_ceedling => #{which_ceedling}", Verbosity::OBNOXIOUS ) + which_ceedling = :gem if (which_ceedling.casecmp( 'gem' ) == 0) + end + end + + # Working directory + if which_ceedling.nil? + if @file_wrapper.directory?( 'vendor/ceedling' ) + which_ceedling = 'vendor/ceedling' + @loginator.log( " > Set which Ceedling to be vendored installation", Verbosity::OBNOXIOUS ) + end + end + + # Default to gem + if which_ceedling.nil? + which_ceedling = :gem + @loginator.log( " > Defaulting to running Ceedling from Gem", Verbosity::OBNOXIOUS ) + end + + # If we're launching from the gem, return :gem and initial Rakefile path + if which_ceedling == :gem + @loginator.log( " > Launching Ceedling from #{app_cfg[:ceedling_root_path]}/", Verbosity::OBNOXIOUS ) + return which_ceedling, app_cfg[:ceedling_rakefile_filepath] + end + + # Otherwise, handle which_ceedling as a base path + ceedling_path = which_ceedling.dup() + @path_validator.standardize_paths( ceedling_path ) + if !@file_wrapper.directory?( ceedling_path ) + raise "Configured Ceedling launch path #{ceedling_path}/ does not exist" + end + + # Update Ceedling installation paths + app_cfg.set_paths( ceedling_path ) + + # Check updated Ceedling paths + if !@file_wrapper.exist?( app_cfg[:ceedling_rakefile_filepath] ) + raise "Configured Ceedling launch path #{ceedling_path}/ contains no Ceedling installation" + end + + # Update variable to full application start path + ceedling_path = app_cfg[:ceedling_rakefile_filepath] + + @loginator.log( " > Launching Ceedling from #{app_cfg[:ceedling_root_path]}/", Verbosity::OBNOXIOUS ) + + return :path, ceedling_path + end + + + def load_ceedling(config:, rakefile_path:, default_tasks:[]) + # Set default tasks + Rake::Task.define_task(:default => default_tasks) if !default_tasks.empty? + + # Load Ceedling application from Rakefile path + require( rakefile_path ) + + # Loading the Rakefile manipulates the config hash, return it as a convenience + return config + end + + + def process_testcase_filters(config:, include:, exclude:, tasks:, default_tasks:) + # Do nothing if no test case filters + return if (include.nil? || include.empty?) && (exclude.nil? || exclude.empty?) + + # TODO: When we can programmatically check if a task is a test task, + # raise an exception if --graceful-fail is set without test operations + + # Add test runner configuration setting necessary to use test case filters + value, _ = @config_walkinator.fetch_value( :test_runner, hash:config ) + if value.nil? + # If no :test_runner section, create the whole thing + config[:test_runner] = {:cmdline_args => true} + else + # If a :test_runner section, just set :cmdlne_args + value[:cmdline_args] = true + end + end + + + def process_graceful_fail(config:, cmdline_graceful_fail:, tasks:, default_tasks:) + # TODO: When we can programmatically check if a task is a test task, + # raise an exception if --graceful-fail is set without test operations + + # Precedence + # 1. Command line option + # 2. Configuration entry + + # If command line option was set, use it + return cmdline_graceful_fail if !cmdline_graceful_fail.nil? + + # If configuration contains :graceful_fail, use it + value, _ = @config_walkinator.fetch_value( :test_build, :graceful_fail, hash:config ) + return value if value.nil? + + return false + end + + + def process_logging_path(config) + build_root, _ = @config_walkinator.fetch_value( :project, :build_root, hash:config ) + + return '' if build_root.nil? + + return File.join( build_root, DEFAULT_BUILD_LOGS_PATH ) + end + + + def process_log_filepath(logging_path, log, logfile) + filepath = nil + + # --log => nil (default / not set), false (explicitly disabled), true (explicitly enabled) + if log == false + return '' + end + + # --logfile => '' default or a path + if not logfile.empty? + filepath = logfile + # If logging is enabled without a filepath in --logfile, then set up the default path + elsif log + filepath = File.join( logging_path, DEFAULT_CEEDLING_LOGFILE ) + elsif logfile.empty? + return '' + end + + filepath = File.expand_path( filepath ) + + dir = File.dirname( filepath ) + + # Ensure logging directory path exists + if !File.exist?( dir ) + @file_wrapper.mkdir( dir ) + end + + # Return filename/filepath + return filepath + end + + + # TODO: This is a hack until Rake is fully removed and plugin/build tasks incorporate a purpose classification + def build_or_plugin_task?(tasks:, default_tasks:) + _tasks = tasks.empty?() ? default_tasks.dup() : tasks.dup() + + # These namespace-less tasks are definitely build tasks + return true if _tasks.include?('test') + return true if _tasks.include?('release') + + # Namespace-less (clobber, clean, etc.), files:, and paths: tasks are not build / plugin tasks + # 1. Filter out tasks lacking a namespace + # 2. Look for any tasks other than paths: or files: + _tasks.select! {|t| t.include?( ':') } + _tasks.reject! {|t| t =~ /(^files:|^paths:)/} + + return !_tasks.empty? + end + + + def print_rake_tasks() + # (This required digging into Rake internals a bit.) + Rake.application.define_singleton_method(:name=) {|n| @name = n} + Rake.application.name = 'ceedling' + Rake.application.options.show_tasks = :tasks + Rake.application.options.show_task_pattern = /^(?!.*build).*$/ + Rake.application.display_tasks_and_comments() + end + + + def run_rake_tasks(tasks) + Rake.application.collect_command_line_tasks( tasks ) + + # Replace Rake's exception message to reduce any confusion + begin + Rake.application.top_level() + + rescue RuntimeError => ex + # Check if exception contains an unknown Rake task message + matches = ex.message.match( /how to build task '(.+)'/i ) + + # If it does, replacing the message with our own + if !matches.nil? and matches.size == 2 + message = "Unrecognized build task '#{matches[1]}'. List available build tasks with `ceedling help`." + raise CeedlingException.new( message ) + + # Otherwise, just re-raise + else + raise + end + end + + end + + + # Set global consts for verbosity and debug + def set_verbosity(verbosity=nil) + # If we have already set verbosity, there's nothing to do here + return PROJECT_VERBOSITY if @system_wrapper.constants_include?('PROJECT_VERBOSITY') + + verbosity = if verbosity.nil? + Verbosity::NORMAL + elsif verbosity.to_i.to_s == verbosity + verbosity.to_i + elsif VERBOSITY_OPTIONS.include? verbosity.to_sym + VERBOSITY_OPTIONS[verbosity.to_sym] + else + raise "Unkown Verbosity '#{verbosity}' specified" + end + + # Create global constant PROJECT_VERBOSITY + Object.module_eval("PROJECT_VERBOSITY = verbosity") + PROJECT_VERBOSITY.freeze() + + # Create global constant PROJECT_DEBUG + debug = (verbosity == Verbosity::DEBUG) + Object.module_eval("PROJECT_DEBUG = debug") + PROJECT_DEBUG.freeze() + + return verbosity + end + + + def dump_yaml(config, filepath, sections) + # Default to dumping entire configuration + _config = config + + # If sections were provided, process them + if !sections.empty? + # Symbolify section names + _sections = sections.map {|section| section.to_sym} + + # Try to extract subconfig from section path + value, _ = @config_walkinator.fetch_value( *_sections, hash:config ) + + # If we fail to find the section path, blow up + if value.nil? + # Reformat list of symbols to list of :
s + _sections.map! {|section| ":#{section.to_s}"} + msg = "Cound not find configuration section #{_sections.join(' ↳ ')}" + raise(msg) + end + + # Update _config to subconfig with final sections path element as container + _config = { _sections.last => value } + end + + File.open( filepath, 'w' ) {|out| YAML.dump( _config, out )} + end + + + def lookup_example_projects(examples_path) + examples = [] + + # Examples directory listing glob + glob = File.join( examples_path, '*' ) + + @file_wrapper.directory_listing(glob).each do |path| + # Skip anything that's not a directory + next if !@file_wrapper.directory?( path ) + + # Split the directory path into elements, indexing the last one + project = (path.split( File::SEPARATOR ))[-1] + + examples << project + end + + return examples + end + + + def copy_docs(ceedling_root, dest) + docs_path = File.join( dest, 'docs' ) + + # Hash that will hold documentation copy paths + # - Key: (modified) destination documentation path + # - Value: source path + doc_files = {} + + # Add docs to list from Ceedling (docs/) and supporting projects (docs/) + { # Source path => docs/ destination path + 'docs' => '.', + 'vendor/unity/docs' => 'unity', + 'vendor/cmock/docs' => 'cmock', + 'vendor/c_exception/docs' => 'c_exception' + }.each do |src, dest| + # Form glob to collect all markdown files + glob = File.join( ceedling_root, src, '*.md' ) + # Look up markdown files + listing = @file_wrapper.directory_listing( glob ) # Already case-insensitive + # For each markdown filepath, add to hash + listing.each do |filepath| + # Reassign destination + _dest = File.join( dest, File.basename(filepath) ) + doc_files[ _dest ] = filepath + end + end + + # Add docs to list from Ceedling plugins (docs/plugins) + glob = File.join( ceedling_root, 'plugins/**/README.md' ) + listing = @file_wrapper.directory_listing( glob ) # Already case-insensitive + listing.each do |path| + # 'README.md' => '.md' where name extracted from containing path + rename = path.split(/\\|\//)[-2] + '.md' + # For each Ceedling plugin readme, add to hash + dest = File.join( 'plugins', rename ) + doc_files[ dest ] = path + end + + # Add licenses from Ceedling (docs/) and supporting projects (docs/) + { # Destination path => Source path + '.' => '.', # Ceedling + 'unity' => 'vendor/unity', + 'cmock' => 'vendor/cmock', + 'c_exception' => 'vendor/c_exception', + }.each do |dest, src| + glob = File.join( ceedling_root, src, 'license.txt' ) + # Look up licenses (use glob as capitalization can be inconsistent) + listing = @file_wrapper.directory_listing( glob ) # Already case-insensitive + # Safety check on nil references since we explicitly reference first element + next if listing.empty? + filepath = listing.first + # Reassign dest + dest = File.join( dest, File.basename( filepath ) ) + doc_files[ dest ] = filepath + end + + # Copy all documentation + doc_files.each_pair do |dest, src| + @actions._copy_file(src, File.join( docs_path, dest ), :force => true) + end + end + + + def vendor_tools(ceedling_root, dest) + vendor_path = File.join( dest, 'vendor', 'ceedling' ) + + # Copy folders from current Ceedling into project + %w{plugins lib bin}.each do |folder| + @actions._directory( + folder, + File.join( vendor_path, folder ), + :force => true + ) + end + + # Mark ceedling as an executable + @actions._chmod( File.join( vendor_path, 'bin', 'ceedling' ), 0755 ) unless @system_wrapper.windows? + + # Assembly necessary subcomponent dirs + components = [ + 'vendor/c_exception/lib/', + 'vendor/cmock/config/', + 'vendor/cmock/lib/', + 'vendor/cmock/src/', + 'vendor/diy/lib/', + 'vendor/unity/auto/', + 'vendor/unity/src/', + ] + + # Copy necessary subcomponent dirs into project + components.each do |path| + _src = path + _dest = File.join( vendor_path, path ) + # Copy entire directory, filter out any junk files + @actions._directory( + _src, _dest, + :force => true + ) + end + + # Add licenses from Ceedling and supporting projects + license_files = {} + [ # Source paths + '.', # Ceedling + 'vendor/unity', + 'vendor/cmock', + 'vendor/c_exception', + 'vendor/diy' + ].each do |src| + # Look up licenses using a Glob as capitalization can be inconsistent + glob = File.join( ceedling_root, src, 'license.txt' ) + listing = @file_wrapper.directory_listing( glob ) # Already case-insensitive + + # Safety check on nil references since we explicitly reference first element + next if listing.empty? + + # Add license copying to hash + license = listing.first + filepath = File.join( vendor_path, src, File.basename( license ) ) + license_files[ filepath ] = license + end + + # Copy license files into place + license_files.each_pair do |dest, src| + @actions._copy_file( src, dest, :force => true) + end + + # Silently copy Git SHA file for version #.#.#-build lookups if it exists + if @file_wrapper.exist?( File.join( ceedling_root, GIT_COMMIT_SHA_FILENAME) ) + @actions._copy_file( + GIT_COMMIT_SHA_FILENAME, + File.join( vendor_path, GIT_COMMIT_SHA_FILENAME ), + :force => true, :verbose => false + ) + end + + # Create executable helper scripts in project root + if @system_wrapper.windows? + # Windows command prompt launch script + @actions._copy_file( + File.join( 'assets', 'ceedling.cmd'), + File.join( dest, 'ceedling.cmd'), + :force => true + ) + else + # Unix shell launch script + launch = File.join( dest, 'ceedling') + @actions._copy_file( + File.join( 'assets', 'ceedling'), + launch, + :force => true + ) + @actions._chmod( launch, 0755 ) + end + end + +end diff --git a/bin/configinator.rb b/bin/configinator.rb new file mode 100644 index 000000000..28fe3ada4 --- /dev/null +++ b/bin/configinator.rb @@ -0,0 +1,111 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'deep_merge' + +class Configinator + + constructor :config_walkinator, :projectinator, :mixinator + + def loadinate(builtin_mixins:, filepath:nil, mixins:[], env:{}, silent:false) + # Aliases for clarity + cmdline_filepath = filepath + cmdline_mixins = mixins || [] + + # Load raw config from command line, environment variable, or default filepath + project_filepath, config = @projectinator.load( filepath:cmdline_filepath, env:env, silent:silent ) + + # Extract cfg_enabled_mixins mixins list plus load paths list from config + cfg_enabled_mixins, cfg_load_paths = @projectinator.extract_mixins( config: config ) + + # Get our YAML file extension + yaml_ext = @projectinator.lookup_yaml_extension( config:config ) + + # Remove any silly redundancies + cfg_enabled_mixins.uniq! + # Use absolute path to ensure proper deduplication + cfg_load_paths.uniq! { |path| File.expand_path(path) } + cmdline_mixins.uniq! + + # Validate :cfg_load_paths from :mixins section of project configuration + @projectinator.validate_mixin_load_paths( cfg_load_paths ) + + # Validate enabled mixins from :mixins section of project configuration + if not @projectinator.validate_mixins( + mixins: cfg_enabled_mixins, + load_paths: cfg_load_paths, + builtins: builtin_mixins, + source: 'Config :mixins ↳ :enabled =>', + yaml_extension: yaml_ext + ) + raise 'Project configuration file section :mixins failed validation' + end + + # Validate command line mixins + if not @projectinator.validate_mixins( + mixins: cmdline_mixins, + load_paths: cfg_load_paths, + builtins: builtin_mixins, + source: 'Mixin', + yaml_extension: yaml_ext + ) + raise 'Command line failed validation' + end + + # Find mixins in project file among load paths or built-in mixins + # Return ordered list of filepaths or built-in mixin names + config_mixins = @projectinator.lookup_mixins( + mixins: cfg_enabled_mixins, + load_paths: cfg_load_paths, + builtins: builtin_mixins, + yaml_extension: yaml_ext + ) + + # Find mixins from command line among load paths or built-in mixins + # Return ordered list of filepaths or built-in mixin names + cmdline_mixins = @projectinator.lookup_mixins( + mixins: cmdline_mixins, + load_paths: cfg_load_paths, + builtins: builtin_mixins, + yaml_extension: yaml_ext + ) + + # Fetch CEEDLING_MIXIN_# environment variables + # Sort into ordered list of hash tuples [{env variable => filepath}...] + env_mixins = @mixinator.fetch_env_filepaths( env ) + @mixinator.validate_env_filepaths( env_mixins ) + + # Eliminate duplicate mixins and return list of mixins in merge order + # [{source => filepath}...] + mixins_assembled = @mixinator.assemble_mixins( + config: config_mixins, + env: env_mixins, + cmdline: cmdline_mixins + ) + + # Merge mixins + @mixinator.merge( builtins:builtin_mixins, config:config, mixins:mixins_assembled ) + + return project_filepath, config + end + + def default_tasks(config:, default_tasks:) + # 1. If :default_tasks set in config, use it + # 2. Otherwise use the function argument (most likely a default set in the first moments of startup) + value, _ = @config_walkinator.fetch_value( :project, :default_tasks, hash:config ) + if value + # Update method parameter to config value + default_tasks = value.dup() + else + # Set key/value in config if it's not set + config.deep_merge( {:project => {:default_tasks => default_tasks}} ) + end + + return default_tasks + end + +end \ No newline at end of file diff --git a/bin/mixinator.rb b/bin/mixinator.rb new file mode 100644 index 000000000..5ff81d628 --- /dev/null +++ b/bin/mixinator.rb @@ -0,0 +1,136 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'deep_merge' + +class Mixinator + + constructor :path_validator, :yaml_wrapper, :loginator + + def setup + # ... + end + + def validate_cmdline_filepaths(paths) + validated = @path_validator.validate( + paths: paths, + source: 'Filepath argument', + ) + + if !validated + raise 'Mixins command line failed validation' + end + end + + def fetch_env_filepaths(env) + var_names = [] + + env.each do |var, filepath| + # Explicitly ignores CEEDLING_MIXIN_0 + var_names << var if var =~ /CEEDLING_MIXIN_[1-9]\d*/ + end + + # Extract numeric string (guranteed to exist) and convert to integer for ascending sorting + var_names.sort_by! {|name| name.match(/\d+$/)[0].to_i() } + + _vars = [] + # Iterate over sorted environment variable names + var_names.each do |name| + # Duplicate the filepath string to get unfrozen copy + # Handle any Windows path shenanigans + # Insert in array {env var name => filepath} + path = env[name].dup() + @path_validator.standardize_paths( path ) + _vars << {name => path} + end + + # Remove any duplicate filepaths by comparing the full absolute path + # Higher numbered environment variables removed + _vars.uniq! {|entry| File.expand_path( entry.values.first )} + + return _vars + end + + def validate_env_filepaths(vars) + validated = true + + vars.each do |entry| + validated &= @path_validator.validate( + paths: [entry.values.first], + source: "Environment variable `#{entry.keys.first}` filepath", + ) + end + + if !validated + raise 'Mixins environment variables failed validation' + end + end + + def assemble_mixins(config:, env:, cmdline:) + assembly = [] + + # Build list of hashses in precedence order to facilitate deduplication + # Any duplicates at greater indexes are removed + cmdline.each {|mixin| assembly << {'command line' => mixin}} + assembly += env + config.each {|mixin| assembly << {'project configuration' => mixin}} + + # Remove duplicates inline + # 1. Expand filepaths to absolute paths for correct deduplication (skip expanding simple mixin names) + # 2. Remove duplicates + assembly.uniq! do |entry| + # If entry is filepath, expand it, otherwise leave entry untouched (it's a mixin name only) + mixin = entry.values.first + @path_validator.filepath?( mixin ) ? File.expand_path( mixin ) : mixin + end + + # Return the compacted list in merge order + # 1. Config + # 2. Environment variable + # 3. Command line + # Later merges take precedence (e.g. command line mixins are last merge) + return assembly.reverse() + end + + def merge(builtins:, config:, mixins:) + mixins.each do |mixin| + source = mixin.keys.first + filepath = mixin.values.first + + _mixin = {} # Empty initial value + + # Load mixin from filepath if it is a filepath + if @path_validator.filepath?( filepath ) + _mixin = @yaml_wrapper.load( filepath ) + + # Log what filepath we used for this mixin + @loginator.log( " + Merging #{'(empty) ' if _mixin.nil?}#{source} mixin using #{filepath}", Verbosity::OBNOXIOUS ) + + # Reference mixin from built-in hash-based mixins + else + _mixin = builtins[filepath.to_sym()] + + # Log built-in mixin we used + @loginator.log( " + Merging built-in mixin '#{filepath}' from #{source}", Verbosity::OBNOXIOUS ) + end + + # Hnadle an empty mixin (it's unlikely but logically coherent and a good safety check) + _mixin = {} if _mixin.nil? + + # Sanitize the mixin config by removing any :mixins section (these should not end up in merges) + _mixin.delete(:mixins) + + # Merge this bad boy + config.deep_merge( _mixin ) + end + + # Validate final configuration + msg = "Final configuration is empty" + raise msg if config.empty? + end + +end diff --git a/bin/mixins.rb b/bin/mixins.rb new file mode 100644 index 000000000..2c95a228e --- /dev/null +++ b/bin/mixins.rb @@ -0,0 +1,5 @@ + +BUILTIN_MIXINS = { + # Mixin name as symbol => mixin config hash + # :mixin => {} +} diff --git a/bin/objects.yml b/bin/objects.yml new file mode 100644 index 000000000..cf63d15aa --- /dev/null +++ b/bin/objects.yml @@ -0,0 +1,80 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +# +# Loaded from lib/ +# ---------------- +# + +file_wrapper: + +yaml_wrapper: + +config_walkinator: + +system_wrapper: + +verbosinator: + +loginator: + compose: + - verbosinator + - file_wrapper + - system_wrapper + +# +# Loaded from bin/ +# ---------------- +# + +actions_wrapper: + +# Separation of logic from CLI user interface +cli_handler: + compose: + - configinator + - projectinator + - cli_helper + - path_validator + - actions_wrapper + - loginator + +cli_helper: + compose: + - file_wrapper + - config_walkinator + - path_validator + - actions_wrapper + - loginator + - system_wrapper + +path_validator: + compose: + - file_wrapper + - loginator + +mixinator: + compose: + - path_validator + - yaml_wrapper + - loginator + +projectinator: + compose: + - file_wrapper + - path_validator + - yaml_wrapper + - loginator + - system_wrapper + +configinator: + compose: + - config_walkinator + - projectinator + - mixinator + + diff --git a/bin/path_validator.rb b/bin/path_validator.rb new file mode 100644 index 000000000..91dd793f5 --- /dev/null +++ b/bin/path_validator.rb @@ -0,0 +1,54 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +class PathValidator + + constructor :file_wrapper, :loginator + + def validate(paths:, source:, type: :filepath) + validated = true + + paths.each do |path| + # Error out on empty paths + if path.empty? + validated = false + @loginator.log( "#{source} contains an empty path", Verbosity::ERRORS ) + next + end + + # Error out if path is not a directory / does not exist + if (type == :directory) and !@file_wrapper.directory?( path ) + validated = false + @loginator.log( "#{source} '#{path}' does not exist as a directory in the filesystem", Verbosity::ERRORS ) + end + + # Error out if filepath does not exist + if (type == :filepath) and !@file_wrapper.exist?( path ) + validated = false + @loginator.log( "#{source} '#{path}' does not exist in the filesystem", Verbosity::ERRORS ) + end + end + + return validated + end + + # Ensure any Windows backslashes are converted to Ruby path forward slashes + # Santization happens inline + def standardize_paths( *paths ) + paths.each do |path| + next if path.nil? or path.empty? + path.gsub!( "\\", '/' ) + end + end + + + def filepath?(str) + # If argument includes a file extension or a path separator, it's a filepath + return (!File.extname( str ).empty?) || (str.include?( File::SEPARATOR )) + end + +end \ No newline at end of file diff --git a/bin/projectinator.rb b/bin/projectinator.rb new file mode 100644 index 000000000..7e7bc221b --- /dev/null +++ b/bin/projectinator.rb @@ -0,0 +1,244 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'ceedling/constants' # From Ceedling application + +class Projectinator + + PROJECT_FILEPATH_ENV_VAR = 'CEEDLING_PROJECT_FILE' + DEFAULT_PROJECT_FILEPATH = './' + DEFAULT_PROJECT_FILENAME + DEFAULT_YAML_FILE_EXTENSION = '.yml' + + constructor :file_wrapper, :path_validator, :yaml_wrapper, :loginator, :system_wrapper + + # Discovers project file path and loads configuration. + # Precendence of attempts: + # 1. Explcit flepath from argument + # 2. Environment variable + # 3. Default filename in working directory + # Returns: + # - Absolute path of project file found and used + # - Config hash loaded from project file + def load(filepath:nil, env:{}, silent:false) + # Highest priority: command line argument + if filepath + @path_validator.standardize_paths( filepath ) + _filepath = File.expand_path( filepath ) + config = load_and_log( _filepath, 'from command line argument', silent ) + return _filepath, config + + # Next priority: environment variable + elsif env[PROJECT_FILEPATH_ENV_VAR] + # ENV lookup is frozen so dup() to operate on the string + filepath = env[PROJECT_FILEPATH_ENV_VAR].dup() + @path_validator.standardize_paths( filepath ) + _filepath = File.expand_path( filepath ) + config = load_and_log( + _filepath, + "from environment variable `#{PROJECT_FILEPATH_ENV_VAR}`", + silent + ) + return _filepath, config + + # Final option: default filepath + elsif @file_wrapper.exist?( DEFAULT_PROJECT_FILEPATH ) + filepath = DEFAULT_PROJECT_FILEPATH + _filepath = File.expand_path( filepath ) + config = load_and_log( _filepath, "from working directory", silent ) + return _filepath, config + + # If no user provided filepath and the default filepath does not exist, + # we have a big problem + else + raise "No project filepath provided and default #{DEFAULT_PROJECT_FILEPATH} not found" + end + + # We'll never get here but return nil/empty for completeness + return nil, {} + end + + + # Determine if project configuration is available. + # - Simplest, default case simply tries to load default project file location. + # - Otherwise, attempts to load a filepath, the default environment variable, + # or both can be specified. + def config_available?(filepath:nil, env:{}, silent:true) + available = true + + begin + load(filepath:filepath, env:env, silent:silent) + rescue + available = false + end + + return available + end + + + def lookup_yaml_extension(config:) + return DEFAULT_YAML_FILE_EXTENSION if config[:extension].nil? + + return DEFAULT_YAML_FILE_EXTENSION if config[:extension][:yaml].nil? + + return config[:extension][:yaml] + end + + + # Pick apart a :mixins projcet configuration section and return components + # Layout mirrors :plugins section + def extract_mixins(config:) + # Get mixins config hash + _mixins = config[:mixins] + + # If no :mixins section, return: + # - Empty enabled list + # - Empty load paths + return [], [] if _mixins.nil? + + # Build list of load paths + # Configured load paths are higher in search path ordering + load_paths = _mixins[:load_paths] || [] + + # Get list of mixins + enabled = _mixins[:enabled] || [] + enabled = enabled.clone # Ensure it's a copy of configuration section + + # Handle any inline Ruby string expansion + load_paths.each do |load_path| + load_path.replace( @system_wrapper.module_eval( load_path ) ) if (load_path =~ RUBY_STRING_REPLACEMENT_PATTERN) + end + + enabled.each do |mixin| + mixin.replace( @system_wrapper.module_eval( mixin ) ) if (mixin =~ RUBY_STRING_REPLACEMENT_PATTERN) + end + + # Remove the :mixins section of the configuration + config.delete( :mixins ) + + return enabled, load_paths + end + + + # Validate :load_paths from :mixins section in project configuration + def validate_mixin_load_paths(load_paths) + validated = @path_validator.validate( + paths: load_paths, + source: 'Config :mixins ↳ :load_paths =>', + type: :directory + ) + + if !validated + raise 'Project configuration file section :mixins failed validation' + end + end + + + # Validate mixins list + def validate_mixins(mixins:, load_paths:, builtins:, source:, yaml_extension:) + validated = true + + mixins.each do |mixin| + # Validate mixin filepaths + if @path_validator.filepath?( mixin ) + if !@file_wrapper.exist?( mixin ) + @loginator.log( "Cannot find mixin at #{mixin}", Verbosity::ERRORS ) + validated = false + end + + # Otherwise, validate that mixin name can be found in load paths or builtins + else + found = false + load_paths.each do |path| + if @file_wrapper.exist?( File.join( path, mixin + yaml_extension ) ) + found = true + break + end + end + + builtins.keys.each {|key| found = true if (mixin == key.to_s)} + + if !found + msg = "#{source} '#{mixin}' cannot be found in mixin load paths as '#{mixin + yaml_extension}' or among built-in mixins" + @loginator.log( msg, Verbosity::ERRORS ) + validated = false + end + end + end + + return validated + end + + + # Yield ordered list of filepaths or built-in mixin names + def lookup_mixins(mixins:, load_paths:, builtins:, yaml_extension:) + _mixins = [] + + # Already validated, so we know: + # 1. Any mixin filepaths exists + # 2. Built-in mixin names exist in the internal hash + + # Fill filepaths array with filepaths or builtin names + mixins.each do |mixin| + # Handle explicit filepaths + if @path_validator.filepath?( mixin ) + _mixins << mixin + next # Success, move on in mixin iteration + end + + # Look for mixin in load paths. + # Move on in mixin iteration if mixin is found. + next if load_paths.any? do |path| + filepath = File.join( path, mixin + yaml_extension ) + exist = @file_wrapper.exist?( filepath ) + _mixins << filepath if exist + exist + end + + # Finally, fall through to simply add the unmodified name to the list. + # It's a built-in mixin. + _mixins << mixin + end + + return _mixins + end + + ### Private ### + + private + + def load_and_log(filepath, method, silent) + begin + # Load the filepath we settled on as our project configuration + config = @yaml_wrapper.load( filepath ) + + # A blank configuration file is technically an option (assuming mixins are merged) + # Redefine config as empty hash + config = {} if config.nil? + + # Log what the heck we loaded + if !silent + msg = "Loaded #{'(empty) ' if config.empty?}project configuration #{method}.\n" + msg += " > Using: #{filepath}\n" + msg += " > Working directory: #{Dir.pwd()}" + + @loginator.log( msg, Verbosity::NORMAL, LogLabels::CONSTRUCT ) + end + + return config + rescue Errno::ENOENT + # Handle special case of user-provided blank filepath + filepath = filepath.empty?() ? '' : filepath + raise "Could not find project filepath #{filepath} #{method}" + + rescue StandardError => e + # Catch-all error handling + raise "Error loading project filepath #{filepath} #{method}: #{e.message}" + end + + end + +end diff --git a/bin/versionator.rb b/bin/versionator.rb new file mode 100644 index 000000000..c90a53682 --- /dev/null +++ b/bin/versionator.rb @@ -0,0 +1,81 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'exceptions' +require 'constants' # Filename constants +require 'version' # Import Ceedling constant symbols from lib/version.rb + +## Definitions +## TAG: <#.#.#> version string used to package the software bundle +## BUILD: TAG combined with git commit hash (<#.#.#>-) + +class Versionator + + attr_reader :ceedling_tag, :ceedling_build, :ceedling_install_path + attr_reader :unity_tag, :cmock_tag, :cexception_tag + + def initialize(ceedling_root_path, ceedling_vendor_path=nil) + + ## + ## Ceedling version info + ## + + @ceedling_install_path = ceedling_root_path.clone() + ceedling_git_sha = nil + + # Set Ceedling tag + @ceedling_tag = Ceedling::Version::TAG + + # Create Ceedling build string + # Lookup the Ceedling Git commit SHA if it exists as simple text file in the root of the codebase + ceedling_commit_sha_filepath = File.join( ceedling_root_path, GIT_COMMIT_SHA_FILENAME ) + @ceedling_build = + if File.exist?( ceedling_commit_sha_filepath ) + # Ingest text from commit SHA file and clean it up + sha = File.read( ceedling_commit_sha_filepath ).strip() + # - + "#{@ceedling_tag}-#{sha}" + else + # + @ceedling_tag + end + + ## + ## Build frameworks version info + ## + + # Do no framework version gathering if it's not asked for + return if ceedling_vendor_path.nil? + + # Create _tag accessors in the Versionator object + + # Anonymous hash to iterate through complementary vendor projects + { 'UNITY' => File.join( 'unity', 'src', 'unity.h' ), + 'CMOCK' => File.join( 'cmock', 'src', 'cmock.h' ), + 'CEXCEPTION' => File.join( 'c_exception', 'lib', 'CException.h' ) + }.each_pair do |name, path| + filename = File.join( ceedling_vendor_path, path ) + + # Actually look up the vendor project version number components + version = [0,0,0] + + begin + File.readlines( filename ).each do |line| + ['VERSION_MAJOR', 'VERSION_MINOR', 'VERSION_BUILD'].each_with_index do |field, i| + m = line.match(/#{name}_#{field}\s+(\d+)/) + version[i] = m[1] unless (m.nil?) + end + end + rescue + raise CeedlingException.new( "Could not collect version information for vendor component: #{filename}" ) + end + + # Splat version and evaluate it to create Versionator object accessor + eval("@#{name.downcase}_tag = '#{version.join(".")}'") + end + end +end diff --git a/ceedling.gemspec b/ceedling.gemspec index 2c1f6b6d8..d29342e9c 100644 --- a/ceedling.gemspec +++ b/ceedling.gemspec @@ -1,6 +1,13 @@ # -*- encoding: utf-8 -*- +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), 'lib')) -require "ceedling/version" +require "version" # lib/version.rb require 'date' Gem::Specification.new do |s| @@ -9,12 +16,14 @@ Gem::Specification.new do |s| s.platform = Gem::Platform::RUBY s.authors = ["Mark VanderVoord", "Michael Karlesky", "Greg Williams"] s.email = ["mark@vandervoord.net", "michael@karlesky.net", "barney.williams@gmail.com"] - s.homepage = "http://throwtheswitch.org/ceedling" - s.summary = "Ceedling is a build automation tool for C unit test suites that packages up Unity, CMock, and Rake-based build management functionality" + s.homepage = "https://throwtheswitch.org/ceedling" + s.summary = "Ceedling is a build automation tool for C unit tests and releases. It's a member of the ThrowTheSwitch.org family of tools. It's built upon Unity and CMock." s.description = <<-DESC Ceedling is a build automation tool that helps you create and run C unit test suites. -Ceedling provides two core functions: [1] It packages up several tools including the C unit test framework Unity, the Ruby-based mock generation tool CMock, and a C exception library CException. [2] It extends Rake with functionality specific to generating, building, and executing C test suites. +Ceedling provides two core functions: + [1] It packages up several tools including the C unit test framework Unity, the mock generation tool CMock, and other features. + [2] It simplifies tool configuration for embedded or native C toolchains and automates the running and reporting of tests. Ceedling projects are created with a YAML configuration file. A variety of conventions within the tool simplify generating mocks from C files and assembling suites of unit test functions. DESC @@ -28,12 +37,14 @@ Ceedling projects are created with a YAML configuration file. A variety of conve "source_code_uri" => "https://github.com/ThrowTheSwitch/Ceedling" } - s.required_ruby_version = ">= 2.7.0" + s.required_ruby_version = ">= 3.0.0" - s.add_dependency "thor", ">= 0.14" + s.add_dependency "thor", "~> 1.3" s.add_dependency "rake", ">= 12", "< 14" s.add_dependency "deep_merge", "~> 1.2" + s.add_dependency "diy", "~> 1.1" s.add_dependency "constructor", "~> 2" + s.add_dependency "unicode-display_width", "~> 3.1" # Files needed from submodules s.files = [] @@ -45,9 +56,9 @@ Ceedling projects are created with a YAML configuration file. A variety of conve s.files += Dir['vendor/unity/auto/**/*.rb'] s.files += Dir['vendor/unity/src/**/*.[ch]'] - s.files += Dir['**/*'] - s.test_files = Dir['test/**/*', 'spec/**/*', 'features/**/*'] - s.executables = Dir['bin/**/*'].map{|f| File.basename(f)} + s.files += Dir['**/*'] + s.test_files = Dir['test/**/*', 'spec/**/*', 'features/**/*'] + s.executables = ['ceedling'] # bin/ceedling s.require_paths = ["lib", "vendor/cmock/lib"] end diff --git a/config/test_environment.rb b/config/test_environment.rb index 377aa1935..b2572abc0 100644 --- a/config/test_environment.rb +++ b/config/test_environment.rb @@ -1,10 +1,14 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= # Setup our load path: [ 'lib', 'test', - 'vendor/behaviors/lib', - 'vendor/hardmock/lib', ].each do |dir| $LOAD_PATH.unshift( File.join( File.expand_path(File.dirname(__FILE__) + "/../"), dir) ) end diff --git a/docs/BreakingChanges.md b/docs/BreakingChanges.md new file mode 100644 index 000000000..13ad8c7d6 --- /dev/null +++ b/docs/BreakingChanges.md @@ -0,0 +1,250 @@ +# đŸŒ± Ceedling Breaking Changes + +These breaking changes are complemented by two other documents: + +1. 🔊 **[Release Notes](ReleaseNotes.md)** for announcements, education, acknowledgements, and known issues. +1. đŸȘ” **[Changelog](Changelog.md)** for a structured list of additions, fixes, changes, and removals. + +--- + +# [1.0.0 pre-release] — 2024-11-28 + +## Explicit `:paths` ↳ `:include` entries in the project file + +The `:paths` ↳ `:include` entries in the project file must now be explicit and complete. + +Eaerlier versions of Ceedling were rather accomodating when assembling the search paths for header files. The full list of directories was pulled from multiple `:paths` entries with de-duplication. If you had header files in your `:source` directories but did not explicitly list those directories in your `:include` paths, Ceedling would helpfully figure it out and use all the paths. + +This behavior is no more. Why? For two interrelated reasons. + +1. For large or complex projects, expansive header file search path lists can exceed command line maximum lengths on some platforms. An enforced, tailored set of search paths helps prevent this problem. +1. In order to support the desired behavior of `TEST_INCLUDE_PATH()` a concice set of “base” header file search paths is necessary. `:paths` ↳ `:include` is that base list. + +Using 0.32 Ceedling with older project files can lead to errors when generating mocks or compiler errors on finding header files. Add all relevant header file search paths to the `:paths` ↳ `:include` project file entry to fix this problem. + +## Format change for `:defines` in the project file + +To better support per-test-executable configurations, the format of `:defines` has changed. See the [official documentation](CeedlingPacket.md) for specifics. + +In brief: + +1. A more logically named hierarchy differentiates `#define`s for test preprocessing, test compilation, and release compilation. +1. Previously, compilation symbols could be specified for a specific C file by name, but these symbols were only defined when compiling that specific file. Further, this matching was only against a file’s full name. Now, pattern matching is also an option. +1. Filename matching for test compilation symbols happens against _only test file names_. More importantly, the configured symbols are applied in compilation of each C file that comprises a test executable. Each test executable is treated as a mini-project. + +Symbols specified for release builds are applied to all files in the release build. + +## Format change for `:flags` in the project file + +To better support per-test-executable configurations, the format and function of `:flags` has changed somewhat. See the [official documentation](CeedlingPacket.md) for specifics. + +In brief: + +1. The format of the `:flags` configuration section is largely the same as in previous versions of Ceedling. However, the behavior of the matching rules is slightly different with more matching options. +1. Within the `:flags` ↳ `:test` context, all matching of file names is now limited to *_test files_*. For any test file name that matches, the specified flags are added to the named build step for _all files that comprise that test executable_. Previously, matching was against individual files (source, test, whatever), and flags were applied to only operations on that single file. Now, all files part of a test executable build get the same treatment as a mini-project. + +Flags specified for release builds are applied to all files in the release build. + +## New `:project` ↳ `:use_test_preprocessor` configuration settings + +Ceedling’s preprocessing features have been greatly improved. Preprocessing is now no longer all-or-nothing with a simple boolean value. + +In place of `true` or `false`, `:use_test_preprocessing` now accepts: + +* `:none` disables preprocessing (equivalent to previous `false` setting). +* `:all` enables preprpocessing for all mockable header files and test C files (equivalent to previous `true` setting). +* `:mocks` enables only preprocessing of header files that are to be mocked. +* `:tests` enables only preprocessing of your test files. + +## `TEST_FILE()` âžĄïž `TEST_SOURCE_FILE()` + +The previously undocumented `TEST_FILE()` build directive macro (#796) available within test files has been renamed and is now officially documented. + +## Preprocessing is unable to handle Unity’s `TEST_CASE()` and `TEST_RANGE()` and has limits for `TEST_INCLUDE_PATH()` + +Ceedling’s preprocessing abilities have been nearly entirely rewritten. The one case Ceedling cannot yet handle is preprocessing test files that contain Unity’s parameterized test case macros. + +`TEST_CASE()` and `TEST_RANGE()` are Unity macros that are positional in a file in relation to the test case functions they modify. While Ceedling's test preprocessing can preserve these macro calls, their position cannot be preserved. + +You may want to wrap `TEST_INCLUDE_PATH()` in conditional compilation preprocessor statements (e.g. `#ifdef`). This will not work as you expect. This macro “marker” must be discovered at the beginning of a test build by Ceedling parsing a test file as plain text. Cyclical dependencies related to preprocessing prevent anything more sophisticated. + +Note: `:project` ↳ `:use_test_preprocessor` is no longer a binary setting (`true`/`false`). Mockable header file preprocessing can now be enabled with a `:mocks` setting while test files are left untouched by preprocessing. This can allow test preprocessing in the common cases of sophtisticate mockable headers while Unity’s `TEST_CASE()` and `TEST_RANGE()` are utilized in a test file untouched by preprocessing. + +## Quoted executables in tool definitions + +While unusual, some executables have names with spaces. This is more common on Windows than Unix derivatives, particularly with proprietary compiler toolchains. + +Originally, Ceedling would automatically add quotes around an executable containing a space when it built a full command line before passing it to a command shell. + +This automagical help can break tools in certain contexts. For example, script-based executables in many Linux shells do not work (e.g. `"python path/script.py" --arg`). + +Automatic quoting has been removed. If you need a quoted executable, simply explicitly include quotes in the appropriate string for your executable (this can occur in multiple locations throughout a Ceedling project). An example of a YAML tool defition follows: + +```yaml +:tools: + :funky_compiler: + :executable: \"Code Cranker\" +``` + +## Build output directory structure changes + +### Test builds + +Each test is now treated as its own mini-project. Differentiating components of the same name that are a part of multiple test executables required further subdirectories in the build directory structure. Generated mocks, compiled object files, linked executables, and preprocessed output all end up one directory deeper than in previous versions of Ceedling. In each case, these files are found inside a subdirectory named for their containing test. + +### Release builds + +Release build object files were previously segregated by their source. The release build output directory had subdirectories `c/` and `asm/`. These subdirectories are no longer in use. + +## Configuration defaults and configuration set up order + +Ceedling’s previous handling of defaults and configuration processing order certainly worked, but it was not as proper as it could be. To oversimplify, default values were applied in an ordering that caused complications for advanced plugins and advanced users. This has been rectified. Default settings are now processed after all user configurations and plugins. + +For some users and some custom plugins, the new ordering may cause unexpected results. The changes had no known impact on existing plugins and typical project configurations. + +## Changes to global constants & accessors + +Some global constant “collections” that were previously key elements of Ceedling have changed or gone away as the build pipeline is now able to process a configuration for each individual test executable in favor of for the entire suite. + +Similarly, various global constant project file accessors have changed, specifically the values within the configuration file they point to as various configuration sections have changed format (examples above). + +See the [official documentation](CeedlingPacket.md) on global constants & accessors for updated lists and information. + +## `raw_output_report` plugin + +This plugin (renamed -- see next section) no longer generates empty log files and no longer generates log files with _test_ and _pass_ in their filenames. Log files are now simply named `.raw.log`. + +## Consolidation of test report generation plugins âžĄïž `report_tests_log_factory` + +The individual `json_tests_report`, `xml_tests_report`, and `junit_tests_report` plugins are superseded by a single plugin `report_tests_log_factory` able to generate each or all of the previous test reports as well as an HTML report and user-defined tests reports. The new plugin requires a small amount of extra configuration the previous individual plugins did not. See the [`report_tests_log_factory` documentation](../plugins/report_tests_log_factory). + +In addition, all references and naming connected to the previous `xml_tests_report` plugin have been updated to refer to _CppUnit_ rather than generic _XML_ as this is the actual format of the report that is processed. + +## Built-in Plugin Name Changes + +The following plugin names must be updated in the `:plugins` ↳ `:enabled` list of your Ceedling project file. + +This renaming was primarily enacted to more clearly organize and relate reporting-oriented plugins. Secondarily, some names were changed simply for greater clarity. + +Some test report generation plugins were not simply renamed but superseded by a new plugin (see preceding section). + +- `fake_function_framework` âžĄïž `fff` +- `compile_commands_json` âžĄïž `compile_commands_json_db` +- `json_tests_report`, `xml_tests_report` & `junit_tests_report` âžĄïž `report_tests_log_factory` +- `raw_output_report` âžĄïž `report_tests_raw_output_log` +- `stdout_gtestlike_tests_report` âžĄïž `report_tests_gtestlike_stdout` +- `stdout_ide_tests_report` âžĄïž `report_tests_ide_stdout` +- `stdout_pretty_tests_report` âžĄïž `report_tests_pretty_stdout` +- `stdout_teamcity_tests_report` âžĄïž `report_tests_teamcity_stdout` +- `warnings_report` âžĄïž `report_build_warnings_log` +- `test_suite_reporter` âžĄïž `report_tests_log_factory` + +## `gcov` plugin coverage report generation name and behavior changes + +The `gcov` plugin and its [documentation](../plugins/gcov) has been significantly revised. See [release notes](ReleaseNotes.md) for all the details. + +The report generation task `utils:gcov` has been renamed and its behavior has been altered. + +Coverage reports are now generated automatically unless the manual report generation task is enabled with a configuration option (the manual report generation option disables the automatc option). See below. If automatic report generation is disabled, the task `report:gcov` becomes available to trigger report generation (a `gcov:` task must be executed before `report:gcov` just as was the case with `utils:gcov`). + +```yaml +:gcov: + :report_task: TRUE +``` + +## Exit code handling (a.k.a. `:graceful_fail`) + +Be default Ceedling terminates with an exit code of `1` when a build succeeds but unit tests fail. + +A previously undocumented project configuration option `:graceful_fail` could force a Ceedling exit code of `0` upon test failures. + +This configuration option has moved (and is now [documented](CeedlingPacket.md)). + +Previously: +```yaml +:graceful_fail: TRUE +``` + +Now: +```yaml +:test_build: + :graceful_fail: TRUE +``` + +## Project file environment variable name change `CEEDLING_MAIN_PROJECT_FILE` âžĄïž `CEEDLING_PROJECT_FILE` + +Options and support for loading a project configuration have expanded significantly, mostly notably with the addition of Mixins. + +The environment variable option for pointing Ceedling to a project file other than _project.yml_ in your working directory has been renamed `CEEDLING_MAIN_PROJECT_FILE` âžĄïž `CEEDLING_PROJECT_FILE`. + +In addition, a previously undocumented feature for merging a second configuration via environment variable `CEEDLING_USER_PROJECT_FILE` has been removed. This feature has been superseded by the new Mixins functionality. + +Thorough documentation on Mixins and the new options for loading a project configuration can be found in _[CeedlingPacket](CeedlingPacket.md))_. + +## Tool definition inline Ruby evaluation replacement removed (inline Ruby string expansion remains) + +Reaching back to the earliest days of Ceedling, tool definitions supported two slightly different string replacement options that executed at different points in a build’s lifetime. Yeah. It was maybe not great. + +Support for `{...}` Ruby evaluation in tool definitions has been removed. + +Support for `#{...}` Ruby string expansion in tool definitions remains and is now evaluated each time a tool is executed during a build. + +## Command Hooks plugin configuration change + +In previous versions of Ceedling, the Command Hooks plugin associated tools and hooks by a naming convention within the top-level `:tools` section of your project configuration. This required some semi-ugly tool names and could lead to a rather unwieldy `:tools` list. Further, this convention also limited a hook to an association with only a single tool. + +Hooks are now enabled within a top-level `:command_hooks` section in your project configuration. Each hook key in this configuration block can now support one or more tools organized beneath it. As such, each hook can execute one or more tools. + +## Subprojects plugin replaced + +The `subprojects` plugin has been completely replaced by the more-powerful `dependencies` plugin. To retain your previous functionality, you need to do a little reorganizing in your `project.yml` file. Obviously the `:subprojects` section is now called `:dependencies`. The collection of `:paths` is now `:deps`. We'll have a little more organizing to do once we're inside that section as well. Your `:source` is now organized in `:paths` ↳ `:source` and, since you're not fetching it form another project, the `:fetch` ↳ `:method` should be set to `:none`. The `:build_root` now becomes `:paths` ↳ `:build`. You'll also need to specify the name of the library produced under `:artifacts` ↳ `:static_libraries`. + +For example: + +``` +:subprojects: + :paths: + - :name: libprojectA + :source: + - ./subprojectA/ + :include: + - ./subprojectA/inc + :build_root: ./subprojectA/build/dir + :defines: + - DEFINE_JUST_FOR_THIS_FILE + - AND_ANOTHER +``` + +The above subproject definition will now look like the following: + +``` +:dependencies: + :deps: + - :name: Project A + :paths: + :fetch: ./subprojectA/ + :source: ./subprojectA/ + :build: ./subprojectA/build/dir + :artifact: ./subprojectA/build/dir + :fetch: + :method: :none + :environment: [] + :build: + - :build_lib + :artifacts: + :static_libraries: + - libprojectA.a + :dynamic_libraries: [] + :includes: + - ../subprojectA/subprojectA.h + :defines: + - DEFINE_JUST_FOR_THIS_FILE + - AND_ANOTHER +``` + +## Undocumented `:project` ↳ `:debug` has been removed + +This project setting existed from Ceedling’s earliest days and was a crude stand-in for command line debug verbosity handling. + +It has been removed as it was rarely if ever utilized and needlessly complicated internal mechanisms for verbosity handling and project validation. + diff --git a/docs/CODE_OF_CONDUCT.md b/docs/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..84e34806a --- /dev/null +++ b/docs/CODE_OF_CONDUCT.md @@ -0,0 +1,138 @@ + +# ThrowTheSwitch.org Code of Conduct + +Thank you for participating in a ThrowTheSwitch.org community project! We want +this to continue to be a warm and inviting place for everyone to share ideas +and get help. To accomplish this goal, we've developed this Code of Conduct. + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, caste, color, religion, or sexual +identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the overall + community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of + any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, + without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official email address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +hello@thingamabyte.com. + +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of +actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or permanent +ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the +community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by +[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 000000000..b233a039b --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,250 @@ +# Contributing to a ThrowTheSwitch.org Project + +👍🎉 _First off, thanks for taking the time to contribute!_ 🎉👍 + +The following is a set of guidelines for contributing to any of ThrowTheSwitch.org's projects or the website itself, hosted at throwtheswitch.org or ThrowTheSwitch's organization on GitHub. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. + +### Table Of Contents + +- [Code of Conduct](#book-code-of-conduct) +- [Asking Questions](#bulb-asking-questions) +- [Opening an Issue](#inbox_tray-opening-an-issue) +- [Feature Requests](#love_letter-feature-requests) +- [Triaging Issues](#mag-triaging-issues) +- [Submitting Pull Requests](#repeat-submitting-pull-requests) +- [Writing Commit Messages](#memo-writing-commit-messages) +- [Code Review](#white_check_mark-code-review) +- [Coding Style](#nail_care-coding-style) +- [Certificate of Origin](#medal_sports-certificate-of-origin) +- [Credits](#pray-credits) + +## :book: Code of Conduct + +Please review our [Code of Conduct](CODE_OF_CONDUCT.md). It is in effect at all times. We expect it to be honored by everyone who contributes to this project. Be a Good Human! + +## :bulb: Asking Questions + +> **Note:** Please don't file an issue to ask a question. We have an official forum where the community chimes in with helpful advice if you have questions. + +* [ThrowTheSwitch Forums](https://throwtheswitch.org/forums) + +### What should I know before I get started? + +ThrowTheSwitch hosts a number of open source projects — Ceedling is the entrypoint for many users. Ceedling is actually built upon the foundation of Unity Test (a flexible C testing framework) and CMock (a mocking tool for C) and it coordinates many other open source and proprietary tools. Please do your best to focus your ideas and questions at the correct tool. We'll do our best to help you find your way, but there will be times where we'll have to direct your attention to another subtool. + +Here are some of the main projects hosted by ThrowTheSwitch.org: + + - [Ceedling](https://www.github.com/throwtheswitch/ceedling) -- Build coordinator for testing C applications, especially embedded C (and optionally your release build too!) + - [CMock](https://www.github.com/throwtheswitch/cmock) -- Mocking tool for automatically creating stubs, mocks, and skeletons in C + - [Unity](https://www.github.com/throwtheswitch/unity) -- Unit Testing framework for C, specially embedded C. + - [MadScienceLabDocker](https://www.github.com/throwtheswitch/madsciencelabdocker) -- Docker image giving you a shortcut to getting running with Ceedling + - [CException](https://www.github.com/throwtheswitch/cexception) -- An exception framework for using simple exceptions in C. + +There are many more, but this list should be a good starting point. + +## :inbox_tray: Opening an Issue + +Before [creating an issue](https://help.github.com/en/github/managing-your-work-on-github/creating-an-issue), check if you are using the latest version of the project. If you are not up-to-date, see if updating fixes your issue first. + +### :beetle: Bug Reports and Other Issues + +A great way to contribute to the project is to send a detailed issue when you encounter a problem. We always appreciate a well-written, thorough bug report. :v: + +In short, since you are most likely a developer, **provide a ticket that you would like to receive**. + +- **Review the documentation** before opening a new issue. + +- **Do not open a duplicate issue!** Search through existing issues to see if your issue has previously been reported. If your issue exists, comment with any additional information you have. You may simply note "I have this problem too", which helps prioritize the most common problems and requests. + +- **Prefer using [reactions](https://github.blog/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/)**, not comments, if you simply want to "+1" an existing issue. + +- **Fully complete the provided issue template.** The bug report template requests all the information we need to quickly and efficiently address your issue. Be clear, concise, and descriptive. Provide as much information as you can, including steps to reproduce, stack traces, compiler errors, library versions, OS versions, and screenshots (if applicable). + +- **Use [GitHub-flavored Markdown](https://help.github.com/en/github/writing-on-github/basic-writing-and-formatting-syntax).** Especially put code blocks and console outputs in backticks (```). This improves readability. + +## :seedling: Feature Requests + +Feature requests are welcome! We don't have all the answers and we truly love the collaborative experience of building software together! That being said, we cannot guarantee your request will be accepted. We want to avoid [feature creep](https://en.wikipedia.org/wiki/Feature_creep). Your idea may be great, but also out-of-scope for the project. If accepted, we'll do our best to tackle it in a timely manner, but cannot make any commitments regarding the timeline for implementation and release. However, you are welcome to submit a pull request to help! + +- **Please don't open a duplicate feature request.** Search for existing feature requests first. If you find your feature (or one very similar) previously requested, comment on that issue. + +- **Fully complete the provided issue template.** The feature request template asks for all necessary information for us to begin a productive conversation. + +- Be precise about the proposed outcome of the feature and how it relates to existing features. Include implementation details if possible. + +## :mag: Triaging Issues + +You can triage issues which may include reproducing bug reports or asking for additional information, such as version numbers or reproduction instructions. Any help you can provide to quickly resolve an issue is very much appreciated! + +## :repeat: Submitting Pull Requests + +We **love** pull requests! Before [forking the repo](https://help.github.com/en/github/getting-started-with-github/fork-a-repo) and [creating a pull request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/proposing-changes-to-your-work-with-pull-requests) for non-trivial changes, it is usually best to first open an issue to discuss the changes, or discuss your intended approach for solving the problem in the comments for an existing issue. + +For most contributions, after your first pull request is accepted and merged, you will be [invited to the project](https://help.github.com/en/github/setting-up-and-managing-your-github-user-account/inviting-collaborators-to-a-personal-repository) and given **push access**. :tada: + +*Note: All contributions will be licensed under the project's license.* + +- **Smaller is better.** Submit **one** pull request per bug fix or feature. A pull request should contain isolated changes pertaining to a single bug fix or feature implementation. **Do not** refactor or reformat code that is unrelated to your change. It is better to **submit many small pull requests** rather than a single large one. Enormous pull requests will take enormous amounts of time to review, or may be rejected altogether. + +- **Coordinate bigger changes.** For large and non-trivial changes, open an issue to discuss a strategy with the maintainers. Otherwise, you risk doing a lot of work for nothing! + +- **Prioritize understanding over cleverness.** Write code clearly and concisely. Remember that source code usually gets written once and read often. Ensure the code is clear to the reader. The purpose and logic should be obvious to a reasonably skilled developer, otherwise you should add a comment that explains it. + +- **Follow existing coding style and conventions.** Keep your code consistent with the style, formatting, and conventions in the rest of the code base. When possible, these will be enforced with a linter. Consistency makes it easier to review and modify in the future. + +- **Include test coverage.** Add unit tests when possible. Follow existing patterns for implementing tests. + +- **Update the example project** if one exists to exercise any new functionality you have added. + +- **Add documentation.** Document your changes with code doc comments or in existing guides. + +- **Update the CHANGELOG** for all enhancements and bug fixes. Include the corresponding issue number if one exists, and your GitHub username. (example: "- Fixed crash in profile view. #123 @jessesquires") + +- **Use the repo's default branch.** Branch from and [submit your pull request](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request-from-a-fork) to the repo's default branch. Usually this is `main`, but it could be `dev`, `develop`, or `master`. + +- **[Resolve any merge conflicts](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/resolving-a-merge-conflict-on-github)** that occur. + +- **Promptly address any CI failures**. If your pull request fails to build or pass tests, please push another commit to fix it. + +- When writing comments, use properly constructed sentences, including punctuation. + +- Use spaces, not tabs. + +## :memo: Writing Commit Messages + +Please [write a great commit message](https://chris.beams.io/posts/git-commit/). + +1. Separate subject from body with a blank line +1. Limit the subject line to 50 characters +1. Capitalize the subject line +1. Do not end the subject line with a period +1. Wrap the body at _about_ 72 characters +1. Use the body to explain **why**, *not what and how* (the code shows that!) +1. If applicable, prefix the title with the relevant component name or emoji (see below. examples: "[Docs] Fix typo", "[Profile] Fix missing avatar") + +``` +:palm_tree: Summary of Amazing Feature Here + +Add a more detailed explanation here, if necessary. Possibly give +some background about the issue being fixed, etc. The body of the +commit message can be several paragraphs. Further paragraphs come +after blank lines and please do proper word-wrap. + +Wrap it to about 72 characters or so. In some contexts, +the first line is treated as the subject of the commit and the +rest of the text as the body. The blank line separating the summary +from the body is critical (unless you omit the body entirely); +various tools like `log`, `shortlog` and `rebase` can get confused +if you run the two together. + +Explain the problem that this commit is solving. Focus on why you +are making this change as opposed to how or what. The code explains +how or what. Reviewers and your future self can read the patch, +but might not understand why a particular solution was implemented. +Are there side effects or other unintuitive consequences of this +change? Here's the place to explain them. + + - Bullet points are awesome, too + + - A hyphen should be used for the bullet, preceded + by a single space, with blank lines in between + +Note the fixed or relevant GitHub issues at the end: + +Resolves: #123 +See also: #456, #789 +``` + +## :white_check_mark: Pull Request Checklist + +Not all Pull Requests require these things, but here's a great list of things to check to see if it makes sense for your situation: + + - [ ] Are the changes complete? + - [ ] Are there tests for the new functionality? + - [ ] Are the changes passing the style checks? + - [ ] Is there documentation for the new functionality? + - [ ] Has the change been added to `Changelog.md`? + - [ ] Has the change been added to `ReleaseNotes.md`? + - [ ] Have new config options been added as defaults to the `project.yml` files? + +## :heart: Who Loves Emoji? + +Commit comments, Issues, Feature Requests... they can all use a little sprucing up, right? Consider using the following emoji for a mix of function and :sparkles: dazzle! We encourage loosely following the conventions of [gitmoji](https://gitmoji.dev) for contributing to Ceedling. + + - actions + - :seedling: `:seedling:` (or other plants) when growing new features. Choose your fav! :cactus: :herb: :evergreen_tree: :palm_tree: :deciduous_tree: :blossom: + - :art: `:art:` when improving the format/structure of the code + - :racehorse: `:racehorse:` when improving performance + - :non-potable_water: `:non-potable_water:` when plugging memory leaks + - :memo: `:memo:` when writing docs + - :bug: `:bug:` (or other insects) when fixing a bug. Maybe :beetle: :ant: or :honeybee: ? + - :fire: `:fire:` when removing code or files + - :green_heart: `:green_heart:` when fixing the CI build + - :white_check_mark: `:white_check_mark:` when adding tests + - :lock: `:lock:` when dealing with security + - :arrow_up: `:arrow_up:` when upgrading dependencies + - :arrow_down: `:arrow_down:` when downgrading dependencies + - :shirt: `:shirt:` when removing linter warnings + + - platforms + - :penguin: `:penguin:` when fixing something on Linux + - :apple: `:apple:` when fixing something on macOS + - :checkered_flag: `:checkered_flag:` when fixing something on Windows + +## :white_check_mark: Code Review + +- **Review the code, not the author.** Look for and suggest improvements without disparaging or insulting the author. Provide actionable feedback and explain your reasoning. + +- **You are not your code.** When your code is critiqued, questioned, or constructively criticized, remember that you are not your code. Do not take code review personally. + +- **Always do your best.** No one writes bugs on purpose. Do your best, and learn from your mistakes. + +- Kindly note any violations to the guidelines specified in this document. + +## :violin: Coding Style + +Consistency is the most important. Following the existing style, formatting, and naming conventions of the file you are modifying and of the overall project. Failure to do so will result in a prolonged review process that has to focus on updating the superficial aspects of your code, rather than improving its functionality and performance. + +For example, if all private properties are prefixed with an underscore `_`, then new ones you add should be prefixed in the same way. Or, if methods are named using camelcase, like `thisIsMyNewMethod`, then do not diverge from that by writing `this_is_my_new_method`. You get the idea. If in doubt, please ask or search the codebase for something similar. + +When possible, style and format will be enforced with a linter. + +### C Styleguide + +C code is linted with [AStyle](https://astyle.sourceforge.net/). + +### Ruby Styleguide + +Ruby code is linted with [Rubocop](https://github.com/rubocop/rubocop) + +## :medal_sports: Certificate of Origin + +*Developer's Certificate of Origin 1.1* + +By making a contribution to this project, I certify that: + +> 1. The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or +> 1. The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or +> 1. The contribution was provided directly to me by some other person who certified (1), (2) or (3) and I have not modified it. +> 1. I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. + +## [No Brown M&M's](https://en.wikipedia.org/wiki/Van_Halen#Contract_riders) + +If you are reading this, bravo dear user and (hopefully) contributor for making it this far! You are awesome. :100: + +To confirm that you have read this guide and are following it as best as possible, **include this emoji at the top** of your issue or pull request: :pineapple: `:pineapple:` + +## :pray: Credits + +Written by [@jessesquires](https://github.com/jessesquires). Lovingly adapted to ThrowTheSwitch.org by [@mvandervoord](https://github.com/mvandervoord). + +**Please feel free to adopt this guide in your own projects. Fork it wholesale or remix it for your needs.** + +*Many of the ideas and prose for the statements in this document were based on or inspired by work from the following communities:* + +- [Alamofire](https://github.com/Alamofire/Alamofire/blob/master/CONTRIBUTING.md) +- [CocoaPods](https://github.com/CocoaPods/CocoaPods/blob/master/CONTRIBUTING.md) +- [Docker](https://github.com/moby/moby/blob/master/CONTRIBUTING.md) +- [Linux](https://elinux.org/Developer_Certificate_Of_Origin) + +*We commend them for their efforts to facilitate collaboration in their projects.* diff --git a/docs/Ceedling Powerful Plugins.pdf b/docs/Ceedling Powerful Plugins.pdf deleted file mode 100755 index 4596b6ab1..000000000 Binary files a/docs/Ceedling Powerful Plugins.pdf and /dev/null differ diff --git a/docs/CeedlingPacket.md b/docs/CeedlingPacket.md index 97d59b09b..b7c9d0ffb 100644 --- a/docs/CeedlingPacket.md +++ b/docs/CeedlingPacket.md @@ -1,26 +1,225 @@ -[All code is copyright © 2010-2021 Ceedling Project -by Mike Karlesky, Mark VanderVoord, and Greg Williams. - -This Documentation Is Released Under a -Creative Commons 3.0 Attribution Share-Alike License] - -What the What? - -Assembling build environments for C projects - especially with -automated unit tests - is a pain. Whether it's Make or Rake or Premake -or what-have-you, set up with an all-purpose build environment -tool is tedious and requires considerable glue code to pull together -the necessary tools and libraries. Ceedling allows you to generate -an entire test and build environment for a C project from a single -YAML configuration file. Ceedling is written in Ruby and works -with the Rake build tool plus other goodness like Unity and CMock — -the unit testing and mocking frameworks for C. Ceedling and -its complementary tools can support the tiniest of embedded -processors, the beefiest 64 bit power houses available, and + +# Ceedling + +All code is copyright © 2010-2023 Ceedling Project +by Michael Karlesky, Mark VanderVoord, and Greg Williams. + +This Documentation is released under a +[Creative Commons 4.0 Attribution Share-Alike Deed][CC4SA]. + +[CC4SA]: https://creativecommons.org/licenses/by-sa/4.0/deed.en + +# Quick Start + +Ceedling is a fancypants build system that greatly simplifies building +C projects. While it can certainly build release targets, it absolutely +shines at running unit test suites. + +## Steps + +Below is a quick overview of how to get started from Ceedling installation +through running build tasks. Jump down just a teeny bit to see what the Ceedling +command line looks like and navigate to all the documentation for the steps +listed immediately below. + +1. Install Ceedling +1. Create a project + * Use Ceedling to generate an example project, or + * Add a Ceedling project file to the root of an existing project, or + * Create a project from scratch: + 1. Create a project directory + 1. Add source code and optionally test code however you'd like it organized + 1. Create a Ceedling project file in the root of your project directory +1. Run Ceedling tasks from the working directory of your project + +Ceedling requires a command line C toolchain be available in your path. It's +flexible enough to work with most anything on any platform. By default, Ceedling +is ready to work with [GCC] out of the box (we recommend the [MinGW] project +on Windows). + +A common build strategy with tooling other than GCC is to use your target +toolchain for release builds (with or without Ceedling) but rely on Ceedling + +GCC for test builds (more on all this [here][packet-section-2]). + +[GCC]: https://gcc.gnu.org + +## Ceedling Command Line & Build Tasks + +Once you have Ceedling installed, you always have access to `ceedling help`. + +And, once you have Ceedling installed, you have options for project creation +using Ceedling’s application commands: + +* `ceedling new ` +* `ceedling examples` to list available example projects and + `ceedling example ` to create a readymade sample + project whose project file you can copy and modify. + +Once you have a Ceedling project file and a project directory structure for your +code, Ceedling build tasks go like this: + +* `ceedling test:MyCodeModule`, or +* `ceedling test:all`, or +* `ceedling release`, or, if you fancy and have the GCov plugin enabled, +* `ceedling clobber test:all gcov:all release --log --verbosity=obnoxious` + +## Quick Start Documentation + +* [Installation][quick-start-1] +* [Sample test code file + Example Ceedling projects][quick-start-2] +* [Simple Ceedling project file][quick-start-3] +* [Ceedling at the command line][quick-start-4] +* [All your Ceedling project configuration file options][quick-start-5] + +[quick-start-1]: #ceedling-installation--set-up +[quick-start-2]: #commented-sample-test-file +[quick-start-3]: #simple-sample-project-file +[quick-start-4]: #now-what-how-do-i-make-it-go-the-command-line +[quick-start-5]: #the-almighty-project-configuration-file-in-glorious-yaml + +
+ +--- + +# Contents + +(Be sure to review **[breaking changes](BreakingChanges.md)** if you are working with +a new release of Ceedling.) + +Building test suites in C requires much more scaffolding than for +a release build. As such, much of Ceedling’s documentation is concerned +with test builds. But, release build documentation is here too. We promise. +It's just all mixed together. + +1. **[Ceedling, a C Build System for All Your Mad Scientisting Needs][packet-section-1]** + + This section provides lots of background, definitions, and links for Ceedling + and its bundled frameworks. It also presents a very simple, example Ceedling + project file. + +1. **[Ceedling, Unity, and CMock’s Testing Abilities][packet-section-2]** + + This section speaks to the philosophy of and practical options for unit testing + code in a variety of scenarios. + +1. **[How Does a Test Case Even Work?][packet-section-3]** + + A brief overview of what a test case is and several simple examples illustrating + how test cases work. + +1. **[Commented Sample Test File][packet-section-4]** + + This sample test file illustrates how to create test cases as well as many of the + conventions that Ceedling relies on to do its work. There's also a brief + discussion of what gets compiled and linked to create an executable test. + +1. **[Anatomy of a Test Suite][packet-section-5]** + + This documentation explains how a unit test grows up to become a test suite. + +1. **[Ceedling Installation & Set Up][packet-section-6]** + + This one is pretty self explanatory. + +1. **[Now What? How Do I Make It _GO_? The Command Line.][packet-section-7]** + + Ceedling’s command line. + +1. **[Important Conventions & Behaviors][packet-section-8]** + + Much of what Ceedling accomplishes — particularly in testing — is by convention. + Code and files structured and named in certain ways trigger sophisticated + Ceedling build features. This section explains all such conventions. + + This section also covers essential high-level behaviors and features including + how to work with search paths, directory structures & file extensions, release + build binary artifacts, build time logging, and Ceedling’s abilities to + preprocess certain code files before they are incorporated into a test build. + +1. **[Using Unity, CMock & CException][packet-section-9]** + + Not only does Ceedling direct the overall build of your code, it also links + together several key tools and frameworks. Those can require configuration of + their own. Ceedling facilitates this. + +1. **[How to Load a Project Configuration. You Have Options, My Friend.][packet-section-10]** + + You can use a command line flag, an environment variable, or rely on a default + file in your working directory to load your base configuration. + + Once your base project configuration is loaded, you have **_Mixins_** for merging + additional configuration for different build scenarios as needed via command line, + environment variable, and/or your project configuration file. + +1. **[The Almighty Ceedling Project Configuration File (in Glorious YAML)][packet-section-11]** + + This is the exhaustive documentation for all of Ceedling’s project file + configuration options — from project paths to command line tools to plugins and + much, much more. + +1. **[Which Ceedling][packet-section-12]** + + Sometimes you may need to point to a different Ceedling to run. + +1. **[Build Directive Macros][packet-section-13]** + + These code macros can help you accomplish your build goals When Ceedling’s + conventions aren’t enough. + +1. **[Ceedling Plugins][packet-section-14]** + + Ceedling is extensible. It includes a number of built-in plugins for code coverage, + test report generation, continuous integration reporting, test file scaffolding + generation, sophisticated release builds, and more. + +1. **[Global Collections][packet-section-15]** + + Ceedling is built in Ruby. Collections are globally available Ruby lists of paths, + files, and more that can be useful for advanced customization of a Ceedling project + file or in creating plugins. + +[packet-section-1]: #ceedling-a-c-build-system-for-all-your-mad-scientisting-needs +[packet-section-2]: #ceedling-unity-and-c-mocks-testing-abilities +[packet-section-3]: #how-does-a-test-case-even-work +[packet-section-4]: #commented-sample-test-file +[packet-section-5]: #anatomy-of-a-test-suite +[packet-section-6]: #ceedling-installation--set-up +[packet-section-7]: #now-what-how-do-i-make-it-go-the-command-line +[packet-section-8]: #important-conventions--behaviors +[packet-section-9]: #using-unity-cmock--cexception +[packet-section-10]: #how-to-load-a-project-configuration-you-have-options-my-friend +[packet-section-11]: #the-almighty-ceedling-project-configuration-file-in-glorious-yaml +[packet-section-12]: #which-ceedling +[packet-section-13]: #build-directive-macros +[packet-section-14]: #ceedling-plugins +[packet-section-15]: #global-collections + +--- + +
+ +# Ceedling, a C Build System for All Your Mad Scientisting Needs + +Ceedling allows you to generate an entire test and release build +environment for a C project from a single, short YAML configuration +file. + +Ceedling and its bundled tools, Unity, CMock, and CException, don’t +want to brag, but they’re also quite adept at supporting the tiniest of +embedded processors, the beefiest 64-bit powerhouses available, and everything in between. -For a build project including unit tests and using the default -toolchain gcc, the configuration file could be as simple as this: +Assembling build environments for C projects — especially with +automated unit tests — is a pain. No matter the all-purpose build +environment tool you use, configuration is tedious and requires +considerable glue code to pull together the necessary tools and +libraries to run unit tests. The Ceedling bundle handles all this +for you. + +## Simple Sample Project File + +For a project including Unity/CMock unit tests and using the default +toolchain `gcc`, the configuration file could be as simple as this: ```yaml :project: @@ -32,39 +231,59 @@ toolchain gcc, the configuration file could be as simple as this: - tests/** :source: - source/** + :include: + - inc/** ``` -From the command line, to build the release version of your project, -you would simply run `ceedling release`. To run all your unit tests, -you would run `ceedling test:all`. That's it! +From the command line, to run all your unit tests, you would run +`ceedling test:all`. To build the release version of your project, +you would simply run `ceedling release`. That's it! Of course, many more advanced options allow you to configure your project with a variety of features to meet a variety of needs. Ceedling can work with practically any command line toolchain and directory structure – all by way of the configuration file. -Further, because Ceedling piggy backs on Rake, you can add your -own Rake tasks to accomplish project tasks outside of testing -and release builds. A facility for plugins also allows you to -extend Ceedling's capabilities for needs such as custom code -metrics reporting and coverage testing. -What's with this Name? +See this [commented project file][example-config-file] +for a much more complete and sophisticated example of a project +configuration. + +See the later [configuration section][project-configuration] for +way more details on your project configuration options. + +A facility for [plugins](#ceedling-plugins) also allows you to +extend Ceedling’s capabilities for needs such as custom code metrics +reporting, build artifact packaging, and much more. A variety of +built-in plugins come with Ceedling. + +[example-config-file]: ../assets/project_as_gem.yml +[project-configuration]: #the-almighty-ceedling-project-configuration-file-in-glorious-yaml + +## What’s with This Name? + +Glad you asked. Ceedling is tailored for unit tested C projects and is built +upon Rake, a Make replacement implemented in the Ruby scripting language. + +So, we've got C, our Rake, and the fertile soil of a build environment in which +to grow and tend your project and its unit tests. Ta da — _Ceedling_. -Glad you asked. Ceedling is tailored for unit tested C projects -and is built upon / around Rake (Rake is a Make replacement implemented -in the Ruby scripting language). So, we've got C, our Rake, and -the fertile soil of a build environment in which to grow and tend -your project and its unit tests. Ta da - _Ceedling_. +Incidentally, though Rake was the backbone of the earliest versions of +Ceedling, it is now being phased out incrementally in successive releases +of this tool. The name Ceedling is not going away, however! -What Do You Mean "tailored for unit tested C projects"? +## What Do You Mean “Tailored for unit tested C projects”? Well, we like to write unit tests for our C code to make it lean and -mean (that whole [Test-Driven Development][tdd] -thing). Along the way, this style of writing C code spawned two -tools to make the job easier: a unit test framework for C called -_Unity_ and a mocking library called _CMock_. And, though it's -not directly related to testing, a C framework for exception -handling called _CException_ also came along. +mean — that whole [Test-Driven Development][tdd] thing. + +Along the way, this style of writing C code spawned two +tools to make the job easier: + +1. A unit test framework for C called _Unity_ +1. A mocking library called _CMock_ + +And, though it's not directly related to testing, a C framework for +exception handling called _CException_ also came along. [tdd]: http://en.wikipedia.org/wiki/Test-driven_development @@ -76,31 +295,41 @@ or created anew for each new project. Ceedling replaces all that tedium and rework with a configuration file that ties everything together. -Though Ceedling is tailored for unit testing, it can also go right ahead -and build your final binary release artifact for you as well. Or, -Ceedling and your tests can live alongside your existing release build -setup. That said, Ceedling is more powerful as a unit test build -environment than it is a general purpose release build environment; -complicated projects including separate bootloaders or multiple library -builds, etc. are not its strong suit. +Though Ceedling is tailored for unit testing, it can also go right +ahead and build your final binary release artifact for you as well. +That said, Ceedling is more powerful as a unit test build environment +than it is a general purpose release build environment. Complicated +projects including separate bootloaders or multiple library builds, +etc. are not necessarily its strong suit (but the +[`subprojects`](../plugins/subprojects/README.md) plugin can +accomplish quite a bit here). + +It's quite common and entirely workable to host Ceedling and your +test suite alongside your existing release build setup. That is, you +can use make, Visual Studio, SCons, Meson, etc. for your release build +and Ceedling for your test build. Your two build systems will simply +“point“ to the same project code. -Hold on. Back up. Ruby? Rake? YAML? Unity? CMock? CException? +## Hold on. Back up. Ruby? Rake? YAML? Unity? CMock? CException? -Seem overwhelming? It's not bad at all, and for the benefits tests +Seems overwhelming? It's not bad at all. And, for the benefits testing bring us, it's all worth it. -[Ruby][] is a handy scripting -language like Perl or Python. It's a modern, full featured language -that happens to be quite handy for accomplishing tasks like code -generation or automating one's workflow while developing in -a compiled language such as C. +### Ruby + +[Ruby] is a handy scripting language like Perl or Python. It's a modern, +full featured language that happens to be quite handy for accomplishing +tasks like code generation or automating one's workflow while developing +in a compiled language such as C. [Ruby]: http://www.ruby-lang.org/en/ -[Rake][] is a utility written in Ruby -for accomplishing dependency tracking and task automation -common to building software. It's a modern, more flexible replacement -for [Make][]). +### Rake + +[Rake] is a utility written in Ruby for accomplishing dependency +tracking and task automation common to building software. It's a modern, +more flexible replacement for [Make]). + Rakefiles are Ruby files, but they contain build targets similar in nature to that of Makefiles (but you can also run Ruby code in your Rakefile). @@ -108,16 +337,29 @@ your Rakefile). [Rake]: http://rubyrake.org/ [Make]: http://en.wikipedia.org/wiki/Make_(software) -[YAML][] is a "human friendly data serialization standard for all -programming languages." It's kinda like a markup language, but don't -call it that. With a YAML library, you can [serialize][] data structures +### YAML + +[YAML] is a "human friendly data serialization standard for all +programming languages." It's kinda like a markup language but don’t +call it that. With a YAML library, you can [serialize] data structures to and from the file system in a textual, human readable form. Ceedling uses a serialized data structure as its configuration input. +YAML has some advanced features that can greatly +[reduce duplication][yaml-anchors-aliases] in a configuration file +needed in complex projects. YAML anchors and aliases are beyond the scope +of this document but may be of use to advanced Ceedling users. Note that +Ceedling does anticipate the use of YAML aliases. It proactively flattens +YAML lists to remove any list nesting that results from the convenience of +aliasing one list inside another. + [YAML]: http://en.wikipedia.org/wiki/Yaml [serialize]: http://en.wikipedia.org/wiki/Serialization +[yaml-anchors-aliases]: https://blog.daemonl.com/2016/02/yaml.html + +### Unity -[Unity] is a [unit test framework][test] for C. It provides facilities +[Unity] is a [unit test framework][unit-testing] for C. It provides facilities for test assertions, executing tests, and collecting / reporting test results. Unity derives its name from its implementation in a single C source file (plus two C header files) and from the nature of its @@ -125,16 +367,26 @@ implementation - Unity will build in any C toolchain and is configurable for even the very minimalist of processors. [Unity]: http://github.com/ThrowTheSwitch/Unity -[test]: http://en.wikipedia.org/wiki/Unit_testing +[unit-testing]: http://en.wikipedia.org/wiki/Unit_testing -[CMock] is a tool written in Ruby able to generate entire -[mock functions][mock] in C code from a given C header file. Mock -functions are invaluable in [interaction-based unit testing][ut]. +### CMock + +[CMock]† is a tool written in Ruby able to generate [function mocks & stubs][test-doubles] +in C code from a given C header file. Mock functions are invaluable in +[interaction-based unit testing][interaction-based-tests]. CMock's generated C code uses Unity. +† Through a [plugin][FFF-plugin], Ceedling also supports +[FFF], _Fake Function Framework_, for [fake functions][test-doubles] as an +alternative to CMock’s mocks and stubs. + [CMock]: http://github.com/ThrowTheSwitch/CMock -[mock]: http://en.wikipedia.org/wiki/Mock_object -[ut]: http://martinfowler.com/articles/mocksArentStubs.html +[test-doubles]: https://blog.pragmatists.com/test-doubles-fakes-mocks-and-stubs-1a7491dfa3da +[FFF]: https://github.com/meekrosoft/fff +[FFF-plugin]: ../plugins/fff +[interaction-based-tests]: http://martinfowler.com/articles/mocksArentStubs.html + +### CException [CException] is a C source and header file that provide a simple [exception mechanism][exn] for C by way of wrapping up the @@ -146,2181 +398,5193 @@ up your return call trace. [exn]: http://en.wikipedia.org/wiki/Exception_handling [setjmp]: http://en.wikipedia.org/wiki/Setjmp.h -Notes ------ +## Notes on Ceedling Dependencies and Bundled Tools -* YAML support is included with Ruby - requires no special installation - or configuration. +* By using the preferred installation option of the Ruby Ceedling gem (see + later installation section), all other Ceedling dependencies will be + installed for you. -* Unity, CMock, and CException are bundled with Ceedling, and - Ceedling is designed to glue them all together for your project - as seamlessly as possible. +* Regardless of installation method, Unity, CMock, and CException are bundled + with Ceedling. Ceedling is designed to glue them all together for your + project as seamlessly as possible. +* YAML support is included with Ruby. It requires no special installation + or configuration. If your project file contains properly formatted YAML + with the recognized names and options (see later sections), you are good + to go. -Installation & Setup: What Exactly Do I Need to Get Started? ------------------------------------------------------------- +
-As a [Ruby gem](http://docs.rubygems.org/read/chapter/1): +# Ceedling, Unity, and CMock’s Testing Abilities -1. [Download and install Ruby](http://www.ruby-lang.org/en/downloads/) +The unit testing Ceedling, Unity, and CMock afford works in practically +any context. -2. Use Ruby's command line gem package manager to install Ceedling: - `gem install ceedling` - (Unity, CMock, and CException come along with Ceedling for free) +The simplest sort of test suite is one crafted to run on the same host +system using the same toolchain as the release artifact under development. -3. Execute Ceedling at command line to create example project - or an empty Ceedling project in your filesystem (executing - `ceedling help` first is, well, helpful). +But, Ceedling, Unity, and CMock were developed for use on a wide variety +of systems and include features handy for low-level system development work. +This is especially of interest to embedded systems developers. -Gem install notes: +## All your sweet, sweet test suite options -1. Steps 1-2 are a one time affair for your local environment. - When steps 1-2 are completed once, only step 3 is needed for - each new project. +Ceedling, Unity, and CMock help you create and run test suites using any +of the following approaches. For more on this topic, please see this +[handy dandy article][tts-which-build] and/or follow the links for each +item listed below. -Getting Started after Ceedling is installed: +[tts-which-build]: https://throwtheswitch.org/build/which -1. Once Ceedling is installed, you'll want to start to integrate it with new - and old projects alike. If you wanted to start to work on a new project - named `foo`, Ceedling can create the skeleton of the project using `ceedling - new foo`. Likewise if you already have a project named `bar` and you want to - integrate Ceedling into it, you would run `ceedling new bar` and Ceedling - will create any files and directories it needs to run. - -2. Now that you have Ceedling integrated with a project, you can start using it. - A good starting point to get use to Ceedling either in a new project or an - existing project is creating a new module to get use to Ceedling by issuing - the command `ceedling module:create[unicorn]`. - -General notes: - -1. Certain advanced features of Ceedling rely on gcc and cpp - as preprocessing tools. In most linux systems, these tools - are already available. For Windows environments, we recommend - the [mingw project](http://www.mingw.org/) (Minimalist - GNU for Windows). This represents an optional, additional - setup / installation step to complement the list above. Upon - installing mingw ensure your system path is updated or set - [:environment][:path] in your `project.yml` file (see - environment section later in this document). - -2. To use a project file name other than the default `project.yml` - or place the project file in a directory other than the one - in which you'll run Rake, create an environment variable - `CEEDLING_MAIN_PROJECT_FILE` with your desired project - file path. - -3. To better understand Rake conventions, Rake execution, - and Rakefiles, consult the [Rake tutorial, examples, and - user guide](http://rubyrake.org/). - -4. When using Ceedling in Windows environments, a test file name may - not include the sequences “patch” or “setup”. The Windows Installer - Detection Technology (part of UAC), requires administrator - privileges to execute file names with these strings. - -Now What? How Do I Make It GO? ------------------------------- - -We're getting a little ahead of ourselves here, but it's good -context on how to drive this bus. Everything is done via the command -line. We'll cover conventions and how to actually configure -your project in later sections. +1. **[Native][tts-build-native].** This option builds and runs code on your + host system. + 1. In the simplest case this means you are testing code that is intended + to run on the same sort of system as the test suite. Your test + compiler toolchain is the same as your release compiler toolchain. + 1. However, a native build can also mean your test compiler is different + than your release compiler. With some thought and effort, code for + another platform can be tested on your host system. This is often + the best approach for embedded and other specialized development. +1. **[Emulator][tts-build-cross].** In this option, you build your test code with your target's + toolchain, and then run the test suite using an emulator provided for + that target. This is a good option for embedded and other specialized + development — if an emulator is available. +1. **[On target][tts-build-cross].** The Ceedling bundle of tools can create test suites that + run on a target platform directly. Particularly in embedded development + — believe it or not — this is often the option of last resort. That is, + you should probably go with the other options in this list. -To run tests, build your release artifact, etc., you will be interacting -with Rake on the command line. Ceedling works with Rake to present -you with named tasks that coordinate the file generation and -build steps needed to accomplish something useful. You can also -add your own independent Rake tasks or create plugins to extend -Ceedling (more on this later). +[tts-build-cross]: https://throwtheswitch.org/build/cross +[tts-build-native]: https://throwtheswitch.org/build/native -* `ceedling [no arguments]`: +
- Run the default Rake task (conveniently recognized by the name default - by Rake). Neither Rake nor Ceedling provide a default task. Rake will - abort if run without arguments when no default task is defined. You can - conveniently define a default task in the Rakefile discussed in the - preceding setup & installation section of this document. +# How Does a Test Case Even Work? -* `ceedling -T`: +## Behold assertions - List all available Rake tasks with descriptions (Rake tasks without - descriptions are not listed). -T is a command line switch for Rake and - not the same as tasks that follow. +In its simplest form, a test case is just a C function with no +parameters and no return value that packages up logical assertions. +If no assertions fail, the test case passes. Technically, an empty +test case function is a passing test since there can be no failing +assertions. -* `ceedling --trace`: +Ceedling relies on the [Unity] project for its unit test framework +(i.e. the thing that provides assertions and counts up passing +and failing tests). - For advanced users troubleshooting a confusing build error, debug - Ceedling or a plugin, --trace provides a stack trace of dependencies - walked during task execution and any Ruby failures along the way. Note - that --trace is a command line switch for Rake and is not the same as - tasks that follow. +An assertion is simply a logical comparison of expected and actual +values. Unity provides a wide variety of different assertions to +cover just about any scenario you might encounter. Getting +assertions right is actually a bit tricky. Unity does all that +hard work for you and has been thoroughly tested itself and battle +hardened through use by many, many developers. -* `ceedling environment`: +### Super simple passing test case - List all configured environment variable names and string values. This - task is helpful in verifying the evaluation of any Ruby expressions in - the [:environment] section of your config file. *: Note: Ceedling may - set some convenience environment variables by default.* +```c +#include "unity.h" -* `ceedling paths:*`: +void test_case(void) { + TEST_ASSERT_TRUE( (1 == 1) ); +} +``` - List all paths collected from [:paths] entries in your YAML config - file where * is the name of any section contained in [:paths]. This - task is helpful in verifying the expansion of path wildcards / globs - specified in the [:paths] section of your config file. +### Super simple failing test case -* `ceedling files:assembly` -* `ceedling files:include` -* `ceedling files:source` -* `ceedling files:support` -* `ceedling files:test` +```c +#include "unity.h" - List all files and file counts collected from the relevant search - paths specified by the [:paths] entries of your YAML config file. The - files:assembly task will only be available if assembly support is - enabled in the [:release_build] section of your configuration file. +void test_a_different_case(void) { + TEST_ASSERT_TRUE( (1 == 2) ); +} +``` -* `ceedling options:*`: +### Realistic simple test case - Load and merge configuration settings into the main project - configuration. Each task is named after a `*.yml` file found in the - configured options directory. See documentation for the configuration - setting [:project][:options_paths] and for options files in advanced - topics. +In reality, we’re probably not testing the static value of an integer +against itself. Instead, we’re calling functions in our source code +and making assertions against return values. -* `ceedling test:all`: +```c +#include "unity.h" +#include "my_math.h" - Run all unit tests (rebuilding anything that's changed along the way). +void test_some_sums(void) { + TEST_ASSERT_EQUALS( 5, mySum( 2, 3) ); + TEST_ASSERT_EQUALS( 6, mySum( 0, 6) ); + TEST_ASSERT_EQUALS( -12, mySum( 20, -32) ); +} +``` -* `ceedling test:build_only`: +If an assertion fails, the test case fails. As soon as an assertion +fails, execution within that test case stops. - Build all unit tests, object files and executable but not run them. +Multiple test cases can live in the same test file. When all the +test cases are run, their results are tallied into simple pass +and fail metrics with a bit of metadata for failing test cases such +as line numbers and names of test cases. -* `ceedling test:delta`: +Ceedling and Unity work together to both automatically run your test +cases and tally up all the results. - Run only those unit tests for which the source or test files have - changed (i.e. incremental build). Note: with the - [:project][:use_test_preprocessor] configuration file option set, - runner files are always regenerated limiting the total efficiency this - text execution option can afford. +### Sample test case output -* `ceedling test:*`: +Successful test suite run: - Execute the named test file or the named source file that has an - accompanying test. No path. Examples: ceedling test:foo.c or ceedling - test:test_foo.c +``` +-------------------- +OVERALL TEST SUMMARY +-------------------- +TESTED: 49 +PASSED: 49 +FAILED: 0 +IGNORED: 0 +``` -* `ceedling test:pattern[*]`: +A test suite with a failing test: - Execute any tests whose name and/or path match the regular expression - pattern (case sensitive). Example: ceedling "test:pattern[(I|i)nit]" will - execute all tests named for initialization testing. Note: quotes may - be necessary around the ceedling parameter to distinguish regex characters - from command line operators. +``` +------------------- +FAILED TEST SUMMARY +------------------- +[test/TestModel.c] + Test: testInitShouldCallSchedulerAndTemperatureFilterInit + At line (21): "Function TaskScheduler_Init() called more times than expected." + +-------------------- +OVERALL TEST SUMMARY +-------------------- +TESTED: 49 +PASSED: 48 +FAILED: 1 +IGNORED: 0 +``` -* `ceedling test:path[*]`: +### Advanced test cases with mocks - Execute any tests whose path contains the given string (case - sensitive). Example: ceedling test:path[foo/bar] will execute all tests - whose path contains foo/bar. Note: both directory separator characters - / and \ are valid. - -* `ceedling test:* --test_case= ` - Execute test case which match **test_case_name**. Option available only after - setting up **cmdline_args** to true under **test_runner** in project.yml: - - ``` - :test_runner: - :cmdline_args: true - ``` +Often you want to test not just what a function returns but how +it interacts with other functions. - For instance, if you have file test_gpio.c with defined 3 tests: +The simple test cases above work well at the "edges" of a +codebase (libraries, state management, some kinds of I/O, etc.). +But, in the messy middle of your code, code calls other code. +One way to handle testing this is with [mock functions][mocks] and +[interaction-based testing][interaction-based-tests]. - - test_gpio_start - - test_gpio_configure_proper - - test_gpio_configure_fail_pin_not_allowed +Mock functions are functions with the same interface as the real +code the mocks replace. A mocked function allows you to control +how it behaves and wrap up assertions within a higher level idea +of expectations. - and you want to run only configure tests, you can call: +What is meant by an expectation? Well
 We _expect_ a certain +function is called with certain arguments and that it will return +certain values. With the appropriate code inside a mocked function +all of this can be managed and checked. - ```ceedling test:gpio --test_case=configure``` +You can write your own mocks, of course. But, it's generally better +to rely on something else to do it for you. Ceedling uses the [CMock] +framework to perform mocking for you. - --- - **Limitation** +Here's some sample code you might want to test: - The Unity implementation use test case name as substring which will be search in your test case names. If you pass only **gpio** and your file under test contains **gpio** in the name, it will run all tests from it. This is connected with the logic, how Unity generates test_ files. In such case, it is suggested to use full name of test case. +```c +#include "other_code.h" + +void doTheThingYo(mode_t input) { + mode_t result = processMode(input); + if (result == MODE_3) { + setOutput(OUTPUT_F); + } + else { + setOutput(OUTPUT_D); + } +} +``` - --- +And, here's what test cases using mocks for that code could look +like: -* `ceedling test:* --exclude_test_case= ` - Execute test case which does not match **test_case_name**. Option available only after - setting up **cmdline_args** to true under **test_runner** in project.yml: - - ``` - :test_runner: - :cmdline_args: true - ``` +```c +#include "mock_other_code.h" - For instance, if you have file test_gpio.c with defined 3 tests: +void test_doTheThingYo_should_enableOutputF(void) { + // Mocks + processMode_ExpectAndReturn(MODE_1, MODE_3); + setOutput_Expect(OUTPUT_F); - - test_gpio_start - - test_gpio_configure_proper - - test_gpio_configure_fail_pin_not_allowed + // Function under test + doTheThingYo(MODE_1); +} - and you want to run only start tests, you can call: +void test_doTheThingYo_should_enableOutputD(void) { + // Mocks + processMode_ExpectAndReturn(MODE_2, MODE_4); + setOutput_Expect(OUTPUT_D); - ```ceedling test:gpio --exclude_test_case=configure``` + // Function under test + doTheThingYo(MODE_2); +} +``` - --- - **Limitation** +Remember, the generated mock code you can’t see here has a whole bunch +of smarts and Unity assertions inside it. CMock scans header files and +then generates mocks (C code) from the function signatures it finds in +those header files. It's kinda magical. - The Unity implementation use test case name as substring which will be search in your test case names. If you pass only **gpio** and your file under test contains **gpio** in the name, it will run all tests from it. This is connected with the logic, how Unity generates test_ files. In such case, it is suggested to use full name of test case. +### That was the basics, but you’ll need more - --- +For more on the assertions and mocking shown above, consult the +documentation for [Unity] and [CMock] or the resources in +Ceedling’s [README][/README.md]. +Ceedling, Unity, and CMock rely on a variety of +[conventions to make your life easier][conventions-and-behaviors]. +Read up on these to understand how to build up test cases +and test suites. -* `ceedling release`: +Also take a look at the very next sections for more examples +and details on how everything fits together. - Build all source into a release artifact (if the release build option - is configured). +[conventions-and-behaviors]: #important-conventions--behaviors -* `ceedling release:compile:*`: +
- Sometimes you just need to compile a single file dagnabit. Example: - ceedling release:compile:foo.c +# Commented Sample Test File -* `ceedling release:assemble:*`: +**Here is a beautiful test file to help get you started
** - Sometimes you just need to assemble a single file doggonit. Example: - ceedling release:assemble:foo.s +## Core concepts in code -* `ceedling module:create[Filename]`: -* `ceedling module:create[Filename]`: +After absorbing this sample code, you’ll have context for much +of the documentation that follows. - It's often helpful to create a file automatically. What's better than - that? Creating a source file, a header file, and a corresponding test - file all in one step! +The sample test file below demonstrates the following: - There are also patterns which can be specified to automatically generate - a bunch of files. Try `ceedling module:create[Poodles,mch]` for example! +1. Making use of the Unity & CMock test frameworks. +1. Adding the source under test (`foo.c`) to the final test + executable by convention (`#include "foo.h"`). +1. Adding two mocks to the final test executable by convention + (`#include "mock_bar.h` and `#include "mock_baz.h`). +1. Adding a source file with no matching header file to the test + executable with a test build directive macro + `TEST_SOURCE_FILE("more.c")`. +1. Creating two test cases with mock expectations and Unity + assertions. - The module generator has several options you can configure. - F.e. Generating the source/header/test file in a subdirectory (by adding when calling module:create). - For more info, refer to the [Module Generator](https://github.com/ThrowTheSwitch/Ceedling/blob/master/docs/CeedlingPacket.md#module-generator) section. +All other conventions and features are documented in the sections +that follow. -* `ceedling module:stub[Filename]`: -* `ceedling module:stub[Filename]`: +```c +// test_foo.c ----------------------------------------------- +#include "unity.h" // Compile/link in Unity test framework +#include "types.h" // Header file with no *.c file -- no compilation/linking +#include "foo.h" // Corresponding source file, foo.c, under test will be compiled and linked +#include "mock_bar.h" // bar.h will be found and mocked as mock_bar.c + compiled/linked in; +#include "mock_baz.h" // baz.h will be found and mocked as mock_baz.c + compiled/linked in - So what happens if you've created your API in your header (maybe even using - TDD to do so?) and now you need to start to implement the corresponding C - module? Why not get a head start by using `ceedilng module:stub[headername]` - to automatically create a function skeleton for every function declared in - that header? Better yet, you can call this again whenever you add new functions - to that header to add just the new functions, leaving the old ones alone! +TEST_SOURCE_FILE("more.c") // foo.c depends on symbols from more.c, but more.c has no matching more.h -* `ceedling logging `: +void setUp(void) {} // Every test file requires this function; + // setUp() is called by the generated runner before each test case function - Enable logging to /logs. Must come before test and release - tasks to log their steps and output. Log names are a concatenation of - project, user, and option files loaded. User and option files are - documented in the advanced topics section of this document. +void tearDown(void) {} // Every test file requires this function; + // tearDown() is called by the generated runner after each test case function -* `ceedling verbosity[x] `: +// A test case function +void test_Foo_Function1_should_Call_Bar_AndGrill(void) +{ + Bar_AndGrill_Expect(); // Function from mock_bar.c that instructs our mocking + // framework to expect Bar_AndGrill() to be called once + TEST_ASSERT_EQUAL(0xFF, Foo_Function1()); // Foo_Function1() is under test (Unity assertion): + // (a) Calls Bar_AndGrill() from bar.h + // (b) Returns a byte compared to 0xFF +} - Change the default verbosity level. [x] ranges from 0 (quiet) to 4 - (obnoxious). Level [3] is the default. The verbosity task must precede - all tasks in the command line list for which output is desired to be - seen. Verbosity settings are generally most meaningful in conjunction - with test and release tasks. +// Another test case function +void test_Foo_Function2_should_Call_Baz_Tec(void) +{ + Baz_Tec_ExpectAnd_Return(1); // Function from mock_baz.c that instructs our mocking + // framework to expect Baz_Tec() to be called once and return 1 + TEST_ASSERT_TRUE(Foo_Function2()); // Foo_Function2() is under test (Unity assertion) + // (a) Calls Baz_Tec() in baz.h + // (b) Returns a value that can be compared to boolean true +} -* `ceedling summary`: +// end of test_foo.c ---------------------------------------- +``` - If plugins are enabled, this task will execute the summary method of - any plugins supporting it. This task is intended to provide a quick - roundup of build artifact metrics without re-running any part of the - build. +## Ceedling actions from the sample test code -* `ceedling clean`: +From the test file specified above Ceedling will generate +`test_foo_runner.c`. This runner file will contain `main()` and will call +both of the example test case functions. - Deletes all toolchain binary artifacts (object files, executables), - test results, and any temporary files. Clean produces no output at the - command line unless verbosity has been set to an appreciable level. +The final test executable will be `test_foo.exe` (Windows) or `test_foo.out` +for Unix-based systems (extensions are configurable. Based on the `#include` +list and test directive macro above, the test executable will be the output +of the linker having processed `unity.o`, `foo.o`, `mock_bar.o`, `mock_baz.o`, +`more.o`, `test_foo.o`, and `test_foo_runner.o`. -* `ceedling clobber`: +Ceedling finds the needed code files, generates mocks, generates a runner, +compiles all the code files, and links everything into the test executable. +Ceedling will then run the test executable and collect test results from it +to be reported to the developer at the command line. - Extends clean task's behavior to also remove generated files: test - runners, mocks, preprocessor output. Clobber produces no output at the - command line unless verbosity has been set to an appreciable level. +## Incidentally, Ceedling comes with example projects -* `ceedling options:export`: +Ceedling comes with entire example projects you can extract. - This allows you to export a snapshot of your current tool configuration - as a yaml file. You can specify the name of the file in brackets `[blah.yml]` - or let it default to `tools.yml`. In either case, the produced file can be - used as the tool configuration for you project if desired, and modified as you - wish. +1. Execute `ceedling examples` in your terminal to list available example + projects. +1. Execute `ceedling example [destination]` to extract the + named example project. -To better understand Rake conventions, Rake execution, and -Rakefiles, consult the [Rake tutorial, examples, and user guide][guide]. +You can inspect the _project.yml_ file and source & test code. Run +`ceedling help` from the root of the example projects to see what you can +do, or just go nuts with `ceedling test:all`. -[guide]: http://rubyrake.org/ +
-At present, none of Ceedling's commands provide persistence. -That is, they must each be specified at the command line each time -they are needed. For instance, Ceedling's verbosity command -only affects output at the time it's run. +# Anatomy of a Test Suite -Individual test and release file tasks -are not listed in `-T` output. Because so many files may be present -it's unwieldy to list them all. +A Ceedling test suite is composed of one or more individual test executables. -Multiple rake tasks can be executed at the command line (order -is executed as provided). For example, `ceedling -clobber test:all release` will removed all generated files; -build and run all tests; and then build all source - in that order. -If any Rake task fails along the way, execution halts before the -next task. +The [Unity] project provides the actual framework for test case assertions +and unit test sucess/failure accounting. If mocks are enabled, [CMock] builds +on Unity to generate mock functions from source header files with expectation +test accounting. Ceedling is the glue that combines these frameworks, your +project’s toolchain, and your source code into a collection of test +executables you can run as a singular suite. -The `clobber` task removes certain build directories in the -course of deleting generated files. In general, it's best not -to add to source control any Ceedling generated directories -below the root of your top-level build directory. That is, leave -anything Ceedling & its accompanying tools generate out of source -control (but go ahead and add the top-level build directory that -holds all that stuff). Also, since Ceedling is pretty smart about -what it rebuilds and regenerates, you needn't clobber often. +## What is a test executable? -Important Conventions -===================== +Put simply, in a Ceedling test suite, each test file becomes a test executable. +Your test code file becomes a single test executable. -Directory Structure, Filenames & Extensions -------------------------------------------- +`test_foo.c` âžĄïž `test_foo.out` (or `test_foo.exe` on Windows) -Much of Ceedling's functionality is driven by collecting files -matching certain patterns inside the paths it's configured -to search. See the documentation for the [:extension] section -of your configuration file (found later in this document) to -configure the file extensions Ceedling uses to match and collect -files. Test file naming is covered later in this section. +A single test executable generally comprises the following. Each item in this +list is a C file compiled into an object file. The entire list is linked into +a final test executable. -Test files and source files must be segregated by directories. -Any directory structure will do. Tests can be held in subdirectories -within source directories, or tests and source directories -can be wholly separated at the top of your project's directory -tree. +* One or more release C code files under test (`foo.c`) +* `unity.c`. +* A test C code file (`test_foo.c`). +* A generated test runner C code file (`test_foo_runner.c`). `main()` is located + in the runner. +* If using mocks: + * `cmock.c` + * One more mock C code files generated from source header files (`mock_bar.c`) -Search Path Order ------------------ - -When Ceedling searches for files (e.g. looking for header files -to mock) or when it provides search paths to any of the default -gcc toolchain executables, it organizes / prioritizes its search -paths. The order is always: test paths, support paths, source -paths, and then include paths. This can be useful, for instance, -in certain testing scenarios where we desire Ceedling or a compiler -to find a stand-in header file in our support directory before -the actual source header file of the same name. - -This convention only holds when Ceedling is using its default -tool configurations and / or when tests are involved. If you define -your own tools in the configuration file (see the [:tools] section -documented later in this here document), you have complete control -over what directories are searched and in what order. Further, -test and support directories are only searched when appropriate. -That is, when running a release build, test and support directories -are not used at all. - -Source Files & Binary Release Artifacts ---------------------------------------- +## Why multiple individual test executables in a suite? -Your binary release artifact results from the compilation and -linking of all source files Ceedling finds in the specified source -directories. At present only source files with a single (configurable) -extension are recognized. That is, `*.c` and `*.cc` files will not -both be recognized - only one or the other. See the configuration -options and defaults in the documentation for the [:extension] -sections of your configuration file (found later in this document). +For several reasons: -Test Files & Executable Test Fixtures -------------------------------------- +* This greatly simplifies the building of your tests. +* C lacks any concept of namespaces or reflection abilities able to segment and + distinguish test cases. +* This allows the same release code to be built differently under different + testing scenarios. Think of how different `#define`s, compiler flags, and + linked libraries might come in handy for different tests of the same + release C code. One source file can be built and tested in different ways + with multiple test files. -Ceedling builds each individual test file with its accompanying -source file(s) into a single, monolithic test fixture executable. -Test files are recognized by a naming convention: a (configurable) -prefix such as "`test_`" in the file name with the same file extension -as used by your C source files. See the configuration options -and defaults in the documentation for the [:project] and [:extension] -sections of your configuration file (found later in this document). -Depending on your configuration options, Ceedling can recognize -a variety of test file naming patterns in your test search paths. -For example: `test_some_super_functionality.c`, `TestYourSourceFile.cc`, -or `testing_MyAwesomeCode.C` could each be valid test file -names. Note, however, that Ceedling can recognize only one test -file naming convention per project. +## Ceedling’s role in your test suite -Ceedling knows what files to compile and link into each individual -test executable by way of the #include list contained in each -test file. Any C source files in the configured search directories -that correspond to the header files included in a test file will -be compiled and linked into the resulting test fixture executable. -From this same #include list, Ceedling knows which files to mock -and compile and link into the test executable (if you use mocks -in your tests). That was a lot of clauses and information in a very -few sentences; the example that follows in a bit will make it clearer. +A test executable is not all that hard to create by hand, but it can be tedious, +repetitive, and error-prone. -By naming your test functions according to convention, Ceedling -will extract and collect into a runner C file calls to all your -test case functions. This runner file handles all the execution -minutiae so that your test file can be quite simple and so that -you never forget to wire up a test function to be executed. In this -generated runner lives the `main()` entry point for the resulting -test executable. There are no configuration options for the -naming convention of your test case functions. A test case function -signature must have these three elements: void return, void -parameter list, and the function name prepended with lowercase -"`test`". In other words, a test function signature should look -like this: `void test``[any name you like]``(void)`. - -A commented sample test file follows on the next page. Also, see -the sample project contained in the Ceedling documentation -bundle. +What Ceedling provides is an ability to perform the process repeatedly and simply +at the push of a button, alleviating the tedium and any forgetfulness. Just as +importantly, Ceedling also does all the work of running each of those test +executables and tallying all the test results. -```c -// test_foo.c ----------------------------------------------- -#include "unity.h" // compile/link in Unity test framework -#include "types.h" // header file with no *.c file -- no compilation/linking -#include "foo.h" // source file foo.c under test -#include "mock_bar.h" // bar.h will be found and mocked as mock_bar.c + compiled/linked in; - // foo.c includes bar.h and uses functions declared in it -#include "mock_baz.h" // baz.h will be found and mocked as mock_baz.c + compiled/linked in - // foo.c includes baz.h and uses functions declared in it +
+# Ceedling Installation & Set Up -void setUp(void) {} // every test file requires this function; - // setUp() is called by the generated runner before each test case function +**How Exactly Do I Get Started?** -void tearDown(void) {} // every test file requires this function; - // tearDown() is called by the generated runner after each test case function +You have two good options for installing and running Ceedling: -// a test case function -void test_Foo_Function1_should_Call_Bar_AndGrill(void) -{ - Bar_AndGrill_Expect(); // setup function from mock_bar.c that instructs our - // framework to expect Bar_AndGrill() to be called once - TEST_ASSERT_EQUAL(0xFF, Foo_Function1()); // assertion provided by Unity - // Foo_Function1() calls Bar_AndGrill() & returns a byte -} +1. The Ceedling Ruby Gem +1. Prepackaged _MadScienceLab_ Docker images -// another test case function -void test_Foo_Function2_should_Call_Baz_Tec(void) -{ - Baz_Tec_ExpectAnd_Return(1); // setup function provided by mock_baz.c that instructs our - // framework to expect Baz_Tec() to be called once and return 1 - TEST_ASSERT_TRUE(Foo_Function2()); // assertion provided by Unity -} +The simplest way to get started with a local installation is to install +Ceedling as a Ruby gem. Gems are simply prepackaged Ruby-based software. +Other options exist, but the Ceedling Gem is the best option for a local +installation. However, you will also need a compiler toolchain (e.g. GNU +Compiler Collection) plus any supporting tools used by any plugins you +enabled. -// end of test_foo.c ---------------------------------------- -``` +If you are familiar with the virtualization technology Docker, our premade +Docker images will get you started with Ceedling and all the accompanying +tools lickety split. Install Docker, pull down one of the _MadScienceLab_ +images and go. -From the test file specified above Ceedling will generate `test_foo_runner.c`; -this runner file will contain `main()` and call both of the example -test case functions. - -The final test executable will be `test_foo.exe` (for Windows -machines or `test_foo.out` for linux systems - depending on default -or configured file extensions). Based on the #include list above, -the test executable will be the output of the linker having processed -`unity.o`, `foo.o`, `mock_bar.o`, `mock_baz.o`, `test_foo.o`, -and `test_foo_runner.o`. Ceedling finds the files, generates -mocks, generates a runner, compiles all the files, and links -everything into the test executable. Ceedling will then run -the test executable and collect test results from it to be reported -to the developer at the command line. - -For more on the assertions and mocks shown, consult the documentation -for Unity and CMock. - -The Magic of Dependency Tracking --------------------------------- - -Ceedling is pretty smart in using Rake to build up your project's -dependencies. This means that Ceedling automagically rebuilds -all the appropriate files in your project when necessary: when -your configuration changes, Ceedling or any of the other tools -are updated, or your source or test files change. For instance, -if you modify a header file that is mocked, Ceedling will ensure -that the mock is regenerated and all tests that use that mock are -rebuilt and re-run when you initiate a relevant testing task. -When you see things rebuilding, it's for a good reason. Ceedling -attempts to regenerate and rebuild only what's needed for a given -execution of a task. In the case of large projects, assembling -dependencies and acting upon them can cause some delay in executing -tasks. - -With one exception, the trigger to rebuild or regenerate a file -is always a disparity in timestamps between a target file and -its source - if an input file is newer than its target dependency, -the target is rebuilt or regenerated. For example, if the C source -file from which an object file is compiled is newer than that object -file on disk, recompilation will occur (of course, if no object -file exists on disk, compilation will always occur). The one -exception to this dependency behavior is specific to your input -configuration. Only if your logical configuration changes -will a system-wide rebuild occur. Reorganizing your input configuration -or otherwise updating its file timestamp without modifying -the values within the file will not trigger a rebuild. This behavior -handles the various ways in which your input configuration can -change (discussed later in this document) without having changed -your actual project YAML file. - -Ceedling needs a bit of help to accomplish its magic with deep -dependencies. Shallow dependencies are straightforward: -a mock is dependent on the header file from which it's generated, -a test file is dependent upon the source files it includes (see -the preceding conventions section), etc. Ceedling handles -these "out of the box." Deep dependencies are specifically a -C-related phenomenon and occur as a consequence of include statements -within C source files. Say a source file includes a header file -and that header file in turn includes another header file which -includes still another header file. A change to the deepest header -file should trigger a recompilation of the source file, a relinking -of all the object files comprising a test fixture, and a new execution -of that test fixture. - -Ceedling can handle deep dependencies but only with the help -of a C preprocessor. Ceedling is quite capable, but a full C preprocessor -it ain't. Your project can be configured to use a C preprocessor -or not. Simple projects or large projects constructed so as to -be quite flat in their include structure generally don't need -deep dependency preprocessing - and can enjoy the benefits of -faster execution. Legacy code, on the other hand, will almost -always want to be tested with deep preprocessing enabled. Set -up of the C preprocessor is covered in the documentation for the -[:project] and [:tools] section of the configuration file (later -in this document). Ceedling contains all the configuration -necessary to use the gcc preprocessor by default. That is, as -long as gcc is in your system search path, deep preprocessing -of deep dependencies is available to you by simply enabling it -in your project configuration file. - -Ceedling's Build Output ------------------------ +## Local Installation As a [Ruby Gem](http://docs.rubygems.org/read/chapter/1): -Ceedling requires a top-level build directory for all the stuff -that it, the accompanying test tools, and your toolchain generate. -That build directory's location is configured in the [:project] -section of your configuration file (discussed later). There -can be a ton of generated files. By and large, you can live a full -and meaningful life knowing absolutely nothing at all about -the files and directories generated below the root build directory. +1. [Download and install Ruby][ruby-install]. Ruby 3 is required. -As noted already, it's good practice to add your top-level build -directory to source control but nothing generated beneath it. -You'll spare yourself headache if you let Ceedling delete and -regenerate files and directories in a non-versioned corner -of your project's filesystem beneath the top-level build directory. +1. Use Ruby’s command line gem package manager to install Ceedling: + `gem install ceedling`. Unity, CMock, and CException come along with + Ceedling at no extra charge. -The `artifacts` directory is the one and only directory you may -want to know about beneath the top-level build directory. The -subdirectories beneath `artifacts` will hold your binary release -target output (if your project is configured for release builds) -and will serve as the conventional location for plugin output. -This directory structure was chosen specifically because it -tends to work nicely with Continuous Integration setups that -recognize and list build artifacts for retrieval / download. +1. Execute Ceedling at command line to create example project + or an empty Ceedling project in your filesystem (executing + `ceedling help` first is, well, helpful). -The Almighty Project Configuration File (in Glorious YAML) ----------------------------------------------------------- +[ruby-install]: http://www.ruby-lang.org/en/downloads/ -Please consult YAML documentation for the finer points of format -and to understand details of our YAML-based configuration file. -We recommend [Wikipedia's entry on YAML](http://en.wikipedia.org/wiki/Yaml) -for this. A few highlights from that reference page: +### Gem install notes -* YAML streams are encoded using the set of printable Unicode - characters, either in UTF-8 or UTF-16 +Steps 1-2 are a one time affair for your local environment. When steps 1-2 +are completed once, only step 3 is needed for each new project. -* Whitespace indentation is used to denote structure; however - tab characters are never allowed as indentation +## _MadScienceLab_ Docker Images -* Comments begin with the number sign ( # ), can start anywhere - on a line, and continue until the end of the line unless enclosed - by quotes +As an alternative to local installation, fully packaged Docker images containing Ruby, Ceedling, the GCC toolchain, and more are also available. [Docker][docker-overview] is a virtualization technology that provides self-contained software bundles that are a portable, well-managed alternative to local installation of tools like Ceedling. -* List members are denoted by a leading hyphen ( - ) with one member - per line, or enclosed in square brackets ( [ ] ) and separated - by comma space ( , ) +Four Docker image variants containing Ceedling and supporting tools exist. These four images are available for both Intel and ARM host platforms (Docker does the right thing based on your host environment). The latter includes ARM Linux and Apple’s M-series macOS devices. -* Hashes are represented using the colon space ( : ) in the form - key: value, either one per line or enclosed in curly braces - ( { } ) and separated by comma space ( , ) +1. **_[MadScienceLab][docker-image-base]_**. This image contains Ruby, Ceedling, CMock, Unity, CException, the GNU Compiler Collection (gcc), and a handful of essential C libraries and command line utilities. +1. **_[MadScienceLab Plugins][docker-image-plugins]_**. This image contains all of the above plus the command line tools that Ceedling’s built-in plugins rely on. Naturally, it is quite a bit larger than option (1) because of the additional tools and dependencies. +1. **_[MadScienceLab ARM][docker-image-arm]_**. This image mirrors (1) with the compiler toolchain replaced with the GNU `arm-none-eabi` variant. +1. **_[MadScienceLab ARM + Plugins][docker-image-arm-plugins]_**. This image is (3) with the addition of all the complementary plugin tooling just like (2) provides. -* Strings (scalars) are ordinarily unquoted, but may be enclosed - in double-quotes ( " ), or single-quotes ( ' ) +See the Docker Hub pages linked above for more documentation on these images. -* YAML requires that colons and commas used as list separators - be followed by a space so that scalar values containing embedded - punctuation can generally be represented without needing - to be enclosed in quotes +Just to be clear here, most users of the _MadScienceLab_ Docker images will probably care about the ability to run unit tests on your own host. If you are one of those users, no matter what host platform you are on — Intel or ARM — you’ll want to go with (1) or (2) above. The tools within the image will automatically do the right thing within your environment. Options (3) and (4) are most useful for specialized cross-compilation scenarios. -* Repeated nodes are initially denoted by an ampersand ( & ) and - thereafter referenced with an asterisk ( * ) +### _MadScienceLab_ Docker Image usage basics -Notes on what follows: +To use a _MadScienceLab_ image from your local terminal: -* Each of the following sections represent top-level entries - in the YAML configuration file. +1. [Install Docker][docker-install] +1. Determine: + 1. The local path of your Ceedling project + 1. The variant and revision of the Docker image you’ll be using +1. Run the container with: + 1. The Docker `run` command and `-it --rm` command line options + 1. A Docker volume mapping from the root of your project to the default project path inside the container (_/home/dev/project_) -* Unless explicitly specified in the configuration file, default - values are used by Ceedling. +See the command line examples in the following two sections. -* These three settings, at minimum, must be specified: - * [:project][:build_root] - * [:paths][:source] - * [:paths][:test] +Note that all of these somewhat lengthy command lines lend themselves well to being wrapped up in simple helper scripts specific to your project and directory structure. -* As much as is possible, Ceedling validates your settings in - properly formed YAML. +### Run a _MadScienceLab_ Docker Image as an interactive terminal -* Improperly formed YAML will cause a Ruby error when the YAML - is parsed. This is usually accompanied by a complaint with - line and column number pointing into the project file. +When the container launches as shown below, it will drop you into a Z-shell command line that has access to all the tools and utilities available within the container. In this usage, the Docker container becomes just another terminal, including ending its execution with `exit`. -* Certain advanced features rely on gcc and cpp as preprocessing - tools. In most linux systems, these tools are already available. - For Windows environments, we recommend the [mingw project](http://www.mingw.org/) - (Minimalist GNU for Windows). +```shell + > docker run -it --rm -v /my/local/project/path:/home/dev/project throwtheswitch/madsciencelab-plugins:1.0.0 +``` -* Ceedling is primarily meant as a build tool to support automated - unit testing. All the heavy lifting is involved there. Creating - a simple binary release build artifact is quite trivial in - comparison. Consequently, most default options and the construction - of Ceedling itself is skewed towards supporting testing though +Once the _MadScienceLab_ container’s command line is available, to run Ceedling, execute it just as you would after installing Ceedling locally: + +```shell + ~/project > ceedling help +``` + +```shell + ~/project > ceedling new ... +``` + +```shell + ~/project > ceedling test:all +``` + +### Run a _MadScienceLab_ Docker Image as a command line utility + +Alternatively, you can run Ceedling through the _MadScienceLab_ Docker container directly from the command line as a command line utility. The general pattern is immediately below. + +```shell + > docker run -it --rm -v /my/local/project/path:/home/dev/project throwtheswitch/madsciencelab-plugins:1.0.0 +``` + +As a specific example, to run all tests in a suite, the command line would be this: + +```shell + > docker run -it --rm -v /my/local/project/path:/home/dev/project throwtheswitch/madsciencelab-plugins:1.0.0 ceedling test:all +``` + +In this usage, the container starts, executes Ceedling, and then ends. + +[docker-overview]: https://www.ibm.com/topics/docker +[docker-install]: https://www.docker.com/products/docker-desktop/ + +[docker-image-base]: https://hub.docker.com/repository/docker/throwtheswitch/madsciencelab +[docker-image-plugins]: https://hub.docker.com/repository/docker/throwtheswitch/madsciencelab-plugins +[docker-image-arm]: https://hub.docker.com/repository/docker/throwtheswitch/madsciencelab-arm-none-eabi +[docker-image-arm-plugins]: https://hub.docker.com/repository/docker/throwtheswitch/madsciencelab-arm-none-eabi-plugins + +## Getting Started after Ceedling is Installed + +1. Once Ceedling is installed, you’ll want to start to integrate it with new + and old projects alike. If you wanted to start to work on a new project + named `foo`, Ceedling can create the skeleton of the project using `ceedling + new foo `. Likewise if you already have a project named `bar` + and you want to “inject” Ceedling into it, you would run `ceedling new bar + `, and Ceedling will create any files and directories it needs. + +1. Now that you have Ceedling integrated with a project, you can start using it. + A good starting point is to enable the [plugin](#ceedling-plugins) + `module_generator` in your project configuration file and create a source + + test code module to get accustomed to Ceedling by issuing the command + `ceedling 'module:create[name]'`. + +## Grab Bag of Ceedling Notes + +1. Certain advanced features of Ceedling rely on `gcc` and `cpp` as + preprocessing tools. In most Linux systems, these tools are already available. + For Windows environments, we recommend the + [MinGW project](http://www.mingw.org/) (Minimalist GNU for Windows). This + represents an optional, additional setup / installation step to complement + the list above. Upon installing MinGW ensure your system path is updated or + set `:environment` ↳ `:path` in your project configuration (see `:environment` + section). + +1. When using Ceedling in Windows environments, a test filename should not + include the sequences “patch” or “setup”. After a test build these test + filenames will become test executables. Windows Installer Detection Technology + (part of UAC) requires administrator privileges to execute filenames including + these strings. + +
+ +# Now What? How Do I Make It _GO_? The Command Line. + +We’re getting a little ahead of ourselves here, but it's good +context on how to drive this bus. Everything is done via the command +line. We'll cover project conventions and how to actually configure +your project in later sections. + +For now, let's talk about the command line. + +To run tests, build your release artifact, etc., you will be using the +trusty command line. Ceedling is transitioning away from being built +around Rake. As such, right now, interacting with Ceedling at the +command line involves two different conventions: + +1. **Application Commands.** Application commands tell Ceedling what to + to do with your project. These create projects, load project files, + begin builds, output version information, etc. These include rich + help and operate similarly to popular command line tools like `git`. +1. **Build & Plugin Tasks.** Build tasks actually execute test suites, + run release builds, etc. These tasks are created from your project + file. These are generated through Ceedling’s Rake-based code and + conform to its conventions — simplistic help, no option flags, but + bracketed arguments. + +In the case of running builds, both come into play at the command line. + +The two classes of command line arguments are clearly labelled in the +summary of all commands provided by `ceedling help`. + +## Quick command line example to get you started + +To exercise the Ceedling command line quickly, follow these steps after +[installing Ceedling](#ceedling-installation--set-up): + +1. Open a terminal and chnage directories to a location suitable for + an example project. +1. Execute `ceedling example temp_sensor` in your terminal. The `example` + argument is an application command. +1. Change directories into the new _temp_sensor/_ directory. +1. Execute `ceedling test:all` in your terminal. The `test:all` is a + build task executed by the default (and omitted) `build` application + command. +1. Take a look at the build and test suite console output as well as + the _project.yml_ file in the root of the example project. + +## Ceedling application commands + +Ceedling provides robust command line help for application commands. +Execute `ceedling help` for a summary view of all application commands. +Execute `ceedling help ` for detailed help. + +_NOTE:_ Because the built-in command line help is thorough, we will only +briefly list and explain the available application commands. + +* `ceedling [no arguments]`: + + Runs the default build tasks. Unless set in the project file, Ceedling + uses a default task of `test:all`. To override this behavior, set your + own default tasks in the project file (see later section). + +* `ceedling build ` or `ceedling `: + + Runs the named build tasks. `build` is optional (i.e. `ceedling test:all` + is equivalent to `ceedling build test:all`). Various option flags + exist to control project configuration loading, verbosity levels, + logging, test task filters, etc. + + See next section to understand the build & plugin tasks this application + command is able to execute. Run `ceedling help build` to understand all + the command line flags that work with build & plugin tasks. + +* `ceedling dumpconfig`: + + Process project configuration and write final result to a YAML file. + Various option flags exist to control project configuration loading, + configuration manipulation, and configuration sub-section extraction. + +* `ceedling environment`: + + Lists project related environment variables: + + * All environment variable names and string values added to your + environment from within Ceedling and through the `:environment` + section of your configuration. This is especially helpful in + verifying the evaluation of any string replacement expressions in + your `:environment` config entries. + * All existing Ceedling-related environment variables set before you + ran Ceedling from the command line. + +* `ceedling example`: + + Extracts an example project from within Ceedling to your local + filesystem. The available examples are listed with + `ceedling examples`. Various option flags control whether the example + contains vendored Ceedling and/or a documentation bundle. + +* `ceedling examples`: + + Lists the available examples within Ceedling. To extract an example, + use `ceedling example`. + +* `ceedling help`: + + Displays summary help for all application commands and detailed help + for each command. `ceedling help` also loads your project + configuration (if available) and lists all build tasks from it. + Various option flags control what project configuration is loaded. + +* `ceedling new`: + + Creates a new project structure. Various option flags control whether + the new project contains vendored Ceedling, a documentation bundle, + and/or a starter project configuration file. + +* `ceedling upgrade`: + + Upgrade vendored installation of Ceedling for an existing project + along with any locally installed documentation bundles. + +* `ceedling version`: + + Displays version information for Ceedling and its components. Version output for Ceedling includes the Git Commit short SHA in Ceedling’s build identifier and Ceedling’s path of origin. + + ``` + đŸŒ± Welcome to Ceedling! + + Ceedling => #.#.#- + ---------------------- + + + Build Frameworks + ---------------------- + CMock => #.#.# + Unity => #.#.# + CException => #.#.# + ``` + + If the short SHA information is unavailable such as in local development, the SHA is omitted. The source for this string is generated and captured in the Gem at the time of Ceedling’s automated build in CI. + +## Ceedling build & plugin tasks + +Build task are loaded from your project configuration. Unlike +application commands that are fixed, build tasks vary depending on your +project configuration and the files within your project structure. + +Ultimately, build & plugin tasks are executed by the `build` application +command (but the `build` keyword can be omitted — see above). + +* `ceedling paths:*`: + + List all paths collected from `:paths` entries in your YAML config + file where `*` is the name of any section contained in `:paths`. This + task is helpful in verifying the expansion of path wildcards / globs + specified in the `:paths` section of your config file. + +* `ceedling files:assembly` +* `ceedling files:header` +* `ceedling files:source` +* `ceedling files:support` +* `ceedling files:test` + + List all files and file counts collected from the relevant search + paths specified by the `:paths` entries of your YAML config file. The + `files:assembly` task will only be available if assembly support is + enabled in the `:release_build` or `:test_build` sections of your + configuration file. + +* `ceedling test:all`: + + Run all unit tests. + +* `ceedling test:*`: + + Execute the named test file or the named source file that has an + accompanying test. No path. Examples: `ceedling test:foo`, `ceedling + test:foo.c` or `ceedling test:test_foo.c` + +* `ceedling test:* --test-case= ` + Execute individual test cases which match `test_case_name`. + + For instance, if you have a test file _test_gpio.c_ containing the following + test cases (test cases are simply `void test_name(void)`: + + - `test_gpio_start` + - `test_gpio_configure_proper` + - `test_gpio_configure_fail_pin_not_allowed` + + 
 and you want to run only _configure_ tests, you can call: + + `ceedling test:gpio --test-case=configure` + + **Test case matching notes** + + * Test case matching is on sub-strings. `--test_case=configure` matches on + the test cases including the word _configure_, naturally. + `--test-case=gpio` would match all three test cases. + +* `ceedling test:* --exclude_test_case= ` + Execute test cases which do not match `test_case_name`. + + For instance, if you have file test_gpio.c with defined 3 tests: + + - `test_gpio_start` + - `test_gpio_configure_proper` + - `test_gpio_configure_fail_pin_not_allowed` + + 
 and you want to run only start tests, you can call: + + `ceedling test:gpio --exclude_test_case=configure` + + **Test case exclusion matching notes** + + * Exclude matching follows the same sub-string logic as discussed in the + preceding section. + +* `ceedling test:pattern[*]`: + + Execute any tests whose name and/or path match the regular expression + pattern (case sensitive). Example: `ceedling "test:pattern[(I|i)nit]"` + will execute all tests named for initialization testing. + + _NOTE:_ Quotes are likely necessary around the regex characters or + entire task to distinguish characters from shell command line operators. + +* `ceedling test:path[*]`: + + Execute any tests whose path contains the given string (case + sensitive). Example: `ceedling test:path[foo/bar]` will execute all tests + whose path contains foo/bar. _Notes:_ + + 1. Both directory separator characters `/` and `\` are valid. + 1. Quotes may be necessary around the task to distinguish the parameter's + characters from shell command line operators. + +* `ceedling release`: + + Build all source into a release artifact (if the release build option + is configured). + +* `ceedling release:compile:*`: + + Sometimes you just need to compile a single file dagnabit. Example: + `ceedling release:compile:foo.c` + +* `ceedling release:assemble:*`: + + Sometimes you just need to assemble a single file doggonit. Example: + `ceedling release:assemble:foo.s` + +* `ceedling summary`: + + If plugins are enabled, this task will execute the summary method of + any plugins supporting it. This task is intended to provide a quick + roundup of build artifact metrics without re-running any part of the + build. + +* `ceedling clean`: + + Deletes all toolchain binary artifacts (object files, executables), + test results, and any temporary files. Clean produces no output at the + command line unless verbosity has been set to an appreciable level. + +* `ceedling clobber`: + + Extends clean task's behavior to also remove generated files: test + runners, mocks, preprocessor output. Clobber produces no output at the + command line unless verbosity has been set to an appreciable level. + +## Ceedling Command Line Tasks, Extra Credit + +### Combining Tasks At the Command Line + +Multiple build tasks can be executed at the command line. + +For example, `ceedling clobber test:all release` will remove all generated +files; build and run all tests; and then build all source — in that order. If +any task fails along the way, execution halts before the next task. + +Task order is executed as provided and can be important! Running `clobber` after +a `test:` or `release:` task will not accomplish much. + +### Build Directory and Revision Control + +The `clobber` task removes certain build directories in the +course of deleting generated files. In general, it's best not +to add to source control any Ceedling generated directories +below the root of your top-level build directory. That is, leave +anything Ceedling & its accompanying tools generate out of source +control (but go ahead and add the top-level build directory that +holds all that stuff if you want). + +### Logging decorators + +Ceedling attempts to bring more joy to your console logging. This may include +fancy Unicode characters, emoji, or color. + +Example: +``` +----------------------- +❌ OVERALL TEST SUMMARY +----------------------- +TESTED: 6 +PASSED: 5 +FAILED: 1 +IGNORED: 0 +``` + +By default, Ceedling makes an educated guess as to which platforms can best +support this. Some platforms (we’re looking at you, Windows) do not typically +have default font support in their terminals for these features. So, by default +this feature is disabled on problematic platforms while enabled on others. + +An environment variable `CEEDLING_DECORATORS` forces decorators on or off with a +`true` (`1`) or `false` (`0`) string value. + +If you find a monospaced font that provides emojis, etc. and works with Windows’ +command prompt, you can (1) Install the font (2) change your command prompt’s +font (3) set `CEEDLING_DECORATORS` to `true`. + +
+ +# Important Conventions & Behaviors + +**How to get things done and understand what’s happening during builds** + +## Directory Structure, Filenames & Extensions + +Much of Ceedling’s functionality is driven by collecting files +matching certain patterns inside the paths it's configured +to search. See the documentation for the `:extension` section +of your configuration file (found later in this document) to +configure the file extensions Ceedling uses to match and collect +files. Test file naming is covered later in this section. + +Test files and source files must be segregated by directories. +Any directory structure will do. Tests can be held in subdirectories +within source directories, or tests and source directories +can be wholly separated at the top of your project’s directory +tree. + +## Search Paths for Test Builds + +Test builds in C are fairly complex. Each test file becomes a test +executable. Each test executable needs generated runner code and +optionally generated mocks. Slicing and dicing what files are +compiled and linked and how search paths are assembled is tricky +business. That’s why Ceedling exists in the first place. Because +of these issues, search paths, in particular, require quite a bit +of special handling. + +Unless your project is relying exclusively on `extern` statements and +uses no mocks for testing, Ceedling _**must**_ be told where to find +header files. Without search path knowledge, mocks cannot be generated, +and test file compilation will fail for lack of symbol definitions +and function declarations. + +Ceedling provides two mechanisms for configuring search paths: + +1. The [`:paths` ↳ `:include`](#paths--include) section within your + project file (or mixin files). +1. The [`TEST_INCLUDE_PATH(...)`](#test_include_path) build directive + macro. This is only available within test files. + +In testing contexts, you have three options for assembling the core of +the search path list used by Ceedling for test builds: + +1. List all search paths within the `:paths` ↳ `:include` subsection + of your project file. This is the simplest and most common approach. +1. Create the search paths for each test file using calls to the + `TEST_INCLUDE_PATH(...)` build directive macro within each test file. +1. Blending the preceding options. In this approach the subsection + within your project file acts as a common, base list of search + paths while the build directive macro allows the list to be + expanded upon for each test file. This method is especially helpful + for large and/or complex projects in trimming down + problematically long compiler command lines. + +As for the complete search path list for test builds created by Ceedling, +it is assembled from a variety of sources. In order: + +1. Mock generation build path (if mocking is enabled) +1. Paths provided via `TEST_INCLUDE_PATH(...)` build directive macro +1. Any paths within `:paths` ↳ `:test` list containing header files +1. `:paths` ↳ `:support` list from your project configuration +1. `:paths` ↳ `:include` list from your project configuration +1. `:paths` ↳ `:libraries` list from your project configuration +1. Internal path for Unity’s unit test framework C code +1. Internal paths for CMock and CException’s C code (if respective + features enabled) +1. `:paths` ↳ `:test_toolchain_include` list from your project + configuration + +The paths lists above are documented in detail in the discussion of +project configuration. + +_**Notes:**_ + +* The order of your `:paths` entries directly translates to the ordering + of search paths. +* The logic of the ordering above is essentially that: + * Everything above (5) should have precedence to allow test-specific + symbols, function signatures, etc. to be found before that of your + source code under test. This is the necessary pattern for effective + testing and test builds. + * Everything below (5) is supporting symbols and function signatures + for your source code. Your source code should be processed before + these for effective builds generally. +* (3) is a balancing act. It is entirely possible that test developers + will choose to create common files of symbols and supporting code + necessary for unit tests and choose to organize it alongside their + test files. A test build must be able to find these references. At the + same time it is highly unlikely every test directory path in a project + is necessary for a test build — particularly in large and sophisticated + projects. To reduce overall search path length and problematic command + lines, this convention tailors the search path. This is low risk + tailoring but could cause gotchas in edge cases or when Ceedling is + combined with other tools. Any other such tailoring is avoided as it + could too easily cause maddening build problems. +* Remember that the ordering of search paths is impacted by the merge + order of any Mixins. Paths specified with Mixins will be added to + path lists in your project configuration in the order of merging. + +## Search Paths for Release Builds + +Unlike test builds, release builds are relatively straightforward. Each +source file is compiled into an object file. All object files are linked. +A Ceedling release build may optionally compile and link in CException +and can handle linking in libraries as well. + +Search paths for release builds are configured with `:paths` ↳ `:include` +in your project configuration. That’s about all there is to it. + +## Conventions for Source Files & Binary Release Artifacts + +Your binary release artifact results from the compilation and +linking of all source files Ceedling finds in the specified source +directories. At present only source files with a single (configurable) +extension are recognized. That is, `*.c` and `*.cc` files will not +both be recognized - only one or the other. See the configuration +options and defaults in the documentation for the `:extension` +sections of your configuration file (found later in this document). + +## Conventions for Test Files & Executable Test Fixtures + +Ceedling builds each individual test file with its accompanying +source file(s) into a single, monolithic test fixture executable. + +### Test File Naming Convention + +Ceedling recognizes test files by a naming convention — a (configurable) +prefix such as "`test_`" at the beginning of the file name with the same +file extension as used by your C source files. See the configuration options +and defaults in the documentation for the `:project` and `:extension` +sections of your configuration file (elsewhere in this document). + +Depending on your configuration options, Ceedling can recognize +a variety of test file naming patterns in your test search paths. +For example, `test_some_super_functionality.c`, `TestYourSourceFile.cc`, +or `testing_MyAwesomeCode.C` could each be valid test file +names. Note, however, that Ceedling can recognize only one test +file naming convention per project. + +### Conventions for Source and Mock Files to Be Compiled & Linked + +Ceedling knows what files to compile and link into each individual +test executable by way of the `#include` list contained in each +test file and optional test directive macros. + +The `#include` list directs Ceedling in two ways: + +1. Any C source files in the configured project directories + corresponding to `#include`d header files will be compiled and + linked into the resulting test fixture executable. +1. If you are using mocks, header files with the appropriate + mocking prefix (e.g. `mock_foo.h`) direct Ceedling to find the + source header file (e.g. `foo.h`), generate a mock from it, and + compile & link that generated code into into the test executable + as well. + +Sometimes the source file you need to add to your test executable has +no corresponding header file — e.g. `file_abc.h` contains symbols +present in `file_xyz.c`. In these cases, you can use the test +directive macro `TEST_SOURCE_FILE(...)` to tell Ceedling to compile +and link the desired source file into the test executable (see +macro documentation elsewhere in this doc). + +That was a lot of information and many clauses in a very few +sentences; the commented example test file code that follows in a +bit will make it clearer. + +### Convention for Test Case Functions + Test Runner Generation + +By naming your test functions according to convention, Ceedling +will extract and collect into a generated test runner C file the +appropriate calls to all your test case functions. This runner +file handles all the execution minutiae so that your test file +can be quite simple. As a bonus, you’ll never forget to wire up +a test function to be executed. + +In this generated runner lives the `main()` entry point for the +resulting test executable. There are no configurable options for +the naming convention of your test case functions. + +A test case function signature must have these elements: + +1. `void` return +1. `void` parameter list +1. A function name prepended with lowercase "`test`". + +In other words, a test function signature should look like this: +`void test(void)`. + +## Ceedling preprocessing behavior for your tests + +### Preprocessing feature background and overview + +Ceedling and CMock are advanced tools that both perform fairly sophisticated +parsing. + +However, neither of these tools fully understands the entire C language, +especially C’s preprocessing statements. + +If your test files rely on macros and `#ifdef` conditionals used in certain +ways (see examples below), there’s a chance that Ceedling will break on trying +to process your test files, or, alternatively, your test suite will build but +not execute as expected. + +Similarly, generating mocks of header files with macros and `#ifdef` +conditionals around or in function signatures can get weird. Of course, it’s +often in sophisticated projects with complex header files that mocking is most +desired in the first place. + +Ceedling includes an optional ability to preprocess the following files before +then extracting test cases and functions to be mocked with text parsing. + +1. Your test files, or +1. Mockable header files, or +1. Both of the above + +See the [`:project` ↳ `:use_test_preprocessor`][project-settings] project +configuration setting. + +This Ceedling feature uses `gcc`’s preprocessing mode and the `cpp` preprocessor +tool to strip down / expand test files and headers to their raw code content +that can then be parsed as text by Ceedling and CMock. These tools must be in +your search path if Ceedling’s preprocessing is enabled. + +**Ceedling’s test preprocessing abilities are directly tied to the features and +output of `gcc` and `cpp`. The default Ceedling tool definitions for these should +not be redefined for other toolchains. It is highly unlikely to work for you. +Future Ceedling improvements will allow for a plugin-style ability to use your +own tools in this highly specialized capacity.** + +[project-settings]: #project-global-project-settings + +### Ceedling preprocessing limitations and gotchas + +#### Preprocessing limitations cheatsheet + +Ceedling’s preprocessing abilities are generally quite useful — especially in +projects with multiple build configurations for different feature sets or +multiple targets, legacy code that cannot be refactored, and complex header +files provided by vendors. + +However, best applying Ceedling’s preprocessing abilities requires understanding +how the feature works, when to use it, and its limitations. + +At a high level, Ceedling’s preprocessing is applicable for cases where macros +or conditional compilation preprocessing statements (e.g. `#ifdef`): + +* Generate or hide/reveal your test files’ `#include` statements. +* Generate or hide/reveal your test files’ test case function signatures + (e.g. `void test_foo()`. +* Generate or hide/reveal mockable header files’ `#include` statements. +* Generate or hide/reveal header files’ mockable function signatures. + +**_NOTE:_ You do not necessarily need to enable Ceedling’s preprocessing only +because you have preprocessing statements in your test files or mockable header +files. The feature is only truly needed if your project meets the conditions +above.** + +The sections that follow flesh out the details of the bulleted list above. + +#### Preprocessing gotchas + +**_IMPORTANT:_ As of Ceedling 1.0.0, Ceedling’s test preprocessing feature +has a limitation that affects Unity features triggered by the following macros.** + +* `TEST_CASE()` +* `TEST_RANGE()` + +`TEST_CASE()` and `TEST_RANGE()` are Unity macros that are positional in a file +in relation to the test case functions they modify. While Ceedling's test file +preprocessing can preserve these macro calls, their position cannot be preserved. + +That is, Ceedling’s preprocessing and these Unity features are not presently +compatible. Note that it _is_ possible to enable preprocessing for mockable +header files apart from enabling it for test files. See the documentation for +`:project` ↳ `:use_test_preprocessing`. This can allow test preprocessing in the +common cases of sophtisticate mockable headers while Unity’s `TEST_CASE()` and +`TEST_RANGE()` are utilized in a test file untouched by preprocessing. + +**_IMPORTANT:_ The following new build directive macro `TEST_INCLUDE_PATH()` +available in Ceedling 1.0.0 is incompatible with enclosing conditional +compilation C preprocessing statements:** + +Wrapping `TEST_INCLUDE_PATH()` in conditional compilation statements +(e.g. `#ifdef`) will not behave as you expect. This macro is used as a marker +for advanced abilities discovered by Ceedling parsing a test file as plain text. +Whether or not Ceedling preprocessing is enabled, Ceedling will always discover +this marker macro in the plain text of a test file. + +Why is `TEST_INCLUDE_PATH()` incompatible with `#ifdef`? Well, it’s because of +a cyclical dependency that cannot be resolved. In order to perform test +preprocessing, we need a full complement of `#include` search paths. These +could be provided, in part, by `TEST_INCLUDE_PATH()`. But, if we allow +`TEST_INCLUDE_PATH()` to be placed within conditional compilation C +preprocessing statements, our search paths may be different after test +preprocessing! The only solution is to disallow this and scan a test file as +plain text looking for this macro at the beginning of a test build. + +**_Notes:_** + +* `TEST_SOURCE_FILE()` _can_ be placed within conditional compilation + C preprocessing statements. +* `TEST_INCLUDE_PATH()` & `TEST_SOURCE_FILE()` can be “hidden” from Ceedling’s + text scanning with traditional C comments. + +### Preprocessing of your test files + +When preprocessing is enabled for test files, Ceedling will expand preprocessor +statements in test files before extracting `#include` conventions and test case +signatures. That is, preprocessing output is used to generate test runners +and assemble the components of a test executable build. + +**_NOTE:_** Conditional directives _inside_ test case functions generally do +not require Ceedling’s test preprocessing ability. Assuming your code is correct, +the C preprocessor within your toolchain will do the right thing for you +in your test build. Read on for more details and the other cases of interest. + +Test file preprocessing by Ceedling is applicable primarily when conditional +preprocessor directives generate the `#include` statements for your test file +and/or generate or enclose full test case functions. Ceedling will not be able +to properly discover your `#include` statements or test case functions unless +they are plainly available in an expanded, raw code version of your test file. +Ceedling’s preprocessing abilities provide that expansion. + +#### Examples of when Ceedling preprocessing **_is_** needed for test files + +Generally, Ceedling preprocessing is needed when: + +1. `#include` statements are generated by macros +1. `#include` statements are conditionally present due to `#ifdef` statements +1. Test case function signatures are generated by macros +1. Test case function signatures are conditionaly present due to `#ifdef` statements + +```c +// #include conventions are not recognized for anything except #include "..." statements +INCLUDE_STATEMENT_MAGIC("header_file") +``` +```c +// Test file scanning will always see this #include statement +#ifdef BUILD_VARIANT_A +#include "mock_FooBar.h" +#endif +``` +```c +// Test runner generation scanning will see the test case function signature and think this test case exists in every build variation +#ifdef MY_SUITE_BUILD +void test_some_test_case(void) { + TEST_ASSERT_EQUALS(...); +} +#endif +``` +```c +// Test runner generation will not recognize this as a test case when scanning the file +void TEST_CASE_MAGIC("foo_bar_case") { + TEST_ASSERT_EQUALS(...); +} +``` + +#### Examples of when test preprocessing is **_not_** needed for test files + +```c +// Code inside a test case is simply code that your toolchain will expand and build as you desire +// You can manage your compile time symbols with the :defines section of your project configuration file +void test_some_test_case(void) { +#ifdef BUILD_VARIANT_A + TEST_ASSERT_EQUALS(...); +#endif + +#ifdef BUILD_VARIANT_B + TEST_ASSERT_EQUALS(...); +#endif +} +``` + +### Preprocessing of mockable header files + +When preprocessing is enabled for mocking, Ceedling will expand preprocessor +statements in header files before generating mocks from them. CMock requires +a clear look at function definitions and types in order to do its work. + +Header files with preprocessor directives and conditional macros can easily +obscure details from CMock’s limited C parser. Advanced C projects tend +to rely on preprocessing directives and macros to accomplish everything from +build variants to OS calls to register access to managing proprietary language +extensions. + +Mocking is often most useful in complicated codebases. As such Ceedling’s +preprocessing abilities tend to be quite necessary to properly expand header +files so CMock can parse them. + +#### Examples of when Ceedling preprocessing **_is_** needed for mockable headers + +Generally, Ceedling preprocessing is needed when: + +1. Function signatures are formed by macros +1. Function signatures are conditionaly present due to surrounding `#ifdef` + statements +1. Macros expand to become function decorators, return types, or parameters + +**_Important Notes:_** + +* Sometimes CMock’s parsing features can be configured to handle scenarios + that fall within (3) above. CMock can match and remove most text strings, + match and replace certain text strings, map custom types to mockable + alternatives, and be extended with a Unity helper to handle complex and + compound types. See [CMock]’s documentation for more. + +* Test preprocessing causes any macros or symbols in a mockable header to + “disappear” in the generated mock. It’s quite common to have needed symbols + or macros in a header file that do not directly impact the function + signatures to be mocked. This can break compilation of your test suite. + + Possible solutions to this problem include: + + 1. Move symbols and macros in your header file that do not impact function + signatures to another source header file that will not be filtered + by Ceedling’s header file preprocessing. + 1. If (1) is not possible, you may duplicate the needed symbols and macros + in a header file that is only available in your test build search paths + and include it in your test file. + +```c +// Header file scanning will see this function signature but mistakenly mock the name of the macro +void FUNCTION_SIGNATURE_MAGIC(...); +``` + +```c +// Header file scanning will always see this function signature +#ifdef BUILD_VARIANT_A +unsigned int someFunction(void); +#endif +``` + +```c +// Header file scanning will either fail for this function signature or extract erroneous type names +INLINE_MAGIC RETURN_TYPE_MAGIC someFunction(PARAMETER_MAGIC); +``` + +## Execution time (duration) reporting in Ceedling operations & test suites + +### Ceedling’s logged run times + +Ceedling logs two execution times for every project run. + +It first logs the set up time necessary to process your project file, parse code +files, build an internal representation of your project, etc. This duration does +not capture the time necessary to load the Ruby runtime itself. + +``` +Ceedling set up completed in 223 milliseconds +``` + +Secondly, each Ceedling run also logs the time necessary to run all the tasks +you specify at the command line. + +``` +Ceedling operations completed in 1.03 seconds +``` + +### Ceedling test suite and Unity test executable run durations + +A test suite comprises one or more Unity test executables (see +[Anatomy of a Test Suite][anatomy-test-suite]). Ceedling times indvidual Unity +test executable run durations. It also sums these into a total test suite +execution time. These duration values are typically used in generating test +reports via plugins. + +Not all test report formats utilize duration values. For those that do, some +effort is usually required to map Ceedling duration values to a relevant test +suite abstraction within a given test report format. + +Because Ceedling can execute builds with multiple threads, care must be taken +to interpret test suite duration values — particularly in relation to +Ceedling’s logged run times. + +In a multi-threaded build it's quite common for the logged Ceedling project run +time to be less than the total suite time in a test report. In multi-threaded +builds on multi-core machines, test executables are run on different processors +simultaneously. As such, the total on-processor time in a test report can +exceed the operation time Ceedling itself logs to the console. Further, because +multi-threading tends to introduce context switching and processor scheduling +overhead, the run duration of a test executable may be reported as longer than +a in a comparable single-threaded build. + +[anatomy-test-suite]: #anatomy-of-a-test-suite + +### Unity test case run times + +Individual test case exection time tracking is specifically a [Unity] feature +(see its documentation for more details). If enabled and if your platform +supports the time mechanism Unity relies on, Ceedling will automatically +collect test case time values — generally made use of by test report plugins. + +To enable test case duration measurements, they must be enabled as a Unity +compilation option. Add `UNITY_INCLUDE_EXEC_TIME` to Unity's compilation +symbols (`:unity` ↳ `:defines`) in your Ceedling project file (see example +below). Unity test case durations as reported by Ceedling default to 0 if the +compilation option is not set. + +```yaml +:unity: + :defines: + - UNITY_INCLUDE_EXEC_TIME +``` + +_NOTE:_ Most test cases are quite short, and most computers are quite fast. As + such, Unity test case execution time is often reported as 0 milliseconds as + the CPU execution time for a test case typically remains in the microseconds + range. Unity would require special rigging that is inconsistently available + across platforms to measure test case durations at a finer resolution. + +## The Magic of Dependency Tracking + +Previous versions of Ceedling used features of Rake to offer +various kinds of smart rebuilds--that is, only regenerating files, +recompiling code files, or relinking executables when changes within +the project had occurred since the last build. Optional Ceedling +features discovered “deep dependencies” such that, for example, a +change in a header file several nested layers deep in `#include` +statements would cause all the correct test executables to be +updated and run. + +These features have been temporarily disabled and/or removed for +test suites and remain in limited form for release build while +Ceedling undergoes a major overhaul. + +Please see the [Release Notes](ReleaseNotes.md). + +### Notes on (Not So) Smart Rebuids + +* New features that are a part of the Ceedling overhaul can + significantly speed up test suite execution and release builds + despite the present behavior of brute force running all build + steps. See the discussion of enabling multi-threaded builds in + later sections. + +* When smart rebuilds return, they will further speed up builds as + will other planned optimizations. + +## Ceedling’s Build Output (Files, That Is) + +Ceedling requires a top-level build directory for all the stuff +that it, the accompanying test tools, and your toolchain generate. +That build directory's location is configured in the top-level +`:project` section of your configuration file (discussed later). There +can be a ton of generated files. By and large, you can live a full +and meaningful life knowing absolutely nothing at all about +the files and directories generated below the root build directory. + +As noted already, it's good practice to add your top-level build +directory to source control but nothing generated beneath it. +you’ll spare yourself headache if you let Ceedling delete and +regenerate files and directories in a non-versioned corner +of your project’s filesystem beneath the top-level build directory. + +The `artifacts/` directory is the one and only directory you may +want to know about beneath the top-level build directory. The +subdirectories beneath `artifacts` will hold your binary release +target output (if your project is configured for release builds) +and will serve as the conventional location for plugin output. +This directory structure was chosen specifically because it +tends to work nicely with Continuous Integration setups that +recognize and list build artifacts for retrieval / download. + +## Build _Errors_ vs. Test _Failures_. Oh, and Exit Codes. + +### Errors vs. Failures + +Ceedling will run a specified build until an **_error_**. An error +refers to a build step encountering an unrecoverable problem. Files +not found, nonexistent paths, compilation errors, missing symbols, +plugin exceptions, etc. are all errors that will cause Ceedling +to immediately end a build. + +A **_failure_** refers to a test failure. That is, an assertion of +an expected versus actual value failed within a unit test case. +A test failure will not stop a build. Instead, the suite will run +to completion with test failures collected and reported along with +all test case statistics. + +### Ceedling Exit Codes + +In its default configuration, Ceedling terminates with an exit code +of `1`: + + * On any build error and immediately terminates upon that build + error. + * On any test case failure but runs the build to completion and + shuts down normally. + +This behavior can be especially handy in Continuous Integration +environments where you typically want an automated CI build to break +upon either build errors or test failures. + +If this exit code convention for test failures does not work for you, +no problem-o. You may be of the mind that running a test suite to +completion should yield a successful exit code (even if tests failed). +Add the following to your project file to force Ceedling to finish a +build with an exit code of 0 even upon test case failures. + +```yaml +# Ceedling terminates with happy `exit(0)` even if test cases fail +:test_build: + :graceful_fail: true +``` + +If you use the option for graceful failures in CI, you’ll want to +rig up some kind of logging monitor that scans Ceedling’s test +summary report sent to `$stdout` and/or a log file. Otherwise, you +could have a successful build but failing tests. + +### Notes on Unity Test Executable Exit Codes + +Ceedling works by collecting multiple Unity test executables together +into a test suite ([more here](#anatomy-of-a-test-suite). + +A Unity test executable's exit code is the number of failed tests. An +exit code of `0` means all tests passed while anything larger than zero +is the number of test failures. + +Because of platform limitations on how big an exit code number can be +and because of the logical complexities of distinguishing test failure +counts from build errors or plugin problems, Ceedling conforms to a +much simpler exit code convention than Unity: `0` = 🙂 while `1` = â˜č. + +
+ +# Using Unity, CMock & CException + +If you jumped ahead to this section but do not follow some of the +lingo here, please jump back to an [earlier section for definitions +and helpful links][helpful-definitions]. + +[helpful-definitions]: #hold-on-back-up-ruby-rake-yaml-unity-cmock-cexception + +## An overview of how Ceedling supports, well, its supporting frameworks + +If you are using Ceedling for unit testing, this means you are using Unity, +the C testing framework. Unity is fully built-in and enabled for test builds. +It cannot be disabled. + +If you want to use mocks in your test cases, you’ll need to enable mocking +and configure CMock with `:project` ↳ `:use_mocks` and the `:cmock` section +of your project configuration respectively. CMock is fully supported by +Ceedling but generally requires some set up for your project’s needs. + +If you are incorporating CException into your release artifact, you’ll need +to enable exceptions and configure CException with `:project` ↳ +`:use_exceptions` and the `:cexception` section of your project +configuration respectively. Enabling CException makes it available in both +release builds and test builds. + +This section provides a high-level view of how the various tools become +part of your builds and fit into Ceedling’s configuration file. Ceedling’s +configuration file is discussed in detail in the next section. + +See [Unity], [CMock], and [CException]’s project documentation for all +your configuration options. Ceedling offers facilities for providing these +frameworks their compilation and configuration settings. Discussing +these tools and all their options in detail is beyond the scope of Ceedling +documentation. + +## Unity Configuration + +Unity is wholly compiled C code. As such, its configuration is entirely +controlled by a variety of compilation symbols. These can be configured +in Ceedling’s `:unity` project settings. + +### Example Unity configurations + +#### Itty bitty processor & toolchain with limited test execution options + +```yaml +:unity: + :defines: + - UNITY_INT_WIDTH=16 # 16 bit processor without support for 32 bit instructions + - UNITY_EXCLUDE_FLOAT # No floating point unit +``` + +#### Great big gorilla processor that grunts and scratches + +```yaml +:unity: + :defines: + - UNITY_SUPPORT_64 # Big memory, big counters, big registers + - UNITY_LINE_TYPE=\"unsigned int\" # Apparently, we’re writing lengthy test files, + - UNITY_COUNTER_TYPE=\"unsigned int\" # and we've got a ton of test cases in those test files + - UNITY_FLOAT_TYPE=\"double\" # You betcha +``` + +#### Example Unity configuration header file + +Sometimes, you may want to funnel all Unity configuration options into a +header file rather than organize a lengthy `:unity` ↳ `:defines` list. Perhaps your +symbol definitions include characters needing escape sequences in YAML that are +driving you bonkers. + +```yaml +:unity: + :defines: + - UNITY_INCLUDE_CONFIG_H +``` + +```c +// unity_config.h +#ifndef UNITY_CONFIG_H +#define UNITY_CONFIG_H + +#include "uart_output.h" // Helper library for your custom environment + +#define UNITY_INT_WIDTH 16 +#define UNITY_OUTPUT_START() uart_init(F_CPU, BAUD) // Helper function to init UART +#define UNITY_OUTPUT_CHAR(a) uart_putchar(a) // Helper function to forward char via UART +#define UNITY_OUTPUT_COMPLETE() uart_complete() // Helper function to inform that test has ended + +#endif +``` + +### Routing Unity’s report output + +Unity defaults to using `putchar()` from C's standard library to +display test results. + +For more exotic environments than a desktop with a terminal — e.g. +running tests directly on a non-PC target — you have options. + +For instance, you could create a routine that transmits a character via +RS232 or USB. Once you have that routine, you can replace `putchar()` +calls in Unity by overriding the function-like macro `UNITY_OUTPUT_CHAR`. + +Even though this override can also be defined in Ceedling YAML, most +shell environments do not handle parentheses as command line arguments +very well. Consult your toolchain and shell documentation. + +If redefining the function and macros breaks your command line +compilation, all necessary options and functionality can be defined in +`unity_config.h`. Unity will need the `UNITY_INCLUDE_CONFIG_H` symbol in the +`:unity` ↳ `:defines` list of your Ceedling project file (see example above). + +## CMock Configuration + +CMock is enabled in Ceedling by default. However, no part of it enters a +test build unless mock generation is triggered in your test files. +Triggering mock generation is done by an `#include` convention. See the +section on [Ceedling conventions and behaviors][conventions] for more. + +You are welcome to disable CMock in the `:project` block of your Ceedling +configuration file. This is typically only useful in special debugging +scenarios or for Ceedling development itself. + +[conventions]: #important-conventions--behaviors + +CMock is a mixture of Ruby and C code. CMock's Ruby components generate +C code for your unit tests. CMock's base C code is compiled and linked into +a test executable in the same way that any C file is — including Unity, +CException, and generated mock C code, for that matter. + +CMock's code generation can be configured using YAML similar to Ceedling +itself. Ceedling’s project file is something of a container for CMock's +YAML configuration (Ceedling also uses CMock's configuration, though). + +See the documentation for the top-level [`:cmock`][cmock-yaml-config] +section within Ceedling’s project file. + +[cmock-yaml-config]: #cmock-configure-cmocks-code-generation--compilation + +Like Unity and CException, CMock's C components are configured at +compilation with symbols managed in your Ceedling project file's +`:cmock` ↳ `:defines` section. + +### Example CMock configurations + +```yaml +:project: + # Shown for completeness -- CMock enabled by default in Ceedling + :use_mocks: TRUE + +:cmock: + :when_no_prototypes: :warn + :enforce_strict_ordering: TRUE + :defines: + # Memory alignment (packing) on 16 bit boundaries + - CMOCK_MEM_ALIGN=1 + :plugins: + - :ignore + :treat_as: + uint8: HEX8 + uint16: HEX16 + uint32: UINT32 + int8: INT8 + bool: UINT8 +``` + +## CException Configuration + +Like Unity, CException is wholly compiled C code. As such, its +configuration is entirely controlled by a variety of `#define` symbols. +These can be configured in Ceedling’s `:cexception` ↳ `:defines` project +settings. + +Unlike Unity which is always available in test builds and CMock that +defaults to available in test builds, CException must be enabled +if you wish to use it in your project. + +### Example CException configurations + +```yaml +:project: + # Enable CException for both test and release builds + :use_exceptions: TRUE + +:cexception: + :defines: + # Possible exception codes of -127 to +127 + - CEXCEPTION_T='signed char' + +``` + +
+ +# How to Load a Project Configuration. You Have Options, My Friend. + +Ceedling needs a project configuration to accomplish anything for you. +Ceedling's project configuration is a large in-memory data structure. +That data structure is loaded from a human-readable file format called +YAML. + +The next section details Ceedling’s project configuration options in +YAML. This section explains all your options for loading and modifying +project configuration. + +## Overview of Project Configuration Loading & Smooshing + +Ceedling has a certain pipeline for loading and manipulating the +configuration it uses to build your projects. It goes something like +this: + +1. Load the base project configuration from a YAML file. +1. Merge the base configuration with zero or more Mixins from YAML files. +1. Load zero or more plugins that provide default configuration values + or alter the base project configuration. +1. Populate the configuration with default values if anything was left + unset to ensure all configuration needed to run is present. + +Ceedling provides reasonably verbose logging at startup telling you which +configuration files were used and in what order they were merged. + +For nitty-gritty details on plugin configuration behavior, see the +_[Plugin Development Guide](PluginDevelopmentGuide.md)_ + +## Options for Loading Your Base Project Configuration + +You have three options for telling Ceedling what single base project +configuration to load. These options are ordered below according to their +precedence. If an option higher in the list is present, it is used. + +1. Command line option flags +1. Environment variable +1. Default file in working directory + +### `--project` command line flags + +Many of Ceedling's [application commands][packet-section-7] include an +optional `--project` flag. When provided, Ceedling will load as its base +configuration the YAML filepath provided. + +Example: `ceedling --project=my/path/build.yml test:all` + +_NOTE:_ Ceedling loads any relative paths within your configuration in +relation to your working directory. This can cause a disconnect between +configuration paths, working directory, and the path to your project +file. + +If the filepath does not exist, Ceedling terminates with an error. + +### Environment variable `CEEDLING_PROJECT_FILE` + +If a `--project` flag is not used at the command line, but the +environment variable `CEEDLING_PROJECT_FILE` is set, Ceedling will use +the path it contains to load your project configuration. The path can +be absolute or relative (to your working directory). + +If the filepath does not exist, Ceedling terminates with an error. + +### Default _project.yml_ in your working directory + +If neither a `--project` command line flag nor the environment variable +`CEEDLING_PROJECT_FILE` are set, then Ceedling tries to load a file +named _project.yml_ in your working directory. + +If this file does not exist, Ceedling terminates with an error. + +## Applying Mixins to Your Base Project Configuration + +Once you have a base configuation loaded, you may want to modify it for +any number of reasons. Some example scenarios: + +* A single project actually contains mutiple build variations. You would + like to maintain a common configuration that is shared among build + variations. +* Your repository contains the configuration needed by your Continuous + Integration server setup, but this is not fun to run locally. You would + like to modify the configuration locally with sources external to your + repository. +* Ceedling's default `gcc` tools do not work for your project needs. You + would like the complex tooling configurations you most often need to + be maintained separately and shared among projects. + +Mixins allow you to merge configuration with your project configuration +just after the base project file is loaded. The merge is so low-level +and generic that you can, in fact, load an empty base configuration +and merge in entire project configurations through mixins. + +## Mixins Example Plus Merging Rules + +Let’s start with an example that also explains how mixins are merged. +Then, the documentation sections that follow will discuss everything +in detail. + +### Mixins Example: Scenario + +In this example, we will load a base project configuration and then +apply three mixins using each of the available means — command line, +envionment variable, and `:mixins` section in the base project +configuration file. + +#### Example environment variable + +`CEEDLING_MIXIN_1` = `./env.yml` + +#### Example command line + +`ceedling --project=base.yml --mixin=support/mixins/cmdline.yml ` + +_NOTE:_ The `--mixin` flag supports more than filepaths and can be used +multiple times in the same command line for multiple mixins (see later +documentation section). + +The example command line above will produce the following logging output. + +``` +đŸŒ± Loaded project configuration from command line argument using base.yml + + Merged command line mixin using support/mixins/cmdline.yml + + Merged CEEDLING_MIXIN_1 mixin using ./env.yml + + Merged project configuration mixin using ./enabled.yml +``` + +_Notes_ + +* The logging output above referencing _enabled.yml_ comes from the + `:mixins` section within the base project configuration file provided below. +* The resulting configuration in this example is missing settings required + by Ceedling. This will cause a validation build error that is not shown + here. + +### Mixins Example: Configuration files + +#### _base.yml_ — Our base project configuration file + +Our base project configuration file: + +1. Sets up a configuration file-baesd mixin. Ceedling will look for a mixin + named _enabled_ in the specified load paths. In this simple configuration + that means Ceedling looks for and merges _support/mixins/enabled.yml_. +1. Creates a `:project` section in our configuration. +1. Creates a `:plugins` section in our configuration and enables the standard + console test report output plugin. + +```yaml +:mixins: # `:mixins` section only recognized in base project configuration + :enabled: # `:enabled` list supports names and filepaths + - enabled # Ceedling looks for name as enabled.yml in load paths and merges if found + :load_paths: + - support/mixins + +:project: + :build_root: build/ + +:plugins: + :enabled: + - report_tests_pretty_stdout +``` + +#### _support/mixins/cmdline.yml_ — Mixin via command line filepath flag + +This mixin will merge a `:project` section with the existing `:project` +section from the base project file per the deep merge rules (noted after +the examples). + +```yaml +:project: + :use_test_preprocessor: :all + :test_file_prefix: Test +``` + +#### _env.yml_ — Mixin via environment variable filepath + +This mixin will merge a `:plugins` section with the existing `:plugins` +section from the base project file per the deep merge rules (noted +after the examples). + +```yaml +:plugins: + :enabled: + - compile_commands_json_db +``` + +#### _support/mixins/enabled.yml_ — Mixin via base project configuration file `:mixins` section + +This mixin listed in the base configuration project file will merge +`:project` and `:plugins` sections with those that already exist from +the base configuration plus earlier mixin merges per the deep merge +rules (noted after the examples). + +```yaml +:project: + :use_test_preprocessor: :none + +:plugins: + :enabled: + - gcov +``` + +### Mixins Example: Resulting project configuration + +Behold the project configuration following mixin merges: + +```yaml +:project: + :build_root: build/ # From base.yml + :use_test_preprocessor: :all # Value in support/mixins/cmdline.yml overwrote value from support/mixins/enabled.yml + :test_file_prefix: Test # Added to :project from support/mixins/cmdline.yml + +:plugins: + :enabled: # :plugins ↳ :enabled from two mixins merged with oringal list in base.yml + - report_tests_pretty_stdout # From base.yml + - compile_commands_json_db # From env.yml + - gcov # From support/mixins/enabled.yml + +# NOTE: Original :mixins section is filtered out of resulting config +``` + +### Mixins deep merge rules + +Mixins are merged in a specific order. See the next documentation +sections for details. + +Smooshing of mixin configurations into the base project configuration +follows a few basic rules: + +* If a configuration key/value pair does not already exist at the time + of merging, it is added to the configuration. +* If a simple value — e.g. boolean, string, numeric — already exists + at the time of merging, that value is replaced by the value being + merged in. +* If a container — e.g. list or hash — already exists at the time of a + merge, the contents are _combined_. In the case of lists, merged + values are added to the end of the existing list. + +_**Note:**_ That last bullet can have a significant impact on how your +various project configuration paths—including those used for header +search paths—are ordered. In brief, the contents of your `:paths` +from your base configuration will come first followed by any additions +from your mixins. See the section [Search Paths for Test Builds][test-search-paths] +for more. + +[test-search-paths]: #search-paths-for-test-builds + +## Options for Loading Mixins + +You have three options for telling Ceedling what mixins to load. These +options are ordered below according to their precedence. A Mixin higher +in the list is merged earlier. In addition, options higher in the list +force duplicate mixin filepaths to be ignored lower in the list. + +Unlike base project file loading that resolves to a single filepath, +multiple mixins can be specified using any or all of these options. + +1. Command line option flags +1. Environment variables +1. Base project configuration file entries + +### `--mixin` command line flags + +As already discussed above, many of Ceedling's application commands +include an optional `--project` flag. Most of these same commands +also recognize optional `--mixin` flags. Note that `--mixin` can be +used multiple times in a single command line. + +When provided, Ceedling will load the specified YAML file and merge +it with the base project configuration. + +A Mixin flag can contain one of two types of values: + +1. A filename or filepath to a mixin yaml file. A filename contains + a file extension. A filepath includes a leading directory path. +1. A simple name (no file extension and no path). This name is used + as a lookup in Ceedling's mixin load paths. + +Example: `ceedling --project=build.yml --mixin=foo --mixin=bar/mixin.yaml test:all` + +Simple mixin names (#2 above) require mixin load paths to search. +A default mixin load path is always in the list and points to within +Ceedling itself (in order to host eventual built-in mixins like +built-in plugins). User-specified load paths must be added through +the `:mixins` section of the base configuration project file. See +the [documentation for the `:mixins` section of your project +configuration][mixins-config-section] for more details. + +Order of precedence is set by the command line mixin order +left-to-right. + +Filepaths may be relative (in relation to the working directory) or +absolute. + +If the `--mixin` filename or filepath does not exist, Ceedling +terminates with an error. If Ceedling cannot find a mixin name in +any load paths, it terminates with an error. + +[mixins-config-section]: #base-project-configuration-file-mixins-section-entries + +### Mixin environment variables + +Mixins can also be loaded through environment variables. Ceedling +recognizes environment variables with a naming scheme of +`CEEDLING_MIXIN_#`, where `#` is any number greater than 0. + +Precedence among the environment variables is a simple ascending +sort of the trailing numeric value in the environment variable name. +For example, `CEEDLING_MIXIN_5` will be merged before +`CEEDLING_MIXIN_99`. + +Mixin environment variables only hold filepaths. Filepaths may be +relative (in relation to the working directory) or absolute. + +If the filepath specified by an environment variable does not exist, +Ceedling terminates with an error. + +### Base project configuration file `:mixins` section entries + +Ceedling only recognizes a `:mixins` section in your base project +configuration file. A `:mixins` section in a mixin is ignored. In addition, +the `:mixins` section of a base project configuration file is filtered +out of the resulting configuration. + +The `:mixins` configuration section can contain up to two subsections. +Each subsection is optional. + +* `:enabled` + + An optional array comprising (A) mixin filenames/filepaths and/or + (B) simple mixin names. + + 1. A filename contains a file extension. A filepath includes a + directory path. The file content is YAML. + 1. A simple name (no file extension and no path) is used + as a file lookup among any configured load paths (see next + section) and as a lookup name among Ceedling’s built-in mixins + (currently none). + + Enabled entries support [inline Ruby string expansion][inline-ruby-string-expansion]. + + **Default**: `[]` + +* `:load_paths` + + Paths containing mixin files to be searched via mixin names. A mixin + filename in a load path has the form _.yml_ by default. If + an alternate filename extension has been specified in your project + configuration (`:extension` ↳ `:yaml`) it will be used for file + lookups in the mixin load paths instead of _.yml_. + + Searches start in the path at the top of the list. + + Both mixin names in the `:enabled` list (above) and on the command + line via `--mixin` flag use this list of load paths for searches. + + Load paths entries support [inline Ruby string expansion][inline-ruby-string-expansion]. + + **Default**: `[]` + +Example `:mixins` YAML blurb: + +```yaml +:mixins: + :enabled: + - foo # Search for foo.yml in proj/mixins & support/ and 'foo' among built-in mixins + - path/bar.yaml # Merge this file with base project conig + :load_paths: + - proj/mixins + - support +``` + +Relating the above example to command line `--mixin` flag handling: + +* A command line flag of `--mixin=foo` is equivalent to the `foo` + entry in the `:enabled` mixin configuration. +* A command line flag of `--mixin=path/bar.yaml` is equivalent to the + `path/bar.yaml` entry in the `:enabled` mixin configuration. +* Note that while command line `--mixin` flags work identically to + entries in `:mixins` ↳ `:enabled`, they are merged first instead of + last in the mixin precedence. + +
+ +# The Almighty Ceedling Project Configuration File (in Glorious YAML) + +See this [commented project file][example-config-file] for a nice +example of a complete project configuration. + +## Some YAML Learnin’ + +Please consult YAML documentation for the finer points of format +and to understand details of our YAML-based configuration file. + +We recommend [Wikipedia's entry on YAML](http://en.wikipedia.org/wiki/Yaml) +for this. A few highlights from that reference page: + +* YAML streams are encoded using the set of printable Unicode + characters, either in UTF-8 or UTF-16. + +* White space indentation is used to denote structure; however, + tab characters are never allowed as indentation. + +* Comments begin with the number sign (`#`), can start anywhere + on a line, and continue until the end of the line unless enclosed + by quotes. + +* List members are denoted by a leading hyphen (`-`) with one member + per line, or enclosed in square brackets (`[...]`) and separated + by comma space (`, `). + +* Hashes are represented using colon space (`: `) in the form + `key: value`, either one per line or enclosed in curly braces + (`{...}`) and separated by comma space (`, `). + +* Strings (scalars) are ordinarily unquoted, but may be enclosed + in double-quotes (`"`), or single-quotes (`'`). + +* YAML requires that colons and commas used as list separators + be followed by a space so that scalar values containing embedded + punctuation can generally be represented without needing + to be enclosed in quotes. + +* Repeated nodes are initially denoted by an ampersand (`&`) and + thereafter referenced with an asterisk (`*`). These are known as + anchors and aliases in YAML speak. + +## Notes on Project File Structure and Documentation That Follows + +* Each of the following sections represent top-level entries + in the YAML configuration file. Top-level means the named entries + are furthest to the left in the hierarchical configuration file + (not at the literal top of the file). + +* Unless explicitly specified in the configuration file by you, + Ceedling uses default values for settings. + +* At minimum, these settings must be specified for a test suite: + * `:project` ↳ `:build_root` + * `:paths` ↳ `:source` + * `:paths` ↳ `:test` + * `:paths` ↳ `:include` and/or use of `TEST_INCLUDE_PATH(...)` + build directive macro within your test files + +* At minimum, these settings must be specified for a release build: + * `:project` ↳ `:build_root` + * `:paths` ↳ `:source` + +* As much as is possible, Ceedling validates your settings in + properly formed YAML. + +* Improperly formed YAML will cause a Ruby error when the YAML + is parsed. This is usually accompanied by a complaint with + line and column number pointing into the project file. + +* Certain advanced features rely on `gcc` and `cpp` as preprocessing + tools. In most Linux systems, these tools are already available. + For Windows environments, we recommend the [MinGW] project + (Minimalist GNU for Windows). + +* Ceedling is primarily meant as a build tool to support automated + unit testing. All the heavy lifting is involved there. Creating + a simple binary release build artifact is quite trivial in + comparison. Consequently, most default options and the construction + of Ceedling itself is skewed towards supporting testing, though Ceedling can, of course, build your binary release artifact - as well. Note that complex binary release artifacts (e.g. - application + bootloader or multiple libraries) are beyond - Ceedling's release build ability. - -Conventions / features of Ceedling-specific YAML: - -* Any second tier setting keys anywhere in YAML whose names end - in `_path` or `_paths` are automagically processed like all - Ceedling-specific paths in the YAML to have consistent directory - separators (i.e. "/") and to take advantage of inline Ruby - string expansion (see [:environment] setting below for further - explanation of string expansion). - -**Let's Be Careful Out There:** Ceedling performs validation -on the values you set in your configuration file (this assumes -your YAML is correct and will not fail format parsing, of course). + as well. Note that some complex binary release builds are beyond + Ceedling’s abilities. See the Ceedling plugin [subprojects] for + extending release build abilities. + +[MinGW]: http://www.mingw.org/ + +## Ceedling-specific YAML Handling & Conventions + +### Inline Ruby string expansion + +Ceedling is able to execute inline Ruby string substitution code within the +entries of certain project file configuration elements. + +In some cases, this evaluation may occurs when elements of the project +configuration are loaded and processed into a data structure for use by the +Ceedling application (e.g. path handling). In other cases, this evaluation +occurs each time a project configuration element is referenced (e.g. tools). + +_Notes:_ +* One good option for validating and troubleshooting inline Ruby string + exapnsion is use of `ceedling dumpconfig` at the command line. This application + command causes your project configuration to be processed and written to a + YAML file with any inline Ruby string expansions, well, expanded along with + defaults set, plugin actions applied, etc. +* A commonly needed expansion is that of referencing an environment variable. + Inline Ruby string expansion supports this. See the example below. + +#### Ruby string expansion syntax + +To exapnd the string result of Ruby code within a configuration value string, +wrap the Ruby code in the substitution pattern `#{
}`. + +Inline Ruby string expansion may constitute the entirety of a configuration +value string, may be embedded within a string, or may be used multiple times +within a string. + +Because of the `#` it’s a good idea to wrap any string values in your YAML that +rely on this feature with quotation marks. Quotation marks for YAML strings are +optional. However, the `#` can cause a YAML parser to see a comment. As such, +explicitly indicating a string to the YAML parser with enclosing quotation +marks alleviates this problem. + +#### Ruby string expansion example + +```yaml +:some_config_section: + :some_key: + - "My env string #{ENV['VAR1']}" + - "My utility result string #{`util --arg`.strip()}" +``` + +In the example above, the two YAML strings will include the strings returned by +the Ruby code within `#{
}`: + +1. The first string uses Ruby’s environment variable lookup `ENV[
]` to fetch +the value assigned to variable `VAR1`. +1. The second string uses Ruby’s backtick shell execution ``
`` to insert the +string generated by a command line utility. + +#### Project file sections that offer inline Ruby string expansion + +* `:mixins` +* `:environment` +* `:paths` plus any second tier configuration key name ending in `_path` or + `_paths` +* `:flags` +* `:defines` +* `:tools` +* `:release_build` ↳ `:artifacts` + +See each section’s documentation for details. + +[inline-ruby-string-expansion]: #inline-ruby-string-expansion + +### Path handling + +Any second tier setting keys anywhere in YAML whose names end in `_path` or +`_paths` are automagically processed like all Ceedling-specific paths in the +YAML to have consistent directory separators (i.e. `/`) and to take advantage +of inline Ruby string expansion (see preceding section for details). + +## Let’s Be Careful Out There + +Ceedling performs validation of the values you set in your +configuration file (this assumes your YAML is correct and will +not fail format parsing, of course). + That said, validation is limited to only those settings Ceedling uses and those that can be reasonably validated. Ceedling does not limit what can exist within your configuration file. In this way, you can take full advantage of YAML as well as add sections and values for use in your own custom plugins (documented later). + The consequence of this is simple but important. A misspelled -configuration section name or value name is unlikely to cause -Ceedling any trouble. Ceedling will happily process that section +configuration section or value name is unlikely to cause Ceedling +any trouble. Ceedling will happily process that section or value and simply use the properly spelled default maintained -internally - thus leading to unexpected behavior without warning. +internally — thus leading to unexpected behavior without warning. + +## `:project`: Global project settings + +**_NOTE:_** In future versions of Ceedling, test-specific and release-specific +build settings presently organized beneath `:project` will likely be renamed +and migrated to the `:test_build` and `:release_build` sections. + +* `:build_root` + + Top level directory into which generated path structure and files are + placed. NOTE: this is one of the handful of configuration values that + must be set. The specified path can be absolute or relative to your + working directory. + + **Default**: (none) + +* `:default_tasks` + + A list of default build / plugin tasks Ceedling should execute if + none are provided at the command line. + + _NOTE:_ These are build & plugin tasks (e.g. `test:all` and `clobber`). + These are not application commands (e.g. `dumpconfig`) or command + line flags (e.g. `--verbosity`). See the documentation + [on using the command line][command-line] to understand the distinction + between application commands and build & plugin tasks. + + Example YAML: + ```yaml + :project: + :default_tasks: + - clobber + - test:all + - release + ``` + **Default**: `['test:all']` + + [command-line]: #now-what-how-do-i-make-it-go-the-command-line + +* `:use_mocks` + + Configures the build environment to make use of CMock. Note that if + you do not use mocks, there's no harm in leaving this setting as its + default value. + + **Default**: TRUE + +* `:use_test_preprocessor` + + This option allows Ceedling to work with test files that contain + tricky conditional compilation statements (e.g. `#ifdef`) as well as mockable + header files containing conditional preprocessor directives and/or macros. + + See the [documentation on test preprocessing][test-preprocessing] for more. + + With any preprocessing enabled, the `gcc` & `cpp` tools must exist in an + accessible system search path. + + * `:none` disables preprocessing. + * `:all` enables preprpocessing for all mockable header files and test C files. + * `:mocks` enables only preprocessing of header files that are to be mocked. + * `:tests` enables only preprocessing of your test files. + + [test-preprocessing]: #preprocessing-behavior-for-tests + + **Default**: `:none` + +* `:test_file_prefix` + + Ceedling collects test files by convention from within the test file + search paths. The convention includes a unique name prefix and a file + extension matching that of source files. + + Why not simply recognize all files in test directories as test files? + By using the given convention, we have greater flexibility in what we + do with C files in the test directories. + + **Default**: "test_" + +* `:release_build` + + When enabled, a release Rake task is exposed. This configuration + option requires a corresponding release compiler and linker to be + defined (`gcc` is used as the default). + + Ceedling is primarily concerned with facilitating the complicated + mechanics of automating unit tests. The same mechanisms are easily + capable of building a final release binary artifact (i.e. non test + code — the thing that is your final working software that you execute + on target hardware). That said, if you have complicated release + builds, you should consider a traditional build tool for these. + Ceedling shines at executing test suites. + + More release configuration options are available in the `:release_build` + section. + + **Default**: FALSE + +* `:compile_threads` + + A value greater than one enables parallelized build steps. Ceedling + creates a number of threads up to `:compile_threads` for build steps. + These build steps execute batched operations including but not + limited to mock generation, code compilation, and running test + executables. + + Particularly if your build system includes multiple cores, overall + build time will drop considerably as compared to running a build with + a single thread. + + Tuning the number of threads for peak performance is an art more + than a science. A special value of `:auto` instructs Ceedling to + query the host system's number of virtual cores. To this value it + adds a constant of 4. This is often a good value sufficient to "max + out" available resources without overloading available resources. + + `:compile_threads` is used for all release build steps and all test + suite build steps except for running the test executables that make + up a test suite. See next section for more. + + **Default**: 1 + +* `:test_threads` + + The behavior of and values for `:test_threads` are identical to + `:compile_threads` with one exception. + + `test_threads:` specifically controls the number of threads used to + run the test executables comprising a test suite. + + Why the distinction from `:compile_threads`? Some test suite builds + rely not on native executables but simulators running cross-compiled + code. Some simulators are limited to running only a single instance at + a time. Thus, with this and the previous setting, it becomes possible + to parallelize nearly all of a test suite build while still respecting + the limits of certain simulators depended upon by test executables. + + **Default**: 1 + +* `:which_ceedling` + + This is an advanced project option primarily meant for development work + on Ceedling itself. This setting tells the code that launches the + Ceedling application where to find the code to launch. + + This entry can be either a directory path or `gem`. + + See the section [Which Ceedling](#which_ceedling) for full details. + + **Default**: `gem` + +* `:use_backtrace` + + When a test executable encounters a ☠ **Segmentation Fault** or other crash + condition, the executable immediately terminates and no further details for + test suite reporting are collected. + + But, fear not. You can bring your dead unit tests back to life. + + By default, in the case of a crash, Ceedling reruns the test executable for + each test case using a special mode to isolate that test case. In this way + Ceedling can iteratively identify which test cases are causing the crash or + exercising release code that is causing the crash. Ceedling then assembles + the final test reporting results from these individual test case runs. + + You have three options for this setting, `:none`, `:simple` or `:gdb`: + + 1. `:none` will simply cause a test report to list each test case as failed + due to a test executable crash. + + Sample Ceedling run output with backtrace `:none`: + + ``` + 👟 Executing + ------------ + Running TestUsartModel.out... + ☠ ERROR: Test executable `TestUsartModel.out` seems to have crashed + + ------------------- + FAILED TEST SUMMARY + ------------------- + [test/TestUsartModel.c] + Test: testGetBaudRateRegisterSettingShouldReturnAppropriateBaudRateRegisterSetting + At line (24): "Test executable crashed" + + Test: testCrash + At line (37): "Test executable crashed" + + Test: testGetFormattedTemperatureFormatsTemperatureFromCalculatorAppropriately + At line (44): "Test executable crashed" + + Test: testShouldReturnErrorMessageUponInvalidTemperatureValue + At line (50): "Test executable crashed" + + Test: testShouldReturnWakeupMessage + At line (56): "Test executable crashed" + + ----------------------- + ❌ OVERALL TEST SUMMARY + ----------------------- + TESTED: 5 + PASSED: 0 + FAILED: 5 + IGNORED: 0 + ``` + + 1. `:simple` causes Ceedling to re-run each test case in the + test executable individually to identify and report the problematic + test case(s). This is the default option and is described above. + + Sample Ceedling run output with backtrace `:simple`: + + ``` + 👟 Executing + ------------ + Running TestUsartModel.out... + ☠ ERROR: Test executable `TestUsartModel.out` seems to have crashed + + ------------------- + FAILED TEST SUMMARY + ------------------- + [test/TestUsartModel.c] + Test: testCrash + At line (37): "Test case crashed" + + ----------------------- + ❌ OVERALL TEST SUMMARY + ----------------------- + TESTED: 5 + PASSED: 4 + FAILED: 1 + IGNORED: 0 + ``` + + 1. `:gdb` uses the [`gdb`][gdb] debugger to identify and report the + troublesome line of code triggering the crash. If this option is enabled, + but `gdb` is not available to Ceedling, project configuration validation + will terminate with an error at startup. + + Sample Ceedling run output with backtrace `:gdb`: + + ``` + 👟 Executing + ------------ + Running TestUsartModel.out... + ☠ ERROR: Test executable `TestUsartModel.out` seems to have crashed + + ------------------- + FAILED TEST SUMMARY + ------------------- + [test/TestUsartModel.c] + Test: testCrash + At line (40): "Test case crashed >> Program received signal SIGSEGV, Segmentation fault. + 0x00005618066ea1fb in testCrash () at test/TestUsartModel.c:40 + 40 uint32_t i = *nullptr;" + + ----------------------- + ❌ OVERALL TEST SUMMARY + ----------------------- + TESTED: 5 + PASSED: 4 + FAILED: 1 + IGNORED: 0 + ``` + + **_Notes:_** + + 1. The default of `:simple` only works in an environment capable of + using command line arguments (passed to the test executable). If you are + targeting a simulator with your test executable binaries, `:simple` is + unlikely to work for you. In the simplest case, you may simply fall back + to `:none`. With some work and using Ceedling’s various features, much + more sophisticated options are possible. + 1. The `:gdb` option currently only supports the native build platform. + That is, the `:gdb` backtrace option cannot handle backtrace for + cross-compiled code or any sort of simulator-based test fixture. + + **Default**: `:simple` + + [gdb]: https://www.sourceware.org/gdb/ + +### Example `:project` YAML blurb + +```yaml +:project: + :build_root: project_awesome/build + :use_exceptions: FALSE + :use_test_preprocessor: :all + :options_paths: + - project/options + - external/shared/options + :release_build: TRUE + :compile_threads: :auto +``` + +## `:mixins` Configuring mixins to merge + +This section of a project configuration file is documented in the +[discussion of project files and mixins][mixins-config-section]. + +**_Notes:_** + +* A `:mixins` section is only recognized within a base project configuration + file. Any `:mixins` sections within mixin files are ignored. +* A `:mixins` section in a Ceedling configuration is entirely filtered out of + the resulting configuration. That is, it is unavailable for use by plugins + and will not be present in any output from `ceedling dumpconfig`. +* A `:mixins` section supports [inline Ruby string expansion][inline-ruby-string-expansion]. + See the full documetation on Mixins for details. + +## `:test_build` Configuring a test build + +**_NOTE:_** In future versions of Ceedling, test-related settings presently +organized beneath `:project` will be renamed and migrated to this section. + +* `:use_assembly` + + This option causes Ceedling to enable an assembler tool and collect a + list of assembly file sources for use in a test suite build. + + The default assembler is the GNU tool `as`; like all other tools, it + may be overridden in the `:tools` section. + + After enabliing this feature, two conditions must be true in order to + inject assembly code into the build of a test executable: + + 1. The assembly files must be visible to Ceedling by way of `:paths` and + `:extension` settings for assembly files. Here, assembly files would be + equivalent to C code files handled in the same ways. + 1. Ceedling must be told into which test executable build to insert a + given assembly file. The easiest way to do so is with the + `TEST_SOURCE_FILE()` build directive macro (documented in a later section). + + **Default**: FALSE + +### Example `:test_build` YAML blurb + +```yaml +:test_build: + :use_assembly: TRUE +``` + +## `:release_build` Configuring a release build + +**_NOTE:_** In future versions of Ceedling, release build-related settings +presently organized beneath `:sproject` will be renamed and migrated to +this section. + +* `:output` + + The name of your release build binary artifact to be found in /artifacts/release. Ceedling sets the default artifact file + extension to that as is explicitly specified in the `:extension` + section or as is system specific otherwise. + + **Default**: `project.exe` or `project.out` + +* `:use_assembly` + + This option causes Ceedling to enable an assembler tool and add any + assembly code present in the project to the release artifact's build. + + The default assembler is the GNU tool `as`; it may be overridden + in the `:tools` section. + + The assembly files must be visible to Ceedling by way of `:paths` and + `:extension` settings for assembly files. + + **Default**: FALSE + +* `:artifacts` + + By default, Ceedling copies to the _/artifacts/release_ + directory the output of the release linker and (optionally) a map + file. Many toolchains produce other important output files as well. + Adding a file path to this list will cause Ceedling to copy that file + to the artifacts directory. + + The artifacts directory is helpful for organizing important build + output files and provides a central place for tools such as Continuous + Integration servers to point to build output. Selectively copying + files prevents incidental build cruft from needlessly appearing in the + artifacts directory. + + Note that [inline Ruby string expansion][inline-ruby-string-expansion] + is available in artifact paths. + + **Default**: `[]` (empty) + +### Example `:release_build` YAML blurb + +```yaml +:release_build: + :output: top_secret.bin + :use_assembly: TRUE + :artifacts: + - build/release/out/c/top_secret.s19 +``` + +## Project `:paths` configuration + +**Paths for build tools and building file collections** + +Ceedling relies on various path and file collections to do its work. File +collections are automagically assembled from paths, matching globs / wildcards, +and file extensions (see project configuration `:extension`). + +Entries in `:paths` help create directory-based bulk file collections. The +`:files` configuration section is available for filepath-oriented tailoring of +these buk file collections. + +Entries in `:paths` ↳ `:include` also specify search paths for header files. + +All of the configuration subsections that follow default to empty lists. In +YAML, list items can be comma separated within brackets or organized per line +with a dash. An empty list can only be denoted as `[]`. Typically, you will see +Ceedling project files use lists broken up per line. + +```yaml +:paths: + :support: [] # Empty list (internal default) + :source: + - files/code # Typical list format + +``` + +Examples that illustrate the many `:paths` entry features follow all +the various path-related documentation sections. + +_**Note:**_ If you use Mixins to build up path lists in your project +configuration, the merge order of those Mixins will dictate the ordering of +your path lists. Particularly given that the search path list built with +`:paths` ↳ `:include` you will want to pay attention to ordering issues +involved in specifying path lists in Mixins. + +*

:paths ↳ :test

+ + All C files containing unit test code. NOTE: this is one of the + handful of configuration values that must be set for a test suite. + + **Default**: `[]` (empty) + +*

:paths ↳ :source

+ + All C files containing release code (code to be tested) + + NOTE: this is one of the handful of configuration values that must + be set for either a release build or test suite. + + **Default**: `[]` (empty) + +*

:paths ↳ :support

+ + Any C files you might need to aid your unit testing. For example, on + occasion, you may need to create a header file containing a subset of + function signatures matching those elsewhere in your code (e.g. a + subset of your OS functions, a portion of a library API, etc.). Why? + To provide finer grained control over mock function substitution or + limiting the size of the generated mocks. + + **Default**: `[]` (empty) + +*

:paths ↳ :include

+ + See these two important discussions to fully understand your options + for header file search paths: + + * [Configuring Your Header File Search Paths][header-file-search-paths] + * [`TEST_INCLUDE_PATH(...)` build directive macro][test-include-path-macro] + + [header-file-search-paths]: #configuring-your-header-file-search-paths + [test-include-path-macro]: #test_include_path + + This set of paths specifies the locations of your header files. If + your header files are intermixed with source files, you must duplicate + some or all of your `:paths` ↳ `:source` entries here. + + In its simplest use, your include paths list can be exhaustive. + That is, you list all path locations where your project’s header files + reside in this configuration list. + + However, if you have a complex project or many, many include paths that + create problematically long search paths at the compilation command + line, you may treat your `:paths` ↳ `:include` list as a base, common + list. Having established that base list, you can then extend it on a + test-by-test basis with use of the `TEST_INCLUDE_PATH(...)` build + directive macro in your test files. + + **Default**: `[]` (empty) + +*

:paths ↳ :test_toolchain_include

+ + System header files needed by the test toolchain - should your + compiler be unable to find them, finds the wrong system include search + path, or you need a creative solution to a tricky technical problem. + + Note that if you configure your own toolchain in the `:tools` section, + this search path is largely meaningless to you. However, this is a + convenient way to control the system include path should you rely on + the default [GCC] tools. + + **Default**: `[]` (empty) + +*

:paths ↳ :release_toolchain_include

+ + Same as preceding albeit related to the release toolchain. + + **Default**: `[]` (empty) + +*

:paths ↳ :libraries

+ + Library search paths. [See `:libraries` section][libraries]. + + **Default**: `[]` (empty) + + [libraries]: #libraries + +*

:paths ↳ :<custom>

+ + Any paths you specify for custom list. List is available to tool + configurations and/or plugins. Note a distinction – the preceding names + are recognized internally to Ceedling and the path lists are used to + build collections of files contained in those paths. A custom list is + just that - a custom list of paths. + +### `:paths` configuration options & notes + +1. A path can be absolute (fully qualified) or relative. +1. A path can include a glob matcher (more on this below). +1. A path can use [inline Ruby string expansion][inline-ruby-string-expansion]. +1. Subtractive paths are possible and useful. See the documentation below. +1. Path order beneath a subsection (e.g. `:paths` ↳ `:include`) is preserved + when the list is iterated internally or passed to a tool. + +### `:paths` Globs + +Globs are effectively fancy wildcards. They are not as capable as full regular +expressions but are easier to use. Various OSs and programming languages +implement them differently. + +For a quick overview, see this [tutorial][globs-tutorial]. + +Ceedling supports globs so you can specify patterns of directories without the +need to list each and every required path. + +Ceedling `:paths` globs operate similarlry to [Ruby globs][ruby-globs] except +that they are limited to matching directories within `:paths` entries and not +also files. In addition, Ceedling adds a useful convention with certain uses of +the `*` and `**` operators. + +Glob operators include the following: `*`, `**`, `?`, `[-]`, `{,}`. + +* `*` + * When used within a character string, `*` is simply a standard wildcard. + * When used after a path separator, `/*` matches all subdirectories of depth 1 + below the parent path, not including the parent path. +* `**`: All subdirectories recursively discovered below the parent path, not + including the parent path. This pattern only makes sense after a path + separator `/**`. +* `?`: Single alphanumeric character wildcard. +* `[x-y]`: Single alphanumeric character as found in the specified range. +* `{x, y, ...}`: Matching any of the comma-separated patterns. Two or more + patterns may be listed within the brackets. Patterns may be specific + character sequences or other glob operators. + +Special conventions: + +* If a globified path ends with `/*` or `/**`, the resulting list of directories + also includes the parent directory. + +See the example `:paths` YAML blurb section. + +[globs-tutotrial]: http://ruby.about.com/od/beginningruby/a/dir2.htm +[ruby-globs]: https://ruby-doc.org/core-3.0.0/Dir.html#method-c-glob + +### Subtractive `:paths` entries + +Globs are super duper helpful when you have many paths to list. But, what if a +single glob gets you 20 nested paths, but you actually want to exclude 2 of +those paths? + +Must you revert to listing all 18 paths individually? No, my friend, we've got +you. Behold, subtractive paths. + +Put simply, with an optional preceding decorator `-:`, you can instruct Ceedling +to remove certain directory paths from a collection after it builds that +collection. + +By default, paths are additive. For pretty alignment in your YAML, you may also +use `+:`, but strictly speaking, it's not necessary. + +Subtractive paths may be simple paths or globs just like any other path entry. + +See examples below. + +_**Note:**_ The resolution of subtractive paths happens after your full paths +lists are assembled. So, if you use `:paths` entries in Mixins to build up your +project configuration, subtractive paths will only be processed after the final +mixin is merged. That is, you can merge in additive and subtractive paths with +Mixins to your heart’s content. The subtractive paths are not removed until all +Mixins have been merged. + +### Example `:paths` YAML blurbs + +_NOTE:_ Ceedling standardizes paths for you. Internally, all paths use forward + slash `/` path separators (including on Windows), and Ceedling cleans up + trailing path separators to be consistent internally. + +#### Simple `:paths` entries + +```yaml +:paths: + # All /*. => test/release compilation input + :source: + - project/src/ # Resulting source list has just two relative directory paths + - project/aux # (Traversal goes no deeper than these simple paths) + + # All => compilation search paths + mock search paths + :include: # All => compilation input + - project/src/inc # Include paths are subdirectory of src/ + - /usr/local/include/foo # Header files for a prebuilt library at fully qualified path + + # All /*. => test compilation input + test suite executables + :test: + - ../tests # Tests have parent directory above working directory +``` + +#### Common `:paths` globs with subtractive path entries + +```yaml +:paths: + :source: + - +:project/src/** # Recursive glob yields all subdirectories of any depth plus src/ + - -:project/src/exp # Exclude experimental code in exp/ from release or test builds + # `+:` is decoration for pretty alignment; only `-:` changes a list + + :include: + - +:project/src/**/inc # Include every subdirectory inc/ beneath src/ + - -:project/src/exp/inc # Remove header files subdirectory for experimental code +``` + +#### Advanced `:paths` entries with globs and string expansion + +```yaml +:paths: + :test: + - test/**/f??? # Every 4 character “f-series" subdirectory beneath test/ + + :my_things: # Custom path list + - "#{PROJECT_ROOT}/other" # Inline Ruby string expansion using Ceedling global constant +``` + +```yaml +:paths: + :test: + - test/{foo,b*,xyz} # Path list will include test/foo/, test/xyz/, and any subdirectories + # beneath test/ beginning with 'b', including just test/b/ +``` + +Globs and inline Ruby string expansion can require trial and error to arrive at +your intended results. Ceedling provides as much validation of paths as is +practical. + +Use the `ceedling paths:*` and `ceedling files:*` command line tasks — +documented in a preceding section — to verify your settings. (Here `*` is +shorthand for `test`, `source`, `include`, etc. Confusing? Sorry.) + +The command line option `ceedling dumpconfig` can also help your troubleshoot +your configuration file. This application command causes Ceedling to process +your configuration file and write the result to another YAML file for your +inspection. + +## `:files` Modify file collections + +**File listings for tailoring file collections** + +Ceedling relies on file collections to do its work. These file collections are +automagically assembled from paths, matching globs / wildcards, and file +extensions (see project configuration `:extension`). + +Entries in `:files` accomplish filepath-oriented tailoring of the bulk file +collections created from `:paths` directory listings and filename pattern +matching. + +On occasion you may need to remove from or add individual files to Ceedling’s +file collections. + +The path grammar documented in the `:paths` configuration section largely +applies to `:files` path entries - albeit with regard to filepaths and not +directory paths. The `:files` grammar and YAML examples are documented below. + +*

:files ↳ :test

+ + Modify the collection of unit test C files. + + **Default**: `[]` (empty) + +*

:files ↳ :source

+ + Modify the collection of all source files used in unit test builds and release builds. + + **Default**: `[]` (empty) + +*

:files ↳ :assembly

+ + Modify the (optional) collection of assembly files used in release builds. + + **Default**: `[]` (empty) + +*

:files ↳ :include

+ + Modify the collection of all source header files used in unit test builds (e.g. for mocking) and release builds. + + **Default**: `[]` (empty) + +*

:files ↳ :support

+ + Modify the collection of supporting C files available to unit tests builds. + + **Default**: `[]` (empty) + +*

:files ↳ :libraries

+ + Add a collection of library paths to be included when linking. + + **Default**: `[]` (empty) + +### `:files` configuration options & notes + +1. A path can be absolute (fully qualified) or relative. +1. A path can include a glob matcher (more on this below). +1. A path can use [inline Ruby string expansion][inline-ruby-string-expansion]. +1. Subtractive paths prepended with a `-:` decorator are possible and useful. + See the documentation below. + +### `:files` Globs + +Globs are effectively fancy wildcards. They are not as capable as full regular +expressions but are easier to use. Various OSs and programming languages +implement them differently. + +For a quick overview, see this [tutorial][globs-tutorial]. + +Ceedling supports globs so you can specify patterns of files as well as simple, +ordinary filepaths. + +Ceedling `:files` globs operate identically to [Ruby globs][ruby-globs] except +that they ignore directory paths. Only filepaths are recognized. + +Glob operators include the following: `*`, `**`, `?`, `[-]`, `{,}`. + +* `*` + * When used within a character string, `*` is simply a standard wildcard. + * When used after a path separator, `/*` matches all subdirectories of depth + 1 below the parent path, not including the parent path. +* `**`: All subdirectories recursively discovered below the parent path, not + including the parent path. This pattern only makes sense after a path + separator `/**`. +* `?`: Single alphanumeric character wildcard. +* `[x-y]`: Single alphanumeric character as found in the specified range. +* `{x, y, ...}`: Matching any of the comma-separated patterns. Two or more + patterns may be listed within the brackets. Patterns may be specific + character sequences or other glob operators. + +### Subtractive `:files` entries + +Tailoring a file collection includes adding to it but also subtracting from it. + +Put simply, with an optional preceding decorator `-:`, you can instruct Ceedling +to remove certain file paths from a collection after it builds that +collection. + +By default, paths are additive. For pretty alignment in your YAML, you may also +use `+:`, but strictly speaking, it's not necessary. + +Subtractive paths may be simple paths or globs just like any other path entry. + +See examples below. + +### Example `:files` YAML blurbs + +#### Simple `:files` tailoring + +```yaml +:paths: + # All /*. => test/release compilation input + :source: + - src/** + +:files: + :source: + - +:callbacks/serial_comm.c # Add source code outside src/ + - -:src/board/atm134.c # Remove board code +``` + +#### Advanced `:files` tailoring + +```yaml +:paths: + # All /*. => test compilation input + test suite executables + :test: + - test/** + +:files: + :test: + # Remove every test file anywhere beneath test/ whose name ends with 'Model'. + # String replacement inserts a global constant that is the file extension for + # a C file. This is an anchor for the end of the filename and automaticlly + # uses file extension settings. + - "-:test/**/*Model#{EXTENSION_SOURCE}" + + # Remove test files at depth 1 beneath test/ with 'analog' anywhere in their names. + - -:test/*{A,a}nalog* + + # Remove test files at depth 1 beneath test/ that are of an “F series” + # test collection FAxxxx, FBxxxx, and FCxxxx where 'x' is any character. + - -:test/F[A-C]???? +``` + +## `:environment:` Insert environment variables into shells running tools + +Ceedling creates environment variables from any key / value pairs in the +environment section. Keys become an environment variable name in uppercase. The +values are strings assigned to those environment variables. These value strings +are either simple string values in YAML or the concatenation of a YAML array +of strings. + +`:environment` is a list of single key / value pair entries processed in the +configured list order. + +`:environment` variable value strings can include +[inline Ruby string expansion][inline-ruby-string-expansion]. Thus, later +entries can reference earlier entries. + +### Special case: `PATH` handling + +In the specific case of specifying an environment key named `:path`, an array +of string values will be concatenated with the appropriate platform-specific +path separation character (i.e. `:` on Unix-variants, `;` on Windows). + +All other instances of environment keys assigned a value of a YAML array use +simple concatenation. + +### Example `:environment` YAML blurb + +Note that `:environment` is a list of key / value pairs. Only one key per entry +is allowed, and that key must be a `:`__. + +```yaml +:environment: + - :license_server: gizmo.intranet # LICENSE_SERVER set with value "gizmo.intranet" + - :license: "#{`license.exe`}" # LICENSE set to string generated from shelling out to + # execute license.exe; note use of enclosing quotes to + # prevent a YAML comment. + + - :logfile: system/logs/thingamabob.log # LOGFILE set with path for a log file + + - :path: # Concatenated with path separator (see special case above) + - Tools/gizmo/bin # Prepend existing PATH with gizmo path + - "#{ENV['PATH']}" # Pattern #{
} triggers ruby evaluation string expansion + # NOTE: value string must be quoted because of '#' to + # prevent a YAML comment. +``` + +## `:extension` Filename extensions used to collect lists of files searched in `:paths` + +Ceedling uses path lists and wildcard matching against filename extensions to collect file lists. + +* `:header`: + + C header files -project: global project settings + **Default**: .h -* `build_root`: +* `:source`: - Top level directory into which generated path structure and files are - placed. Note: this is one of the handful of configuration values that - must be set. The specified path can be absolute or relative to your - working directory. + C code files (whether source or test files) - **Default**: (none) + **Default**: .c -* `use_exceptions`: +* `:assembly`: - Configures the build environment to make use of CException. Note that - if you do not use exceptions, there's no harm in leaving this as its - default value. + Assembly files (contents wholly assembler instructions) - **Default**: TRUE + **Default**: .s -* `use_mocks`: +* `:object`: - Configures the build environment to make use of CMock. Note that if - you do not use mocks, there's no harm in leaving this setting as its - default value. + Resulting binary output of C code compiler (and assembler) - **Default**: TRUE + **Default**: .o -* `use_test_preprocessor`: +* `:executable`: - This option allows Ceedling to work with test files that contain - conditional compilation statements (e.g. #ifdef) and header files you - wish to mock that contain conditional preprocessor statements and/or - macros. + Binary executable to be loaded and executed upon target hardware - Ceedling and CMock are advanced tools with sophisticated parsers. - However, they do not include entire C language preprocessors. - Consequently, with this option enabled, Ceedling will use gcc's - preprocessing mode and the cpp preprocessor tool to strip down / - expand test files and headers to their applicable content which can - then be processed by Ceedling and CMock. + **Default**: .exe or .out (Win or Linux) - With this option enabled, the gcc & cpp tools must exist in an - accessible system search path and test runner files are always - regenerated. +* `:testpass`: - **Default**: FALSE + Test results file (not likely to ever need a redefined value) -* `use_preprocessor_directives`: + **Default**: .pass - After standard preprocessing when `use_test_preprocessor` is used - macros are fully expanded to C code. Some features, for example - TEST_CASE() or TEST_RANGE() from Unity require not-fully preprocessed - file to be detected by Ceedling. To do this gcc directives-only - option is used to expand only conditional compilation statements, - handle directives, but do not expand macros preprocessor and leave - the other content of file untouched. +* `:testfail`: - With this option enabled, `use_test_preprocessor` must be also enabled - and gcc must exist in an accessible system search path. For other - compilers behavior can be changed by `test_file_preprocessor_directives` - compiler tool. + Test results file (not likely to ever need a redefined value) - **Default**: FALSE + **Default**: .fail -* `use_deep_dependencies`: +* `:dependencies`: - The base rules and tasks that Ceedling creates using Rake capture most - of the dependencies within a standard project (e.g. when the source - file accompanying a test file changes, the corresponding test fixture - executable will be rebuilt when tests are re-run). However, deep - dependencies cannot be captured this way. If a typedef or macro - changes in a header file three levels of #include statements deep, - this option allows the appropriate incremental build actions to occur - for both test execution and release builds. + File containing make-style dependency rules created by the `gcc` preprocessor - This is accomplished by using the dependencies discovery mode of gcc. - With this option enabled, gcc must exist in an accessible system - search path. + **Default**: .d - **Default**: FALSE +### Example `:extension` YAML blurb -* `generate_deep_dependencies`: +```yaml +:extension: + :source: .cc + :executable: .bin +``` - When `use_deep_dependencies` is set to TRUE, Ceedling will run a separate - build step to generate the deep dependencies. If you are using gcc as your - primary compiler, or another compiler that can generate makefile rules as - a side effect of compilation, then you can set this to FALSE to avoid the - extra build step but still use the deep dependencies data when deciding - which source files to rebuild. +## `:defines` Command line symbols used in compilation - **Default**: TRUE +Ceedling’s internal, default compiler tool configurations (see later `:tools` section) +execute compilation of test and source C files. -* `test_file_prefix`: +These default tool configurations are a one-size-fits-all approach. If you need to add to +the command line symbols for individual tests or a release build, the `:defines` section +allows you to easily do so. - Ceedling collects test files by convention from within the test file - search paths. The convention includes a unique name prefix and a file - extension matching that of source files. +Particularly in testing, symbol definitions in the compilation command line are often needed: - Why not simply recognize all files in test directories as test files? - By using the given convention, we have greater flexibility in what we - do with C files in the test directories. +1. You may wish to control aspects of your test suite. Conditional compilation statements + can control which test cases execute in which circumstances. (Preprocessing must be + enabled, `:project` ↳ `:use_test_preprocessor`.) - **Default**: "test_" +1. Testing means isolating the source code under test. This can leave certain symbols + unset when source files are compiled in isolation. Adding symbol definitions in your + Ceedling project file for such cases is one way to meet this need. + +Entries in `:defines` modify the command lines for compilers used at build time. In the +default case, symbols listed beneath `:defines` become `-D` arguments. + +### `:defines` verification (Ceedling does none) + +Ceedling does no verification of your configured `:define` symbols. + +Unity, CMock, and CException conditional compilation statements, your toolchain's +preprocessor, and/or your toolchain's compiler will complain appropriately if your +specified symbols are incorrect, incomplete, or incompatible. + +Ceedling _does_ validate your `:defines` block in your project configuration. + +### `:defines` organization: Contexts and Matchers + +The basic layout of `:defines` involves the concept of contexts. + +General case: +```yaml +:defines: + :: # :test, :release, etc. + - # Simple list of symbols added to all compilation + - ... +``` + +Advanced matching for **_test_** or **_preprocess_** build handling only: +```yaml +:defines: + :test: + : # Matches a subset of test executables + - # List of symbols added to that subset's compilation + - ... + :preprocess: # Only applicable if :project ↳ :use_test_preprocessor enabled + : # Matches a subset of test executables + - # List of symbols added to that subset's compilation + - ... +``` -* `options_paths`: +A context is the build context you want to modify — `:release`, `:preprocess`, or `:test`. +Plugins can also hook into `:defines` with their own context. - Just as you may have various build configurations for your source - codebase, you may need variations of your project configuration. +You specify the symbols you want to add to a build step beneath a `:`. In many +cases this is a simple YAML list of strings that will become symbols defined in a +compiler's command line. - By specifying options paths, Ceedling will search for other project - YAML files, make command line tasks available (ceedling options:variation - for a variation.yml file), and merge the project configuration of - these option files in with the main project file at runtime. See - advanced topics. +Specifically in the `:test` and `:preprocess` contexts you also have the option to +create test file matchers that create symbol definitions for some subset of your build. - Note these Rake tasks at the command line - like verbosity or logging - control - must come before the test or release task they are meant to - modify. +*

:defines ↳ :release

+ This project configuration entry adds the items of a simple YAML list as symbols to + the compilation of every C file in a release build. + **Default**: `[]` (empty) -* `release_build`: +*

:defines ↳ :test

- When enabled, a release Rake task is exposed. This configuration - option requires a corresponding release compiler and linker to be - defined (gcc is used as the default). + This project configuration entry adds the specified items as symbols to compilation of C + components in a test executable’s build. + + Symbols may be represented in a simple YAML list or with a more sophisticated file matcher + YAML key plus symbol list. Both are documented below. - More release configuration options are available in the release_build - section. + Every C file that comprises a test executable build will be compiled with the symbols + configured that match the test filename itself. + + **Default**: `[]` (empty) - **Default**: FALSE +*

:defines ↳ :preprocess

+ + This project configuration entry adds the specified items as symbols to any needed + preprocessing of components in a test executable’s build. Preprocessing must be enabled + for this matching to have any effect. (See `:project` ↳ `:use_test_preprocessor`.) + + Preprocessing here refers to handling macros, conditional includes, etc. in header files + that are mocked and in complex test files before runners are generated from them. + (See more about the [Ceedling preprocessing](#ceedling-preprocessing-behavior-for-your-tests) + feature.) + + Like the `:test` context, compilation symbols may be represented in a simple YAML list + or with a more sophisticated file matcher YAML key plus symbol list. Both are documented + below. + + _NOTE:_ Left unspecified, `:preprocess` symbols default to be identical to `:test` + symbols. Override this behavior by adding `:defines` ↳ `:preprocess` symbols. If you want + no additional symbols for preprocessing regardless of `test` symbols, specify an + empty list `[]` in your `:preprocess` matcher. + + **Default**: Identical to `:test` context unless specified + +*

:defines ↳ :<plugin context>

+ + Some advanced plugins make use of build contexts as well. For instance, the Ceedling + Gcov plugin uses a context of `:gcov`, surprisingly enough. For any plugins with tools + that take advantage of Ceedling’s internal mechanisms, you can add to those tools' + compilation symbols in the same manner as the built-in contexts. + +### `:defines` options + +* `:use_test_definition`: + + If enabled, add a symbol to test compilation derived from the test file name. The + resulting symbol is a sanitized, uppercase, ASCII version of the test file name. + Any non ASCII characters (e.g. Unicode) are replaced by underscores as are any + non-alphanumeric characters. Underscores and dashes are preserved. The symbol name + is wrapped in underscores unless they already exist in the leading and trailing + positions. Example: _test_123abc-xyzđŸ˜”.c_ âžĄïž `_TEST_123ABC-XYZ_`. + + **Default**: False + +### Simple `:defines` configuration + +A simple and common need is configuring conditionally compiled features in a code base. +The following example illustrates using simple YAML lists for symbol definitions at +compile time. + +```yaml +:defines: + :test: # All compilation of all C files for all test executables + - FEATURE_X=ON + - PRODUCT_CONFIG_C + :release: # All compilation of all C files in a release artifact + - FEATURE_X=ON + - PRODUCT_CONFIG_C +``` + +Given the YAML blurb above, the two symbols will be defined in the compilation command +lines for all C files in all test executables within a test suite build and for all C +files in a release build. + +### Advanced `:defines` per-test matchers +Ceedling treats each test executable as a mini project. As a reminder, each test file, +together with all C sources and frameworks, becomes an individual test executable of +the same name. -Example `[:project]` YAML blurb +**_In the `:test` and `:preprocess` contexts only_**, symbols may be defined for only +those test executable builds that match filename criteria. Matchers match on test +filenames only, and the specified symbols are added to the build step for all files +that are components of matched test executables. + +In short, for instance, this means your compilation of _TestA_ can have different +symbols than compilation of _TestB_. Those symbols will be applied to every C file +that is compiled as part those individual test executable builds. Thus, in fact, with +separate test files unit testing the same source C file, you may exercise different +conditional compilations of the same source. See the example in the section below. + +#### `:defines` per-test matcher examples with YAML + +Before detailing matcher capabilities and limits, here are examples to illustrate the +basic ideas of test file name matching. + +This first example builds on the previous simple symbol list example. The imagined scenario +is that of unit testing the same single source C file with different product features +enabled. The per-test matchers shown here use test filename substring matchers. ```yaml -:project: - :build_root: project_awesome/build - :use_exceptions: FALSE - :use_test_preprocessor: TRUE - :use_deep_dependencies: TRUE - :options_paths: - - project/options - - external/shared/options - :release_build: TRUE +# Imagine three test files all testing aspects of a single source file Comms.c with +# different features enabled via conditional compilation. +:defines: + :test: + # Tests for FeatureX configuration + :CommsFeatureX: # Matches a test executable name including 'CommsFeatureX' + - FEATURE_X=ON + - FEATURE_Z=OFF + - PRODUCT_CONFIG_C + # Tests for FeatureZ configuration + :CommsFeatureZ: # Matches a test executable name including 'CommsFeatureZ' + - FEATURE_X=OFF + - FEATURE_Z=ON + - PRODUCT_CONFIG_C + # Tests of base functionality + :CommsBase: # Matches a test executable name including 'CommsBase' + - FEATURE_X=OFF + - FEATURE_Z=OFF + - PRODUCT_BASE ``` -Ceedling is primarily concerned with facilitating the somewhat -complicated mechanics of automating unit tests. The same mechanisms -are easily capable of building a final release binary artifact -(i.e. non test code; the thing that is your final working software -that you execute on target hardware). +This example illustrates each of the test file name matcher types. -* `use_backtrace_gdb_reporter`: - Set this value to true if you project use gcc compiler and you want to collect - backtrace from test runners which fail with **Segmentation fault** error. - The .fail files will contain testsuite with information, which test failed. - Backtrace is fully integrated with **junit_tests_report** plugin. +```yaml +:defines: + :test: + :*: # Wildcard: Add '-DA' for compilation all files for all test executables + - A + :Model: # Substring: Add '-DCHOO' for compilation of all files of any test executable with 'Model' in its name + - CHOO + :/M(ain|odel)/: # Regex: Add '-DBLESS_YOU' for all files of any test executable with 'Main' or 'Model' in its name + - BLESS_YOU + :Comms*Model: # Wildcard: Add '-DTHANKS' for all files of any test executables that have zero or more characters + - THANKS # between 'Comms' and 'Model' +``` - **Default**: FALSE +#### Using `:defines` per-test matchers -* `output`: +These matchers are available: - The name of your release build binary artifact to be found in /artifacts/release. Ceedling sets the default artifact file - extension to that as is explicitly specified in the [:extension] - section or as is system specific otherwise. +1. Wildcard (`*`) + 1. If specified in isolation, matches all tests. + 1. If specified within a string, matches any test filename with that + wildcard expansion. +1. Substring — Matches on part of a test filename (up to all of it, including + full path). +1. Regex (`/.../`) — Matches test file names against a regular expression. - **Default**: `project.exe` or `project.out` +Notes: +* Substring filename matching is case sensitive. +* Wildcard matching is effectively a simplified form of regex. That is, multiple + approaches to matching can match the same filename. -* `use_assembly`: +Symbols by matcher are cumulative. This means the symbols from multiple +matchers can be applied to all compilation for any single test executable. - If assembly code is present in the source tree, this option causes - Ceedling to create appropriate build directories and use an assembler - tool (default is the GNU tool as - override available in the [:tools] - section. +Referencing the example above, here are the extra compilation symbols for a +handful of test executables: - **Default**: FALSE +* _test_Something_: `-DA` +* _test_Main_: `-DA -DBLESS_YOU` +* _test_Model_: `-DA -DCHOO -DBLESS_YOU` +* _test_CommsSerialModel_: `-DA -DCHOO -DBLESS_YOU -DTHANKS` -* `artifacts`: +The simple `:defines` list format remains available for the `:test` and `:preprocess` +contexts. Of course, this format is limited in that it applies symbols to the +compilation of all C files for all test executables. - By default, Ceedling copies to the /artifacts/release - directory the output of the release linker and (optionally) a map - file. Many toolchains produce other important output files as well. - Adding a file path to this list will cause Ceedling to copy that file - to the artifacts directory. The artifacts directory is helpful for - organizing important build output files and provides a central place - for tools such as Continuous Integration servers to point to build - output. Selectively copying files prevents incidental build cruft from - needlessly appearing in the artifacts directory. Note that inline Ruby - string replacement is available in the artifacts paths (see discussion - in the [:environment] section). +This simple list format for `:test` and `:preprocess` contexts
 - **Default**: `[]` (empty) +```yaml +:defines: + :test: + - A +``` -Example `[:release_build]` YAML blurb +
is equivalent to this matcher version: ```yaml -:release_build: - :output: top_secret.bin - :use_assembly: TRUE - :artifacts: - - build/release/out/c/top_secret.s19 +:defines: + :test: + :*: + - A ``` -**paths**: options controlling search paths for source and header -(and assembly) files +#### Distinguishing similar or identical filenames with `:defines` per-test matchers -* `test`: +You may find yourself needing to distinguish test files with the same name or test +files with names whose base naming is identical. - All C files containing unit test code. Note: this is one of the - handful of configuration values that must be set. +Of course, identical test filenames have a natural distinguishing feature in their +containing directory paths. Files of the same name can only exist in different +directories. As such, your matching must include the path. - **Default**: `[]` (empty) +```yaml +:defines: + :test: + :hardware/test_startup: # Match any test names beginning with 'test_startup' in hardware/ directory + - A + :network/test_startup: # Match any test names beginning with 'test_startup' in network/ directory + - B +``` -* `source`: +It's common in C file naming to use the same base name for multiple files. Given the +following example list, care must be given to matcher construction to single out +test_comm_startup.c. - All C files containing release code (code to be tested). Note: this is - one of the handful of configuration values that must be set. +* tests/test_comm_hw.c +* tests/test_comm_startup.c +* tests/test_comm_startup_timers.c - **Default**: `[]` (empty) +```yaml +:defines: + :test: + :test_comm_startup.c: # Full filename with extension distinguishes this file test_comm_startup_timers.c + - FOO +``` -* `support`: +The preceding examples use substring matching, but, regular expression matching +could also be appropriate. - Any C files you might need to aid your unit testing. For example, on - occasion, you may need to create a header file containing a subset of - function signatures matching those elsewhere in your code (e.g. a - subset of your OS functions, a portion of a library API, etc.). Why? - To provide finer grained control over mock function substitution or - limiting the size of the generated mocks. +#### Using YAML anchors & aliases for complex testing scenarios with `:defines` - **Default**: `[]` (empty) +See the short but helpful article on [YAML anchors & aliases][yaml-anchors-aliases] to +understand these features of YAML. + +Particularly in testing complex projects, per-test file matching may only get you so +far in meeting your symbol definition needs. For instance, you may need to use the +same symbols across many test files, but no convenient name matching scheme works. +Advanced YAML features can help you copy the same symbols into multiple `:defines` +test file matchers. + +The following advanced example illustrates how to create a set of compilation symbols +for test preprocessing that are identical to test compilation with one addition. + +In brief, this example uses YAML features to copy the `:test` matcher configuration +that matches all test executables into the `:preprocess` context and then add an +additional compilation symbol to the list. + +```yaml +:defines: + :test: &config-test-defines # YAML anchor + :*: &match-all-tests # YAML anchor + - PRODUCT_FEATURE_X + - ASSERT_LEVEL=2 + - USES_RTOS=1 + :test_foo: + - DRIVER_FOO=1u + :test_bar: + - DRIVER_BAR=5u + :preprocess: + <<: *config-test-defines # Insert all :test defines file matchers via YAML alias + :*: # Override wildcard matching key in copy of *config-test-defines + - *match-all-tests # Copy test defines for all files via YAML alias + - RTOS_SPECIAL_THING # Add single additional symbol to all test executable preprocessing + # test_foo, test_bar, and any other matchers are present because of <<: above +``` -* `include`: +## `:libraries` - Any header files not already in the source search path. Note there's - no practical distinction between this search path and the source - search path; it's merely to provide options or to support any - peculiar source tree organization. +Ceedling allows you to pull in specific libraries for release and test builds with a +few levels of support. +*

:libraries ↳ :test

+ + Libraries that should be injected into your test builds when linking occurs. + + These can be specified as naked library names or with relative paths if search paths + are specified with `:paths` ↳ `:libraries`. Otherwise, absolute paths may be used + here. + + These library files **must** exist when tests build. + **Default**: `[]` (empty) -* `test_toolchain_include`: +*

:libraries ↳ :release

+ + Libraries that should be injected into your release build when linking occurs. + + These can be specified as naked library names or with relative paths if search paths + are specified with `:paths` ↳ `:libraries`. Otherwise, absolute paths may be used + here. + + These library files **must** exist when the release build occurs **unless** you + are using the _subprojects_ plugin. In that case, the plugin will attempt to build + the needed library for you as a dependency. + + **Default**: `[]` (empty) - System header files needed by the test toolchain - should your - compiler be unable to find them, finds the wrong system include search - path, or you need a creative solution to a tricky technical problem. - Note that if you configure your own toolchain in the [:tools] section, - this search path is largely meaningless to you. However, this is a - convenient way to control the system include path should you rely on - the default gcc tools. +*

:libraries ↳ :system

+ + Libraries listed here will be injected into releases and tests. + + These libraries are assumed to be findable by the configured linker tool, should need + no path help, and can be specified by common linker shorthand for libraries. + + For example, specifying `m` will include the math library per the GCC convention. The + file itself on a Unix-like system will be `libm` and the `gcc` command line argument + will be `-lm`. + + **Default**: `[]` (empty) + +### `:libraries` options + +* `:flag`: + + Command line argument format for specifying a library. + + **Default**: `-l${1}` (GCC format) + +* `:path_flag`: + + Command line argument format for adding a library search path. + + Library search paths may be added to your project with `:paths` ↳ `:libraries`. + + **Default**: `-L "${1}”` (GCC format) + +### `:libraries` example with YAML blurb + +```yaml +:paths: + :libraries: + - proj/libs # Linker library search paths + +:libraries: + :test: + - test/commsstub.lib # Imagined communication library that logs to console without traffic + :release: + - release/comms.lib # Imagined production communication library + :system: + - math # Add system math library to test & release builds + :flag: -Lib=${1} # This linker does not follow the gcc convention +``` + +### `:libraries` notes + +* If you've specified your own link step, you are going to want to add `${4}` to your + argument list in the position where library files should be added to the command line. + For `gcc`, this is often at the very end. Other tools may vary. See the `:tools` + section for more. + +## `:flags` Configure preprocessing, compilation & linking command line flags + +Ceedling’s internal, default tool configurations execute compilation and linking of test +and source files among a variety of other tooling needs. (See later `:tools` section.) + +These default tool configurations are a one-size-fits-all approach. If you need to add +flags to the command line for individual tests or a release build, the `:flags` section +allows you to easily do so. + +Entries in `:flags` modify the command lines for tools used at build time. + +### Flags organization: Contexts, Operations, and Matchers + +The basic layout of `:flags` involves the concepts of contexts and operations. +General case: +```yaml +:flags: + :: # :test or :release + :: # :preprocess, :compile, :assemble, or :link + - + - ... +``` + +Advanced matching for **_test_** build handling only: +```yaml +:flags: + :test: + :: # :preprocess, :compile, :assemble, or :link + :: # Matches a subset of test executables + - # List of flags added to that subset's build operation command line + - ... +``` + +A context is the build context you want to modify — `:test` or `:release`. Plugins can +also hook into `:flags` with their own context. + +An operation is the build step you wish to modify — `:preprocess`, `:compile`, `:assemble`, +or `:link`. + +* The `:preprocess` operation is only used from within the `:test` context. +* The `:assemble` operation is only of use within the `:test` or `:release` contexts if + assembly support has been enabled in `:test_build` or `:release_build`, respectively, and + assembly files are a part of the project. + +You specify the flags you want to add to a build step beneath `:` ↳ `:`. +In many cases this is a simple YAML list of strings that will become flags in a tool's +command line. + +**_Specifically and only in the `:test` context_** you also have the option to create test +file matchers that apply flags to some subset of your test build. Note that file matchers +and the simpler flags list format cannot be mixed for `:flags` ↳ `:test`. + +*

:flags ↳ :release ↳ :compile

+ + This project configuration entry adds the items of a simple YAML list as flags to + compilation of every C file in a release build. + **Default**: `[]` (empty) -* `release_toolchain_include`: +*

:flags ↳ :release ↳ :link

- Same as preceding albeit related to the release toolchain. + This project configuration entry adds the items of a simple YAML list as flags to + the link step of a release build artifact. + + **Default**: `[]` (empty) + +*

:flags ↳ :test ↳ :compile

+ This project configuration entry adds the specified items as flags to compilation of C + components in a test executable's build. + + Flags may be represented in a simple YAML list or with a more sophisticated file matcher + YAML key plus flag list. Both are documented below. + **Default**: `[]` (empty) -* `` +*

:flags ↳ :test ↳ :preprocess

+ + This project configuration entry adds the specified items as flags to any needed + preprocessing of components in a test executable’s build. Preprocessing must be enabled + for this matching to have any effect. (See `:project` ↳ `:use_test_preprocessor`.) + + Preprocessing here refers to handling macros, conditional includes, etc. in header files + that are mocked and in complex test files before runners are generated from them. + (See more about the [Ceedling preprocessing](#ceedling-preprocessing-behavior-for-your-tests) + feature.) + + Flags may be represented in a simple YAML list or with a more sophisticated file matcher + YAML key plus flag list. Both are documented below. + + _NOTE:_ Left unspecified, `:preprocess` flags default to behaving identically to `:compile` + flags. Override this behavior by adding `:test` ↳ `:preprocess` flags. If you want no + additional flags for preprocessing regardless of test compilation flags, simply specify + an empty list `[]`. + + **Default**: Same flags as specified for test compilation + +*

:flags ↳ :test ↳ :link

+ + This project configuration entry adds the specified items as flags to the link step of + test executables. + + Flags may be represented in a simple YAML list or with a more sophisticated file matcher + YAML key plus flag list. Both are documented below. + + **Default**: `[]` (empty) - Any paths you specify for custom list. List is available to tool - configurations and/or plugins. Note a distinction. The preceding names - are recognized internally to Ceedling and the path lists are used to - build collections of files contained in those paths. A custom list is - just that - a custom list of paths. +*

:flags ↳ :<plugin context>

+ + Some advanced plugins make use of build contexts as well. For instance, the Ceedling + Gcov plugin uses a context of `:gcov`, surprisingly enough. For any plugins with tools + that take advantage of Ceedling’s internal mechanisms, you can add to those tools' + flags in the same manner as the built-in contexts and operations. + +### Simple `:flags` configuration + +A simple and common need is enforcing a particular C standard. The following example +illustrates simple YAML lists for flags. + +```yaml +:flags: + :release: + :compile: + - -std=c99 # Add `-std=c99` to compilation of all C files in the release build + :test: + :compile: + - -std=c99 # Add `-std=c99` to the compilation of all C files in all test executables +``` -Notes on path grammar within the [:paths] section: +Given the YAML blurb above, when test or release compilation occurs, the flag specifying +the C standard will be in the command line for compilation of all C files. -* Order of search paths listed in [:paths] is preserved when used by an - entry in the [:tools] section +### Advanced `:flags` per-test matchers -* Wherever multiple path lists are combined for use Ceedling prioritizes - path groups as follows: - test paths, support paths, source paths, include paths. +Ceedling treats each test executable as a mini project. As a reminder, each test file, +together with all C sources and frameworks, becomes an individual test executable of +the same name. - This can be useful, for instance, in certain testing scenarios where - we desire Ceedling or the compiler to find a stand-in header file before - the actual source header file of the same name. +_In the `:test` context only_, flags can be applied to build step operations — +preprocessing, compilation, and linking — for only those test executables that match +file name criteria. Matchers match on test filenames only, and the specified flags +are added to the build step for all files that are components of matched test +executables. -* Paths: +In short, for instance, this means your compilation of _TestA_ can have different flags +than compilation of _TestB_. And, in fact, those flags will be applied to every C file +that is compiled as part those individual test executable builds. - 1. can be absolute or relative +#### `:flags` per-test matcher examples with YAML - 2. can be singly explicit - a single fully specified path +Before detailing matcher capabilities and limits, here are examples to illustrate the +basic ideas of test file name matching. - 3. can include a glob operator (more on this below) +```yaml +:flags: + :test: + :compile: + :*: # Wildcard: Add '-foo' for all files compiled for all test executables + - -foo + :Model: # Substring: Add '-Wall' for all files compiled for any test executable with 'Model' in its filename + - -Wall + :/M(ain|odel)/: # Regex: Add đŸŽâ€â˜ ïž flag for all files compiled for any test executable with 'Main' or 'Model' in its filename + - -đŸŽâ€â˜ ïž + :Comms*Model: + - --freak # Wildcard: Add your `--freak` flag for all files compiled for any test executable with zero or more + # characters between 'Comms' and 'Model' + :link: + :tests/comm/TestUsart.c: # Substring: Add '--bar --baz' to the link step of the TestUsart executable + - --bar + - --baz +``` - 4. can use inline Ruby string replacement (see [:environment] - section for more) +#### Using `:flags` per-test matchers - 5. default as an addition to a specific search list (more on this - in the examples) +These matchers are available: - 6. can act to subtract from a glob included in the path list (more - on this in the examples) +1. Wildcard (`*`) + 1. If specified in isolation, matches all tests. + 1. If specified within a string, matches any test filename with that + wildcard expansion. +1. Substring — Matches on part of a test filename (up to all of it, including + full path). +1. Regex (`/.../`) — Matches test file names against a regular expression. -[Globs](http://ruby.about.com/od/beginningruby/a/dir2.htm) -as used by Ceedling are wildcards for specifying directories -without the need to list each and every required search path. -Ceedling globs operate just as Ruby globs except that they are -limited to matching directories and not files. Glob operators -include the following * ** ? [-] {,} (note: this list is space separated -and not comma separated as commas are used within the bracket -operators). +Notes: +* Substring filename matching is case sensitive. +* Wildcard matching is effectively a simplified form of regex. That is, + multiple approaches to matching can match the same filename. -* `*`: +Flags by matcher are cumulative. This means the flags from multiple matchers can be +applied to all files processed by the named build operation for any single test executable. - All subdirectories of depth 1 below the parent path and including the - parent path +Referencing the example above, here are the extra compilation flags for a handful of +test executables: -* `**`: +* _test_Something_: `-foo` +* _test_Main_: `-foo -đŸŽâ€â˜ ïž` +* _test_Model_: `-foo -Wall -đŸŽâ€â˜ ïž` +* _test_CommsSerialModel_: `-foo -Wall -đŸŽâ€â˜ ïž --freak` - All subdirectories recursively discovered below the parent path and - including the parent path +The simple `:flags` list format remains available for the `:test` context. Of course, +this format is limited in that it applies flags to all C files processed by the named +build operation for all test executables. -* `?`: +This simple list format for the `:test` context
 - Single alphanumeric character wildcard +```yaml +:flags: + :test: + :compile: + - -foo +``` -* `[x-y]`: +
is equivalent to this matcher version: - Single alphanumeric character as found in the specified range +```yaml +:flags: + :test: + :compile: + :*: + - -foo +``` -* `{x,y}`: +#### Distinguishing similar or identical filenames with `:flags` per-test matchers - Single alphanumeric character from the specified list +You may find yourself needing to distinguish test files with the same name or test +files with names whose base naming is identical. -Example [:paths] YAML blurbs +Of course, identical test filenames have a natural distinguishing feature in their +containing directory paths. Files of the same name can only exist in different +directories. As such, your matching must include the path. ```yaml -:paths: - :source: #together the following comprise all source search paths - - project/source/* #expansion yields all subdirectories of depth 1 plus parent directory - - project/lib #single path - :test: #all test search paths - - project/**/test? #expansion yields any subdirectory found anywhere in the project that - #begins with "test" and contains 5 characters +:flags: + :test: + :compile: + :hardware/test_startup: # Match any test names beginning with 'test_startup' in hardware/ directory + - A + :network/test_startup: # Match any test names beginning with 'test_startup' in network/ directory + - B +``` -:paths: - :source: #all source search paths - - +:project/source/** #all subdirectories recursively discovered plus parent directory - - -:project/source/os/generated #subtract os/generated directory from expansion of above glob - #note that '+:' notation is merely aesthetic; default is to add +It's common in C file naming to use the same base name for multiple files. Given the +following example list, care must be given to matcher construction to single out +test_comm_startup.c. - :test: #all test search paths - - project/test/bootloader #explicit, single search paths (searched in the order specified) - - project/test/application - - project/test/utilities +* tests/test_comm_hw.c +* tests/test_comm_startup.c +* tests/test_comm_startup_timers.c - :custom: #custom path list - - "#{PROJECT_ROOT}/other" #inline Ruby string expansion +```yaml +:flags: + :test: + :compile: + :test_comm_startup.c: # Full filename with extension distinguishes this file test_comm_startup_timers.c + - FOO ``` -Globs and inline Ruby string expansion can require trial and -error to arrive at your intended results. Use the `ceedling paths:*` -command line options (documented in preceding section) to verify -your settings. +The preceding examples use substring matching, but, regular expression matching +could also be appropriate. + +#### Using YAML anchors & aliases for complex testing scenarios with `:flags` + +See the short but helpful article on [YAML anchors & aliases][yaml-anchors-aliases] to +understand these features of YAML. + +Particularly in testing complex projects, per-test file matching may only get you so +far in meeting your build step flag needs. For instance, you may need to set various +flags for operations across many test files, but no convenient name matching scheme +works. Advanced YAML features can help you copy the same flags into multiple `:flags` +test file matchers. + +Please see the discussion in `:defines` for a complete example. + +## `:cexception` Configure CException’s features + +* `:defines`: + + List of symbols used to configure CException's features in its source and header files + at compile time. + + See [Using Unity, CMock & CException](#using-unity-cmock--cexception) for much more on + configuring and making use of these frameworks in your build. + + To manage overall command line length, these symbols are only added to compilation when + a CException C source file is compiled. + + No symbols must be set unless CException's defaults are inappropriate for your + environment and needs. + + Note CException must be enabled for it to be added to a release or test build and for + these symbols to be added to a build of CException (see link referenced earlier for more). + + **Default**: `[]` (empty) -Ceedling relies on file collections automagically assembled -from paths, globs, and file extensions. File collections greatly -simplify project set up. However, sometimes you need to remove -from or add individual files to those collections. +## `:cmock` Configure CMock’s code generation & compilation +Ceedling sets values for a subset of CMock settings. All CMock options are +available to be set, but only those options set by Ceedling in an automated +fashion are documented below. See CMock documentation. -* `test`: +Ceedling sets values for a subset of CMock settings. All CMock options are +available to be set, but only those options set by Ceedling in an automated +fashion are documented below. See [CMock] documentation. - Modify the collection of unit test C files. +* `:enforce_strict_ordering`: - **Default**: `[]` (empty) + Tests fail if expected call order is not same as source order + + **Default**: TRUE -* `source`: +* `:verbosity`: - Modify the collection of all source files used in unit test builds and release builds. + If not set, defaults to Ceedling’s verbosity level +* `:defines`: + + Adds list of symbols used to configure CMock’s C code features in its source and header + files at compile time. + + See [Using Unity, CMock & CException](#using-unity-cmock--cexception) for much more on + configuring and making use of these frameworks in your build. + + To manage overall command line length, these symbols are only added to compilation when + a CMock C source file is compiled. + + No symbols must be set unless CMock’s defaults are inappropriate for your environment + and needs. + **Default**: `[]` (empty) -* `assembly`: +* `:plugins`: - Modify the (optional) collection of assembly files used in release builds. + To enable CMock’s optional and advanced features available via CMock plugin, simply add + `:cmock` ↳ `:plugins` to your configuration and specify your desired additional CMock + plugins as a simple list of the plugin names. + + See [CMock's documentation][cmock-docs] to understand plugin options. + + [cmock-docs]: https://github.com/ThrowTheSwitch/CMock/blob/master/docs/CMock_Summary.md **Default**: `[]` (empty) -* `include`: +* `:unity_helper_path`: + + A Unity helper is a simple header file used by convention to support your specialized + test case needs. For example, perhaps you want a Unity assertion macro for the + contents of a struct used throughout your project. Write the macro you need in a Unity + helper header file and `#include` that header file in your test file. - Modify the collection of all source header files used in unit test builds (e.g. for mocking) and release builds. + When a Unity helper is provided to CMock, it takes on more significance, and more + magic happens. CMock parses Unity helper header files and uses macros of a certain + naming convention to extend CMock’s handling of mocked parameters. + + See the [Unity] and [CMock] documentation for more details. + + `:unity_helper_path` may be a single string or a list. Each value must be a relative + path from your Ceedling working directory to a Unity helper header file (these are + typically organized within containing Ceedling `:paths` ↳ `:support` directories). **Default**: `[]` (empty) -* `support`: +* `:includes`: - Modify the collection of supporting C files available to unit tests builds. + In certain advanced testing scenarios, you may need to inject additional header files + into generated mocks. The filenames in this list will be transformed into `#include` + directives created at the top of every generated mock. + + If `:unity_helper_path` is in use (see preceding), the filenames at the end of any + Unity helper file paths will be automatically injected into this list provided to + CMock. **Default**: `[]` (empty) -* `libraries`: +### Notes on Ceedling’s nudges for CMock strict ordering - Add a collection of library paths to be included when linking. +The preceding settings are tied to other Ceedling settings; hence, why they are +documented here. + +The first setting above, `:enforce_strict_ordering`, defaults to `FALSE` within +CMock. However, it is set to `TRUE` by default in Ceedling as our way of +encouraging you to use strict ordering. + +Strict ordering is teeny bit more expensive in terms of code generated, test +execution time, and complication in deciphering test failures. However, it’s +good practice. And, of course, you can always disable it by overriding the +value in the Ceedling project configuration file. +## `:unity` Configure Unity’s features + +* `:defines`: + + Adds list of symbols used to configure Unity's features in its source and header files + at compile time. + + See [Using Unity, CMock & CException](#using-unity-cmock--cexception) for much more on + configuring and making use of these frameworks in your build. + + To manage overall command line length, these symbols are only added to compilation when + a Unity C source file is compiled. + + **_Note_**: No symbols must be set unless Unity's defaults are inappropriate for your + environment and needs. + **Default**: `[]` (empty) +* `:use_param_tests`: + + Configures Unity test runner generation and `#define`s for test compilation to support + Unity’s parameterized test cases. + + Example parameterized test case: + + ```C + TEST_RANGE([5, 100, 5]) + void test_should_handle_divisible_by_5_for_parameterized_test_range(int num) { + TEST_ASSERT_EQUAL(0, (num % 5)); + } + ``` + + See [Unity] documentation for more on parameterized test cases. + + **Default**: false + +## `:test_runner` Configure test runner generation + +The format of Ceedling test files — the C files that contain unit test cases — +is intentionally simple. It’s pure code and all legit, simple C with `#include` +statements, test case functions, and optional `setUp()` and `tearDown()` +functions. + +To create test executables, we need a `main()` and a variety of calls to the +Unity framework to “hook up” all your test cases into a test suite. You can do +this by hand, of course, but it's tedious and needed updates as code evolves +are easily forgotten. + +So, Unity provides a script able to generate a test runner in C for you. It +relies on [ceedling-conventions] used in your test files. Ceedling takes this +a step further by calling this script for you with all the needed parameters. + +Test runner generation is configurable. The `:test_runner` section of your +Ceedling project file allows you to pass options to Unity’s runner generation +script. Based on other Ceedling options, Ceedling also sets certain test runner +generation configuration values for you. + +[Test runner configuration options are documented in the Unity project][unity-runner-options]. + +**_Notes:_** -Note: All path grammar documented in [:paths] section applies -to [:files] path entries - albeit at the file path level and not -the directory level. +* **Unless you have advanced or unique needs, Unity test runner generation + configuration in Ceedling is generally not needed.** +* In previous versions of Ceedling, the test runner option + `:cmdline_args` was needed for certain advanced test suite features. This + option is still needed, but Ceedling automatically sets it for you in the + scenarios requiring it. Be aware that this option works well in desktop, + native testing but is generally unsupported by emulators running test + executables (the idea of command line arguments passed to an executable is + generally only possible with desktop command line terminals.) -Example [:files] YAML blurb +Example configuration: ```yaml -:files: - :source: - - callbacks/comm.c # entry defaults to file addition - - +:callbacks/comm*.c # add all comm files matching glob pattern - - -:source/board/atm134.c # not our board - :test: - - -:test/io/test_output_manager.c # remove unit tests from test build +:test_runner: + # Insert additional #include statements in a generated runner + :includes: + - Foo.h + - Bar.h ``` -**environment:** inserts environment variables into the shell -instance executing configured tools - -Ceedling creates environment variables from any key / value -pairs in the environment section. Keys become an environment -variable name in uppercase. The values are strings assigned -to those environment variables. These value strings are either -simple string values in YAML or the concatenation of a YAML array. +[ceedling-conventions]: #important-conventions--behaviors +[unity-runner-options]: https://github.com/ThrowTheSwitch/Unity/blob/master/docs/UnityHelperScriptsGuide.md#options-accepted-by-generate_test_runnerrb -Ceedling is able to execute inline Ruby string substitution -code to set environment variables. This evaluation occurs when -the project file is first processed for any environment pair's -value string including the Ruby string substitution pattern -`#{
}`. Note that environment value strings that _begin_ with -this pattern should always be enclosed in quotes. YAML defaults -to processing unquoted text as a string; quoting text is optional. -If an environment pair's value string begins with the Ruby string -substitution pattern, YAML will interpret the string as a Ruby -comment (because of the `#`). Enclosing each environment value -string in quotes is a safe practice. +## `:tools` Configuring command line tools used for build steps -[:environment] entries are processed in the configured order -(later entries can reference earlier entries). +Ceedling requires a variety of tools to work its magic. By default, the GNU +toolchain (`gcc`, `cpp`, `as` — and `gcov` via plugin) are configured and ready +for use with no additions to your project configuration YAML file. -Special case: PATH handling +A few items before we dive in: -In the specific case of specifying an environment key named _path_, -an array of string values will be concatenated with the appropriate -platform-specific path separation character (e.g. ':' on linux, -';' on Windows). All other instances of environment keys assigned -YAML arrays use simple concatenation. +1. Sometimes Ceedling’s built-in tools are _nearly_ what you need but not + quite. If you only need to add some arguments to all uses of tool's command + line, Ceedling offers a shortcut to do so. See the + [final section of the `:tools`][tool-definition-shortcuts] documentation for + details. +1. If you need fine-grained control of the arguments Ceedling uses in the build + steps for test executables, see the documentation for [`:flags`][flags]. + Ceedling allows you to control the command line arguments for each test + executable build — with a variety of pattern matching options. +1. If you need to link libraries — your own or standard options — please see + the [top-level `:libraries` section][libraries] available for your + configuration file. Ceedling supports a number of useful options for working + with pre-compiled libraries. If your library linking needs are super simple, + the shortcut in (1) might be the simplest option. -Example [:environment] YAML blurb +[flags]: #flags-configure-preprocessing-compilation--linking-command-line-flags +[tool-definition-shortcuts]: #ceedling-tool-modification-shortcuts -```yaml -:environment: - - :license_server: gizmo.intranet #LICENSE_SERVER set with value "gizmo.intranet" - - :license: "#{`license.exe`}" #LICENSE set to string generated from shelling out to - #execute license.exe; note use of enclosing quotes +### Ceedling tools for test suite builds - - :path: #concatenated with path separator (see special case above) - - Tools/gizmo/bin #prepend existing PATH with gizmo path - - "#{ENV['PATH']}" #pattern #{
} triggers ruby evaluation string substitution - #note: value string must be quoted because of '#' - - - :logfile: system/logs/thingamabob.log #LOGFILE set with path for a log file -``` +Our recommended approach to writing and executing test suites relies on the GNU +toolchain. _*Yes, even for embedded system work on platforms with their own, +proprietary C toolchain.*_ Please see +[this section of documentation][sweet-suite] to understand this recommendation +among all your options. -**extension**: configure file name extensions used to collect lists of files searched in [:paths] +You can and sometimes must run a Ceedling test suite in an emulator or on +target, and Ceedling allows you to do this through tool definitions documented +here. Generally, you’ll likely want to rely on the default definitions. -* `header`: +[sweet-suite]: #all-your-sweet-sweet-test-suite-options - C header files +### Ceedling tools for release builds - **Default**: .h +More often than not, release builds require custom tool definitions. The GNU +toolchain is configured for Ceeding release builds by default just as with test +builds. you’ll likely need your own definitions for `:release_compiler`, +`:release_linker`, and possibly `:release_assembler`. -* `source`: +### Ceedling plugin tools - C code files (whether source or test files) +Ceedling plugins are free to define their own tools that are loaded into your +project configuration at startup. Plugin tools are defined using the same +mechanisns as Ceedling’s built-in tools and are called the same way. That is, +all features available to you for working with tools as an end users are +generally available for working with plugin-based tools. This presumes a +plugin author followed guidance and convention in creating any command line +actions. - **Default**: .c +### Ceedling tool definitions -* `assembly`: +Contained in this section are details on Ceedling’s default tool definitions. +For sake of space, the entirety of a given definition is not shown. If you need +to get in the weeds or want a full example, see the file `defaults.rb` in +Ceedling’s lib/ directory. - Assembly files (contents wholly assembly instructions) +#### Tool definition overview - **Default**: .s +Listed below are the built-in tool names, corresponding to build steps along +with the numbered parameters that Ceedling uses to fill out a full command line +for the named tool. The full list of fundamental elements for a tool definition +are documented in the sections that follow along with examples. -* `object`: +Not every numbered parameter listed immediately below must be referenced in a +Ceedling tool definition. If `${4}` isn’t referenced by your custom tool, +Ceedling simply skips it while expanding a tool definition into a command line. - Resulting binary output of C code compiler (and assembler) +The numbered parameters below are references that expand / are replaced with +actual values when the corresponding command line is constructed. If the values +behind these parameters are lists, Ceedling expands the containing reference +multiple times with the contents of the value. A conceptual example is +instructive
 - **Default**: .o +#### Simplified tool definition / expansion example -* `executable`: +A partial tool definition: - Binary executable to be loaded and executed upon target hardware +```yaml +:tools: + :power_drill: + :executable: dewalt.exe + :arguments: + - "--X${3}" +``` - **Default**: .exe or .out (Win or linux) +Let's say that `${3}` is a list inside Ceedling, `[2, 3, 7]`. The expanded tool +command line for `:tools` ↳ `:power_drill` would look like this: -* `testpass`: +```shell + > dewalt.exe --X2 --X3 --X7 +``` - Test results file (not likely to ever need a new value) +#### Ceedling’s default build step tool definitions - **Default**: .pass +**_NOTE:_** Ceedling’s tool definitions for its preprocessing and backtrace +features are not documented here. Ceedling’s use of tools for these features +are tightly coupled to the options and output of those tools. Drop-in +replacements using other tools are not practically possible. Eventually, an +improved plugin system will provide options for integrating alternative tools. -* `testfail`: +* `:test_compiler`: - Test results file (not likely to ever need a new value) + Compiler for test & source-under-test code - **Default**: .fail + - `${1}`: Input source + - `${2}`: Output object + - `${3}`: Optional output list + - `${4}`: Optional output dependencies file + - `${5}`: Header file search paths + - `${6}`: Command line #defines -* `dependencies`: + **Default**: `gcc` - File containing make-style dependency rules created by gcc preprocessor +* `:test_assembler`: - **Default**: .d + Assembler for test assembly code + - `${1}`: input assembly source file + - `${2}`: output object file + - `${3}`: search paths + - `${4}`: #define symbols (accepted but ignored by GNU assembler) -Example [:extension] YAML blurb + **Default**: `as` - :extension: - :source: .cc - :executable: .bin +* `:test_linker`: -**defines**: command line defines used in test and release compilation by configured tools + Linker to generate test fixture executables -* `test`: + - `${1}`: input objects + - `${2}`: output binary + - `${3}`: optional output map + - `${4}`: optional library list + - `${5}`: optional library path list - Defines needed for testing. Useful for: + **Default**: `gcc` - 1. test files containing conditional compilation statements (i.e. - tests active in only certain contexts) +* `:test_fixture`: - 2. testing legacy source wherein the isolation of source under test - afforded by Ceedling and its complementary tools leaves certain - symbols unset when source files are compiled in isolation + Executable test fixture - **Default**: `[]` (empty) + - `${1}`: simulator as executable with`${1}` as input binary file argument or native test executable -* `test_preprocess`: + **Default**: `${1}` - If [:project][:use_test_preprocessor] or - [:project][:use_deep_dependencies] is set and code is structured in a - certain way, the gcc preprocessor may need symbol definitions to - properly preprocess files to extract function signatures for mocking - and extract deep dependencies for incremental builds. +* `:release_compiler`: - **Default**: `[]` (empty) + Compiler for release source code -* ``: + - `${1}`: input source + - `${2}`: output object + - `${3}`: optional output list + - `${4}`: optional output dependencies file - Replace standard `test` definitions for specified ``definitions. For example: -```yaml - :defines: - :test: - - FOO_STANDARD_CONFIG - :test_foo_config: - - FOO_SPECIFIC_CONFIG -``` - `ceedling test:foo_config` will now have `FOO_SPECIFIC_CONFIG` defined instead of - `FOO_STANDARD_CONFIG`. None of the other tests will have `FOO_SPECIFIC_SPECIFIC`. + **Default**: `gcc` - **Default**: `[]` (empty) +* `:release_assembler`: -* `release`: + Assembler for release assembly code - Defines needed for the release build binary artifact. + - `${1}`: input assembly source file + - `${2}`: output object file + - `${3}`: search paths + - `${4}`: #define symbols (accepted but ignored by GNU assembler) - **Default**: `[]` (empty) + **Default**: `as` -* `release_preprocess`: +* `:release_linker`: - If [:project][:use_deep_dependencies] is set and code is structured in - a certain way, the gcc preprocessor may need symbol definitions to - properly preprocess files for incremental release builds due to deep - dependencies. + Linker for release source code - **Default**: `[]` (empty) + - `${1}`: input objects + - `${2}`: output binary + - `${3}`: optional output map + - `${4}`: optional library list + - `${5}`: optional library path list -* `use_test_definition`: + **Default**: `gcc` - When this option is used the `-D` flag is added to the build option. +#### Tool defintion configurable elements - **Default**: FALSE +1. `:executable` - Command line executable (required). -Example [:defines] YAML blurb + NOTE: If an executable contains a space (e.g. `Code Cruncher`), and the + shell executing the command line generated from the tool definition needs + the name quoted, add escaped quotes in the YAML: -```yaml -:defines: - :test: - - UNIT_TESTING #for select cases in source to allow testing with a changed behavior or interface - - OFF=0 - - ON=1 - - FEATURE_X=ON - :source: - - FEATURE_X=ON -``` + ```yaml + :tools: + :test_compiler: + :executable: \"Code Cruncher\" + ``` +1. `:arguments` - List (array of strings) of command line arguments and + substitutions (required). -**libraries**: command line defines used in test and release compilation by configured tools +1. `:name` - Simple name (i.e. "nickname") of tool beyond its + executable name. This is optional. If not explicitly set + then Ceedling will form a name from the tool's YAML entry key. -Ceedling allows you to pull in specific libraries for the purpose of release and test builds. -It has a few levels of support for this. Start by adding a :libraries main section in your -configuration. In this section, you can optionally have the following subsections: +1. `:stderr_redirect` - Control of capturing `$stderr` messages + {`:none`, `:auto`, `:win`, `:unix`, `:tcsh`}. + Defaults to `:none` if unspecified. You may create a custom entry by + specifying a simple string instead of any of the recognized + symbols. As an example, the `:unix` symbol maps to the string `2>&1` + that is automatically inserted at the end of a command line. -* `test`: + This option is rarely necessary. `$stderr` redirection was originally + often needed in early versions of Ceedling. Shell output stream handling + is now automatically handled. This option is preserved for possible edge + cases. - Library files that should be injected into your tests when linking occurs. - These can be specified as either relative or absolute paths. These files MUST - exist when the test attempts to build. +1. `:optional` - By default a tool you define is required for operation. This + means a build will be aborted if Ceedling cannot find your tool’s executable + in your environment. However, setting `:optional` to `true` causes this + check to be skipped. This is most often needed in plugin scenarios where a + tool is only needed if an accompanying configuration option requires it. In + such cases, a programmatic option available in plugin Ruby code using the + Ceedling class `ToolValidator` exists to process tool definitions as needed. -* `release`: +#### Tool element runtime substitution - Library files that should be injected into your release when linking occurs. These - can be specified as either relative or absolute paths. These files MUST exist when - the release attempts to build UNLESS you are using the subprojects plugin. In that - case, it will attempt to build that library for you as a dynamic dependency. +To accomplish useful work on multiple files, a configured tool will most often +require that some number of its arguments or even the executable itself change +for each run. Consequently, every tool’s argument list and executable field +possess two means for substitution at runtime. -* `system`: +Ceedling provides inline Ruby string expansion and a notation for populating +tool elements with dynamically gathered values within the build environment. - These libraries are assumed to be in the tool path somewhere and shouldn't need to be - specified. The libraries added here will be injected into releases and tests. For example - if you specify `-lm` you can include the math library. The `-l` portion is only necessary - if the `:flag` prefix below doesn't specify it already for you other libraries. +##### Tool element runtime substitution: Inline Ruby string expansion -* `flag`: +`"#{...}"`: This notation is that of the beloved +[inline Ruby string expansion][inline-ruby-string-expansion] available in a +variety of configuration file sections. This string expansion occurs each +time a tool configuration is executed during a build. - This is the method of adding an argument for each library. For example, gcc really likes - it when you specify “-l${1}” +##### Tool element runtime substitution: Notational substitution -* `path_flag`: +A Ceedling tool's other form of dynamic substitution relies on a `$` notation. +These `$` operators can exist anywhere in a string and can be decorated in any +way needed. To use a literal `$`, escape it as `\\$`. - This is the method of adding an path argument for each library path. For example, gcc really - likes it when you specify “-L \"${1}\"” +* `$`: Simple substitution for value(s) globally available within the runtime + (most often a string or an array). -Notes: +* `${#}`: When a Ceedling tool's command line is expanded from its configured + representation, runs of that tool will be made with a parameter list of + substitution values. Each numbered substitution corresponds to a position in + a parameter list. -* If you've specified your own link step, you are going to want to add ${4} to your argument -list in the place where library files should be added to the command call. For gcc, this is -often the very end. Other tools may vary. + * In the case of a compiler `${1}` will be a C code file path, and `$ + {2}` will be the file path of the resulting object file. + * For a linker `${1}` will be an array of object files to link, and `$ + {2}` will be the resulting binary executable. -**flags**: configure per-file compilation and linking flags + * For an executable test fixture `${1}` is either the binary executable + itself (when using a local toolchain such as GCC) or a binary input file + given to a simulator in its arguments. -Ceedling tools (see later [:tools] section) are used to configure -compilation and linking of test and source files. These tool -configurations are a one-size-fits-all approach. Should individual files -require special compilation or linking flags, the settings in the -[:flags] section work in conjunction with tool definitions by way of -argument substitution to achieve this. +### Example `:tools` YAML blurb -* `release`: +```yaml +:tools: + :test_compiler: + :executable: compiler # Exists in system search path + :name: 'acme test compiler' + :arguments: + - -I"${5}" # Expands to -I search paths from `:paths` section + build directive path macros + - -D"${6}" # Expands to all -D defined symbols from `:defines` section + - --network-license # Simple command line argument + - -optimize-level 4 # Simple command line argument + - "#{`args.exe -m acme.prj`}" # In-line Ruby call to shell out & build string of arguments + - -c ${1} # Source code input file + - -o ${2} # Object file output + + :test_linker: + :executable: /programs/acme/bin/linker.exe # Full file path + :name: 'acme test linker' + :arguments: + - ${1} # List of object files to link + - -l$-lib: # In-line YAML array substitution to link in foo-lib and bar-lib + - foo + - bar + - -o ${2} # Binary output artifact + + :test_fixture: + :executable: tools/bin/acme_simulator.exe # Relative file path to command line simulator + :name: 'acme test fixture' + :stderr_redirect: :win # Inform Ceedling what model of $stderr capture to use + :arguments: + - -mem large # Simple command line argument + - -f "${1}" # Binary executable input file for simulator +``` - [:compile] or [:link] flags for release build +#### `:tools` example blurb notes -* `test`: +* `${#}` is a replacement operator expanded by Ceedling with various + strings, lists, etc. assembled internally. The meaning of each + number is specific to each predefined default tool (see + documentation above). - [:compile] or [:link] flags for test build +* See [search path order][##-search-path-order] to understand how + the `-I"${5}"` term is expanded. -Notes: +* At present, `$stderr` redirection is primarily used to capture + errors from test fixtures so that they can be displayed at the + conclusion of a test run. For instance, if a simulator detects + a memory access violation or a divide by zero error, this notice + might go unseen in all the output scrolling past in a terminal. -* Ceedling works with the [:release] and [:test] build contexts - as-is; plugins can add additional contexts +* The built-in preprocessing tools _can_ be overridden with + non-GCC equivalents. However, this is highly impractical to do + as preprocessing features are quite dependent on the + idiosyncrasies and features of the GCC toolchain. -* Only [:compile] and [:link] are recognized operations beneath - a context +#### Example Test Compiler Tooling -* File specifiers do not include a path or file extension +Resulting compiler command line construction from preceding example +`:tools` YAML blurb
 -* File specifiers are case sensitive (must match original file - name) +```shell +> compiler -I"/usr/include” -I”project/tests” + -I"project/tests/support” -I”project/source” -I”project/include” + -DTEST -DLONG_NAMES -network-license -optimize-level 4 arg-foo + arg-bar arg-baz -c project/source/source.c -o + build/tests/out/source.o +``` -* File specifiers do support regular expressions if encased in quotes +Notes on compiler tooling example: -* '`*`' is a special (optional) file specifier to provide flags - to all files not otherwise specified +- `arg-foo arg-bar arg-baz` is a fabricated example string collected from + `$stdout` as a result of shell execution of `args.exe`. +- The `-c` and `-o` arguments are fabricated examples simulating a single + compilation step for a test; `${1}` & `${2}` are single files. +#### Example Test Linker Tooling -Example [:flags] YAML blurb +Resulting linker command line construction from preceding example +`:tools` YAML blurb
 -```yaml -:flags: - :release: - :compile: - :main: # add '-Wall' to compilation of main.c - - -Wall - :fan: # add '--O2' to compilation of fan.c - - --O2 - :'test_.+': # add '-pedantic' to all test-files - - -pedantic - :*: # add '-foo' to compilation of all files not main.c or fan.c - - -foo - :test: - :compile: - :main: # add '--O1' to compilation of main.c as part of test builds including main.c - - --O1 - :link: - :test_main: # add '--bar --baz' to linking of test_main.exe - - --bar - - --baz +```shell +> \programs\acme\bin\linker.exe thing.o unity.o + test_thing_runner.o test_thing.o mock_foo.o mock_bar.o -lfoo-lib + -lbar-lib -o build\tests\out\test_thing.exe ``` -**import**: Load additional config files - -In some cases it is nice to have config files (project.yml, options files) which can -load other config files, for commonly re-used definitions (target processor, -common code modules, etc). +Notes on linker tooling example: -These can be recursively nested, the included files can include other files. +- In this scenario `${1}` is an array of all the object files needed to + link a test fixture executable. -To import config files, either provide an array of files to import, or use hashes to set imports. The former is useful if you do not anticipate needing to replace a given file for different configurations (project: or options:). If you need to replace/remove imports based on different configuration files, use the hashed version. The two methods cannot be mixed in the same .yml. +#### Example Test Fixture Tooling -Example [:import] YAML blurb using array +Resulting test fixture command line construction from preceding example +`:tools` YAML blurb
 -```yaml -:import: - - path/to/config.yml - - path/to/another/config.yml +```shell +> tools\bin\acme_simulator.exe -mem large -f "build\tests\out\test_thing.bin 2>&1” ``` -Example [:import] YAML blurb using hashes -```yaml -:import: - :configA: path/to/config.yml - :configB: path/to/another/config.yml -``` +Notes on test fixture tooling example: +1. `:executable` could have simply been `${1}` if we were compiling + and running native executables instead of cross compiling. That is, + if the output of the linker runs on the host system, then the test + fixture _is_ `${1}`. +1. We’re using `$stderr` redirection to allow us to capture simulator error + messages to `$stdout` for display at the run's conclusion. -Ceedling sets values for a subset of CMock settings. All CMock -options are available to be set, but only those options set by -Ceedling in an automated fashion are documented below. See CMock -documentation. +### Ceedling tool modification shortcuts -**cmock**: configure CMock's code generation options and set symbols used to modify CMock's compiled features -Ceedling sets values for a subset of CMock settings. All CMock options are available to be set, but only those options set by Ceedling in an automated fashion are documented below. See CMock documentation. +Sometimes Ceedling’s default tool defininitions are _this close_ to being just +what you need. But, darn, you need one extra argument on the command line, or +you just need to hack the tool executable. You’d love to get away without +overriding an entire tool definition just in order to tweak it. -* `enforce_strict_ordering`: +We got you. - Tests fail if expected call order is not same as source order +#### Ceedling tool executable replacement - **Default**: TRUE +Sometimes you need to do some sneaky stuff. We get it. This feature lets you +replace the executable of a tool definition — including an internal default — +with your own. -* `mock_path`: +To use this shortcut, simply add a configuration section to your project file at +the top-level, `:tools_` ↳ `:executable`. Of course, you can +combine this with the following modification option in a single block for the +tool. Executable replacement can make use of +[inline Ruby string expansion][inline-ruby-string-expansion]. - Path for generated mocks +See the list of tool names at the beginning of the `:tools` documentation to +identify the named options. Plugins can also include their own tool definitions +that can be modified with this same option. - **Default**: /tests/mocks +This example YAML... -* `defines`: +```yaml +:tools_test_compiler: + :executable: foo +``` - List of conditional compilation symbols used to configure CMock's - compiled features. See CMock documentation to understand available - options. No symbols must be set unless defaults are inappropriate for - your specific environment. All symbols are used only by Ceedling to - compile CMock C code; contents of [:defines] are ignored by CMock's - Ruby code when instantiated. +... will produce the following: - **Default**: `[]` (empty) +```shell + > foo +``` -* `verbosity`: +#### Ceedling tool arguments addition shortcut - If not set, defaults to Ceedling's verbosity level +Now, this little feature only allows you to add arguments to the end of a tool +command line. Not the beginning. And, you can’t remove arguments with this +option. -* `plugins`: +Further, this little feature is a blanket application across all uses of a tool. +If you need fine-grained control of command line flags in build steps per test +executable, please see the [`:flags` configuration documentation][flags]. - If [:project][:use_exceptions] is enabled, the internal plugins list is pre-populated with 'cexception'. +To use this shortcut, simply add a configuration section to your project file at +the top-level, `:tools_` ↳ `:arguments`. Of course, you can +combine this with the preceding modification option in a single block for the +tool. - Whether or not you have included [:cmock][:plugins] in your - configuration file, Ceedling automatically adds 'cexception' to the - plugin list if exceptions are enabled. To add to the list Ceedling - provides CMock, simply add [:cmock][:plugins] to your configuration - and specify your desired additional plugins. +See the list of tool names at the beginning of the `:tools` documentation to +identify the named options. Plugins can also include their own tool definitions +that can be modified with this same hack. - Each of the plugins have their own additional documentation. +This example YAML... +```yaml +:tools_test_compiler: + :arguments: + - --flag # Add `--flag` to the end of all test C file compilation +``` -* `includes`: +... will produce the following (for the default executable): + +```shell + > gcc --flag +``` - If [:cmock][:unity_helper] set, pre-populated with unity_helper file - name (no path). +## `:plugins` Ceedling extensions - The [:cmock][:includes] list works identically to the plugins list - above with regard to adding additional files to be inserted within - mocks as #include statements. +See the section below dedicated to plugins for more information. This section +pertains to enabling plugins in your project configuration. +Ceedling includes a number of built-in plugins. See the collection within +the project at [plugins/][ceedling-plugins] or the [documentation section below](#ceedling-plugins) +dedicated to Ceedling’s plugins. Each built-in plugin subdirectory includes +thorough documentation covering its capabilities and configuration options. -The last four settings above are directly tied to other Ceedling -settings; hence, why they are listed and explained here. The -first setting above, [:enforce_strict_ordering], defaults -to FALSE within CMock. It is set to TRUE by default in Ceedling -as our way of encouraging you to use strict ordering. It's a teeny -bit more expensive in terms of code generated, test execution -time, and complication in deciphering test failures. However, -it's good practice. And, of course, you can always disable it -by overriding the value in the Ceedling YAML configuration file. +_Note_: Many users find that the handy-dandy [Command Hooks plugin][command-hooks] +is often enough to meet their needs. This plugin allows you to connect your own +scripts and command line tools to Ceedling build steps. +[custom-plugins]: PluginDevelopmentGuide.md +[ceedling-plugins]: ../plugins/ +[command-hooks]: ../plugins/command_hooks/ -**cexception**: configure symbols used to modify CException's compiled features +* `:load_paths`: -* `defines`: + Base paths to search for plugin subdirectories or extra Ruby functionality. - List of conditional compilation symbols used to configure CException's - features in its source and header files. See CException documentation - to understand available options. No symbols must be set unless the - defaults are inappropriate for your specific environment. + Ceedling maintains the Ruby load path for its built-in plugins. This list of + paths allows you to add your own directories for custom plugins or simpler + Ruby files referenced by your Ceedling configuration options elsewhere. **Default**: `[]` (empty) +* `:enabled`: -**unity**: configure symbols used to modify Unity's compiled features + List of plugins to be used - a plugin's name is identical to the + subdirectory that contains it. -* `defines`: + **Default**: `[]` (empty) - List of conditional compilation symbols used to configure Unity's - features in its source and header files. See Unity documentation to - understand available options. No symbols must be set unless the - defaults are inappropriate for your specific environment. Most Unity - defines can be easily configured through the YAML file. +Plugins can provide a variety of added functionality to Ceedling. In +general use, it's assumed that at least one reporting plugin will be +used to format test results (usually `report_tests_pretty_stdout`). - **Default**: `[]` (empty) +If no reporting plugins are specified, Ceedling will print to `$stdout` the +(quite readable) raw test results from all test fixtures executed. -Example [:unity] YAML blurbs -```yaml -:unity: #itty bitty processor & toolchain with limited test execution options - :defines: - - UNITY_INT_WIDTH=16 #16 bit processor without support for 32 bit instructions - - UNITY_EXCLUDE_FLOAT #no floating point unit +### Example `:plugins` YAML blurb -:unity: #great big gorilla processor that grunts and scratches - :defines: - - UNITY_SUPPORT_64 #big memory, big counters, big registers - - UNITY_LINE_TYPE=\"unsigned int\" #apparently we're using really long test files, - - UNITY_COUNTER_TYPE=\"unsigned int\" #and we've got a ton of test cases in those test files - - UNITY_FLOAT_TYPE=\"double\" #you betcha -``` - - -Notes on Unity configuration: - -* **Verification** - Ceedling does no verification of your configuration - values. In a properly configured setup, your Unity configuration - values are processed, collected together with any test define symbols - you specify elsewhere, and then passed to your toolchain during test - compilation. Unity's conditional compilation statements, your - toolchain's preprocessor, and/or your toolchain's compiler will - complain appropriately if your specified configuration values are - incorrect, incomplete, or incompatible. - -* **Routing $stdout** - Unity defaults to using `putchar()` in C's - standard library to display test results. For more exotic environments - than a desktop with a terminal (e.g. running tests directly on a - non-PC target), you have options. For example, you could create a - routine that transmits a character via RS232 or USB. Once you have - that routine, you can replace `putchar()` calls in Unity by overriding - the function-like macro `UNITY_OUTPUT_CHAR`. Consult your toolchain - and shell documentation. Eventhough this can also be defined in the YAML file - most shell environments do not handle parentheses as command line arguments - very well. To still be able to add this functionality all necessary - options can be defined in the `unity_config.h`. Unity needs to be told to look for - the `unity_config.h` in the YAML file, though. - -Example [:unity] YAML blurbs ```yaml -:unity: - :defines: - - UNITY_INCLUDE_CONFIG_H -``` +:plugins: + :load_paths: + - project/tools/ceedling/plugins # Home to your collection of plugin directories. + - project/support # Home to some ruby code your custom plugins share. + :enabled: + - report_tests_pretty_stdout # Nice test results at your command line. + - our_custom_code_metrics_report # You created a plugin to scan all code to collect + # line counts and complexity metrics. Its name is a + # subdirectory beneath the first `:load_path` entry. -Example unity_config.h ``` -#ifndef UNITY_CONFIG_H -#define UNITY_CONFIG_H -#include "uart_output.h" //Helper library for your custom environment +
-#define UNITY_INT_WIDTH 16 -#define UNITY_OUTPUT_START() uart_init(F_CPU, BAUD) //Helperfunction to init UART -#define UNITY_OUTPUT_CHAR(a) uart_putchar(a) //Helperfunction to forward char via UART -#define UNITY_OUTPUT_COMPLETE() uart_complete() //Helperfunction to inform that test has ended +# Which Ceedling -#endif -``` +In certain scenarios you may need to run a different version of Ceedling. +Typically, Ceedling developers need this ability. But, it could come in +handy in certain advanced Continuous Integration build scenarios or some +sort of version behavior comparison. +It’s not uncommon in Ceedling development work to have the last production +gem installed while modifying the application code in a locally cloned +repository. Or, you may be bouncing between local versions of Ceedling to +troubleshoot changes. -**tools**: a means for representing command line tools for use under -Ceedling's automation framework +Which Ceedling handling gives you options on what gets run. -Ceedling requires a variety of tools to work its magic. By default, -the GNU toolchain (`gcc`, `cpp`, `as`) are configured and ready for -use with no additions to the project configuration YAML file. -However, as most work will require a project-specific toolchain, -Ceedling provides a generic means for specifying / overriding -tools. +## Which Ceedling background -* `test_compiler`: +Ceedling is usually packaged and installed as a Ruby Gem. This gem ends +up installed in an appropriate place by the `gem` package installer. +Inside the gem installation is the entire Ceedling project. The `ceedling` +command line launcher lives in `bin/` while the Ceedling application lives +in `lib/`. The code in `/bin` manages lots of startup details and base +configuration. Ultimately, it then launches the main application code from +`lib/`. - Compiler for test & source-under-test code +The features and conventions controlling _which ceedling_ dictate which +application code the `ceedling` command line handler launches. - - `${1}`: input source - - `${2}`: output object - - `${3}`: optional output list - - `${4}`: optional output dependencies file +_NOTE:_ If you are a developer working on the code in Ceedling’s `bin/` +and want to run it while a gem is installed, you must take the additional +step of specifying the path to the `ceedling` launcher in your file system. - **Default**: `gcc` +In Unix-like systems, this will look like: +`> my/ceedling/changes/bin/ceedling `. -* `test_linker`: +On Windows systems, you may need to run: +`> ruby my\ceedling\changes\bin\ceedling `. - Linker to generate test fixture executables +## Which Ceedling options and precedence - - `${1}`: input objects - - `${2}`: output binary - - `${3}`: optional output map - - `${4}`: optional library list - - `${5}`: optional library path list +When Ceedling starts up, it evaluates a handful of conditions to determine +which Ceedling location to launch. - **Default**: `gcc` +The following are evaluated in order: -* `test_fixture`: +1. Environment variable `WHICH_CEEDLING`. If this environment variable is + set, its value is used. +1. Configuration entry `:project` ↳ `:which_ceedling`. If this is set, + its value is used. +1. The path `vendor/ceedling`. If this path exists in your working + directory — typically because of a `--local` vendored installation at + project creation — its contents are used to launch Ceedling. +1. If none of the above exist, the `ceedling` launcher defaults to using + the `lib/` directory next to the `bin/` directory from which the + `ceedling` launcher is running. In the typical case this is the default + gem installation. - Executable test fixture +_NOTE:_ Configuration entry (2) does not make sense in some scenarios. +When running `ceedling new`, `ceedling examples`, or `ceedling example` +there is no project file to read. Similarly, `ceedling upgrade` does not +load a project file; it merely works with the directory structure and +contets of a project. In these cases, the environment variable is your +only option to set which Ceedling to launch. - - `${1}`: simulator as executable with`${1}` as input binary file argument or native test executable +## Which Ceedling settings - **Default**: `${1}` +The environment variable and configuration entry for _Which Ceedling_ can +contain two values: -* `test_includes_preprocessor`: +1. The value `gem` indicates that the command line `ceedling` launcher + should run the application packaged alongside it in `lib/` (these + paths are typically found in the gem installation location). +1. A relative or absolute path in your file system. Such a path should + point to the top-level directory that contains Ceedling’s `bin/` and + `lib/` sub-directories. - Extractor of #include statements +
- - `${1}`: input source file +# Build Directive Macros - **Default**: `cpp` +## Overview of Build Directive Macros -* `test_file_preprocessor`: +Ceedling supports a small number of build directive macros. At present, +these macros are only for use in test files. - Preprocessor of test files (macros, conditional compilation statements) - - `${1}`: input source file - - `${2}`: preprocessed output source file +By placing these macros in your test files, you may control aspects of an +individual test executable's build from within the test file itself. - **Default**: `gcc` +These macros are actually defined in Unity, but they evaluate to empty +strings. That is, the macros do nothing and only serve as text markers for +Ceedling to parse. But, by placing them in your test files they +communicate instructions to Ceedling when scanned at the beginning of a +test build. -* `test_file_preprocessor_directives`: +**_Notes:_** - Preprocessor of test files to expand only conditional compilation statements, - handle directives, but do not expand macros +- Since these macros are defined in _unity.h_, it’s essential to + `#include "unity.h"` before making use of them in your test file. + Typically, _unity.h_ is referenced at or near the top of a test file + anyhow, but this is an important detail to call out. +- **`TEST_SOURCE_FILE()` and `TEST_INCLUDE_PATH()`, new in Ceedling + 1.0.0, are incompatible with enclosing conditional compilation C + preprocessing statements.** See + [Ceedling’s preprocessing documentation](#preprocessing-gotchas) + for more details. - - `${1}`: input source file - - `${2}`: not-fully preprocessed output source file +## `TEST_SOURCE_FILE()` - **Default**: `gcc` +### `TEST_SOURCE_FILE()` Purpose -* `test_dependencies_generator`: +The `TEST_SOURCE_FILE()` build directive allows the simple injection of +a specific source file into a test executable’s build. - Discovers deep dependencies of source & test (for incremental builds) +The Ceedling [convention][ceedling-conventions] of compiling and linking +any C file that corresponds in name to an `#include`d header file does +not always work. A given source file may not have a header file that +corresponds directly to its name. In some specialized cases, a source +file may not rely on a header file at all. - - `${1}`: input source file - - `${2}`: compiled object filepath - - `${3}`: output dependencies file +Attempting to `#include` a needed C source file directly is both ugly and +can cause various build problems with duplicated symbols, etc. - **Default**: `gcc` +`TEST_SOURCE_FILE()` is the way to cleanly and simply add a given C file +to the executable built from a test file. `TEST_SOURCE_FILE()` is also one +of the best methods for adding an assembly file to the build of a given +test executable—if assembly support is enabled for test builds. -* `release_compiler`: +### `TEST_SOURCE_FILE()` Usage - Compiler for release source code +The argument for the `TEST_SOURCE_FILE()` build directive macro is a +single filename or filepath as a string enclosed in quotation marks. Use +forward slashes for path separators. The filename or filepath must be +present within Ceedling’s source file collection. - - `${1}`: input source - - `${2}`: output object - - `${3}`: optional output list - - `${4}`: optional output dependencies file +To understand your source file collection: - **Default**: `gcc` +- See the documentation for project file configuration section + [`:paths`](#project-paths-configuration). +- Dump a listing your project’s source files with the command line task + `ceedling files:source`. -* `release_assembler`: +Multiple uses of `TEST_SOURCE_FILE()` are perfectly fine. You’ll likely +want one per line within your test file. - Assembler for release assembly code +### `TEST_SOURCE_FILE()` Example - - `${1}`: input assembly source file - - `${2}`: output object file +```c +/* + * Test file test_mycode.c to exercise functions in mycode.c. + */ + +#include "unity.h" // Contains TEST_SOURCE_FILE() definition +#include "support.h" // Needed symbols and macros +//#include "mycode.h" // Header file corresponding to mycode.c by convention does not exist - **Default**: `as` +// Tell Ceedling to compile and link mycode.c as part of the test_mycode executable +TEST_SOURCE_FILE("foo/bar/mycode.c") -* `release_linker`: +// --- Unit test framework calls --- - Linker for release source code +void setUp(void) { + ... +} - - `${1}`: input objects - - `${2}`: output binary - - `${3}`: optional output map - - `${4}`: optional library list - - `${5}`: optional library path list +void test_MyCode_FooBar(void) { + ... +} +``` - **Default**: `gcc` +## `TEST_INCLUDE_PATH()` -* `release_dependencies_generator`: +### `TEST_INCLUDE_PATH()` Purpose - Discovers deep dependencies of source files (for incremental builds) +The `TEST_INCLUDE_PATH()` build directive allows a header search path to +be injected into the build of an individual test executable. - - `${1}`: input source file - - `${2}`: compiled object filepath - - `${3}`: output dependencies file +Unless you have a pretty funky C project, generally at least one search path entry +is necessary for every test executable build. That path can come from a `:paths` +↳ `:include` entry in your project configuration or by using `TEST_INCLUDE_PATH()` +in a test file. - **Default**: `gcc` +Please see [Configuring Your Header File Search Paths][header-file-search-paths] +for an overview of Ceedling’s options and conventions for header file search paths. +### `TEST_INCLUDE_PATH()` Usage -A Ceedling tool has a handful of configurable elements: +`TEST_INCLUDE_PATH()` entries in your test file are only an additive customization. +The path will be added to the base / common path list specified by +`:paths` ↳ `:include` in the project file. If no list is specified in your project +configuration, `TEST_INCLUDE_PATH()` entries will comprise the entire header search +path list. -1. [:executable] - Command line executable (required) +The argument for the `TEST_INCLUDE_PATH()` build directive macro is a single +filepath as a string enclosed in quotation marks. Use forward slashes for +path separators. -2. [:arguments] - List of command line arguments - and substitutions (required) +**_Note_**: At present, a limitation of the `TEST_INCLUDE_PATH()` build directive +macro is that paths are relative to the working directory from which you are +executing `ceedling`. A change to your working directory could require updates to +the path arguments of dall instances of `TEST_INCLUDE_PATH()`. -3. [:name] - Simple name (e.g. "nickname") of tool beyond its - executable name (if not explicitly set then Ceedling will - form a name from the tool's YAML entry name) +Multiple uses of `TEST_INCLUDE_PATH()` are perfectly fine. You’ll likely want one +per line within your test file. -4. [:stderr_redirect] - Control of capturing $stderr messages - {:none, :auto, :win, :unix, :tcsh}. - Defaults to :none if unspecified; create a custom entry by - specifying a simple string instead of any of the available - symbols. +[header-file-search-paths]: #configuring-your-header-file-search-paths -5. [:background_exec] - Control execution as background process - {:none, :auto, :win, :unix}. - Defaults to :none if unspecified. +### `TEST_INCLUDE_PATH()` Example -6. [:optional] - By default a tool is required for operation, which - means tests will be aborted if the tool is not present. However, - you can set this to `TRUE` if it's not needed for testing. +```c +/* + * Test file test_mycode.c to exercise functions in mycode.c. + */ +#include "unity.h" // Contains TEST_INCLUDE_PATH() definition +#include "somefile.h" // Needed symbols and macros -Tool Element Runtime Substitution ---------------------------------- +// Add the following to the compiler's -I search paths used to +// compile all components comprising the test_mycode executable. +TEST_INCLUDE_PATH("foo/bar/") +TEST_INCLUDE_PATH("/usr/local/include/baz/") -To accomplish useful work on multiple files, a configured tool will most -often require that some number of its arguments or even the executable -itself change for each run. Consequently, every tool's argument list and -executable field possess two means for substitution at runtime. Ceedling -provides two kinds of inline Ruby execution and a notation for -populating elements with dynamically gathered values within the build -environment. +// --- Unit test framework calls --- -Tool Element Runtime Substitution: Inline Ruby Execution --------------------------------------------------------- +void setUp(void) { + ... +} -In-line Ruby execution works similarly to that demonstrated for the -[:environment] section except that substitution occurs as the tool is -executed and not at the time the configuration file is first scanned. +void test_MyCode_FooBar(void) { + ... +} +``` -* `#{...}`: +
- Ruby string substitution pattern wherein the containing string is - expanded to include the string generated by Ruby code between the - braces. Multiple instances of this expansion can occur within a single - tool element entry string. Note that if this string substitution - pattern occurs at the very beginning of a string in the YAML - configuration the entire string should be enclosed in quotes (see the - [:environment] section for further explanation on this point). +# Ceedling Plugins -* `{...} `: +Ceedling includes a number of plugins. See the collection of built-in [plugins/][ceedling-plugins] +or consult the list with summaries and links to documentation in the subsection +that follows. Each plugin subdirectory includes full documentation of its +capabilities and configuration options. - If an entire tool element string is enclosed with braces, it signifies - that Ceedling should execute the Ruby code contained within those - braces. Say you have a collection of paths on disk and some of those - paths include spaces. Further suppose that a single tool that must use - those paths requires those spaces to be escaped, but all other uses of - those paths requires the paths to remain unchanged. You could use this - Ceedling feature to insert Ruby code that iterates those paths and - escapes those spaces in the array as used by the tool of this example. +To enable built-in plugins or your own custom plugins, see the documentation for +the `:plugins` section in Ceedling project configuation options. -Tool Element Runtime Substitution: Notational Substitution ----------------------------------------------------------- +Many users find that the handy-dandy [Command Hooks plugin][command-hooks] +is often enough to meet their needs. This plugin allows you to connect your own +scripts and tools to Ceedling build steps. -A Ceedling tool's other form of dynamic substitution relies on a '$' -notation. These '$' operators can exist anywhere in a string and can be -decorated in any way needed. To use a literal '$', escape it as '\\$'. +As mentioned, you can create your own plugins. See the [guide][custom-plugins] +for how to create custom plugins. -* `$`: +[//]: # (Links in this section already defined above) - Simple substitution for value(s) globally available within the runtime - (most often a string or an array). +## Ceedling’s built-in plugins, a directory -* `${#}`: +### Ceedling plugin `report_tests_pretty_stdout` - When a Ceedling tool's command line is expanded from its configured - representation and used within Ceedling Ruby code, certain calls to - that tool will be made with a parameter list of substitution values. - Each numbered substitution corresponds to a position in a parameter - list. Ceedling Ruby code expects that configured compiler and linker - tools will contain ${1} and ${2} replacement arguments. In the case of - a compiler ${1} will be a C code file path, and ${2} will be the file - path of the resulting object file. For a linker ${1} will be an array - of object files to link, and ${2} will be the resulting binary - executable. For an executable test fixture ${1} is either the binary - executable itself (when using a local toolchain such as gcc) or a - binary input file given to a simulator in its arguments. +[This plugin][report_tests_pretty_stdout] is meant to tbe the default for +printing test results to the console. Without it, readable test results are +still produced but are not nicely formatted and summarized. +Plugin output includes a well-formatted list of summary statistics, ignored and +failed tests, and any extraneous output (e.g. `printf()` statements or +simulator memory errors) collected from executing the test fixtures. -Example [:tools] YAML blurbs +Alternatives to this plugin are: + + * `report_tests_ide_stdout` + * `report_tests_gtestlike_stdout` -```yaml -:tools: - :test_compiler: - :executable: compiler #exists in system search path - :name: 'acme test compiler' - :arguments: - - -I"$": COLLECTION_PATHS_TEST_TOOLCHAIN_INCLUDE #expands to -I search paths - - -I"$": COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR #expands to -I search paths - - -D$: COLLECTION_DEFINES_TEST_AND_VENDOR #expands to all -D defined symbols - - --network-license #simple command line argument - - -optimize-level 4 #simple command line argument - - "#{`args.exe -m acme.prj`}" #in-line ruby sub to shell out & build string of arguments - - -c ${1} #source code input file (Ruby method call param list sub) - - -o ${2} #object file output (Ruby method call param list sub) - :test_linker: - :executable: /programs/acme/bin/linker.exe #absolute file path - :name: 'acme test linker' - :arguments: - - ${1} #list of object files to link (Ruby method call param list sub) - - -l$-lib: #inline yaml array substitution to link in foo-lib and bar-lib - - foo - - bar - - -o ${2} #executable file output (Ruby method call param list sub) - :test_fixture: - :executable: tools/bin/acme_simulator.exe #relative file path to command line simulator - :name: 'acme test fixture' - :stderr_redirect: :win #inform Ceedling what model of $stderr capture to use - :arguments: - - -mem large #simple command line argument - - -f "${1}" #binary executable input file to simulator (Ruby method call param list sub) -``` +Both of the above write to the console test results with a format that is useful +to IDEs generally in the case of the former, and GTest-aware reporting tools in +the case of the latter. -Resulting command line constructions from preceding example [:tools] YAML blurbs +[report_tests_pretty_stdout]: ../plugins/report_tests_pretty_stdout - > compiler -I"/usr/include” -I”project/tests” - -I"project/tests/support” -I”project/source” -I”project/include” - -DTEST -DLONG_NAMES -network-license -optimize-level 4 arg-foo - arg-bar arg-baz -c project/source/source.c -o - build/tests/out/source.o +### Ceedling plugin `report_tests_ide_stdout` -[notes: (1.) "arg-foo arg-bar arg-baz" is a fabricated example -string collected from $stdout as a result of shell execution -of args.exe -(2.) the -c and -o arguments are -fabricated examples simulating a single compilation step for -a test; ${1} & ${2} are single files] +[This plugin][report_tests_ide_stdout] prints to the console test results +formatted similarly to `report_tests_pretty_stdout` with one key difference. +This plugin's output is formatted such that an IDE executing Ceedling tasks can +recognize file paths and line numbers in test failures, etc. - > \programs\acme\bin\linker.exe thing.o unity.o - test_thing_runner.o test_thing.o mock_foo.o mock_bar.o -lfoo-lib - -lbar-lib -o build\tests\out\test_thing.exe +This plugin's formatting is often recognized in an IDE's build window and +automatically linked for file navigation. With such output, you can select a +test result in your IDE's execution window and jump to the failure (or ignored +test) in your test file (more on using [IDEs] with Ceedling, Unity, and +CMock). -[note: in this scenario ${1} is an array of all the object files -needed to link a test fixture executable] +If enabled, this plugin should be used in place of +`report_tests_pretty_stdout`. - > tools\bin\acme_simulator.exe -mem large -f "build\tests\out\test_thing.bin 2>&1” +[report_tests_ide_stdout]: ../plugins/report_tests_ide_stdout -[note: (1.) :executable could have simply been ${1} - if we were compiling -and running native executables instead of cross compiling (2.) we're using -$stderr redirection to allow us to capture simulator error messages to -$stdout for display at the run's conclusion] +[IDEs]: https://www.throwtheswitch.org/ide +### Ceedling plugin `report_tests_teamcity_stdout` -Notes: +[TeamCity] is one of the original Continuous Integration server products. -* The upper case names are Ruby global constants that Ceedling - builds +[This plugin][report_tests_teamcity_stdout] processes test results into TeamCity +service messages printed to the console. TeamCity's service messages are unique +to the product and allow the CI server to extract build steps, test results, +and more from software builds if present. -* "COLLECTION_" indicates that Ceedling did some work to assemble - the list. For instance, expanding path globs, combining multiple - path globs into a convenient summation, etc. +The output of this plugin is useful in actual CI builds but is unhelpful in +local developer builds. See the plugin's documentation for options to enable +this plugin only in CI builds and not in local builds. -* At present, $stderr redirection is primarily used to capture - errors from test fixtures so that they can be displayed at the - conclusion of a test run. For instance, if a simulator detects - a memory access violation or a divide by zero error, this notice - might go unseen in all the output scrolling past in a terminal. +[TeamCity]: https://jetbrains.com/teamcity +[report_tests_teamcity_stdout]: ../plugins/report_tests_teamcity_stdout -* The preprocessing tools can each be overridden with non-gcc - equivalents. However, this is an advanced feature not yet - documented and requires that the replacement toolchain conform - to the same conventions used by gcc. +### Ceedling plugin `report_tests_gtestlike_stdout` -**Ceedling Collection Used in Compilation**: +[This plugin][report_tests_gtestlike_stdout] collects test results and prints +them to the console in a format that mimics [Google Test's output][gtest-sample-output]. +Google Test output is both human readable and recognized +by a variety of reporting tools, IDEs, and Continuous Integration servers. -* `COLLECTION_PATHS_TEST`: +If enabled, this plugin should be used in place of +`report_tests_pretty_stdout`. - All test paths +[gtest-sample-output]: +https://subscription.packtpub.com/book/programming/9781800208988/11/ch11lvl1sec31/controlling-output-with-google-test +[report_tests_gtestlike_stdout]: ../plugins/report_tests_gtestlike_stdout -* `COLLECTION_PATHS_SOURCE`: +### Ceedling plugin `command_hooks` - All source paths +[This plugin][command-hooks] provides a simple means for connecting Ceedling’s build events to +Ceedling tool entries you define in your project configuration (see `:tools` +documentation). In this way you can easily connect your own scripts or command +line utilities to build steps without creating an entire custom plugin. -* `COLLECTION_PATHS_INCLUDE`: +[//]: # (Links defined in a previous section) - All include paths +### Ceedling plugin `module_generator` -* `COLLECTION_PATHS_SUPPORT`: +A pattern emerges in day-to-day unit testing, especially in the practice of +Test- Driven Development. Again and again, one needs a triplet of a source +file, header file, and test file — scaffolded in such a way that they refer to +one another. - All test support paths +[This plugin][module_generator] allows you to save precious minutes by creating +these templated files for you with convenient command line tasks. -* `COLLECTION_PATHS_SOURCE_AND_INCLUDE`: +[module_generator]: ../plugins/module_generator - All source and include paths +### Ceedling plugin `fff` -* `COLLECTION_PATHS_SOURCE_INCLUDE_VENDOR`: +The Fake Function Framework, [FFF], is an alternative approach to [test doubles][test-doubles] +than that used by CMock. - All source and include paths + applicable vendor paths (e.g. - CException's source path if exceptions enabled) +[This plugin][FFF-plugin] replaces Ceedling generation of CMock-based mocks and +stubs in your tests with FFF-generated fake functions instead. -* `COLLECTION_PATHS_TEST_TOOLCHAIN_INCLUDE`: +[//]: # (FFF links are defined up in an introductory section explaining CMock) - All test toolchain include paths +### Ceedling plugin `beep` -* `COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE`: +[This plugin][beep] provides a simple audio notice when a test build completes suite +execution or fails due to a build error. It is intended to support developers +running time-consuming test suites locally (i.e. in the background). - All test, source, and include paths +The plugin provides a variety of options for emitting audio notificiations on +various desktop platforms. -* `COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR`: +[beep]: ../plugins/beep - All test, source, include, and applicable vendor paths (e.g. Unity's - source path plus CMock and CException's source paths if mocks and - exceptions are enabled) +### Ceedling plugin `bullseye` -* `COLLECTION_PATHS_RELEASE_TOOLCHAIN_INCLUDE`: +[This plugin][bullseye-plugin] adds additional Ceedling tasks to execute tests +with code coverage instrumentation provided by the commercial code coverage +tool provided by [Bullseye]. The Bullseye tool provides visualization and report +generation from the coverage results produced by an instrumented test suite. - All release toolchain include paths +[bullseye]: http://www.bullseye.com +[bullseye-plugin]: ../plugins/bullseye -* `COLLECTION_DEFINES_TEST_AND_VENDOR`: +### Ceedling plugin `gcov` - All symbols specified in [:defines][:test] + symbols defined for - enabled vendor tools - e.g. [:unity][:defines], [:cmock][:defines], - and [:cexception][:defines] +[This plugin][gcov-plugin] adds additional Ceedling tasks to execute tests with GNU code +coverage instrumentation. Coverage reports of various sorts can be generated +from the coverage results produced by an instrumented test suite. -* `COLLECTION_DEFINES_RELEASE_AND_VENDOR`: +This plugin manages the use of up to three coverage reporting tools. The GNU +[gcov] tool provides simple coverage statitics to the console as well as to the +other supported reporting tools. Optional Python-based [GCovr] and .Net-based +[ReportGenerator] produce fancy coverage reports in XML, JSON, HTML, etc. +formats. - All symbols specified in [:defines][:release] plus symbols defined by -[:cexception][:defines] if exceptions are enabled +[gcov-plugin]: ../plugins/gcov +[gcov]: http://gcc.gnu.org/onlinedocs/gcc/Gcov.html +[GCovr]: https://www.gcovr.com/ +[ReportGenerator]: https://reportgenerator.io +### Ceedling plugin `report_tests_log_factory` -Notes: +[This plugin][report_tests_log_factory] produces any or all of three useful test +suite reports in JSON, JUnit, or CppUnit format. It further provides a +mechanism for users to create their own custom reports with a small amount of +custom Ruby rather than a full plugin. -* Other collections exist within Ceedling. However, they are - only useful for advanced features not yet documented. +[report_tests_log_factory]: ../plugins/report_tests_log_factory -* Wherever multiple path lists are combined for use Ceedling prioritizes - path groups as follows: test paths, support paths, source paths, include - paths. - This can be useful, for instance, in certain testing scenarios - where we desire Ceedling or the compiler to find a stand-in header file - before the actual source header file of the same name. +### Ceedling plugin `report_build_warnings_log` +[This plugin][report_build_warnings_log] scans the output of build tools for console +warning notices and produces a simple text file that collects all such warning +messages. -**plugins**: Ceedling extensions +[report_build_warnings_log]: ../plugins/report_build_warnings_log -* `load_paths`: +### Ceedling plugin `report_tests_raw_output_log` - Base paths to search for plugin subdirectories or extra ruby functionalit +[This plugin][report_tests_raw_output_log] captures extraneous console output +generated by test executables — typically for debugging — to log files named +after the test executables. - **Default**: `[]` (empty) +[report_tests_raw_output_log]: ../plugins/report_tests_raw_output_log -* `enabled`: +### Ceedling plugin `subprojects` - List of plugins to be used - a plugin's name is identical to the - subdirectory that contains it (and the name of certain files within - that subdirectory) +[This plugin][subprojects] supports subproject release builds of static +libraries. It manages differing sets of compiler flags and linker flags that +fit the needs of different library builds. - **Default**: `[]` (empty) +[subprojects]: ../plugins/subprojects +### Ceedling plugin `dependencies` -Plugins can provide a variety of added functionality to Ceedling. In -general use, it's assumed that at least one reporting plugin will be -used to format test results. However, if no reporting plugins are -specified, Ceedling will print to `$stdout` the (quite readable) raw -test results from all test fixtures executed. +[This plugin][dependencies] manages release build dependencies including +fetching those dependencies and calling a given dependenc's build process. +Ultimately, this plugin generates the components needed by your Ceedling +release build target. -Example [:plugins] YAML blurb +[dependencies]: ../plugins/dependencies -```yaml -:plugins: - :load_paths: - - project/tools/ceedling/plugins #home to your collection of plugin directories - - project/support #maybe home to some ruby code your custom plugins share - :enabled: - - stdout_pretty_tests_report #nice test results at your command line - - our_custom_code_metrics_report #maybe you needed line count and complexity metrics, so you - #created a plugin to scan all your code and collect that info -``` +### Ceedling plugin `compile_commands_json_db` -* `stdout_pretty_tests_report`: +[This plugin][compile_commands_json_db] create a [JSON Compilation Database][json-compilation-database]. +This file is useful to [any code editor or IDE][lsp-tools] that implements +syntax highlighting, etc. by way of the LLVM project’s [`clangd`][clangd] +Language Server Protocol conformant language server. - Prints to $stdout a well-formatted list of ignored and failed tests, - final test counts, and any extraneous output (e.g. printf statements - or simulator memory errors) collected from executing the test - fixtures. Meant to be used with runs at the command line. +[compile_commands_json_db]: ../plugins/compile_commands_json_db +[lsp-tools]: https://microsoft.github.io/language-server-protocol/implementors/tools/ +[clangd]: https://clangd.llvm.org +[json-compilation-database]: https://clang.llvm.org/docs/JSONCompilationDatabase.html -* `stdout_ide_tests_report`: +
- Prints to $stdout simple test results formatted such that an IDE - executing test-related Rake tasks can recognize file paths and line - numbers in test failures, etc. Thus, you can click a test result in - your IDE's execution window and jump to the failure (or ignored test) - in your test file (obviously meant to be used with an [IDE like - Eclipse][ide], etc). +# Global Collections - [ide]: http://throwtheswitch.org/white-papers/using-with-ides.html +Collections are Ruby arrays and Rake FileLists (that act like +arrays). Ceedling did work to populate and assemble these by +processing the project file, using internal knowledge, +expanding path globs, etc. at startup. -* `xml_tests_report`: +Collections are globally available Ruby constants. These +constants are documented below. Collections are also available +via accessors on the `Configurator` object (same names but all +lower case methods). - Creates an XML file of test results in the xUnit format (handy for - Continuous Integration build servers or as input to other reporting - tools). Produces a file report.xml in /artifacts/tests. +Global collections are typically used in Rakefiles, plugins, +and Ruby scripts where the contents tend to be especially +handy for crafting custom functionality. -* `bullseye`: +Once upon a time collections were a core component of Ceedling. +As the tool has grown in sophistication and as many of its +features now operate per test executable, the utility of and +number of collections has dwindled. Previously, nearly all +Ceedling actions happened in bulk and with the same +collections used for all tasks. This is no longer true. - Adds additional Rake tasks to execute tests with the commercial code - coverage tool provided by [Bullseye][]. See readme.txt inside the bullseye - plugin directory for configuration and use instructions. Note: - Bullseye only works with certain compilers and linkers (healthy list - of supported toolchains though). +* `COLLECTION_PROJECT_OPTIONS`: - [bullseye]: http://www.bullseye.com + All project option files with path found in the configured + options paths having the configured YAML file extension. -* `gcov`: +* `COLLECTION_ALL_TESTS`: - Adds additional Rake tasks to execute tests with the GNU code coverage - tool [gcov][]. See readme.txt inside the gcov directory for configuration - and use instructions. Only works with GNU compiler and linker. + All files with path found in the configured test paths + having the configured source file extension. - [gcov]: http://gcc.gnu.org/onlinedocs/gcc/Gcov.html +* `COLLECTION_ALL_ASSEMBLY`: -* `warnings_report`: + All files with path found in the configured source and + test support paths having the configured assembly file + extension. - Scans compiler and linker `$stdout / $stderr` output for the word - 'warning' (case insensitive). All code warnings (or tool warnings) are - logged to a file warnings.log in the appropriate `/artifacts` directory (e.g. test/ for test tasks, `release/` for a - release build, or even `bullseye/` for bullseye runs). +* `COLLECTION_ALL_SOURCE`: -Module Generator -======================== -Ceedling includes a plugin called module_generator that will create a source, header and test file for you. -There are several possibilities to configure this plugin through your project.yml to suit your project's needs. + All files with path found in the configured source paths + having the configured source file extension. -Directory Structure -------------------------------------------- +* `COLLECTION_ALL_HEADERS`: -The default configuration for directory/project structure is: -```yaml -:module_generator: - :project_root: ./ - :source_root: src/ - :test_root: test/ -``` -You can change these variables in your project.yml file to comply with your project's directory structure. + All files with path found in the configured include, + support, and test paths having the configured header file + extension. -If you call `ceedling module:create`, it will create three files: -1. A source file in the source_root -2. A header file in the source_root -3. A test file in the test_root +* `COLLECTION_ALL_SUPPORT`: -If you want your header file to be in another location, -you can specify the ':inc_root:" in your project.yml file: -```yaml -:module_generator: - :inc_root: inc/ -``` -The module_generator will then create the header file in your defined ':inc_root:'. -By default, ':inc_root:' is not defined so the module_generator will use the source_root. - -Sometimes, your project can't be divided into a single src, inc, and test folder. You have several directories -with sources/..., something like this for example: - - - myDriver - - src - - inc - - test - - myOtherDriver - - src - - inc - - test - - ... - -Don't worry, you don't have to manually create the source/header/test files. -The module_generator can accept a path to create a source_root/inc_root/test_root folder with your files: -`ceedling module:create[:]` - -F.e., applied to the above project structure: -`ceedling module:create[myOtherDriver:driver]` -This will make the module_generator run in the subdirectory 'myOtherDriver' and generate the module files -for you in that directory. So, this command will generate the following files: -1. A source file 'driver.c' in /myOtherDriver/ -2. A header file 'driver.h' in /myOtherDriver/ (or if specified) -3. A test file 'test_driver.c' in /myOtherDriver/ - -Naming -------------------------------------------- -By default, the module_generator will generate your files in lowercase. -`ceedling module:create[mydriver]` and `ceedling module:create[myDriver]`(note the uppercase) will generate the same files: -1. mydriver.c -2. mydriver.h -3. test_mydriver.c - -You can configure the module_generator to use a differect naming mechanism through the project.yml: -```yaml -:module_generator: - :naming: "camel" -``` -There are other possibilities as well (bumpy, camel, snake, caps). -Refer to the unity module generator for more info (the unity module generator is used under the hood by module_generator). + All files with path found in the configured test support + paths having the configured source file extension. +* `COLLECTION_PATHS_INCLUDE`: -Boilerplate header -------------------------------------------- -There are two ways of adding a boilerplate header comment to your generated files: -* With a defined string in the project.yml file: + All configured include paths. -```yaml -:module_generator: - :boilerplates: - :src: '/* This is Boilerplate code. */' -``` +* `COLLECTION_PATHS_SOURCE`: -Using the command **ceedling module:create[foo]** it creates the source module as follows: + All configured source paths. -```c -/* This is Boilerplate code. */ -#include "foo.h" -``` +* `COLLECTION_PATHS_SUPPORT`: + + All configured support paths. -It would be the same for **:tst:** and **:inc:** adding its respective options. +* `COLLECTION_PATHS_TEST`: -* Defining an external file with boileplate code: + All configured test paths. -```yml -:module_generator: - :boilerplate_files: - :src: '\src_boilerplate.txt' - :inc: '\inc_boilerplate.txt' - :tst: '\tst_boilerplate.txt' -``` +* `COLLECTION_PATHS_SOURCE_AND_INCLUDE`: -For whatever file names in whichever folder you desire. + All configured source and include paths. +* `COLLECTION_PATHS_SOURCE_INCLUDE_VENDOR`: -Advanced Topics (Coming) -======================== + All configured source and include paths plus applicable + vendor paths (Unity's source path plus CMock and + CException's source paths if mocks and exceptions are + enabled). -Modifying Your Configuration without Modifying Your Project File: Option Files & User Files -------------------------------------------------------------------------------------------- +* `COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE`: -Modifying your project file without modifying your project file + All configured test, support, source, and include paths. -Debugging and/or printf() -------------------------- +* `COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR`: -When you gotta get your hands dirty... + All test, support, source, include, and applicable + vendor paths (Unity's source path plus CMock and + CException's source paths if mocks and exceptions are + enabled). -Ceedling Plays Nice with Others - Using Ceedling for Tests Alongside Another Release Build Setup ------------------------------------------------------------------------------------------------- +* `COLLECTION_PATHS_RELEASE_TOOLCHAIN_INCLUDE`: -You've got options. + All configured release toolchain include paths. -Adding Handy Rake Tasks for Your Project (without Fancy Pants Custom Plugins) ------------------------------------------------------------------------------ +* `COLLECTION_PATHS_TEST_TOOLCHAIN_INCLUDE`: -Add a file `rakefile.rb` at the root of your project that loads Ceedling. This -differs whether you are using the gem version or a local Ceedling version. + All configured test toolchain include paths. -Gem Version: -```ruby -require('ceedling') -Ceedling.load_project -``` +* `COLLECTION_PATHS_VENDOR`: -Local Ceedling Version (assuming local ceedling is in `vendor/ceedling`): -```ruby -PROJECT_CEEDLING_ROOT = "vendor/ceedling" -load "#{PROJECT_CEEDLING_ROOT}/lib/ceedling.rb" -Ceedling.load_project -``` + Unity's source path plus CMock and CException's source + paths if mocks and exceptions are enabled. -Now you simply add your rake task to the file e.g.: -```ruby -desc "Print hello world in sh" # Only tasks with description are listed by ceedling -T -task :hello_world do - sh "echo Hello World!" -end -``` +* `COLLECTION_VENDOR_FRAMEWORK_SOURCES`: -The task can now be called with: `ceedling hello_world` + Unity plus CMock, and CException's .c filenames (without + paths) if mocks and exceptions are enabled. -Working with Non-Desktop Testing Environments ---------------------------------------------- +* `COLLECTION_RELEASE_BUILD_INPUT`: -For those crazy platforms lacking command line simulators and for which -cross-compiling on the desktop just ain't gonna get it done. + * All files with path found in the configured source + paths having the configured source file extension. + * If exceptions are enabled, the source files for + CException. + * If assembly support is enabled, all assembly files + found in the configured paths having the configured + assembly file extension. -Creating Custom Plugins ------------------------ +* `COLLECTION_EXISTING_TEST_BUILD_INPUT`: + + * All files with path found in the configured source + paths having the configured source file extension. + * All files with path found in the configured test + paths having the configured source file extension. + * Unity's source files. + * If exceptions are enabled, the source files for + CException. + * If mocks are enabled, the C source files for CMock. + * If assembly support is enabled, all assembly files + found in the configured paths having the configured + assembly file extension. + + This collection does not include .c files generated by + Ceedling and its supporting frameworks at build time + (e.g. test runners and mocks). Further, this collection + does not include source files added to a test + executable's build list with the `TEST_SOURCE_FILE()` + build directive macro. + +* `COLLECTION_RELEASE_ARTIFACT_EXTRA_LINK_OBJECTS`: + + If exceptions are enabled, CException's .c filenames + (without paths) remapped to configured object file + extension. + +* `COLLECTION_TEST_FIXTURE_EXTRA_LINK_OBJECTS`: + + All test support source filenames (without paths) + remapped to configured object file extension. -Oh boy. This is going to take some explaining. +
diff --git a/docs/Changelog.md b/docs/Changelog.md new file mode 100644 index 000000000..8ed5e2783 --- /dev/null +++ b/docs/Changelog.md @@ -0,0 +1,472 @@ +# đŸŒ± Ceedling Changelog + +This format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +This changelog is complemented by two other documents: + +1. 🔊 **[Release Notes](ReleaseNotes.md)** for announcements, education, acknowledgements, and known issues. +1. 💔 **[Breaking Changes](BreakingChanges.md)** for a list of impacts to existing Ceedling projects. + +--- + +# [1.0.0 pre-release] — 2024-11-28 + +## 🌟 Added + +### Parallel execution of build steps + +As was explained in the _[Highlights](#-Highlights)_, Ceedling can now run its internal tasks in parallel and take full advantage of your build system’s resources. Even lacking various optimizations (see _[Known Issues](#-Known-Issues)_) builds are now often quite speedy. + +Enabling this speedup requires either or both of two simple configuration settings. See Ceedling’s [documentation](CeedlingPacket.md) for `:project` ↳ `:compile_threads` and `:project` ↳ `:test_threads`. + +### A proper command line + +Ceedling now offers a full command line interface with rich help, useful order-independent option flags, and more. + +The existing `new`, `upgrade`, `example`, and `exampples` commands remain but have been improved. For those commands that support it, you may now specify the project file to load (see new, related mixins feature discussed elsewhere), log file to write to, exit code handling behavior, and more from the command line. + +Try `ceedling help` and then `ceedling help ` to get started. + +See the _[Release Notes](ReleaseNotes.md)_ and _[CeedlingPacket](CeedlingPacket.md)_ for more on the new and improved command line. + +### `TEST_INCLUDE_PATH(...)` & `TEST_SOURCE_FILE(...)` + +Issue [#743](https://github.com/ThrowTheSwitch/Ceedling/issues/743) + +Using what we are calling test build directive macros, you can now provide Ceedling certain configuration details from inside a test file. + +See the [documentation](CeedlingPacket.md) discussion on include paths, Ceedling conventions, and these macros to understand all the details. + +_Notes:_ + +* Ceedling can preserves these test build directive macros through preprocessing of test files. However, wrapping `TEST_INCLUDE_PATH()` in conditional compilation C preprocessing statements (e.g. `#ifdef`) can produce unexpected resutls (see documentation for more). +* Both `TEST_INCLUDE_PATH()` & `TEST_SOURCE_FILE()` can be disabled with standard C comments. +* However, preprocessing of mockable header files can now be enabled separately (see `:project` ↳ `:use_test_preprocessor`). + +#### `TEST_INCLUDE_PATH(...)` + +In short, `TEST_INCLUDE_PATH()` allows you to add a header file search path to the build of the test executable in which it is found. This can mean much shorter compilation command lines and good flexibility for complicated projects. Unlike , `TEST_SOURCE_FILE()` this should _**not**_ be wrapped in conditional compilation preprocessing statements (see documentation for more). + +#### `TEST_SOURCE_FILE(...)` + +In short, `TEST_SOURCE_FILE()` allows you to be explicit as to which source C files should be compiled and linked into a test executable. Sometimes Ceedling’s convention for matching source files with test files by way of `#include`d header files does not meet the need. This solves the problems of those scenarios. Unlike `TEST_INCLUDE_PATH()` , this test build directive _can_ be wrapped in in conditional compilation preprocessing statements (see documentation for more). + +### Mixins for modifying your configuration + +Thorough documentation on Mixins can be found in _[CeedlingPacket](CeedlingPacket.md)_. + +### Additional options for loading a base configuration from a project file + +Once upon a time, you could load a project configuration in just two simple ways — _project.yml_ in your working directory and an environment variable pointing to a different file. Those days are over. + +You may now: + +* Load your project configuration from a filepath provided at the command line. +* Load your project configuration from an environment variable hoding a filepath. +* Load your project configuration from the default _project.yml_ in your working directory. +* Modify your configuration with Mixins loaded from your project file, environment variables, and/or from the command line. + +All the options for loading and modifying a project configuration are thoroughly documented in _[CeedlingPacket](CeedlingPacket.md))_. + +### Additional and improved configuration validation + +Ceedling’s validation of your configuration has been significantly expanded to cover more sections and with more helpful error messages. + +### Broader crash detection in test suites and new backtrace abilities + +Previously Ceedling had a limited ability to detect and report segmentation faults and primarily only on Unix-like platforms. This has been expanded and improved to crash detection more broadly. Invalid memory accesses, stack overflows, heap errors, and branching problems can all lead to crashed test executables. Ceedling is now able to detect these across platforms and report on them appropriately. + +Ceedling defaults to executing this new behavior. Optionally, it can be disabled or its reporting enhanced further by enabling the use of `gdb`. + +See _[CeedlingPacket](CeedlingPacket.md))_ for the new `:project` ↳ `:use_backtrace` feature to control how much detail is extracted from a crashed test executable to help you find the cause. + +### More better `:flags` handling + +Issue [#43](https://github.com/ThrowTheSwitch/Ceedling/issues/43) + +Each test executable is now built as a mini project. Using improved `:flags` handling and an updated section format within Ceedling’s project configuration, you have much better options for specifying flags presented to the various tools within your build, particulary within test builds. + +### More better `:defines` handling + +Each test executable is now built as a mini project. Using improved `:defines` handling and an updated section format within Ceedling’s project configuration, you have much better options for specifying symbols used in your builds' compilation steps, particulary within test builds. + +One powerful new feature is the ability to test the same source file built differently for different tests. Imagine a source file has three different conditional compilation sections. You can now write unit tests for each of those sections without complicated gymnastics to cause your test suite to build and run properly. + +### Inline Ruby string expansion for `:flags` and `:defines` configuration entries + +Inline Ruby string expansion has been, well, expanded for use in `:flags` and `:defines` entries to complement existing such functionality in `:environment`, `:paths`, `:tools`, etc. + +The previously distributed documentation for inline Ruby string expansion has been collected into a single subsection within the project file documentation and improved. + +### `:unity` ↳ `:use_param_tests` + +Previous versions of Ceedling had limited support for enabling builds of Unity’s parameterized test cases. Multiple configuration settings were needed to enable test builds with these test case features. Now, setting this single configuration value in the `:unity` section of your Ceedling project configuration automatically assembles the correct compilation and test runner generation options. + +### `report_tests_log_factory` plugin + +This new plugin consolidates a handful of previously discrete report gernation plugins into a single plugin that also enables low-code, custom, end-user created reports. + +The abilities of these previously independent plugins are now supersededed by configuration options for the single, new `report_tests_log_factory` plugin: + +1. `junit_tests_report` +1. `json_tests_report` +1. `xml_tests_report` + +This new plugin also includes the option to generate an HTML report (see next section). + +### HTML tests report + +A community member submitted an [HTML report generation plugin](https://github.com/ThrowTheSwitch/Ceedling/pull/756/) that was not officially released before 0.32. It has been absorbed into the new `report_tests_log_factory` plugin (see previous section). + +### Improved Segfault Handling + +Segmentation faults are now reported as failures with as much details as possible instead of as build errors. If `gdb` is available, Ceedling can now even automatically highlight the source of the segfault (see section above on crash detection). + +### Support for assembly code in test builds with `:test_build` ↳ `:use_assembly` + +Previous versions of Ceedling included support for including assembly code in release builds. Now Ceedling can include assembly code in test builds as well. + +The default assembler is the GNU tool `as`. Like all other tools it may be overridden in the `:tools` section. + +To enable this feature, use the following in your project configuration: + +```yaml +:test_build: + :use_assembly: TRUE +``` + +In order to inject assembly code files into the build of a test executable after enabling test build assembly code, two conditions must be true: + +1. The assembly files must be visible to Ceedling by way of `:paths` and `:extension` settings for assembly files — just like C code files. +1. Ceedling must be told into which test executable build to insert a given assembly file. The easiest way to do so is with the new `TEST_SOURCE_FILE()` build directive macro — described elsewhere in this Changelog and documented in _[CeedlingPacket](CeedlingPacket.md)_. + +When either `:test_build` ↳ `:use_assembly` or `:release_build` ↳ `:use_assembly` are enabled, Ceedling’s command line `files:` tasks will list assembly files among the files it discovers from your project configuration. That is, you can confirm your settings for assembly code with those tasks. See _[CeedlingPacket](CeedlingPacket.md)_’s documentation for the command line. + +### Pretty logging output + +Ceedling logging now optionally includes emoji and nice Unicode characters. Ceedling will attempt to determine if your platform supports it. You can use the environment variable `CEEDLING_DECORATORS` to force the feature on or off. See the documentation for logging decorators in _[CeedlingPacket](CeedlingPacket.md)_. + +### Vendored license files + +The application commands `ceedling new` and `ceedling upgrade` at the command line provide project creation and management functions. Optionally, these commands can vendor tools and libraries locally alongside your project. These vendoring options now include license files along with the source of the vendored tools and libraries. + +### Additional details in Ceedling version output + +`ceedling version` output now includes the Git Commit short SHA in Ceedling’s build identifier and Ceedling’s path of origin. + +``` +đŸŒ± Welcome to Ceedling! + + Ceedling => #.#.#- + ---------------------- + + + Build Frameworks + ---------------------- + CMock => #.#.# + Unity => #.#.# + CException => #.#.# +``` + +If the short SHA information is unavailable such as in local development, the SHA is omitted. The source for this string is generated and captured in the Gem at the time of Ceedling’s automated build in CI. + +### Tool definition modification shortcuts expanded for `:executable` + +A shortcut for adding arguments to an existing tool defition already existed. The handling for this shortcut has been expanded to allow `:executable` to be redefined. + +```yaml +:tools_test_compiler: + :executable: foo # Shell out for `foo` instead of `gcc` + :arguments: # Existing functionality + - --flag1 # Add the following at the end of existing list of command line arguments + - --flag2 +``` + +### Gcov plugin: Support for `gcovr`'s `--merge-mode-functions` for v6.0+ + +Starting with `gcovr` v6.0 (now at v7.2), report generation can encounters a fatal error if multiple coverage results exist for the same function. This is a very possible scenario with Ceedling 1.0.0 now being able to build and run the same same test executable multiple ways. + +Support for this option, enacted based on `gcovr`’s reported version, has been added to the Gcov plugin with a reasonable default setting. + +## đŸ’Ș Fixed + +### `:paths` and `:files` handling bug fixes and clarification + +Most project configurations are relatively simple, and Ceedling’s features for collecting paths worked fine enough. However, bugs and ambiguities lurked. Further, insufficient validation left users resorting to old fashioned trial-and-error troubleshooting. + +Much glorious filepath and pathfile handling now abounds: + +* The purpose and use of `:paths` and `:files` has been clarified in both code and documentation. `:paths` are directory-oriented while `:files` are filepath-oriented. +* [Documentation](CeedlingPacket.md) is now accurate and complete. +* Path handling edge cases have been properly resolved (`./foo/bar` is the same as `foo/bar` but was not always processed as such). +* Matching globs were advertised in the documentation (erroneously, incidentally) but lacked full programmatic support. +* Ceedling now tells you if your matching patterns don't work. Unfortunately, all Ceedling can determine is if a particular pattern yielded 0 results. + +### Ceedling’s `:use_test_preprocessor` and CMock’s `:treat_inlines` now work together properly + +This fix addresses the problem detailed in PR [#728](https://github.com/ThrowTheSwitch/Ceedling/pull/728) and related issues. + +CMock can optionally mock inline functions. This requires ingesting, modifying, and rewriting a source hearder file along with then mocking it. Sophisticated header files with complex macros can require that the source header file be preprocessed before CMock then processes it a second time. In previous versions of Ceedling the preprocessing steps and handoff to CMock were not working as intended. This has been fixed. + +### Bug fix for test runner generation + +Issue [#621](https://github.com/ThrowTheSwitch/Ceedling/issues/621) + +For certain advanced testing scenarios test runners generated by Ceedling + Unity must have the same `#include` list as that of the test file itself from which a runner is gnerated. Previous versions of Ceedling did not provide the proper list of `#include` directives to runner generation. This has been fixed. + +### Bug fixes for command line build tasks `files:header` and `files:support` + +Longstanding bugs produced duplicate and sometimes incorrect lists of header files. Similarly, support file lists were not properly expanded from globs. Both of these problems have been fixed. The `files:header` command line task has replaced the `files:include` task. + +### Dashed filename handling bug fix + +Issue [#780](https://github.com/ThrowTheSwitch/Ceedling/issues/780) + +In certain combinations of Ceedling features, a dash in a C filename could cause Ceedling to exit with an exception. This has been fixed. + +### Source filename extension handling bug fix + +Issue [#110](https://github.com/ThrowTheSwitch/Ceedling/issues/110) + +Ceedling has long had the ability to configure a source filename extension other than `.c` (`:extension` ↳ `:source`). However, in most circumstances this ability would lead to broken builds. Regardless of user-provided source files and filename extenion settings, Ceedling’s supporting frameworks — Unity, CMock, and CException — all have `.c` file components. Ceedling also generates mocks and test runners with `.c` filename extensions regardless of any filename extension setting. Changing the source filename extension would cause Ceedling to miss its own core source files. This has been fixed. + +### Bug fixes for `gcov` plugin + +The most commonly reported bugs have been fixed: + +* `nil` references +* Exit code issues with recent releases of `gcov` +* Empty coverage results and related build failures + +### Bug fixes for `beep` plugin + +A handful of small bugs in using shell `echo` with the ASCII bell character have been fixed. + +### Which Ceedling handling includes new environment variable `WHICH_CEEDLING` + +A previously semi-documented feature allowed you to point to a version of Ceedling on disk to run from within your project file, `:project` ↳ `:which_ceedling`. + +This feature is primarily of use to Ceedling developers but can be useful in other specialized scenarios. See the documentation in _[CeedlingPacket](CeedlingPacket.md))_ for full deatils as this is an advanced feature. + +The existing feature has been improved with logging and validation as well as proper documentation. An environment variable `WHICH_CEEDLING` is now also supported. If set, this variable supersedes any other settings. In the case of `ceedling new` and `ceedling upgrade`, it is the only way to change which Ceedling is in use as a project file either does not exist for the former or is not loaded for the latter. + +### Ceedling now handles C files with dots in their filenames + +Previous versions of Ceedling made assumptions about file naming conventions — specifically that the only place a period occurred was in a filename extension. In reality, C supports the same basic filenames as any file system does. Filenames can include periods throughout their name. + +As an example, some legacy code includes a versioning scheme in the name itself — _foo.12.h_. Such names would previously break builds. This has been fixed. + +## ⚠ Changed + +### Preprocessing improvements + +Issues [#806](https://github.com/ThrowTheSwitch/Ceedling/issues/806) + [#796](https://github.com/ThrowTheSwitch/Ceedling/issues/796) + +Preprocessing refers to expanding macros and other related code text manipulations often needed in sophisticated projects before key test suite generation steps. Without (optional) preprocessing in complicated code bases, generating test runners from test files and generating mocks from header files lead to all manner of build shenanigans. + +The preprocessing needed by Ceedling for sophisticated projects has always been a difficult feature to implement. The most significant reason is simply that there is no readily available cross-platform C code preprocessing tool that provides Ceedling everything it needs to accomplish this task. Even gcc’s `cpp` preprocessor tool comes up short. Over time Ceedling’s attempt at preprocessing grew more brittle and complicated as community contribturs attempted to fix it or cause it to work properly with other new features. + +This release of Ceedling stripped the feature back to basics and largely rewrote it. Complicated regular expressions and Ruby-generated temporary files have been eliminated. Instead, Ceedling now blends two reports from gcc’s `cpp` tool and complements this with additional context. In addition, preprocessing now occurs at the right moments in the build pipeline. + +While this new approach is not 100% foolproof, it is far more robust and far simpler than previous attempts. Other new Ceedling features should be able to address shortcomings in edge cases. + +### Project file environment variable name change `CEEDLING_MAIN_PROJECT_FILE` âžĄïž `CEEDLING_PROJECT_FILE` + +Options and support for loading a project configuration have expanded significantly, mostly notably with the addition of Mixins. + +The environment variable option for pointing Ceedling to a project file other than _project.yml_ in your working directory has been renamed `CEEDLING_MAIN_PROJECT_FILE` âžĄïž `CEEDLING_PROJECT_FILE`. + +Documentation on Mixins and the new options for loading a project configuration are thoroughly documented in _[CeedlingPacket](CeedlingPacket.md))_. + +### Configuration defaults and configuration set up order + +Ceedling’s previous handling of defaults and configuration processing order certainly worked, but it was not as proper as it could be. To oversimplify, default values were applied in an ordering that caused complications for advanced plugins and advanced users. This has been rectified. Default settings are now processed after all user configurations and plugins. + +### `:project` ↳ `:use_test_preprocessor` has new configuration options + +`:project` ↳ `:use_test_preprocessor` is no longer a binary setting (true/false). What is preprocessed can be chosen with the options `:none`, `:tests`, `:mocks`, `:all`. + +* `:none` maps to the previous false option (preprocessing disabled). +* `:all` maps to the previous true option running preprpocessing for all mockable header files and test C files. +* `:mocks` enables only preprocessing of header files that are to be mocked. +* `:tests` enables only preprocessing of your test files. + +### Plugin system improvements + +1. The plugin subsystem has incorporated logging to trace plugin activities at high verbosity levels. +1. Additional events have been added for test preprocessing steps (the popular and useful [`command_hooks` plugin](plugins/command_hooks/README.md) has been updated accordingly). +1. Built-in plugins have been updated for thread-safety as Ceedling is now able to execute builds with multiple threads. + +### Logging improvements + +Logging messages are more useful. A variety of logging messages have been added throughout Ceedling builds. Message labels (e.g. `ERROR:`) are now applied automatically). Exception handling is now centralized and significantly cleans up exception messages (backtraces are available with debug verbosity). + +### Exit code options for test suite failures + +Be default Ceedling terminates with an exit code of `1` when a build succeeds but unit tests fail. + +A previously undocumented project configuration option `:graceful_fail` could force a Ceedling exit code of `0` upon test failures. + +This configuration option has moved but is now [documented](CeedlingPacket.md). It is also available as a new command line argument (`--graceful-fail`). + +```yaml +:test_build: + :graceful_fail: TRUE +``` + +### Improved Segfault Handling in Test Suites + +Segmentation faults are now reported as failures instead of an error with as much detail as possible. See the project configuration file documentation discussing the `:project` ↳ `:use_backtrace` option for more! + +### Altered local documentation file directory structure + +The application commands `ceedling new` and `ceedling upgrade` at the command line provide options for local copies of documentation when creating or upgrading a project. Previous versions of Ceedling used a flat file structure for the _docs/_ directory. Ceedling now uses subdirectories to organize plugin and tool documentation within the _docs/_ directory for clearer organization and preserving original filenames. + +### JUnit, XML & JSON test report plugins consolidation + +The three previously discrete plugins listed below have been consolidated into a single new plugin, `report_tests_log_factory`: + +1. `junit_tests_report` +1. `json_tests_report` +1. `xml_tests_report` + +`report_tests_log_factory` is able to generate all 3 reports of the plugins it replaces, a new HTML report, and custom report formats with a small amount of user-written Ruby code (i.e. not an entire Ceedling plugun). See its [documentation](../plugins/report_tests_log_factory) for more. + +The report format of the previously independent `xml_tests_report` plugin has been renamed from _XML_ in all instances to _CppUnit_ as this is the specific test reporting format the former plugin and new `report_tests_log_factory` plugin outputs. + +In some circumstances, JUnit report generation would yield an exception in its routines for reorganizing test results (Issues [#829](https://github.com/ThrowTheSwitch/Ceedling/issues/829) & [#833](https://github.com/ThrowTheSwitch/Ceedling/issues/833)). The true source of the nil test results entries has likely been fixed but protections have also been added in JUnit report generation as well. + +### Improvements and changes for Gcov plugin + +1. Documentation has been significantly updated including a _Troubleshooting_ for common issues. +1. Compilation with coverage now only occurs for the source files under test and no longer for all C files (i.e. coverage for unity.c, mocks, and test files that is meaningless noise has been eliminated). +1. Coverage summaries printed to the console after `gcov:` test task runs now only concern the source files exercised instead of all source files. A final coverage tally has been restored. +1. Coverage summaries can now be disabled. +1. Coverage reports are now automatically generated after `gcov:` test tasks are executed. This behvaior can be disabled with a new configuration option. When enabled, a separate task is made available to trigger report generation. +1. To maintain consistency, repports generated by `gcovr` and `reportgenerator` are written to subdirectories named for the respective tools benath the `gcov/` artifacts path. + +See the [gcov plugin’s documentation](plugins/gcov/README.md). + +### Improvements for `compile_commands_json_db` plugin + +1. The plugin creates a compilation database that distinguishes the same code file compiled multiple times with different configurations as part of the new test suite build structure. It has been updated to work with other Ceedling changes. +1. Documentation has been greatly revised. + +### Improvements for Beep plugin + +1. Additional sound tools — `:tput`, `:beep`, and `:say` — have been added for more platform sound output options and fun. +1. Documentation has been greatly revised. +1. The plugin more properly uses looging and system shell calls. + +## Improvements for Command Hooks plugin + +In previous versions of Ceedling, the Command Hooks plugin associated tools and hooks by a naming convention within the top-level `:tools` section of your project configuration. This required some semi-ugly tool names and could lead to a rather unwieldy `:tools` list. Further, this convention also limited a hook to an association with only a single tool. + +Hooks are now enabled within a top-level `:command_hooks` section in your project configuration. Each hook key in this configuration block can now support one or more tools organized beneath it. As such, each hook can execute one or more tools. + +### Tool definition inline Ruby string expansion now happens at each execution + +Reaching back to the earliest days of Ceedling, tool definitions supported two slightly different string replacement options that executed at different points in a build’s lifetime. Yeah. It was maybe not great. This has been simplfied. + +Only support for `#{...}` Ruby string expansion in tool definitions remains. Any such expansions are now evaluated each time a tool is executed during a build. + +## 👋 Removed + +### `verbosity` and `log` command line tasks have been replaced with command line switches + +These command line features were implemented using Rake. That is, they were Rake tasks, not command line switches, and they were subject to the peculiarities of Rake tasks. Specifically, order mattered — these tasks had to precede build tasks they were to affect — and `verbosity` required a non-standard parameter convention for numeric values. + +These command line tasks no longer exist. They are now proper command line flags. These are most useful (and, in the case of logging, only availble) with Ceedling’s new `build` command line argument. The `build` command takes a list of build & plugin tasks to run. It is now complmented by `--verbosity`, `--log`, and `--logfile` flags. See the detailed help at `ceedling help build` for these. + +The `build` keyword is optional. That is, omitting it is allowed and operates largely equivalent to the historical Ceedling command line. + +The previous command line of `ceedling verbosity[4] test:all release` or `ceedling verbosity:obnoxious test:all release` can now be any of the following: + +* `ceedling test:all release --verbosity=obnoxious` +* `ceedling test:all release -v 4` +* `ceedling --verbosity=obnoxious test:all release` +* `ceedling -v 4 test:all release` + +Note: In the above list Ceedling is actually executing as though `ceedling build ` were entered at the command line. It is entirely acceptable to use the full form. The above list is provided as its form is the simplest to enter and consistent with the command line conventions of previous Ceedling versions. + +### `options:` tasks have been removed + +Options files were a simple but limited way to merge configuration with your base configuration from the command line. This feature has been superseded by Ceedling Mixins. + +### `:import` project configuration section is no longer supported + +The `:import` project configuration section was a simple but limited way to merge configuration with your base configuration. This feature has been superseded by all new and more powerful Ceedling Mixins. + +### Test suite smart rebuilds have been temporarily removed + +All “smart” test suite rebuild features built around Rake no longer exist. That is, incremental test suite builds for only changed files are no longer possible. Any test build is a full rebuild of its components (the speed increase due to parallel build tasks more than makes up for this). + +These project configuration options related to smart builds are no longer recognized and likely will not return in this form: + - `:use_deep_dependencies` + - `:generate_deep_dependencies` + - `:auto_link_deep_dependencies` + +In future revisions of Ceedling, smart rebuilds will be brought back (without relying on Rake) and without a list of possibly conflicting configuation options to control related features. + +Note: Release builds do retain a fair amount of smart rebuild capabilities. Release builds continue to rely on Rake (for now). + +### Temporarily removed preprocessor support for Unity’s parameterized test case macros `TEST_CASE()` and `TEST_RANGE()` + +Unity’s `TEST_CASE()` and `TEST_RANGE()` continue to work but only when `:project` ↳ `:use_test_preprocessor` is not enabled for test files. The previous project configuration option `:use_preprocessor_directives` that preserved these and other directive macros when preprocessing is enabled is no longer recognized. + +`TEST_CASE()` and `TEST_RANGE()` are macros that disappear when the GNU preprocessor digests a test file. After preprocessing, these macros no longer exist in the test file that is compiled. They and some other macros are largely used as markers for advanced abilities discovered by parsing a test file rather than compiling it. + +In future revisions of Ceedling, support for `TEST_CASE()` and `TEST_RANGE()` when test file preprocessing is enabled will be brought back (very likely without a dedicated configuration option — hopefully, we’ll get it to just work). + +Note: `:project` ↳ `:use_test_preprocessor` is no longer a binary setting (`true`/`false`). Mockable header file preprocessing can now be enabled with a `:mocks` setting while test files are left untouched by preprocessing. This should support the majority of advanced use cases for preprocessing. + +### Removed background task execution + +Background task execution for tool configurations (`:background_exec`) has been deprecated. This option was one of Ceedling’s earliest features attempting to speed up builds within the constraints of relying on Rake. This feature has rarely, if ever, been used in practice, and other, better options exist to manage any scenario that might motivate a background task. + +### Removed `colour_report` plugin + +Colored build output and test results in your terminal is glorious. Long ago the `colour_report` plugin provided this. It was a simple plugin that hooked into Ceedling in a somewhat messy way. Its approach to coloring output was also fairly brittle. It long ago stopped coloring build output as intended. It has been removed. + +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. + +### Bullseye code coverage 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. + +### Gcov plugin’s support for deprecated features removed + +The configuration format for the `gcovr` utility changed when support for the `reportgenerator` utility was added. A format that accomodated a more uniform and common layout was adopted. However, support for the older, deprecated `gcovr`-only configuration was maintained. This support for the deprecated `gcovr` configuration format has been removed. + +Please consult the [gcov plugin’s documentation](plugins/gcov/README.md) to update any old-style `gcovr` configurations. + +### Gcov plugin’s `:abort_on_uncovered` option temporarily removed + +Like Ceedling’s preprocessing features, the Gcov plugin had grown in features and complexity over time. The plugin had become difficult to maintain and some of its features had become user unfriendly at best and misleading at worst. + +The Gcov plugin’s `:abort_on_uncovered` option plus the related `:uncovered_ignore_list` option were not preserved in this release. They will be brought back after some noodling on how to make these features user friendly again. + +### Undocumented environment variable `CEEDLING_USER_PROJECT_FILE` support removed + +A previously undocumented feature for merging a second configuration via environment variable `CEEDLING_USER_PROJECT_FILE` has been removed. This feature has been superseded by the new Mixins functionality. + +### Tool definition inline Ruby _evaluation_ replacement removed (inline Ruby string _expansion_ remains) + +Reaching back to the earliest days of Ceedling, tool definitions supported two slightly different string replacement options that executed at different points in a build’s lifetime. Yeah. It was maybe not great. + +Support for `{...}` Ruby evaluation in tool definitions has been removed. Support for `#{...}` Ruby string expansion in tool definitions remains. + +### Tool definition defaults use of environment variables + +Ceedling’s internal default tool definitions no longer incorporate (undocumented) environment variable lookups. + +When Ceedling was very young and tool definitions were relatively simple, Ceedling’s defaults were configured to incorporate commonly used environment variables (e.g. `CC_FLAGS`). This made sense at the time. As Ceedling’s tool processing grew in sophistication, this convention no longer made sense for a variety of reasons. All such references have been removed. + +If you want to incorporate environment variables into your tool definitions, you may still do so. See the documentation for inline Ruby string exapnsion and the various options for defining or modifying a tool definition. In short, you may incorporate `"#{ENV['']}"` strings into your tooling. + +### Undocumented `:project` ↳ `:debug` has been removed + +This project setting existed from Ceedling’s earliest days and was a crude stand-in for command line debug verbosity handling. + +It has been removed as it was rarely if ever utilized and needlessly complicated internal mechanisms for verbosity handling and project validation. + diff --git a/docs/PluginDevelopmentGuide.md b/docs/PluginDevelopmentGuide.md new file mode 100644 index 000000000..64565ad9b --- /dev/null +++ b/docs/PluginDevelopmentGuide.md @@ -0,0 +1,766 @@ +# Developing Plugins for Ceedling + +This guide walks you through the process of creating custom plugins for +[Ceedling](https://github.com/ThrowTheSwitch/Ceedling). + +It is assumed that the reader has a working installation of Ceedling and some +basic usage experience, *i.e.* project creation/configuration and running tasks. + +Some experience with Ruby and Rake will be helpful but not absolutely required. +You can learn the basics as you go — often by looking at other, existing +Ceedling plugins or by simply searching for code examples online. + +## Contents + +* [Custom Plugins Overview](#custom-plugins-overview) +* [Plugin Conventions & Architecture](#plugin-conventions--architecture) + 1. [Configuration Plugin](#plugin-option-1-configuration) + 1. [Programmatic `Plugin` subclass](#plugin-option-2-plugin-subclass) + 1. [Rake Tasks Plugin](#plugin-option-3-rake-tasks) + +## Development Roadmap & Notes + +_November 28, 2024_ + +(See Ceedling's _[release notes](ReleaseNotes.md)_ for more.) + +* Ceedling 0.32 marks the beginning of moving all of Ceedling away from relying + on Rake. New, Rake-based plugins should not be developed. Rake dependencies + among built-in plugins will be refactored as the transition occurs. +* Ceedling's entire plugin architecture will be overhauled in future releases. + The current structure is too dependent on Rake and provides both too little + and too much access to Ceedling's core. +* Certain aspects of Ceedling's plugin structure have developed organically. + Consistency, coherence, and usability may not be high — particularly for + build step hook argument hashes and test results data structures used in + programmatic plugins. +* Because of iterating on Ceedling's core design and features, documentation + here may not always be perfectly up to date. + +--- + +# Custom Plugins Overview + +Ceedling plugins extend Ceedling without modifying its core code. They are +implemented in YAML and the Ruby programming language and are loaded by +Ceedling at runtime. + +Plugins provide the ability to customize the behavior of Ceedling at various +stages of a build — preprocessing, compiling, linking, building, testing, and +reporting. + +See _[CeedlingPacket]_ for basic details of operation (`:plugins` configuration +section) and for a [directory of built-in plugins][plugins-directory]. + +[CeedlingPacket]: CeedlingPacket.md +[plugins-directory]: CeedlingPacket.md#ceedlings-built-in-plugins-a-directory + +# Plugin Conventions & Architecture + +Plugins are enabled and configured from within a Ceedling project's YAML +configuration file (`:plugins` section). + +Conventions & requirements: + +* Plugin configuration names, the containing directory names, and filenames + must: + * All match (i.e. identical names) + * Be snake_case (lowercase with connecting underscores). +* Plugins must be organized in a containing directory (the name of the plugin + as used in the project configuration `:plugins` ↳ `:enabled` list is its + containing directory name). +* A plugin's containing directory must be located in a Ruby load path. Load + paths may be added to a Ceedling project using the `:plugins` ↳ `:load_paths` + list. +* Rake plugins place their Rakefiles in the root of thecontaining plugin + directory. +* Programmatic plugins must contain either or both `config/` and `lib/` + subdirectories within their containing directories. +* Configuration plugins must place their files within a `config/` subdirectory + within the plugin's containing directory. + +Ceedling provides 3 options to customize its behavior through a plugin. Each +strategy is implemented with source files conforming to location and naming +conventions. These approaches can be combined. + +1. Configuration (YAML & Ruby) +1. `Plugin` subclass (Ruby) +1. Rake tasks (Ruby) + +# Plugin Option 1: Configuration + +The configuration option, surprisingly enough, provides Ceedling configuration +values. Configuration plugin values can supplement or override project +configuration values. + +Not long after Ceedling plugins were developed the `option:` feature was added +to Ceedling to merge in secondary configuration files. This feature is +typically a better way to manage nultiple configurations and in many ways +supersedes a configuration plugin. + +That said, a configuration plugin is more capable than the `option:` feature and +can be appropriate in some circumstances. Further, Ceedling's configuration +pluging abilities are often a great way to provide configuration to +programmatic `Plugin` subclasses (Ceedling plugins options #2). + +## Three flavors of configuration plugins exist + +1. **YAML defaults.** The data of a simple YAML file is incorporated into + Ceedling's configuration defaults during startup. +1. **Programmatic (Ruby) defaults.** Ruby code creates a configuration hash + that Ceedling incorporates into its configuration defaults during startup. + This provides the greatest flexibility in creating configuration values. +1. **YAML configurations.** The data of a simple YAML file is incorporated into + Ceedling's configuration much like your project configuration file. + +## Example configuration plugin layout + +Project configuration file: + +```yaml +:plugins: + :load_paths: + - support/plugins + :enabled: + - zoom_zap +``` + +Ceedling project directory sturcture: + +(Third flavor of configuration plugin shown.) + +``` +project/ +├── project.yml +└── support/ + └── plugins/ + └── zoom_zap/ + └── config/ + └── zoom_zap.yml +``` + +## Ceedling configuration build & use + +Configuration is developed at startup by assembling defaults, collecting +user-configured settings, and then populating any missing values with defaults. + +Defaults: + +1. Ceedling loads its own defaults separately from your project configuration +1. Supporting framework defaults such as for CMock are populated into (1) +1. Any plugin defaults are merged with (2). + +Final project configuration: + +1. Your project file is loaded and any mixins merged +1. Supporting framework settings that depend on project configuration are populated +1. Plugin configurations are merged with the result of (1) and (2) +1. Defaults are populated into your project configuration +1. Path standardization, string replacement, and related occur throughout the final + configuration + +Merging means that existing simple configuration valuees are replaced or, in the +case of containers such as lists and hashes, values are added to. If no such +key/value pairs already exist, they are simply inserted into the configuration. + +Populating means inserting a configuration value if none already exists. As an +example, if Ceedling finds no compiler defined for test builds in your project +configuration, it populates your configuration with its own internal tool definition. + +A plugin may implement its own code to use extract custom configuration from +the Ceedling project file. See the built-in plugins for examples. For instance, the +Beep plugin makes use of a top-level `:beep` section in project configuration. In +such cases, it's typically wise to make use of a plugin's option for defining +default values. Configuration handling code is greatly simplified if values are +guaranteed to exist in some form. This elimiates a great deal of presence checking +and related code. + +## Configuration Plugin Flavors + +### Configuration Plugin Flvaor A: YAML Defaults + +Naming and location convention: `/config/defaults.yml` + +Configuration values are defined inside a YAML file just as the Ceedling project +configuration file. + +Keys and values are defined in Ceedling's “base” configuration along with all +default values Ceedling loads at startup. If a particular key/value pair is +already set at the time the plugin attempts to set it, it will not be +redefined. + +YAML values are static apart from Ceedling's ability to perform string +substitution at configuration load time (see _[CeedlingPacket]_ for more). +Programmatic Ruby defaults (next section) are more flexible but more +complicated. + +```yaml +# Any valid YAML is appropriate +:key: + :value: +``` + +### Configuration Plugin Flvaor B: Programmatic (Ruby) Defaults + +Naming and location convention: `/config/defaults_.rb` + +Configuration values are defined in a Ruby hash returned by a “naked” function +`get_default_config()` in a Ruby file. The Ruby file is loaded and evaluated at +Ceedling startup. It can contain anything allowed in a Ruby script file but +must contain the accessor function. The returned hash's top-level keys will +live in Ceedling's configuration at the same level in the configuration +hierarchy as a Ceedling project file's top-level keys ('top-level' refers to +the left-most keys in the YAML, not to how “high” the keys are towards the top +of the file). + +Keys and values are defined in Ceedling's “base” configuration along with all +default values Ceedling loads at startup. If a particular key/value pair is +already set at the time the plugin attempts to set it, it will not be +redefined. + +This configuration option is more flexible than that documented in the previous +section as full Ruby execution is possible in creating the defaults hash. + +### Configuration Plugin Flvaor C: YAML Values + +Naming and location convention: `/config/.yml` + +Configuration values are defined inside a YAML file just as the Ceedling project +configuration file. + +Keys and values are defined in Ceedling's “base” configuration along with all +default values Ceedling loads at startup. If a particular key/value pair is +already set at the time the plugin attempts to set it, it will not be +redefined. + +YAML values are static apart from Ceedling's ability to perform string +substitution at configuration load time (see _[CeedlingPacket]_ for more). +Programmatic Ruby defaults (next section) are more flexible but more +complicated. + +```yaml +# Any valid YAML is appropriate +:key: + :value: +``` + +# Plugin Option 2: `Plugin` Subclass + +Naming and location conventions: + +* `/lib/.rb` +* The plugin's class name must be the camelized version (a.k.a. “bumpy case") + of the plugin filename — `whiz_bang.rb` âžĄïž `WhizBang`. + +This plugin option allows full programmatic ability connceted to any of a number +of predefined Ceedling build steps. + +The contents of `.rb` must implement a class that subclasses +`Plugin`, Ceedling's plugin base class. + +## Example `Plugin` subclass + +An incomplete `Plugin` subclass follows to illustate the basics. + +```ruby +# whiz_bang/lib/whiz_bang.rb +require 'ceedling/plugin' + +class WhizBang < Plugin + def setup + # ... + end + + # Build step hook + def pre_test(test) + # ... + end + + # Build step hook + def post_test(test) + # ... + end +end +``` + +## Example programmatic plugin layout + +Project configuration file: + +```yaml +:plugins: + :load_paths: + - support/plugins + :enabled: + - whiz_bang +``` + +Ceedling project directory sturcture: + +``` +project/ +├── project.yml +└── support/ + └── plugins/ + └── whiz_bang/ + └── lib/ + └── whiz_bang.rb +``` + +It is possible and often convenient to add more `.rb` files to the containing +`lib/` directory to allow good organization of plugin code. No Ceedling +conventions exist for these supplemental code files. Only standard Ruby +constaints exists for these filenames and content. + +## `Plugin` instance variables + +Each `Plugin` sublcass has access to the following instance variables: + +* `@name` +* `@ceedling` + +`@name` is self explanatory. `@ceedling` is a hash containing every object +within the Ceedling application; its keys are the filenames of the objects +minus file extension. + +Objects commonly used in plugins include: + +* `@ceedling[:configurator]` — Project configuration +* `@ceedling[:streaminator]` — Logging +* `@ceedling[:reportinator]` — String formatting for logging +* `@ceedling[:file_wrapper]` — File operations +* `@ceedling[:plugin_reportinator]` — Various needs including gathering test + results + +## `Plugin` method `setup()` + +If your plugin defines this method, it will be called during plugin creation at +Ceedling startup. It is effectively your constructor for your custom `Plugin` +subclass. + +## `Plugin` hook methods `pre_` and `post_` conventions & concerns + +### Multi-threaded protections + +Because Ceedling can run build operations in multiple threads, build step hook +handliers must be thread safe. Practically speaking, this generally requires +a `Mutex` object `synchronize()`d around any code that writes to or reads from +a common data structure instantiated within a plugin. + +A common example is collecting test results filepaths from the +`post_test_fixture_execute()` hook. A hash or array accumulating these +filepaths as text executables complete their runs must have appropriate +threading protections. + +### Command line tool shell results + +Pre and post build step hooks are often called on either side of a command line +tool operation. If a command line tool is executed for a build step (e.g. test +compilation), the `arg_hash` will be the same for the pre and post hooks with +one difference. + +In the `post_` hook, the `arg_hash` parameter will contain a `shell_result` key +whose associated value is itself a hash with the following contents: + +```ruby +{ + :output => "", # String holding any $stdout / redirected $stderr output + :status => , # Ruby object of type Process::Status + :exit_code => , # Command line exit code (extracted from :status object) + :time => # Seconds elapsed for shell operation +} +``` + +_**Note:**_ Test preprocessing steps are quite sophissticated and involve various +combination of tool executions. The `post_` preprocessing hooks do not inlucde shell +results. Future updates to Ceedling’s plugin system will create a more robust means +of attaching custom behaviors to test preprocessing or connecting your own preprocessing +pipeline with toolchains other than GCC. + +## `Plugin` hook methods `pre_mock_preprocess(arg_hash)` and `post_mock_preprocess(arg_hash)` + +These methods are called before and after execution of preprocessing for header +files to be mocked (see [CeedlingPacket] to understand preprocessing). If a +project does not enable preprocessing or a build does not include tests, these +are not called. This pair of methods is called a number of times equal to the +number of mocks in a test build. + +The argument `arg_hash` follows the structure below: + +```ruby +arg_hash = { + # Filepath of header file to be preprocessed on its way to being mocked + :header_file => "", + # Filepath of processed header file + :preprocessed_header_file => "", + # Filepath of tests C file the mock will be used by + :test => "", + # List of flags to be provided to `cpp` GNU preprocessor tool + :flags => [], + # List of search paths to be provided to `cpp` GNU preprocessor tool + :include_paths => [], + # List of compilation symbols to be provided to `cpp` GNU preprocessor tool + :defines => [] +} +``` + +## `Plugin` hook methods `pre_test_preprocess(arg_hash)` and `post_test_preprocess(arg_hash)` + +These methods are called before and after execution of test file preprocessing +(see [CeedlingPacket] to understand preprocessing). If a project does not +enable preprocessing or a build does not include tests, these are not called. +This pair of methods is called a number of times equal to the number of test +files in a test build. + +The argument `arg_hash` follows the structure below: + +```ruby +arg_hash = { + # Filepath of C test file to be preprocessed on its way to being used to generate runner + :test_file => "", + # Filepath of processed tests file + :preprocessed_test_file => "", + # Filepath of tests C file the mock will be used by + :test => "", + # List of flags to be provided to `cpp` GNU preprocessor tool + :flags => [], + # List of search paths to be provided to `cpp` GNU preprocessor tool + :include_paths => [], + # List of compilation symbols to be provided to `cpp` GNU preprocessor tool + :defines => [] +} +``` + +## `Plugin` hook methods `pre_mock_generate(arg_hash)` and `post_mock_generate(arg_hash)` + +These methods are called before and after mock generation. If a project does not +enable mocks or a build does not include tests, these are not called. This pair +of methods is called a number of times equal to the number of mocks in a test +build. + +The argument `arg_hash` follows the structure below: + +```ruby +arg_hash = { + # Filepath of the header file being mocked. + :header_file => "", + # Additional context passed by the calling function. + # Ceedling passes the :test symbol by default while plugins may provide another + :context => :, + # Filepath of the tests C file that references the requested mock + :test => "", + # Filepath of the generated mock C code. + :output_path => "" +} +``` + +## `Plugin` hook methods `pre_runner_generate(arg_hash)` and `post_runner_generate(arg_hash)` + +These methods are called before and after execution of test runner generation. A +test runner includes all the necessary C scaffolding (and `main()` entry point) +to call the test cases defined in a test file when a test executable runs. If a +build does not include tests, these are not called. This pair of methods is +called a number of times equal to the number of test files in a test build. + +The argument `arg_hash` follows the structure below: + +```ruby +arg_hash = { + # Additional context passed by the calling function. + # Ceedling passes the :test symbol by default while plugins may provide another + :context => :, + # Filepath of the tests C file. + :test_file => "", + # Filepath of the test file to be processed (if preprocessing enabled, this is not the same as :test_file). + :input_file => "", + # Filepath of the generated tests runner file. + :runner_file => "" +} +``` + +## `Plugin` hook methods `pre_compile_execute(arg_hash)` and `post_compile_execute(arg_hash)` + +These methods are called before and after source file compilation. These are +called in both test and release builds. This pair of methods is called a number +of times equal to the number of C files in a test or release build. + +The argument `arg_hash` follows the structure below: + +```ruby +arg_hash = { + :tool => { + # Hash holding compiler tool elements (see CeedlingPacket) + }, + # Symbol of the operation being performed, e.g. :compile, :assemble or :link + :operation => :, + # Additional context passed by the calling function. + # Ceedling provides :test or :release by default while plugins may provide another. + :context => :, + # Filepath of the input C file + :source => "", + # Filepath of the output object file + :object => "", + # List of flags to be provided to compiler tool + :flags => [], + # List of search paths to be provided to compiler tool + :search_paths => [], + # List of compilation symbols to be provided to compiler tool + :defines => [], + # Filepath of the listing file, e.g. .lst file + :list => "", + # Filepath of the dependencies file, e.g. .d file + :dependencies => "" +} +``` + +## `Plugin` hook methods `pre_link_execute(arg_hash)` and `post_link_execute(arg_hash)` + +These methods are called before and after linking an executable. These are +called in both test and release builds. These are called for each test +executable and each release artifact. This pair of methods is called a number +of times equal to the number of test files in a test build or release artifacts +in a release build. + +The argument `arg_hash` follows the structure below: + +```ruby +arg_hash = { + # Hash holding linker tool properties. + :tool => { + # Hash holding compiler tool elements (see CeedlingPacket) + }, + # Additional context passed by the calling function. + # Ceedling provides :test or :release by default while plugins may provide another. + :context => :, + # List of object files paths being linked, e.g. .o files + :objects => [], + # List of flags to be provided to linker tool + :flags => [], + # Filepath of the output file, e.g. .out file + :executable => "", + # Filepath of the map file, e.g. .map file + :map => "", + # List of libraries to link, e.g. those passed to the (GNU) linker with -l + :libraries => [], + # List of libraries paths, e.g. the ones passed to the (GNU) linker with -L + :libpaths => [] +} +``` + +## `Plugin` hook methods `pre_test_fixture_execute(arg_hash)` and `post_test_fixture_execute(arg_hash)` + +These methods are called before and after running a test executable. If a build +does not include tests, these are not called. This pair of methods is called +for each test executable in a build (each test file is ultimately built into a +test executable). + +The argument `arg_hash` follows the structure below: + +```ruby +arg_hash = { + # Hash holding execution tool properties. + :tool => { + # Hash holding compiler tool elements (see CeedlingPacket) + }, + # Additional context passed by the calling function. + # Ceedling provides :test or :release by default while plugins may provide another. + :context => :, + # Name of the test file minus path and extension (`test/TestIness.c` -> 'TestIness') + :test_name => "", + # Filepath of original tests C file that became the test executable + :test_filepath => "", + # Path to the tests executable file, e.g. .out file + :executable => "", + # Path to the tests result file, e.g. .pass/.fail file + :result_file => "" +} +``` + +## `Plugin` hook methods `pre_test(test)` and `post_test(test)` + +These methods are called before and after performing all steps needed to run a +test file — i.e. configure, preprocess, compile, link, run, get results, etc. +This pair of methods is called for each test file in a test build. + +The argument `test` corresponds to the path of the test C file being processed. + +## `Plugin` hook methods `pre_release()` and `post_release()` + +These methods are called before and after performing all steps needed to run the +release task — i.e. configure, preprocess, compile, link, etc. + +## `Plugin` hook methods `pre_build` and `post_build` + +These methods are called before and after executing any ceedling task — e.g: +test, release, coverage, etc. + +## `Plugin` hook methods `post_error()` + +This method is called at the conclusion of a Ceedling build that encounters any +error that halts the build process. To be clear, a test build with failing test +cases is not a build error. + +## `Plugin` hook methods `summary()` + +This method is called when invoking the summary task, `ceedling summary`. This +method facilitates logging the results of the last build without running the +previous build again. + +## Validating a plugin’s tools + +By default, Ceedling validates configured tools at startup according to a +simple setting within the tool definition. This works just fine for default +core tools and options. However, in the case of plugins, tools may not be even +relevant to a plugin's operation depending on its configurable options. It's +a bit silly for a tool not needed by your project to fail validation if +Ceedling can't find it in your `$PATH`. Similarly, it's irresponsible to skip +validating a tool just because it may not be needed. + +Ceedling provides optional, programmatic tool validation for these cases. +`@ceedling]:tool_validator].validate()` can be forced to ignore a tool's +`required:` setting to validate it. In such a scenario, a plugin should +configure its own tools as `:optional => true` but forcibly validate them at +plugin startup if the plugin's configuration options require said tool. + +An example from the `gcov` plugin illustrates this. + +```ruby +# Validate gcov summary tool if coverage summaries are enabled (summaries rely on the `gcov` tool) +if summaries_enabled?( @project_config ) + @ceedling[:tool_validator].validate( + tool: TOOLS_GCOV_SUMMARY, # Tool defintion as Ruby hash + boom: true # Ignore optional status (raise exception if invalid) + ) +end +``` + +The tool `TOOLS_GCOV_SUMMARY` is defined with a Ruby hash in the plugin code. +It is configured with `:optional => true`. At plugin startup, configuration +options determine if the tool is needed. It is forcibly validated if the plugin +configuration requires it. + +## Collecting test results from within `Plugin` subclass + +Some testing-specific plugins need access to test results to do their work. A +utility method is available for this purpose. + +`@ceedling[:plugin_reportinator].assemble_test_results()` + +This method takes as an argument a list of results filepaths. These typically +correspond directly to the collection of test files Ceedling processed in a +given test build. It's common for this list of filepaths to be assembled from +the `post_test_fixture_execute` build step execution hook. + +The data that `assemble_test_results()` returns hss a structure as follows. In +this example, actual results from a single, real test file are presented as +hash/array Ruby code with comments and with some edits to reduce line length. + +```ruby +{ + # Associates each test executable (i.e. test file) with an execution run time + :times => { + "test/TestUsartModel.c" => 0.21196400001645088 + }, + + # List of succeeding test cases, grouped by test file. + :successes => [ + { + :source => {:file => "test/TestUsartModel.c", :dirname => "test", :basename => "TestUsartModel.c"}, + :collection => [ + # If Unity is configured to do so, it will output execution run time for each test case. + # Ceedling creates a zero entry if the Unity option is not enabled. + {:test => "testCase1", :line => 17, :message => "", :unity_test_time => 0.0}, + {:test => "testCase2", :line => 31, :message => "", :unity_test_time => 0.0} + ] + } + ], + + # List of failing test cases, grouped by test file. + :failures => [ + { + :source => {:file => "test/TestUsartModel.c", :dirname => "test", :basename => "TestUsartModel.c"}, + :collection => [ + {:test => "testCase3", :line => 25, :message => "", :unity_test_time => 0.0} + ] + } + ], + + # List of ignored test cases, grouped by test file. + :ignores => [ + { + :source => {:file => "test/TestUsartModel.c", :dirname => "test", :basename => "TestUsartModel.c"}, + :collection => [ + {:test => "testCase4", :line => 39, :message => "", :unity_test_time => 0.0} + ] + } + ], + + # List of strings printed to $stdout, grouped by test file. + :stdout => [ + { + :source => {:file => "test/TestUsartModel.c", :dirname => "test", :basename => "TestUsartModel.c"}, + # Calls to print to $stdout are outside Unity's scope, preventing attaching test file line numbers + :collection => [ + "<$stdout string (e.g. printf() call)>" + ] + } + ], + + # Test suite run stats + :counts => { + :total => 4, + :passed => 2, + :failed => 1, + :ignored => 1, + :stdout => 1}, + + # The sum of all test file execution run times + :total_time => 0.21196400001645088 +} +``` + +# Plugin Option 3: Rake Tasks + +This plugin type adds custom Rake tasks to your project that can be run with `ceedling `. + +Naming and location conventions: `/.rake` + +## Example Rake task + +```ruby +# Only tasks with description are listed by `ceedling -T` +desc "Print hello world to console" +task :hello_world do + sh "echo Hello World!" +end +``` + +Resulting, example command line: + +```shell + > ceedling hello_world + > Hello World! +``` + +## Example Rake plugin layout + +Project configuration file: + +```yaml +:plugins: + :load_paths: + - support/plugins + :enabled: + - hello_world +``` + +Ceedling project directory sturcture: + +``` +project/ +├── project.yml +└── support/ + └── plugins/ + └── hello_world/ + └── hello_world.rake +``` \ No newline at end of file diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md new file mode 100644 index 000000000..8eebe9ff5 --- /dev/null +++ b/docs/ReleaseNotes.md @@ -0,0 +1,345 @@ +# đŸŒ± Ceedling Release Notes + +These release notes are complemented by two other documents: + +1. đŸȘ” **[Changelog](Changelog.md)** for a structured list of additions, fixes, changes, and removals. +1. 💔 **[Breaking Changes](BreakingChanges.md)** for a list of impacts to existing Ceedling projects. + +--- + +# 1.0.0 pre-release — November 28, 2024 + +**This Ceedling release is probably the most significant since the project was first [posted to SourceForge in 2009][sourceforge].** + +Ceedling now runs in Ruby 3. Builds can now run much faster than previous versions because of parallelized tasks. In test suites, header file search paths, code defines, and tool run flags are now customizable per test executable. Ceedling now also offers integrated debugging options to find the cause of crashing tests. + +* See all the [Highlights](#-highlights) below for an overview of everything in this big release. +* See the [Changelog](Changelog.md) for a detailed list of new features, fixes, and changes in this release. +* We’ve included below a [project configuration cheatsheet](#-project-configuration-cheatsheet-for-100-changes) for those already familiar with Ceedling that want to understand the various configuration changes. + +## đŸŽâ€â˜ ïž Avast, Breaking Changes, Ye Scallywags! + +**_Ahoy!_** There be plenty o’ **[breaking changes](BreakingChanges.md)** ahead too, mateys! Arrr
 + +## 📣 Shout-outs and Special Thank-You’s + +A **HUGE** Thanks to the ThrowTheSwitch.org community, for continuing to use, critique, and contribute to these tools. We're making the C world a better place and we appreciate all of you! + +We'd like to make some quick shout-outs to some especially helpful contributions. THANK YOU! + + -- Mark VanderVoord & Machael Karlesky, ThrowTheSwitch.org Project Maintainers + +### Sponsors of Ceedling 1.0.0 + + - [ThingamaByte, LLC](https://thingamabyte.com) - For continuing to nurture these projects and community with so much of their time. + - [Kamstrup, A/S](https://kamstrup.com) - For sponsoring and testing Ceedling's new parallel build/test support + - [Fraunhofer Institute for Integrated Systems and Device Technology IISB](https://iisb.fraunhofer.de) - For also sponsoring and testing Ceedling's new parallel build/test support + - [Peter Membrey](https://github.com/pmembrey) - For sponsoring, helping to plan, and validating Ceedling's new dependencies plugin + +### Major Code/Doc Contributors + +These individuals contributed significant features, bugfixes, and improvements. This list was generated by git, so if you feel you should be here, you're probably right. Please let us know and we're very sorry to have missed you! + + - Dom Postorivo + - Cezary Gapinski + - Aaron Schlicht + - Tim Bates + - Patrick Junger + - Austin Glaser + - Ɓukasz Ć»egliƄski + - Anthony Male + - Peter Membrey + - Laurens Miers + - Alejandro Rosso + - Tony Webb + - Greg Williams + - John Van Enk + - Job Vranish + +### Major Forum Contributors + +These individuals have been a huge help in answering questions and guiding newcomers on our forums. Thanks for making this a welcoming community! + + - @Letme + - @dpostorivo + - @swaldhoer + - @CezaryGapinski + - @aschlicht + +### Also, thanks for your contributions! + +Hiroaki Yamazoe, Lucas Becker, Rafael Campos Las Heras, Scott Vokes, Zane D. Purvis, Đ”Đ°ĐœĐžĐ»Đ° Đ’Đ°Đ»ŃŒĐșĐŸĐČДц, Aaron Schlicht, +Carl-Oskar Larsson, Javier TiĂĄ, Jesper L. Nielsen, MrRedKite, Nik Krause, Rasmus Melchior Jacobsen, serjche, +Andrei Korigodskii, Eder Clayton, Felipe Balbi, Hannes Bachl, Mike D. Lowis, Peter Horn, Rasmus Uth Pedersen, +Simon Grossenbacher, AlexandreMarkus, AndrĂ© Draszik, Aurelien CHAPPUIS, Austin Yates, Ben Wainwright, +Christopher Cichiwskyj, Crt Mori, Horacio Mijail AntĂłn Quiles, John Hutchinson, Joseph Gaudet, Julien Peyregne, KGons, +Kalle MĂžller, Peter Kempter, Luca Cavalli, Maksim Chichikalov, Marcelo Jo, Matt Chernosky, Niall Cooling, +Olivier C. Larocque, Patrick Little, Richard Eklycke, Serjche, Spencer Russell, Stavros Vagionitis, Steven Huang, +Toby Mole, Tom Hotston, Yuanqing Liu, afacotti, ccarrizosa, diachini, Steven Willard + +## 📖 Project Configuration Cheatsheet for 1.0.0 Changes + +The following is not a complete project configuration. But, for those already familiar with Ceedling, this cheatsheet illustrates some of the important changes in this latest release of Ceedling through the lens of a project configuration. + +To be clear, more has changed than what is referenced in this YAML blurb. Most notably: + +* Ceedling’s command line is more robust and conforms to common CLI conventions. +* Various plugins have grown in functionality and changed in name and configuration convention. +* Build directive macros are available that provide build customization abilities through their use in your test files. + +```yaml +# Mixins are an all new feature that allow you to easily merge alternate project configurations. +# Mixins replace a variety of other features, namely the :import project file section and option: command line task. +# A frequent need is a base project configuration complemented by variants of projects for different flavors of the same codebase. Mixins provide the features to support these kinds of needs. +# Mixins have few limitations as compared to the features Mixins replace. +# Mixins can be merged from within your project configuration file (shown here), from paths in environment variables, and from command line --mixin path switches. +# --------------------------- +:mixins: + :enabled: # :enabled list supports names and filepaths + - enabled # Look for enabled.yml in load paths and merge if found + - my/mixin/cfg.yml # A full path to a configuration file to merge + :load_paths: # Load paths to search for mixins specified by name only in :enabled + - support/mixins + +# Importing project configuration files with :import is superseded by the more capable Mixins (above). +# --------------------------- +# :import: +# - path/to/config.yml + +:project: + # Ceedling is now multi-threaded for speedy builds. + # The default is a single thread (setting of 1). :auto tells Ceedling to query your system and make an educated guess on a number of threads to use. + # Threading is broken into two categories to provide for controlling threading where build tooling and test executable tooling have different restrictions. + # --------------------------- + :compile_threads: :auto + :test_threads: :auto + + # Ceedling's preprocessing abilities have been totally revamped to fix bugs, eliminate complexity, and improve results. + # Preprocessing can now be selectively applied to test files and to mockable headers using options :none, :mocks, :tests, or :all. + # --------------------------- + :use_test_preprocessor: :all + + # The following configuration options have been deprecated. + # These are no longer available as the underlying functionality has been reimplemented with no need to configure it. + # If these settings remain in your configuration file, they are harmless but do zilch for you. + # --------------------------- + # :use_deep_dependencies + # :generate_deep_dependencies + # :auto_link_deep_dependencies + + # Backtrace is an all new feature in Ceedling. + # When enabled (default is :simple), backtrace figures out which test case in a crashed test executable is exercising the bug causing you grief. + # If the :gdb option is enabled (and the GNU debugger is installed), Ceedling will provide you the trace to the line of code causing a test case to crash. + # --------------------------- + :use_backtrace: :simple + +# Complemeneting its existing abilities to build assembly code as part of a release artifact, Ceedling can now also incorporate assembly code into test builds. +# See CeedlingPacket for full details on how to make use of this feature after enabling it. +# In short, your assembly code will need to be findable in your project paths, and you will need to use the new test directive macro TEST_SOURCE_FILE() inside your test files to tell Ceedling which assembly file to include in the respective test executable build. +:test_build: + :use_assembly: TRUE + +# Ceedling executables are now built as self-contained mini-projects. +# You can now define symbols for a release build and each test executable build. +# Symbols defined for a test executable are applied during compilation for each component of the executable. +# The :defines section supports several syntaxes. +# - Sophisticated matchers are available for test executable builds (shown here). +# - Simple lists to apply flags to all files in a build step are supported for release builds (only option) and test builds. +# See Ceedling Packet for details. +# --------------------------- +:defines: + :test: + :*: # Wildcard: Add '-DA' for compilation of all files for all tests + - A + :Model: # Substring: Add '-DCHOO' for compilation of all files of any test with 'Model' in its name + - CHOO + :/M(ain|odel)/: # Regex: Add '-DBLESS_YOU' for all files of any test with 'Main' or 'Model' in its name + - BLESS_YOU + :Comms*Model: # Wildcard: Add '-DTHANKS' for all files of any test that have zero or more characters + - THANKS # between 'Comms' and 'Model' + + :release: + - FEATURE_X=ON # Add these two symbols to compilation of all release C files (:test supports this syntax too) + - PRODUCT_CONFIG_C + +# Ceedling executables are now built as self-contained mini-projects. +# Flags can be specified for each build step and for each component of a build step. +# The :flags section supports several syntaxes. +# - Sophisticated matchers are available for test executable builds (shown here). +# - Simple lists to apply flags to all files in a build step are supported for release builds (only option) and test builds. +# See Ceedling Packet for details. +# --------------------------- +:flags: + :test: + :compile: + :*: # Wildcard: Add '-foo' for all files for all tests + - -foo + :Model: # Substring: Add '-Wall' for all files of any test with 'Model' in its name + - -Wall + :/M(ain|odel)/: # Regex: Add đŸŽâ€â˜ ïž flag for all files of any test with 'Main' or 'Model' in its name + - -đŸŽâ€â˜ ïž + :Comms*Model: + - --freak # Wildcard: Add your `--freak` flag for all files of any test name with zero or more + # characters between 'Comms' and 'Model' + :release: + :compile: + - -std=c99 # Add `-std=c99` to compilation of all release build C files (:test supports this syntax too) + +# Ceedling’s Unity configuration now properly supports test executable builds for Unity's parameterized test cases. +# Previously a handful of settings were required throughout a project configuration to successfully use these abilities. +# --------------------------- +:unity: + :use_param_tests: TRUE + +``` + +## 👀 Highlights + +### Big Deal Highlights 🏅 + +#### Ruby3 + +Ceedling now runs in Ruby3. This latest version of Ceedling is _not_ backwards compatible with earlier versions of Ruby. + +#### Way faster execution with parallel build steps + +Previously, Ceedling builds were depth-first and limited to a single line of execution. This limitation was an artifact of how Ceedling was architected and relying on general purpose Rake for the build pipeline. Rake does, in fact, support multi-threaded builds, but, Ceedling was unable to take advantage of this. As such, builds were limited to a single line of execution no matter how many CPU resources were available. + +Ceedling 1.0.0 introduces a new build pipeline that batches build steps breadth-first. This means all test preprocessor steps, all compilation steps, all linking steps, etc. can benefit from concurrent and parallel execution. This speedup applies to both test suite and release builds. + +#### Per-test-executable configurations + +In previous versions of Ceedling each test executable was built with essentially the same global configuration. In the case of `#define`s and tool command line flags, individual files could be handled differently, but configuring Ceedling for doing so for all the files in any one test executable was tedious and error prone. + +Now Ceedling builds each test executable as a mini project where header file search paths, compilation `#define` symbols, and tool flags can be specified per test executable. That is, each file that ultimately comprises a test executable is handled with the same configuration as the other files that make up that test executable. + +Now you can have tests with quite different configurations and behaviors. Two tests need different mocks of the same header file? No problem. You want to test the same source file two different ways? We got you. + +The following new features (discussed in later sections) contribute to this new ability: + +- `TEST_INCLUDE_PATH(...)`. This build directive macro can be used within a test file to tell Ceedling which header search paths should be used during compilation. These paths are only used for compiling the files that comprise that test executable. +- `:defines` handling. `#define`s are now specified for the compilation of all modules comprising a test executable. Matching is only against test file names but now includes wildcard and regular expression options. +- `:flags` handling. Flags (e.g. `-std=c99`) are now specified for the build steps — preprocessing, compilation, and linking — of all modules comprising a test executable. Matching is only against test file names and now includes more sensible and robust wildcard and regular expression options. + +#### Mixins for configuration variations + +Ever wanted to smoosh in some extra configuration selectively? Let’s say you have different build scenarios and you'd like to run different variations of your project for them. Maybe you have core configuration that is common to all those scenarios. Previous versions of Ceedling included a handful of features that partially met these sorts of needs. + +All such features have been superseded by _Mixins_. Mixins are simply additional YAML that gets merged into you base project configuration. However, Mixins provide several key improvements over previous features: + +1. Mixins can be as little or as much configuration as you want. You could push all your configuration into mixins with a base project file including nothing but a `:mixins` section. +1. Mixins can be specified in your project configuration, via environment variables, and from the command line. A clear order of precedence controls the order of merging. Any conflicts or duplicates are automatically resolved. +1. Logging makes clear what proejct file and mixins are loaded and merged at startup. +1. Like built-in plugins, Ceedling will soon come with built-in mixins available for common build scenarios. + +#### A proper command line + +Until this release, Ceedling depended on Rake for most of its command line handling. Rake’s task conventions provide poor command line handling abilities. The core problems with Rake command line handling include: + +1. Only brief, limited help statements. +1. No optional flags to modify a task — verbosity, logging, etc. were their own tasks. +1. Complex/limited parameterization (e.g. `verbosity[3]` instead of `--verbosity normal`). +1. Tasks are order-dependent. So, for example, `test:all verbosity[5]` changes verbosity after the tests are run. + +Ceedling now offers a full command line interface with rich help, useful order-independent option flags, and more. + +The existing `new`, `upgrade`, `example`, and `exampples` commands remain but have been improved. For those commands that support it, you may now specify the project file to load (see new, related mixins feature discussed elsewhere), log file to write to, exit code handling behavior, and more from the command line. + +Try `ceedling help` and then `ceedling help ` to get started. + +**_Important Notes on the New Command Line:_** + +* The new and improved features for running build tasks — loading a project file, merging mixins, verbosity handling, etc. — are documented within the application command `build` keyword. A build command line such as the following is now possible: `ceedling test:all --verbosity obnoxious --logfile my/path/build.log`. Run `ceedling help build` to learn more and definitely see the next bullet point as well. +* The `build` keyword is assumed by Ceedling. That is, it’s optional. `ceedling test:all` is the same as `ceedling build test:all`. The `build` keyword handling tells the Ceedling application to execute the named build task dynamically generated from your project configuration. +* In the transition to remove Rake from Ceedling, two categories of command line interactions now exist. Note this distinction in the `help` headings. + 1. **Application Commands** — `help`, `build`, `new`, `upgrade`, `environment`, `examples`, `example`, `dumpconfig`, and `version`. These have full help via `ceedling help ` and a variety of useful command line switches that conform to typical command line conventions. + 1. **Build & Plugin Tasks** — Operations dynamically generated from your project configuration. These have only summary help (listed in `ceedling help`) and work just as they previously did. Common command line tasks including `ceedling test:all` and `ceedling release` are in this category. + +### Medium Deal Highlights đŸ„ˆ + +#### `TEST_SOURCE_FILE(...)` + +In previous versions of Ceedling, a new, undocumented build directive feature was introduced. Adding a call to the macro `TEST_FILE(...)` with a C file’s name added that C file to the compilation and linking list for a test executable. + +This approach was helpful when relying on a Ceedling convention was problematic. Specifically, `#include`ing a header file would cause any correspondingly named source file to be added to the build list for a test executable. This convention could cause problems if, for example, the header file defined symbols that complicated test compilation or behavior. Similarly, if a source file did not have a corresponding header file of the same name, sometimes the only option was to `#include` it directly; this was ugly and problematic in its own way. + +The previously undocumented build directive macro `TEST_FILE(...)` has been renamed to `TEST_SOURCE_FILE(...)` and is now [documented](CeedlingPacket.md). + +#### Preprocessing improvements + +Ceedling has been around for a number of years and has had the benefit of many contributors over that time. Preprocessing (e.g. expanding macros in test files and header files to be mocked) is quite tricky to get right but is essential for big, complicated test suites. Over Ceedling’s long life various patches and incremental improvements have evolved in such a way that preprocessing had become quite complicated and often did the wrong thing. Much of this has been fixed and improved in this release. Considerable memory and performance improvements have been made as well. + +#### Test Suite Crash Handling + +Previously, if a test executable ran into a segmentation fault (usually caused by memory issues in the code), the entire test executable would report nothing but a simple error. This behavior has been expanded to handle any crash condition and further improved. + +By default, a crashed test executable is automatically rerun for each test case individually to narrow down which test case(s) caused the problem. If `gdb` is properly installed and configured the specific line that caused the crash can be reported. + +The `:simple` and `:gdb` options for this feature fully and correctly report each test case’s status for a crashed test executable. Crashed test cases are counted as failures. The `:none` option does not run each test case individually. Instead, in the case of crashed test executable, it marks each test case as a failure reporting that the entire test executable crashed. + +See _[CeedlingPacket](CeedlingPacket.md))_ for the new `:project` ↳ `:use_backtrace` feature to control how much detail is extracted from a crashed test executable to help you find the cause. + +#### Test builds can now incorporate assembly code + +See the documentation for `:test_build` ↳ `:use_assembly` to understand how to incorporate assembly code into a given test executable’s build. This complementes Ceedling’s existing ability to incorporate assembly code in a release artifact build. + +#### Configuration defaults and configuration set up order + +Ceedling’s previous handling of defaults and configuration processing order certainly worked, but it was not as proper as it could be. To oversimplify, default values were applied in an ordering that caused complications for advanced plugins and advanced users. This has been rectified. Default settings are now processed after all user configurations and plugins. + +#### Documentation + +The Ceedling user guide, _[CeedlingPacket](CeedlingPacket.md)_, has been significantly revised and expanded. We will expand it further in future releases and eventually break it up into multiple documents or migrate it to a full documentation management system. + +Many of the plugins have received documentation updates as well. + +There’s more to be done, but Ceedling’s documentation is more complete and accurate than it’s ever been. + +### Small Deal Highlights đŸ„‰ + +- Effort has been invested across the project to improve error messages, exception handling, and exit code processing. Noisy backtraces have been relegated to the verbosity level of DEBUG as intended. +- Logical ambiguity and functional bugs within `:paths` and `:files` configuration handling have been resolved along with updated documentation. +- A variety of small improvements and fixes have been made throughout the plugin system and to many plugins. +- 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 1.0.0 release. Future releases will have far shorter notes. +- Optional Unicode and emoji decorators have been added for your output stream enjoyment. See the documentation for logging decorators in _[CeedlingPacket](CeedlingPacket.md)_. + +## 🚹 Important Changes in Behavior to Be Aware Of + +- **Test suite build order 🔱.** Ceedling no longer builds each test executable one at a time. From the tasks you provide at the command line, Ceedling now collects up and batches all preprocessing steps, all mock generation, all test runner generation, all compilation, etc. Previously you would see each of these done for a single test executable and then repeated for the next executable and so on. Now, each build step happens to completion for all specified tests before moving on to the next build step. +- **Logging output order 🔱.** When multi-threaded builds are enabled, logging output may not be what you expect. Progress statements may be all batched together or interleaved in ways that are misleading. The steps are happening in the correct order. How you are informed of them may be somewhat out of order. +- **Files generated multiple times 🔀.** Now that each test is essentially a self-contained mini-project, some output may be generated multiple times. For instance, if the same mock is required by multiple tests, it will be generated multiple times. The same holds for compilation of source files into object files. A coming version of Ceedling will concentrate on optimizations to reuse any output that is truly identical across tests. +- **Test suite plugin runs đŸƒđŸ».** Because build steps are run to completion across all the tests you specify at the command line (e.g. all the mocks for your tests are generated at one time) you may need to adjust how you depend on build steps. + +Together, these changes may cause you to think that Ceedling is running steps out of order or duplicating work. While bugs are always possible, more than likely, the output you see and the build ordering is expected. + +## đŸ©Œ Known Issues + +1. The new internal pipeline that allows builds to be parallelized and configured per-test-executable can mean a fair amount of duplication of steps. A header file may be mocked identically multiple times. The same source file may be compiled identically multiple times. The speed gains due to parallelization help make up for this. Future releases will concentrate on optimizing away duplication of build steps. +1. While header file search paths are now customizable per executable, this currently only applies to the search paths the compiler uses. Distinguishing test files or header files of the same name in different directories for test runner and mock generation respectively continues to rely on educated guesses in Ceedling code. +1. Any path for a C file specified with `TEST_SOURCE_FILE(...)` is in relation to **_project root_** — that is, from where you execute `ceedling` at the command line. If you move source files or change your directory structure, many of your `TEST_SOURCE_FILE(...)` calls may need to be updated. A more flexible and dynamic approach to path handling will come in a future update. +1. Ceedling’s many test preprocessing improvements are not presently able to preserve Unity’s special `TEST_CASE()` and `TEST_RANGE()` features. However, preprocessing of test files is much less frequently needed than preprocessing of mockable header files. Test preprocessing can now be configured to enable only one or the other. As such, these advanced Unity features can still be used in even sophisticated projects. + +## 📚 Background Knowledge + +### Parallel execution of build steps + +You may have heard that Ruby is actually only single-threaded or may know of its Global Interpreter Lock (GIL) that prevents parallel execution. To oversimplify a complicated subject, the Ruby implementations most commonly used to run Ceedling afford concurrency and true parallelism speedups but only in certain circumstances. It so happens that these circumstances are precisely the workload that Ceedling manages. + +“Mainstream” Ruby implementations — not JRuby, for example — offer the following that Ceedling takes advantage of: + +#### Native thread context switching on I/O operations + +Since version 1.9, Ruby supports native threads and not only green threads. However, native threads are limited by the GIL to executing one at a time regardless of the number of cores in your processor. But, the GIL is “relaxed” for I/O operations. + +When a native thread blocks for I/O, Ruby allows the OS scheduler to context switch to a thread ready to execute. This is the original benefit of threads when they were first developed back when CPUs contained a single core and multi-processor systems were rare and special. Ceedling does a fair amount of file and standard stream I/O in its pure Ruby code. Thus, when multiple threads are enabled in the proejct configuration file, execution can speed up for these operations. + +#### Process spawning + +Ruby’s process spawning abilities have always mapped directly to OS capabilities. When a processor has multiple cores available, the OS tends to spread multiple child processes across those cores in true parallel execution. + +Much of Ceedling’s workload is executing a tool — such as a compiler — in a child process. With multiple threads enabled, each thread can spawn a child process for a build tool used by a build step. These child processes can be spread across multiple cores in true parallel execution. + + +[sourceforge]: https://sourceforge.net/projects/ceedling/ "Ceedling’s public debut" \ No newline at end of file diff --git a/examples/blinky/project.yml b/examples/blinky/project.yml deleted file mode 100644 index 75f453d3d..000000000 --- a/examples/blinky/project.yml +++ /dev/null @@ -1,101 +0,0 @@ ---- - -# Notes: -# This is a fully tested project that demonstrates the use -# of a timer ISR to blink the on board LED of an Arduino UNO -:project: - :use_exceptions: FALSE - :use_test_preprocessor: TRUE - :use_auxiliary_dependencies: TRUE - :build_root: build - :release_build: TRUE - :test_file_prefix: test_ - :which_ceedling: gem - :ceedling_version: '?' - -#You'll have to specify these -:environment: - - :mcu: atmega328p - - :f_cpu: 16000000UL - - :serial_port: COM8 #change this to the serial port you are using!!! - - :objcopy: avr-objcopy - # Uncomment these lines if you are using windows and don't have these tools in your path - # - :path: - # - C:\mingw\bin - # - C:\WinAVR-20100110\bin - # - C:\WinAVR-20100110\utils\bin - # - #{ENV['PATH']} - -:extension: - :executable: .bin - -:release_build: - :output: blinky - -:paths: - :test: - - +:test/** - - -:test/support - :source: - - src/** - :support: - - test/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) - :common: &common_defines [] - :test: - - *common_defines - - TEST - - UNITY_OUTPUT_COLOR #this is just here to make sure it gets removed by ceedling - :test_preprocess: - - *common_defines - - TEST - -:tools: - :release_compiler: - :executable: avr-gcc - :arguments: - - ${1} - - -DTARGET - - -DF_CPU=#{ENV['F_CPU']} - - -mmcu=#{ENV['MCU']} - - -Iinclude/ - - -Wall - - -Os - - -c - - -o ${2} - :release_linker: - :executable: avr-gcc - :arguments: - - -mmcu=#{ENV['MCU']} - - ${1} - - -o ${2}.bin - -:cmock: - :mock_prefix: mock_ - :when_no_prototypes: :warn - :enforce_strict_ordering: TRUE - :plugins: - - :ignore - :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: - - "#{Ceedling.load_path}" - :enabled: - - stdout_pretty_tests_report - - module_generator -... diff --git a/examples/blinky/rakefile.rb b/examples/blinky/rakefile.rb deleted file mode 100644 index 37b0fe701..000000000 --- a/examples/blinky/rakefile.rb +++ /dev/null @@ -1,30 +0,0 @@ -require "ceedling" -Ceedling.load_project - -task :default => %w[ test:all release ] - -# Dummy task to ensure that the SERIAL_PORT environment variable is set. -# It can be set on the command line as follows: -# $ rake SERIAL_PORT=[serial port name] -task :serial_port do - unless ENV['SERIAL_PORT'] - raise "SERIAL_PORT is not defined in the environment!" - end -end - -desc "Convert the output binary to a hex file for programming to the Arduino" -task :convert => :release do - bin_file = "build/release/#{RELEASE_BUILD_OUTPUT}.bin" - hex_file = "build/release/#{RELEASE_BUILD_OUTPUT}.hex" - cmd = "#{ENV['OBJCOPY']} -O ihex -R .eeprom #{bin_file} #{hex_file}" - puts cmd - sh cmd -end - -desc "Program the Arduino over the serial port." -task :program => [:convert, :serial_port] do - hex_file = "build/release/#{RELEASE_BUILD_OUTPUT}.hex" - cmd = "avrdude -F -V -c arduino -p #{ENV['MCU']} -P #{ENV['SERIAL_PORT']} -b 115200 -U flash:w:#{hex_file}" - puts cmd - sh cmd -end diff --git a/examples/blinky/src/BlinkTask.c b/examples/blinky/src/BlinkTask.c deleted file mode 100644 index 7ab3e686e..000000000 --- a/examples/blinky/src/BlinkTask.c +++ /dev/null @@ -1,21 +0,0 @@ -// #include - -#include "BlinkTask.h" - -#ifdef TEST - #define LOOP - #include "stub_io.h" -#else - #include - #include - #define LOOP while(1) -#endif // TEST - - - -void BlinkTask(void) -{ - /* toggle the LED */ - PORTB ^= _BV(PORTB5); - -} diff --git a/examples/blinky/src/BlinkTask.h b/examples/blinky/src/BlinkTask.h deleted file mode 100644 index d9887810f..000000000 --- a/examples/blinky/src/BlinkTask.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef BlinkTask_H -#define BlinkTask_H - -void BlinkTask(void); - -#endif diff --git a/examples/blinky/src/Configure.c b/examples/blinky/src/Configure.c deleted file mode 100644 index 11e506baa..000000000 --- a/examples/blinky/src/Configure.c +++ /dev/null @@ -1,36 +0,0 @@ -#include "Configure.h" -#include "main.h" -#ifdef TEST - #include "stub_io.h" - #include "stub_interrupt.h" -#else - #include - #include -#endif // TEST - -/* setup timer 0 to divide bus clock by 64. - This results in a 1.024ms overflow interrupt -16000000/64 - 250000 - -0.000 004s *256 -0.001024 -*/ -void Configure(void) -{ - /* disable interrupts */ - cli(); - - /* Configure TIMER0 to use the CLK/64 prescaler. */ - TCCR0B = _BV(CS00) | _BV(CS01); - - /* enable the TIMER0 overflow interrupt */ - TIMSK0 = _BV(TOIE0); - - /* confiure PB5 as an output. */ - DDRB |= _BV(DDB5); - - /* enable interrupts. */ - sei(); -} - diff --git a/examples/blinky/src/Configure.h b/examples/blinky/src/Configure.h deleted file mode 100644 index 2399d39d5..000000000 --- a/examples/blinky/src/Configure.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef Configure_H -#define Configure_H - -void Configure(void); - -#endif // Configure_H diff --git a/examples/blinky/src/main.c b/examples/blinky/src/main.c deleted file mode 100644 index 6533aaae6..000000000 --- a/examples/blinky/src/main.c +++ /dev/null @@ -1,51 +0,0 @@ -// #include - -#include "main.h" -#include "BlinkTask.h" -#include "Configure.h" - -//Most OS's frown upon direct memory access. -//So we'll have to use fake registers during testing. -#ifdef TEST - #define LOOP - #include "stub_io.h" - #include "stub_interrupt.h" -#else - #include - #include - #define LOOP while(1) - //The target will need a main. - //Our test runner will provide it's own and call AppMain() - int main(void) - { - return AppMain(); - } -#endif // TEST - -int AppMain(void) -{ - Configure(); - - LOOP - { - if(BlinkTaskReady==0x01) - { - BlinkTaskReady = 0x00; - BlinkTask(); - } - } - return 0; -} - -ISR(TIMER0_OVF_vect) -{ - /* toggle every thousand ticks */ - if (tick >= 1000) - { - /* signal our periodic task. */ - BlinkTaskReady = 0x01; - /* reset the tick */ - tick = 0; - } - tick++; -} diff --git a/examples/blinky/src/main.h b/examples/blinky/src/main.h deleted file mode 100644 index 17c176c52..000000000 --- a/examples/blinky/src/main.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef __MAIN_H__ -#define __MAIN_H__ - -int main(void); -int AppMain(void); -volatile int BlinkTaskReady; -int tick; - -#endif /* __MAIN_H__ */ diff --git a/examples/blinky/test/support/stub_interrupt.h b/examples/blinky/test/support/stub_interrupt.h deleted file mode 100644 index 7410f2149..000000000 --- a/examples/blinky/test/support/stub_interrupt.h +++ /dev/null @@ -1,347 +0,0 @@ -/* Copyright (c) 2002,2005,2007 Marek Michalkiewicz - Copyright (c) 2007, Dean Camera - - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of the copyright holders nor the names of - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. */ - -/* $Id: interrupt.h,v 1.25.2.1 2008/01/05 06:33:11 dmix Exp $ */ - -#ifndef _AVR_INTERRUPT_H_ -#define _AVR_INTERRUPT_H_ - -// #include -#include "stub_io.h" -#if !defined(__DOXYGEN__) && !defined(__STRINGIFY) -/* Auxiliary macro for ISR_ALIAS(). */ -#define __STRINGIFY(x) #x -#endif /* !defined(__DOXYGEN__) */ - -/** -\file -\@{ -*/ - - -/** \name Global manipulation of the interrupt flag - - The global interrupt flag is maintained in the I bit of the status - register (SREG). -*/ - -// #if defined(__DOXYGEN__) -/** \def sei() - \ingroup avr_interrupts - - \code #include \endcode - - Enables interrupts by setting the global interrupt mask. This function - actually compiles into a single line of assembly, so there is no function - call overhead. */ -// #define sei() -void sei(void); // Redefine the macro as a function so that it can be mocked by CMock -// #else /* !DOXYGEN */ -// # define sei() __asm__ __volatile__ ("sei" ::) -// #endif /* DOXYGEN */ - -// #if defined(__DOXYGEN__) -/** \def cli() - \ingroup avr_interrupts - - \code #include \endcode - - Disables all interrupts by clearing the global interrupt mask. This function - actually compiles into a single line of assembly, so there is no function - call overhead. */ -// #define cli() -void cli(void); // Redefine the macro as a function so that it can be mocked by CMock -// #else /* !DOXYGEN */ -// # define cli() __asm__ __volatile__ ("cli" ::) -// #endif /* DOXYGEN */ - - -/** \name Macros for writing interrupt handler functions */ - - -// #if defined(__DOXYGEN__) -/** \def ISR(vector [, attributes]) - \ingroup avr_interrupts - - \code #include \endcode - - Introduces an interrupt handler function (interrupt service - routine) that runs with global interrupts initially disabled - by default with no attributes specified. - - The attributes are optional and alter the behaviour and resultant - generated code of the interrupt routine. Multiple attributes may - be used for a single function, with a space seperating each - attribute. - - Valid attributes are ISR_BLOCK, ISR_NOBLOCK, ISR_NAKED and - ISR_ALIASOF(vect). - - \c vector must be one of the interrupt vector names that are - valid for the particular MCU type. -*/ -// # define ISR(vector, [attributes]) -#define ISR void ISR -// #else /* real code */ - -// #if (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) || (__GNUC__ > 4) -// # define __INTR_ATTRS used, externally_visible -// #else /* GCC < 4.1 */ -// # define __INTR_ATTRS used -// #endif - -// #ifdef __cplusplus -// # define ISR(vector, ...) \ -// extern "C" void vector (void) __attribute__ ((signal,__INTR_ATTRS)) __VA_ARGS__; \ -// void vector (void) -// #else -// # define ISR(vector, ...) \ -// void vector (void) __attribute__ ((signal,__INTR_ATTRS)) __VA_ARGS__; \ -// void vector (void) -// #endif - -// #endif /* DOXYGEN */ - -#if defined(__DOXYGEN__) -/** \def SIGNAL(vector) - \ingroup avr_interrupts - - \code #include \endcode - - Introduces an interrupt handler function that runs with global interrupts - initially disabled. - - This is the same as the ISR macro without optional attributes. - \deprecated Do not use SIGNAL() in new code. Use ISR() instead. -*/ -# define SIGNAL(vector) -#else /* real code */ - -#ifdef __cplusplus -# define SIGNAL(vector) \ - extern "C" void vector(void) __attribute__ ((signal, __INTR_ATTRS)); \ - void vector (void) -#else -# define SIGNAL(vector) \ - void vector (void) __attribute__ ((signal, __INTR_ATTRS)); \ - void vector (void) -#endif - -#endif /* DOXYGEN */ - -#if defined(__DOXYGEN__) -/** \def EMPTY_INTERRUPT(vector) - \ingroup avr_interrupts - - \code #include \endcode - - Defines an empty interrupt handler function. This will not generate - any prolog or epilog code and will only return from the ISR. Do not - define a function body as this will define it for you. - Example: - \code EMPTY_INTERRUPT(ADC_vect);\endcode */ -# define EMPTY_INTERRUPT(vector) -#else /* real code */ - -#ifdef __cplusplus -# define EMPTY_INTERRUPT(vector) \ - extern "C" void vector(void) __attribute__ ((signal,naked,__INTR_ATTRS)); \ - void vector (void) { __asm__ __volatile__ ("reti" ::); } -#else -# define EMPTY_INTERRUPT(vector) \ - void vector (void) __attribute__ ((signal,naked,__INTR_ATTRS)); \ - void vector (void) { __asm__ __volatile__ ("reti" ::); } -#endif - -#endif /* DOXYGEN */ - -#if defined(__DOXYGEN__) -/** \def ISR_ALIAS(vector, target_vector) - \ingroup avr_interrupts - - \code #include \endcode - - Aliases a given vector to another one in the same manner as the - ISR_ALIASOF attribute for the ISR() macro. Unlike the ISR_ALIASOF - attribute macro however, this is compatible for all versions of - GCC rather than just GCC version 4.2 onwards. - - \note This macro creates a trampoline function for the aliased - macro. This will result in a two cycle penalty for the aliased - vector compared to the ISR the vector is aliased to, due to the - JMP/RJMP opcode used. - - \deprecated - For new code, the use of ISR(..., ISR_ALIASOF(...)) is - recommended. - - Example: - \code - ISR(INT0_vect) - { - PORTB = 42; - } - - ISR_ALIAS(INT1_vect, INT0_vect); - \endcode -*/ -# define ISR_ALIAS(vector, target_vector) -#else /* real code */ - -#ifdef __cplusplus -# if defined(__AVR_MEGA__) && __AVR_MEGA__ -# define ISR_ALIAS(vector, tgt) extern "C" void vector (void) \ - __attribute__((signal, naked, __INTR_ATTRS)); \ - void vector (void) { asm volatile ("jmp " __STRINGIFY(tgt) ::); } -# else /* !__AVR_MEGA */ -# define ISR_ALIAS(vector, tgt) extern "C" void vector (void) \ - __attribute__((signal, naked, __INTR_ATTRS)); \ - void vector (void) { asm volatile ("rjmp " __STRINGIFY(tgt) ::); } -# endif /* __AVR_MEGA__ */ -#else /* !__cplusplus */ -# if defined(__AVR_MEGA__) && __AVR_MEGA__ -# define ISR_ALIAS(vector, tgt) void vector (void) \ - __attribute__((signal, naked, __INTR_ATTRS)); \ - void vector (void) { asm volatile ("jmp " __STRINGIFY(tgt) ::); } -# else /* !__AVR_MEGA */ -# define ISR_ALIAS(vector, tgt) void vector (void) \ - __attribute__((signal, naked, __INTR_ATTRS)); \ - void vector (void) { asm volatile ("rjmp " __STRINGIFY(tgt) ::); } -# endif /* __AVR_MEGA__ */ -#endif /* __cplusplus */ - -#endif /* DOXYGEN */ - -#if defined(__DOXYGEN__) -/** \def reti() - \ingroup avr_interrupts - - \code #include \endcode - - Returns from an interrupt routine, enabling global interrupts. This should - be the last command executed before leaving an ISR defined with the ISR_NAKED - attribute. - - This macro actually compiles into a single line of assembly, so there is - no function call overhead. -*/ -# define reti() -#else /* !DOXYGEN */ -# define reti() __asm__ __volatile__ ("reti" ::) -#endif /* DOXYGEN */ - -#if defined(__DOXYGEN__) -/** \def BADISR_vect - \ingroup avr_interrupts - - \code #include \endcode - - This is a vector which is aliased to __vector_default, the vector - executed when an ISR fires with no accompanying ISR handler. This - may be used along with the ISR() macro to create a catch-all for - undefined but used ISRs for debugging purposes. -*/ -# define BADISR_vect -#else /* !DOXYGEN */ -# define BADISR_vect __vector_default -#endif /* DOXYGEN */ - -/** \name ISR attributes */ - -#if defined(__DOXYGEN__) -/** \def ISR_BLOCK - \ingroup avr_interrupts - - \code# include \endcode - - Identical to an ISR with no attributes specified. Global - interrupts are initially disabled by the AVR hardware when - entering the ISR, without the compiler modifying this state. - - Use this attribute in the attributes parameter of the ISR macro. -*/ -# define ISR_BLOCK - -/** \def ISR_NOBLOCK - \ingroup avr_interrupts - - \code# include \endcode - - ISR runs with global interrupts initially enabled. The interrupt - enable flag is activated by the compiler as early as possible - within the ISR to ensure minimal processing delay for nested - interrupts. - - This may be used to create nested ISRs, however care should be - taken to avoid stack overflows, or to avoid infinitely entering - the ISR for those cases where the AVR hardware does not clear the - respective interrupt flag before entering the ISR. - - Use this attribute in the attributes parameter of the ISR macro. -*/ -# define ISR_NOBLOCK - -/** \def ISR_NAKED - \ingroup avr_interrupts - - \code# include \endcode - - ISR is created with no prologue or epilogue code. The user code is - responsible for preservation of the machine state including the - SREG register, as well as placing a reti() at the end of the - interrupt routine. - - Use this attribute in the attributes parameter of the ISR macro. -*/ -# define ISR_NAKED - -/** \def ISR_ALIASOF(target_vector) - \ingroup avr_interrupts - - \code#include \endcode - - The ISR is linked to another ISR, specified by the vect parameter. - This is compatible with GCC 4.2 and greater only. - - Use this attribute in the attributes parameter of the ISR macro. -*/ -# define ISR_ALIASOF(target_vector) -#else /* !DOXYGEN */ -# define ISR_BLOCK -# define ISR_NOBLOCK __attribute__((interrupt)) -# define ISR_NAKED __attribute__((naked)) -# define ISR_ALIASOF(v) __attribute__((alias(__STRINGIFY(v)))) -#endif /* DOXYGEN */ - -/* \@} */ - -#endif diff --git a/examples/blinky/test/support/stub_io.h b/examples/blinky/test/support/stub_io.h deleted file mode 100644 index e9646daf6..000000000 --- a/examples/blinky/test/support/stub_io.h +++ /dev/null @@ -1,421 +0,0 @@ -/* Copyright (c) 2002,2003,2005,2006,2007 Marek Michalkiewicz, Joerg Wunsch - Copyright (c) 2007 Eric B. Weddington - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of the copyright holders nor the names of - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. */ - -/* $Id: io.h,v 1.52.2.28 2009/12/20 17:02:53 arcanum Exp $ */ - -/** \file */ -/** \defgroup avr_io : AVR device-specific IO definitions - \code #include \endcode - - This header file includes the apropriate IO definitions for the - device that has been specified by the -mmcu= compiler - command-line switch. This is done by diverting to the appropriate - file <avr/ioXXXX.h> which should - never be included directly. Some register names common to all - AVR devices are defined directly within <avr/common.h>, - which is included in <avr/io.h>, - but most of the details come from the respective include file. - - Note that this file always includes the following files: - \code - #include - #include - #include - #include - \endcode - See \ref avr_sfr for more details about that header file. - - Included are definitions of the IO register set and their - respective bit values as specified in the Atmel documentation. - Note that inconsistencies in naming conventions, - so even identical functions sometimes get different names on - different devices. - - Also included are the specific names useable for interrupt - function definitions as documented - \ref avr_signames "here". - - Finally, the following macros are defined: - - - \b RAMEND -
- The last on-chip RAM address. -
- - \b XRAMEND -
- The last possible RAM location that is addressable. This is equal to - RAMEND for devices that do not allow for external RAM. For devices - that allow external RAM, this will be larger than RAMEND. -
- - \b E2END -
- The last EEPROM address. -
- - \b FLASHEND -
- The last byte address in the Flash program space. -
- - \b SPM_PAGESIZE -
- For devices with bootloader support, the flash pagesize - (in bytes) to be used for the \c SPM instruction. - - \b E2PAGESIZE -
- The size of the EEPROM page. - -*/ - -#ifndef _AVR_IO_H_ -#define _AVR_IO_H_ - -// #include -#include "stub_sfr_defs.h" -// #if defined (__AVR_AT94K__) -// # include -// #elif defined (__AVR_AT43USB320__) -// # include -// #elif defined (__AVR_AT43USB355__) -// # include -// #elif defined (__AVR_AT76C711__) -// # include -// #elif defined (__AVR_AT86RF401__) -// # include -// #elif defined (__AVR_AT90PWM1__) -// # include -// #elif defined (__AVR_AT90PWM2__) -// # include -// #elif defined (__AVR_AT90PWM2B__) -// # include -// #elif defined (__AVR_AT90PWM3__) -// # include -// #elif defined (__AVR_AT90PWM3B__) -// # include -// #elif defined (__AVR_AT90PWM216__) -// # include -// #elif defined (__AVR_AT90PWM316__) -// # include -// #elif defined (__AVR_AT90PWM81__) -// # include -// #elif defined (__AVR_ATmega8U2__) -// # include -// #elif defined (__AVR_ATmega16M1__) -// # include -// #elif defined (__AVR_ATmega16U2__) -// # include -// #elif defined (__AVR_ATmega16U4__) -// # include -// #elif defined (__AVR_ATmega32C1__) -// # include -// #elif defined (__AVR_ATmega32M1__) -// # include -// #elif defined (__AVR_ATmega32U2__) -// # include -// #elif defined (__AVR_ATmega32U4__) -// # include -// #elif defined (__AVR_ATmega32U6__) -// # include -// #elif defined (__AVR_ATmega64C1__) -// # include -// #elif defined (__AVR_ATmega64M1__) -// # include -// #elif defined (__AVR_ATmega128__) -// # include -// #elif defined (__AVR_ATmega1280__) -// # include -// #elif defined (__AVR_ATmega1281__) -// # include -// #elif defined (__AVR_ATmega1284P__) -// # include -// #elif defined (__AVR_ATmega128RFA1__) -// # include -// #elif defined (__AVR_ATmega2560__) -// # include -// #elif defined (__AVR_ATmega2561__) -// # include -// #elif defined (__AVR_AT90CAN32__) -// # include -// #elif defined (__AVR_AT90CAN64__) -// # include -// #elif defined (__AVR_AT90CAN128__) -// # include -// #elif defined (__AVR_AT90USB82__) -// # include -// #elif defined (__AVR_AT90USB162__) -// # include -// #elif defined (__AVR_AT90USB646__) -// # include -// #elif defined (__AVR_AT90USB647__) -// # include -// #elif defined (__AVR_AT90USB1286__) -// # include -// #elif defined (__AVR_AT90USB1287__) -// # include -// #elif defined (__AVR_ATmega64__) -// # include -// #elif defined (__AVR_ATmega640__) -// # include -// #elif defined (__AVR_ATmega644__) || defined (__AVR_ATmega644A__) -// # include -// #elif defined (__AVR_ATmega644P__) -// # include -// #elif defined (__AVR_ATmega644PA__) -// # include -// #elif defined (__AVR_ATmega645__) || defined (__AVR_ATmega645A__) || defined (__AVR_ATmega645P__) -// # include -// #elif defined (__AVR_ATmega6450__) || defined (__AVR_ATmega6450A__) || defined (__AVR_ATmega6450P__) -// # include -// #elif defined (__AVR_ATmega649__) || defined (__AVR_ATmega649A__) -// # include -// #elif defined (__AVR_ATmega6490__) || defined (__AVR_ATmega6490A__) || defined (__AVR_ATmega6490P__) -// # include -// #elif defined (__AVR_ATmega649P__) -// # include -// #elif defined (__AVR_ATmega64HVE__) -// # include -// #elif defined (__AVR_ATmega103__) -// # include -// #elif defined (__AVR_ATmega32__) -// # include -// #elif defined (__AVR_ATmega323__) -// # include -// #elif defined (__AVR_ATmega324P__) || defined (__AVR_ATmega324A__) -// # include -// #elif defined (__AVR_ATmega324PA__) -// # include -// #elif defined (__AVR_ATmega325__) -// # include -// #elif defined (__AVR_ATmega325P__) -// # include -// #elif defined (__AVR_ATmega3250__) -// # include -// #elif defined (__AVR_ATmega3250P__) -// # include -// #elif defined (__AVR_ATmega328P__) || defined (__AVR_ATmega328__) -// # include -# include -// #elif defined (__AVR_ATmega329__) -// # include -// #elif defined (__AVR_ATmega329P__) || defined (__AVR_ATmega329PA__) -// # include -// #elif defined (__AVR_ATmega3290__) -// # include -// #elif defined (__AVR_ATmega3290P__) -// # include -// #elif defined (__AVR_ATmega32HVB__) -// # include -// #elif defined (__AVR_ATmega406__) -// # include -// #elif defined (__AVR_ATmega16__) -// # include -// #elif defined (__AVR_ATmega16A__) -// # include -// #elif defined (__AVR_ATmega161__) -// # include -// #elif defined (__AVR_ATmega162__) -// # include -// #elif defined (__AVR_ATmega163__) -// # include -// #elif defined (__AVR_ATmega164P__) || defined (__AVR_ATmega164A__) -// # include -// #elif defined (__AVR_ATmega165__) || defined (__AVR_ATmega165A__) -// # include -// #elif defined (__AVR_ATmega165P__) -// # include -// #elif defined (__AVR_ATmega168__) || defined (__AVR_ATmega168A__) -// # include -// #elif defined (__AVR_ATmega168P__) -// # include -// #elif defined (__AVR_ATmega169__) || defined (__AVR_ATmega169A__) -// # include -// #elif defined (__AVR_ATmega169P__) -// # include -// #elif defined (__AVR_ATmega169PA__) -// # include -// #elif defined (__AVR_ATmega8HVA__) -// # include -// #elif defined (__AVR_ATmega16HVA__) -// # include -// #elif defined (__AVR_ATmega16HVA2__) -// # include -// #elif defined (__AVR_ATmega16HVB__) -// # include -// #elif defined (__AVR_ATmega8__) -// # include -// #elif defined (__AVR_ATmega48__) || defined (__AVR_ATmega48A__) -// # include -// #elif defined (__AVR_ATmega48P__) -// # include -// #elif defined (__AVR_ATmega88__) || defined (__AVR_ATmega88A__) -// # include -// #elif defined (__AVR_ATmega88P__) -// # include -// #elif defined (__AVR_ATmega88PA__) -// # include -// #elif defined (__AVR_ATmega8515__) -// # include -// #elif defined (__AVR_ATmega8535__) -// # include -// #elif defined (__AVR_AT90S8535__) -// # include -// #elif defined (__AVR_AT90C8534__) -// # include -// #elif defined (__AVR_AT90S8515__) -// # include -// #elif defined (__AVR_AT90S4434__) -// # include -// #elif defined (__AVR_AT90S4433__) -// # include -// #elif defined (__AVR_AT90S4414__) -// # include -// #elif defined (__AVR_ATtiny22__) -// # include -// #elif defined (__AVR_ATtiny26__) -// # include -// #elif defined (__AVR_AT90S2343__) -// # include -// #elif defined (__AVR_AT90S2333__) -// # include -// #elif defined (__AVR_AT90S2323__) -// # include -// #elif defined (__AVR_AT90S2313__) -// # include -// #elif defined (__AVR_ATtiny2313__) -// # include -// #elif defined (__AVR_ATtiny2313A__) -// # include -// #elif defined (__AVR_ATtiny13__) -// # include -// #elif defined (__AVR_ATtiny13A__) -// # include -// #elif defined (__AVR_ATtiny25__) -// # include -// #elif defined (__AVR_ATtiny4313__) -// # include -// #elif defined (__AVR_ATtiny45__) -// # include -// #elif defined (__AVR_ATtiny85__) -// # include -// #elif defined (__AVR_ATtiny24__) -// # include -// #elif defined (__AVR_ATtiny24A__) -// # include -// #elif defined (__AVR_ATtiny44__) -// # include -// #elif defined (__AVR_ATtiny44A__) -// # include -// #elif defined (__AVR_ATtiny84__) -// # include -// #elif defined (__AVR_ATtiny261__) -// # include -// #elif defined (__AVR_ATtiny261A__) -// # include -// #elif defined (__AVR_ATtiny461__) -// # include -// #elif defined (__AVR_ATtiny461A__) -// # include -// #elif defined (__AVR_ATtiny861__) -// # include -// #elif defined (__AVR_ATtiny861A__) -// # include -// #elif defined (__AVR_ATtiny43U__) -// # include -// #elif defined (__AVR_ATtiny48__) -// # include -// #elif defined (__AVR_ATtiny88__) -// # include -// #elif defined (__AVR_ATtiny87__) -// # include -// #elif defined (__AVR_ATtiny167__) -// # include -// #elif defined (__AVR_AT90SCR100__) -// # include -// #elif defined (__AVR_ATxmega16A4__) -// # include -// #elif defined (__AVR_ATxmega16D4__) -// # include -// #elif defined (__AVR_ATxmega32A4__) -// # include -// #elif defined (__AVR_ATxmega32D4__) -// # include -// #elif defined (__AVR_ATxmega64A1__) -// # include -// #elif defined (__AVR_ATxmega64A3__) -// # include -// #elif defined (__AVR_ATxmega64D3__) -// # include -// #elif defined (__AVR_ATxmega128A1__) -// # include -// #elif defined (__AVR_ATxmega128A3__) -// # include -// #elif defined (__AVR_ATxmega128D3__) -// # include -// #elif defined (__AVR_ATxmega192A3__) -// # include -// #elif defined (__AVR_ATxmega192D3__) -// # include -// #elif defined (__AVR_ATxmega256A3__) -// # include -// #elif defined (__AVR_ATxmega256A3B__) -// # include -// #elif defined (__AVR_ATxmega256D3__) -// # include -// #elif defined (__AVR_ATA6289__) -// # include -// /* avr1: the following only supported for assembler programs */ -// #elif defined (__AVR_ATtiny28__) -// # include -// #elif defined (__AVR_AT90S1200__) -// # include -// #elif defined (__AVR_ATtiny15__) -// # include -// #elif defined (__AVR_ATtiny12__) -// # include -// #elif defined (__AVR_ATtiny11__) -// # include -// #else -// # if !defined(__COMPILING_AVR_LIBC__) -// # warning "device type not defined" -// # endif -// #endif - -// #include - -// #include - -// #include - -// /* Include fuse.h after individual IO header files. */ -// #include - -// /* Include lock.h after individual IO header files. */ -// #include - -#endif /* _AVR_IO_H_ */ diff --git a/examples/blinky/test/support/stub_iom328p.h b/examples/blinky/test/support/stub_iom328p.h deleted file mode 100644 index 76f8974c3..000000000 --- a/examples/blinky/test/support/stub_iom328p.h +++ /dev/null @@ -1,883 +0,0 @@ -/* Copyright (c) 2007 Atmel Corporation - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of the copyright holders nor the names of - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. -*/ - -/* $Id: iom328p.h,v 1.3.2.14 2009/02/11 18:05:28 arcanum Exp $ */ - -/* avr/iom328p.h - definitions for ATmega328P. */ - -/* This file should only be included from , never directly. */ - -// #ifndef _AVR_IO_H_ -// # error "Include instead of this file." -// #endif - -// #ifndef _AVR_IOXXX_H_ -// # define _AVR_IOXXX_H_ "iom328p.h" -// #else -// # error "Attempt to include more than one file." -// #endif - - -#ifndef _AVR_IOM328P_H_ -#define _AVR_IOM328P_H_ 1 - -/* Registers and associated bit numbers */ - -// #define PINB _SFR_IO8(0x03) -// #define PINB0 0 -// #define PINB1 1 -// #define PINB2 2 -// #define PINB3 3 -// #define PINB4 4 -// #define PINB5 5 -// #define PINB6 6 -// #define PINB7 7 - -// #define DDRB _SFR_IO8(0x04) -char DDRB; -#define DDB0 0 -#define DDB1 1 -#define DDB2 2 -#define DDB3 3 -#define DDB4 4 -#define DDB5 5 -#define DDB6 6 -#define DDB7 7 - -// #define PORTB _SFR_IO8(0x05) -char PORTB; -#define PORTB0 0 -#define PORTB1 1 -#define PORTB2 2 -#define PORTB3 3 -#define PORTB4 4 -#define PORTB5 5 -#define PORTB6 6 -#define PORTB7 7 - -// #define PINC _SFR_IO8(0x06) -// #define PINC0 0 -// #define PINC1 1 -// #define PINC2 2 -// #define PINC3 3 -// #define PINC4 4 -// #define PINC5 5 -// #define PINC6 6 - -// #define DDRC _SFR_IO8(0x07) -// #define DDC0 0 -// #define DDC1 1 -// #define DDC2 2 -// #define DDC3 3 -// #define DDC4 4 -// #define DDC5 5 -// #define DDC6 6 - -// #define PORTC _SFR_IO8(0x08) -// #define PORTC0 0 -// #define PORTC1 1 -// #define PORTC2 2 -// #define PORTC3 3 -// #define PORTC4 4 -// #define PORTC5 5 -// #define PORTC6 6 - -// #define PIND _SFR_IO8(0x09) -char PIND; -#define PIND0 0 -#define PIND1 1 -#define PIND2 2 -#define PIND3 3 -#define PIND4 4 -#define PIND5 5 -#define PIND6 6 -#define PIND7 7 - -// #define DDRD _SFR_IO8(0x0A) -char DDRD; -#define DDD0 0 -#define DDD1 1 -#define DDD2 2 -#define DDD3 3 -#define DDD4 4 -#define DDD5 5 -#define DDD6 6 -#define DDD7 7 - -// #define PORTD _SFR_IO8(0x0B) -char PORTD; -#define PORTD0 0 -#define PORTD1 1 -#define PORTD2 2 -#define PORTD3 3 -#define PORTD4 4 -#define PORTD5 5 -#define PORTD6 6 -#define PORTD7 7 - -// #define TIFR0 _SFR_IO8(0x15) -// #define TOV0 0 -// #define OCF0A 1 -// #define OCF0B 2 - -// #define TIFR1 _SFR_IO8(0x16) -// #define TOV1 0 -// #define OCF1A 1 -// #define OCF1B 2 -// #define ICF1 5 - -// #define TIFR2 _SFR_IO8(0x17) -// #define TOV2 0 -// #define OCF2A 1 -// #define OCF2B 2 - -// #define PCIFR _SFR_IO8(0x1B) -// #define PCIF0 0 -// #define PCIF1 1 -// #define PCIF2 2 - -// #define EIFR _SFR_IO8(0x1C) -// #define INTF0 0 -// #define INTF1 1 - -// #define EIMSK _SFR_IO8(0x1D) -// #define INT0 0 -// #define INT1 1 - -// #define GPIOR0 _SFR_IO8(0x1E) -// #define GPIOR00 0 -// #define GPIOR01 1 -// #define GPIOR02 2 -// #define GPIOR03 3 -// #define GPIOR04 4 -// #define GPIOR05 5 -// #define GPIOR06 6 -// #define GPIOR07 7 - -// #define EECR _SFR_IO8(0x1F) -// #define EERE 0 -// #define EEPE 1 -// #define EEMPE 2 -// #define EERIE 3 -// #define EEPM0 4 -// #define EEPM1 5 - -// #define EEDR _SFR_IO8(0x20) -// #define EEDR0 0 -// #define EEDR1 1 -// #define EEDR2 2 -// #define EEDR3 3 -// #define EEDR4 4 -// #define EEDR5 5 -// #define EEDR6 6 -// #define EEDR7 7 - -// #define EEAR _SFR_IO16(0x21) - -// #define EEARL _SFR_IO8(0x21) -// #define EEAR0 0 -// #define EEAR1 1 -// #define EEAR2 2 -// #define EEAR3 3 -// #define EEAR4 4 -// #define EEAR5 5 -// #define EEAR6 6 -// #define EEAR7 7 - -// #define EEARH _SFR_IO8(0x22) -// #define EEAR8 0 -// #define EEAR9 1 - -// #define _EEPROM_REG_LOCATIONS_ 1F2021 - -// #define GTCCR _SFR_IO8(0x23) -// #define PSRSYNC 0 -// #define PSRASY 1 -// #define TSM 7 - -// #define TCCR0A _SFR_IO8(0x24) -// #define WGM00 0 -// #define WGM01 1 -// #define COM0B0 4 -// #define COM0B1 5 -// #define COM0A0 6 -// #define COM0A1 7 - -// #define TCCR0B _SFR_IO8(0x25) -char TCCR0B; -#define CS00 0 -#define CS01 1 -#define CS02 2 -#define WGM02 3 -#define FOC0B 6 -#define FOC0A 7 - -// #define TCNT0 _SFR_IO8(0x26) -char TCNT0; -#define TCNT0_0 0 -#define TCNT0_1 1 -#define TCNT0_2 2 -#define TCNT0_3 3 -#define TCNT0_4 4 -#define TCNT0_5 5 -#define TCNT0_6 6 -#define TCNT0_7 7 - -// #define OCR0A _SFR_IO8(0x27) -// #define OCR0A_0 0 -// #define OCR0A_1 1 -// #define OCR0A_2 2 -// #define OCR0A_3 3 -// #define OCR0A_4 4 -// #define OCR0A_5 5 -// #define OCR0A_6 6 -// #define OCR0A_7 7 - -// #define OCR0B _SFR_IO8(0x28) -// #define OCR0B_0 0 -// #define OCR0B_1 1 -// #define OCR0B_2 2 -// #define OCR0B_3 3 -// #define OCR0B_4 4 -// #define OCR0B_5 5 -// #define OCR0B_6 6 -// #define OCR0B_7 7 - -// #define GPIOR1 _SFR_IO8(0x2A) -// #define GPIOR10 0 -// #define GPIOR11 1 -// #define GPIOR12 2 -// #define GPIOR13 3 -// #define GPIOR14 4 -// #define GPIOR15 5 -// #define GPIOR16 6 -// #define GPIOR17 7 - -// #define GPIOR2 _SFR_IO8(0x2B) -// #define GPIOR20 0 -// #define GPIOR21 1 -// #define GPIOR22 2 -// #define GPIOR23 3 -// #define GPIOR24 4 -// #define GPIOR25 5 -// #define GPIOR26 6 -// #define GPIOR27 7 - -// #define SPCR _SFR_IO8(0x2C) -// #define SPR0 0 -// #define SPR1 1 -// #define CPHA 2 -// #define CPOL 3 -// #define MSTR 4 -// #define DORD 5 -// #define SPE 6 -// #define SPIE 7 - -// #define SPSR _SFR_IO8(0x2D) -// #define SPI2X 0 -// #define WCOL 6 -// #define SPIF 7 - -// #define SPDR _SFR_IO8(0x2E) -// #define SPDR0 0 -// #define SPDR1 1 -// #define SPDR2 2 -// #define SPDR3 3 -// #define SPDR4 4 -// #define SPDR5 5 -// #define SPDR6 6 -// #define SPDR7 7 - -// #define ACSR _SFR_IO8(0x30) -// #define ACIS0 0 -// #define ACIS1 1 -// #define ACIC 2 -// #define ACIE 3 -// #define ACI 4 -// #define ACO 5 -// #define ACBG 6 -// #define ACD 7 - -// #define SMCR _SFR_IO8(0x33) -// #define SE 0 -// #define SM0 1 -// #define SM1 2 -// #define SM2 3 - -// #define MCUSR _SFR_IO8(0x34) -// #define PORF 0 -// #define EXTRF 1 -// #define BORF 2 -// #define WDRF 3 - -// #define MCUCR _SFR_IO8(0x35) -// #define IVCE 0 -// #define IVSEL 1 -// #define PUD 4 -// #define BODSE 5 -// #define BODS 6 - -// #define SPMCSR _SFR_IO8(0x37) -// #define SELFPRGEN 0 -// #define PGERS 1 -// #define PGWRT 2 -// #define BLBSET 3 -// #define RWWSRE 4 -// #define RWWSB 6 -// #define SPMIE 7 - -// #define WDTCSR _SFR_MEM8(0x60) -// #define WDP0 0 -// #define WDP1 1 -// #define WDP2 2 -// #define WDE 3 -// #define WDCE 4 -// #define WDP3 5 -// #define WDIE 6 -// #define WDIF 7 - -// #define CLKPR _SFR_MEM8(0x61) -// #define CLKPS0 0 -// #define CLKPS1 1 -// #define CLKPS2 2 -// #define CLKPS3 3 -// #define CLKPCE 7 - -// #define PRR _SFR_MEM8(0x64) -// #define PRADC 0 -// #define PRUSART0 1 -// #define PRSPI 2 -// #define PRTIM1 3 -// #define PRTIM0 5 -// #define PRTIM2 6 -// #define PRTWI 7 - -// #define OSCCAL _SFR_MEM8(0x66) -// #define CAL0 0 -// #define CAL1 1 -// #define CAL2 2 -// #define CAL3 3 -// #define CAL4 4 -// #define CAL5 5 -// #define CAL6 6 -// #define CAL7 7 - -// #define PCICR _SFR_MEM8(0x68) -// #define PCIE0 0 -// #define PCIE1 1 -// #define PCIE2 2 - -// #define EICRA _SFR_MEM8(0x69) -// #define ISC00 0 -// #define ISC01 1 -// #define ISC10 2 -// #define ISC11 3 - -// #define PCMSK0 _SFR_MEM8(0x6B) -// #define PCINT0 0 -// #define PCINT1 1 -// #define PCINT2 2 -// #define PCINT3 3 -// #define PCINT4 4 -// #define PCINT5 5 -// #define PCINT6 6 -// #define PCINT7 7 - -// #define PCMSK1 _SFR_MEM8(0x6C) -// #define PCINT8 0 -// #define PCINT9 1 -// #define PCINT10 2 -// #define PCINT11 3 -// #define PCINT12 4 -// #define PCINT13 5 -// #define PCINT14 6 - -// #define PCMSK2 _SFR_MEM8(0x6D) -// #define PCINT16 0 -// #define PCINT17 1 -// #define PCINT18 2 -// #define PCINT19 3 -// #define PCINT20 4 -// #define PCINT21 5 -// #define PCINT22 6 -// #define PCINT23 7 - -// #define TIMSK0 _SFR_MEM8(0x6E) -char TIMSK0; -#define TOIE0 0 -#define OCIE0A 1 -#define OCIE0B 2 - -// #define TIMSK1 _SFR_MEM8(0x6F) -// #define TOIE1 0 -// #define OCIE1A 1 -// #define OCIE1B 2 -// #define ICIE1 5 - -// #define TIMSK2 _SFR_MEM8(0x70) -// #define TOIE2 0 -// #define OCIE2A 1 -// #define OCIE2B 2 - -// #ifndef __ASSEMBLER__ -// #define ADC _SFR_MEM16(0x78) -// #endif -// #define ADCW _SFR_MEM16(0x78) - -// #define ADCL _SFR_MEM8(0x78) -// #define ADCL0 0 -// #define ADCL1 1 -// #define ADCL2 2 -// #define ADCL3 3 -// #define ADCL4 4 -// #define ADCL5 5 -// #define ADCL6 6 -// #define ADCL7 7 - -// #define ADCH _SFR_MEM8(0x79) -// #define ADCH0 0 -// #define ADCH1 1 -// #define ADCH2 2 -// #define ADCH3 3 -// #define ADCH4 4 -// #define ADCH5 5 -// #define ADCH6 6 -// #define ADCH7 7 - -// #define ADCSRA _SFR_MEM8(0x7A) -// #define ADPS0 0 -// #define ADPS1 1 -// #define ADPS2 2 -// #define ADIE 3 -// #define ADIF 4 -// #define ADATE 5 -// #define ADSC 6 -// #define ADEN 7 - -// #define ADCSRB _SFR_MEM8(0x7B) -// #define ADTS0 0 -// #define ADTS1 1 -// #define ADTS2 2 -// #define ACME 6 - -// #define ADMUX _SFR_MEM8(0x7C) -// #define MUX0 0 -// #define MUX1 1 -// #define MUX2 2 -// #define MUX3 3 -// #define ADLAR 5 -// #define REFS0 6 -// #define REFS1 7 - -// #define DIDR0 _SFR_MEM8(0x7E) -// #define ADC0D 0 -// #define ADC1D 1 -// #define ADC2D 2 -// #define ADC3D 3 -// #define ADC4D 4 -// #define ADC5D 5 - -// #define DIDR1 _SFR_MEM8(0x7F) -// #define AIN0D 0 -// #define AIN1D 1 - -// #define TCCR1A _SFR_MEM8(0x80) -// #define WGM10 0 -// #define WGM11 1 -// #define COM1B0 4 -// #define COM1B1 5 -// #define COM1A0 6 -// #define COM1A1 7 - -// #define TCCR1B _SFR_MEM8(0x81) -// #define CS10 0 -// #define CS11 1 -// #define CS12 2 -// #define WGM12 3 -// #define WGM13 4 -// #define ICES1 6 -// #define ICNC1 7 - -// #define TCCR1C _SFR_MEM8(0x82) -// #define FOC1B 6 -// #define FOC1A 7 - -// #define TCNT1 _SFR_MEM16(0x84) - -// #define TCNT1L _SFR_MEM8(0x84) -// #define TCNT1L0 0 -// #define TCNT1L1 1 -// #define TCNT1L2 2 -// #define TCNT1L3 3 -// #define TCNT1L4 4 -// #define TCNT1L5 5 -// #define TCNT1L6 6 -// #define TCNT1L7 7 - -// #define TCNT1H _SFR_MEM8(0x85) -// #define TCNT1H0 0 -// #define TCNT1H1 1 -// #define TCNT1H2 2 -// #define TCNT1H3 3 -// #define TCNT1H4 4 -// #define TCNT1H5 5 -// #define TCNT1H6 6 -// #define TCNT1H7 7 - -// #define ICR1 _SFR_MEM16(0x86) - -// #define ICR1L _SFR_MEM8(0x86) -// #define ICR1L0 0 -// #define ICR1L1 1 -// #define ICR1L2 2 -// #define ICR1L3 3 -// #define ICR1L4 4 -// #define ICR1L5 5 -// #define ICR1L6 6 -// #define ICR1L7 7 - -// #define ICR1H _SFR_MEM8(0x87) -// #define ICR1H0 0 -// #define ICR1H1 1 -// #define ICR1H2 2 -// #define ICR1H3 3 -// #define ICR1H4 4 -// #define ICR1H5 5 -// #define ICR1H6 6 -// #define ICR1H7 7 - -// #define OCR1A _SFR_MEM16(0x88) - -// #define OCR1AL _SFR_MEM8(0x88) -// #define OCR1AL0 0 -// #define OCR1AL1 1 -// #define OCR1AL2 2 -// #define OCR1AL3 3 -// #define OCR1AL4 4 -// #define OCR1AL5 5 -// #define OCR1AL6 6 -// #define OCR1AL7 7 - -// #define OCR1AH _SFR_MEM8(0x89) -// #define OCR1AH0 0 -// #define OCR1AH1 1 -// #define OCR1AH2 2 -// #define OCR1AH3 3 -// #define OCR1AH4 4 -// #define OCR1AH5 5 -// #define OCR1AH6 6 -// #define OCR1AH7 7 - -// #define OCR1B _SFR_MEM16(0x8A) - -// #define OCR1BL _SFR_MEM8(0x8A) -// #define OCR1BL0 0 -// #define OCR1BL1 1 -// #define OCR1BL2 2 -// #define OCR1BL3 3 -// #define OCR1BL4 4 -// #define OCR1BL5 5 -// #define OCR1BL6 6 -// #define OCR1BL7 7 - -// #define OCR1BH _SFR_MEM8(0x8B) -// #define OCR1BH0 0 -// #define OCR1BH1 1 -// #define OCR1BH2 2 -// #define OCR1BH3 3 -// #define OCR1BH4 4 -// #define OCR1BH5 5 -// #define OCR1BH6 6 -// #define OCR1BH7 7 - -// #define TCCR2A _SFR_MEM8(0xB0) -// #define WGM20 0 -// #define WGM21 1 -// #define COM2B0 4 -// #define COM2B1 5 -// #define COM2A0 6 -// #define COM2A1 7 - -// #define TCCR2B _SFR_MEM8(0xB1) -// #define CS20 0 -// #define CS21 1 -// #define CS22 2 -// #define WGM22 3 -// #define FOC2B 6 -// #define FOC2A 7 - -// #define TCNT2 _SFR_MEM8(0xB2) -// #define TCNT2_0 0 -// #define TCNT2_1 1 -// #define TCNT2_2 2 -// #define TCNT2_3 3 -// #define TCNT2_4 4 -// #define TCNT2_5 5 -// #define TCNT2_6 6 -// #define TCNT2_7 7 - -// #define OCR2A _SFR_MEM8(0xB3) -// #define OCR2_0 0 -// #define OCR2_1 1 -// #define OCR2_2 2 -// #define OCR2_3 3 -// #define OCR2_4 4 -// #define OCR2_5 5 -// #define OCR2_6 6 -// #define OCR2_7 7 - -// #define OCR2B _SFR_MEM8(0xB4) -// #define OCR2_0 0 -// #define OCR2_1 1 -// #define OCR2_2 2 -// #define OCR2_3 3 -// #define OCR2_4 4 -// #define OCR2_5 5 -// #define OCR2_6 6 -// #define OCR2_7 7 - -// #define ASSR _SFR_MEM8(0xB6) -// #define TCR2BUB 0 -// #define TCR2AUB 1 -// #define OCR2BUB 2 -// #define OCR2AUB 3 -// #define TCN2UB 4 -// #define AS2 5 -// #define EXCLK 6 - -// #define TWBR _SFR_MEM8(0xB8) -// #define TWBR0 0 -// #define TWBR1 1 -// #define TWBR2 2 -// #define TWBR3 3 -// #define TWBR4 4 -// #define TWBR5 5 -// #define TWBR6 6 -// #define TWBR7 7 - -// #define TWSR _SFR_MEM8(0xB9) -// #define TWPS0 0 -// #define TWPS1 1 -// #define TWS3 3 -// #define TWS4 4 -// #define TWS5 5 -// #define TWS6 6 -// #define TWS7 7 - -// #define TWAR _SFR_MEM8(0xBA) -// #define TWGCE 0 -// #define TWA0 1 -// #define TWA1 2 -// #define TWA2 3 -// #define TWA3 4 -// #define TWA4 5 -// #define TWA5 6 -// #define TWA6 7 - -// #define TWDR _SFR_MEM8(0xBB) -// #define TWD0 0 -// #define TWD1 1 -// #define TWD2 2 -// #define TWD3 3 -// #define TWD4 4 -// #define TWD5 5 -// #define TWD6 6 -// #define TWD7 7 - -// #define TWCR _SFR_MEM8(0xBC) -// #define TWIE 0 -// #define TWEN 2 -// #define TWWC 3 -// #define TWSTO 4 -// #define TWSTA 5 -// #define TWEA 6 -// #define TWINT 7 - -// #define TWAMR _SFR_MEM8(0xBD) -// #define TWAM0 0 -// #define TWAM1 1 -// #define TWAM2 2 -// #define TWAM3 3 -// #define TWAM4 4 -// #define TWAM5 5 -// #define TWAM6 6 - -// #define UCSR0A _SFR_MEM8(0xC0) -// #define MPCM0 0 -// #define U2X0 1 -// #define UPE0 2 -// #define DOR0 3 -// #define FE0 4 -// #define UDRE0 5 -// #define TXC0 6 -// #define RXC0 7 - -// #define UCSR0B _SFR_MEM8(0xC1) -// #define TXB80 0 -// #define RXB80 1 -// #define UCSZ02 2 -// #define TXEN0 3 -// #define RXEN0 4 -// #define UDRIE0 5 -// #define TXCIE0 6 -// #define RXCIE0 7 - -// #define UCSR0C _SFR_MEM8(0xC2) -// #define UCPOL0 0 -// #define UCSZ00 1 -// #define UCPHA0 1 -// #define UCSZ01 2 -// #define UDORD0 2 -// #define USBS0 3 -// #define UPM00 4 -// #define UPM01 5 -// #define UMSEL00 6 -// #define UMSEL01 7 - -// #define UBRR0 _SFR_MEM16(0xC4) - -// #define UBRR0L _SFR_MEM8(0xC4) -// #define UBRR0_0 0 -// #define UBRR0_1 1 -// #define UBRR0_2 2 -// #define UBRR0_3 3 -// #define UBRR0_4 4 -// #define UBRR0_5 5 -// #define UBRR0_6 6 -// #define UBRR0_7 7 - -// #define UBRR0H _SFR_MEM8(0xC5) -// #define UBRR0_8 0 -// #define UBRR0_9 1 -// #define UBRR0_10 2 -// #define UBRR0_11 3 - -// #define UDR0 _SFR_MEM8(0xC6) -// #define UDR0_0 0 -// #define UDR0_1 1 -// #define UDR0_2 2 -// #define UDR0_3 3 -// #define UDR0_4 4 -// #define UDR0_5 5 -// #define UDR0_6 6 -// #define UDR0_7 7 - - - -// /* Interrupt Vectors */ -// /* Interrupt Vector 0 is the reset vector. */ -// #define INT0_vect _VECTOR(1) /* External Interrupt Request 0 */ -// #define INT1_vect _VECTOR(2) /* External Interrupt Request 1 */ -// #define PCINT0_vect _VECTOR(3) /* Pin Change Interrupt Request 0 */ -// #define PCINT1_vect _VECTOR(4) /* Pin Change Interrupt Request 0 */ -// #define PCINT2_vect _VECTOR(5) /* Pin Change Interrupt Request 1 */ -// #define WDT_vect _VECTOR(6) /* Watchdog Time-out Interrupt */ -// #define TIMER2_COMPA_vect _VECTOR(7) /* Timer/Counter2 Compare Match A */ -// #define TIMER2_COMPB_vect _VECTOR(8) /* Timer/Counter2 Compare Match A */ -// #define TIMER2_OVF_vect _VECTOR(9) /* Timer/Counter2 Overflow */ -// #define TIMER1_CAPT_vect _VECTOR(10) /* Timer/Counter1 Capture Event */ -// #define TIMER1_COMPA_vect _VECTOR(11) /* Timer/Counter1 Compare Match A */ -// #define TIMER1_COMPB_vect _VECTOR(12) /* Timer/Counter1 Compare Match B */ -// #define TIMER1_OVF_vect _VECTOR(13) /* Timer/Counter1 Overflow */ -// #define TIMER0_COMPA_vect _VECTOR(14) /* TimerCounter0 Compare Match A */ -// #define TIMER0_COMPB_vect _VECTOR(15) /* TimerCounter0 Compare Match B */ -// #define TIMER0_OVF_vect _VECTOR(16) /* Timer/Couner0 Overflow */ -// #define SPI_STC_vect _VECTOR(17) /* SPI Serial Transfer Complete */ -// #define USART_RX_vect _VECTOR(18) /* USART Rx Complete */ -// #define USART_UDRE_vect _VECTOR(19) /* USART, Data Register Empty */ -// #define USART_TX_vect _VECTOR(20) /* USART Tx Complete */ -// #define ADC_vect _VECTOR(21) /* ADC Conversion Complete */ -// #define EE_READY_vect _VECTOR(22) /* EEPROM Ready */ -// #define ANALOG_COMP_vect _VECTOR(23) /* Analog Comparator */ -// #define TWI_vect _VECTOR(24) /* Two-wire Serial Interface */ -// #define SPM_READY_vect _VECTOR(25) /* Store Program Memory Read */ - -// #define _VECTORS_SIZE (26 * 4) - - - -// /* Constants */ -// #define SPM_PAGESIZE 128 -// #define RAMEND 0x8FF /* Last On-Chip SRAM Location */ -// #define XRAMSIZE 0 -// #define XRAMEND RAMEND -// #define E2END 0x3FF -// #define E2PAGESIZE 4 -// #define FLASHEND 0x7FFF - - - -// /* Fuses */ -// #define FUSE_MEMORY_SIZE 3 - -// /* Low Fuse Byte */ -// #define FUSE_CKSEL0 (unsigned char)~_BV(0) /* Select Clock Source */ -// #define FUSE_CKSEL1 (unsigned char)~_BV(1) /* Select Clock Source */ -// #define FUSE_CKSEL2 (unsigned char)~_BV(2) /* Select Clock Source */ -// #define FUSE_CKSEL3 (unsigned char)~_BV(3) /* Select Clock Source */ -// #define FUSE_SUT0 (unsigned char)~_BV(4) /* Select start-up time */ -// #define FUSE_SUT1 (unsigned char)~_BV(5) /* Select start-up time */ -// #define FUSE_CKOUT (unsigned char)~_BV(6) /* Clock output */ -// #define FUSE_CKDIV8 (unsigned char)~_BV(7) /* Divide clock by 8 */ -// #define LFUSE_DEFAULT (FUSE_CKSEL0 & FUSE_CKSEL2 & FUSE_CKSEL3 & FUSE_SUT0 & FUSE_CKDIV8) - -// /* High Fuse Byte */ -// #define FUSE_BODLEVEL0 (unsigned char)~_BV(0) /* Brown-out Detector trigger level */ -// #define FUSE_BODLEVEL1 (unsigned char)~_BV(1) /* Brown-out Detector trigger level */ -// #define FUSE_BODLEVEL2 (unsigned char)~_BV(2) /* Brown-out Detector trigger level */ -// #define FUSE_EESAVE (unsigned char)~_BV(3) /* EEPROM memory is preserved through chip erase */ -// #define FUSE_WDTON (unsigned char)~_BV(4) /* Watchdog Timer Always On */ -// #define FUSE_SPIEN (unsigned char)~_BV(5) /* Enable Serial programming and Data Downloading */ -// #define FUSE_DWEN (unsigned char)~_BV(6) /* debugWIRE Enable */ -// #define FUSE_RSTDISBL (unsigned char)~_BV(7) /* External reset disable */ -// #define HFUSE_DEFAULT (FUSE_SPIEN) - -// /* Extended Fuse Byte */ -// #define FUSE_BOOTRST (unsigned char)~_BV(0) -// #define FUSE_BOOTSZ0 (unsigned char)~_BV(1) -// #define FUSE_BOOTSZ1 (unsigned char)~_BV(2) -// #define EFUSE_DEFAULT (FUSE_BOOTSZ0 & FUSE_BOOTSZ1) - - - -// /* Lock Bits */ -// #define __LOCK_BITS_EXIST -// #define __BOOT_LOCK_BITS_0_EXIST -// #define __BOOT_LOCK_BITS_1_EXIST - - -// /* Signature */ -// #define SIGNATURE_0 0x1E -// #define SIGNATURE_1 0x95 -// #define SIGNATURE_2 0x0F - - -#endif /* _AVR_IOM328P_H_ */ diff --git a/examples/blinky/test/support/stub_sfr_defs.h b/examples/blinky/test/support/stub_sfr_defs.h deleted file mode 100644 index c3bdc2cc9..000000000 --- a/examples/blinky/test/support/stub_sfr_defs.h +++ /dev/null @@ -1,269 +0,0 @@ -/* Copyright (c) 2002, Marek Michalkiewicz - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - - * Neither the name of the copyright holders nor the names of - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE - LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR - CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF - SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - POSSIBILITY OF SUCH DAMAGE. */ - -/* avr/sfr_defs.h - macros for accessing AVR special function registers */ - -/* $Id: sfr_defs.h,v 1.18.2.1 2008/04/28 22:05:42 arcanum Exp $ */ - -#ifndef _AVR_SFR_DEFS_H_ -#define _AVR_SFR_DEFS_H_ 1 - -/** \defgroup avr_sfr_notes Additional notes from - \ingroup avr_sfr - - The \c file is included by all of the \c - files, which use macros defined here to make the special function register - definitions look like C variables or simple constants, depending on the - _SFR_ASM_COMPAT define. Some examples from \c to - show how to define such macros: - -\code -#define PORTA _SFR_IO8(0x02) -#define EEAR _SFR_IO16(0x21) -#define UDR0 _SFR_MEM8(0xC6) -#define TCNT3 _SFR_MEM16(0x94) -#define CANIDT _SFR_MEM32(0xF0) -\endcode - - If \c _SFR_ASM_COMPAT is not defined, C programs can use names like - PORTA directly in C expressions (also on the left side of - assignment operators) and GCC will do the right thing (use short I/O - instructions if possible). The \c __SFR_OFFSET definition is not used in - any way in this case. - - Define \c _SFR_ASM_COMPAT as 1 to make these names work as simple constants - (addresses of the I/O registers). This is necessary when included in - preprocessed assembler (*.S) source files, so it is done automatically if - \c __ASSEMBLER__ is defined. By default, all addresses are defined as if - they were memory addresses (used in \c lds/sts instructions). To use these - addresses in \c in/out instructions, you must subtract 0x20 from them. - - For more backwards compatibility, insert the following at the start of your - old assembler source file: - -\code -#define __SFR_OFFSET 0 -\endcode - - This automatically subtracts 0x20 from I/O space addresses, but it's a - hack, so it is recommended to change your source: wrap such addresses in - macros defined here, as shown below. After this is done, the - __SFR_OFFSET definition is no longer necessary and can be removed. - - Real example - this code could be used in a boot loader that is portable - between devices with \c SPMCR at different addresses. - -\verbatim -: #define SPMCR _SFR_IO8(0x37) -: #define SPMCR _SFR_MEM8(0x68) -\endverbatim - -\code -#if _SFR_IO_REG_P(SPMCR) - out _SFR_IO_ADDR(SPMCR), r24 -#else - sts _SFR_MEM_ADDR(SPMCR), r24 -#endif -\endcode - - You can use the \c in/out/cbi/sbi/sbic/sbis instructions, without the - _SFR_IO_REG_P test, if you know that the register is in the I/O - space (as with \c SREG, for example). If it isn't, the assembler will - complain (I/O address out of range 0...0x3f), so this should be fairly - safe. - - If you do not define \c __SFR_OFFSET (so it will be 0x20 by default), all - special register addresses are defined as memory addresses (so \c SREG is - 0x5f), and (if code size and speed are not important, and you don't like - the ugly \#if above) you can always use lds/sts to access them. But, this - will not work if __SFR_OFFSET != 0x20, so use a different macro - (defined only if __SFR_OFFSET == 0x20) for safety: - -\code - sts _SFR_ADDR(SPMCR), r24 -\endcode - - In C programs, all 3 combinations of \c _SFR_ASM_COMPAT and - __SFR_OFFSET are supported - the \c _SFR_ADDR(SPMCR) macro can be - used to get the address of the \c SPMCR register (0x57 or 0x68 depending on - device). */ - -#ifdef __ASSEMBLER__ -#define _SFR_ASM_COMPAT 1 -#elif !defined(_SFR_ASM_COMPAT) -#define _SFR_ASM_COMPAT 0 -#endif - -#ifndef __ASSEMBLER__ -/* These only work in C programs. */ -#include - -#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr)) -#define _MMIO_WORD(mem_addr) (*(volatile uint16_t *)(mem_addr)) -#define _MMIO_DWORD(mem_addr) (*(volatile uint32_t *)(mem_addr)) -#endif - -#if _SFR_ASM_COMPAT - -#ifndef __SFR_OFFSET -/* Define as 0 before including this file for compatibility with old asm - sources that don't subtract __SFR_OFFSET from symbolic I/O addresses. */ -# if __AVR_ARCH__ >= 100 -# define __SFR_OFFSET 0x00 -# else -# define __SFR_OFFSET 0x20 -# endif -#endif - -#if (__SFR_OFFSET != 0) && (__SFR_OFFSET != 0x20) -#error "__SFR_OFFSET must be 0 or 0x20" -#endif - -#define _SFR_MEM8(mem_addr) (mem_addr) -#define _SFR_MEM16(mem_addr) (mem_addr) -#define _SFR_MEM32(mem_addr) (mem_addr) -#define _SFR_IO8(io_addr) ((io_addr) + __SFR_OFFSET) -#define _SFR_IO16(io_addr) ((io_addr) + __SFR_OFFSET) - -#define _SFR_IO_ADDR(sfr) ((sfr) - __SFR_OFFSET) -#define _SFR_MEM_ADDR(sfr) (sfr) -#define _SFR_IO_REG_P(sfr) ((sfr) < 0x40 + __SFR_OFFSET) - -#if (__SFR_OFFSET == 0x20) -/* No need to use ?: operator, so works in assembler too. */ -#define _SFR_ADDR(sfr) _SFR_MEM_ADDR(sfr) -#elif !defined(__ASSEMBLER__) -#define _SFR_ADDR(sfr) (_SFR_IO_REG_P(sfr) ? (_SFR_IO_ADDR(sfr) + 0x20) : _SFR_MEM_ADDR(sfr)) -#endif - -#else /* !_SFR_ASM_COMPAT */ - -#ifndef __SFR_OFFSET -# if __AVR_ARCH__ >= 100 -# define __SFR_OFFSET 0x00 -# else -# define __SFR_OFFSET 0x20 -# endif -#endif - -#define _SFR_MEM8(mem_addr) _MMIO_BYTE(mem_addr) -#define _SFR_MEM16(mem_addr) _MMIO_WORD(mem_addr) -#define _SFR_MEM32(mem_addr) _MMIO_DWORD(mem_addr) -#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET) -#define _SFR_IO16(io_addr) _MMIO_WORD((io_addr) + __SFR_OFFSET) - -#define _SFR_MEM_ADDR(sfr) ((uint16_t) &(sfr)) -#define _SFR_IO_ADDR(sfr) (_SFR_MEM_ADDR(sfr) - __SFR_OFFSET) -#define _SFR_IO_REG_P(sfr) (_SFR_MEM_ADDR(sfr) < 0x40 + __SFR_OFFSET) - -#define _SFR_ADDR(sfr) _SFR_MEM_ADDR(sfr) - -#endif /* !_SFR_ASM_COMPAT */ - -#define _SFR_BYTE(sfr) _MMIO_BYTE(_SFR_ADDR(sfr)) -#define _SFR_WORD(sfr) _MMIO_WORD(_SFR_ADDR(sfr)) -#define _SFR_DWORD(sfr) _MMIO_DWORD(_SFR_ADDR(sfr)) - -/** \name Bit manipulation */ - -/*@{*/ -/** \def _BV - \ingroup avr_sfr - - \code #include \endcode - - Converts a bit number into a byte value. - - \note The bit shift is performed by the compiler which then inserts the - result into the code. Thus, there is no run-time overhead when using - _BV(). */ - -#define _BV(bit) (1 << (bit)) - -/*@}*/ - -#ifndef _VECTOR -#define _VECTOR(N) __vector_ ## N -#endif - -#ifndef __ASSEMBLER__ - - -/** \name IO register bit manipulation */ - -/*@{*/ - - - -/** \def bit_is_set - \ingroup avr_sfr - - \code #include \endcode - - Test whether bit \c bit in IO register \c sfr is set. - This will return a 0 if the bit is clear, and non-zero - if the bit is set. */ - -#define bit_is_set(sfr, bit) (_SFR_BYTE(sfr) & _BV(bit)) - -/** \def bit_is_clear - \ingroup avr_sfr - - \code #include \endcode - - Test whether bit \c bit in IO register \c sfr is clear. - This will return non-zero if the bit is clear, and a 0 - if the bit is set. */ - -#define bit_is_clear(sfr, bit) (!(_SFR_BYTE(sfr) & _BV(bit))) - -/** \def loop_until_bit_is_set - \ingroup avr_sfr - - \code #include \endcode - - Wait until bit \c bit in IO register \c sfr is set. */ - -#define loop_until_bit_is_set(sfr, bit) do { } while (bit_is_clear(sfr, bit)) - -/** \def loop_until_bit_is_clear - \ingroup avr_sfr - - \code #include \endcode - - Wait until bit \c bit in IO register \c sfr is clear. */ - -#define loop_until_bit_is_clear(sfr, bit) do { } while (bit_is_set(sfr, bit)) - -/*@}*/ - -#endif /* !__ASSEMBLER__ */ - -#endif /* _SFR_DEFS_H_ */ diff --git a/examples/blinky/test/test_BlinkTask.c b/examples/blinky/test/test_BlinkTask.c deleted file mode 100644 index be761188c..000000000 --- a/examples/blinky/test/test_BlinkTask.c +++ /dev/null @@ -1,42 +0,0 @@ -#include "unity.h" -#include "BlinkTask.h" -#include "stub_io.h" - -// every test file requires this function -// setUp() is called by the generated runner before each test case function -void setUp(void) -{ - PORTB = 0; -} - -// every test file requires this function -// tearDown() is called by the generated runner before each test case function -void tearDown(void) -{ -} - -void test_BlinkTask_should_toggle_led(void) -{ - /* Ensure known test state */ - - /* Setup expected call chain */ - - /* Call function under test */ - BlinkTask(); - - /* Verify test results */ - TEST_ASSERT_EQUAL(0x20, PORTB); -} -void test_BlinkTask_should_toggle_led_LOW(void) -{ - /* Ensure known test state */ - PORTB = 0x20; - - /* Setup expected call chain */ - - /* Call function under test */ - BlinkTask(); - - /* Verify test results */ - TEST_ASSERT_EQUAL(0, PORTB); -} diff --git a/examples/blinky/test/test_Configure.c b/examples/blinky/test/test_Configure.c deleted file mode 100644 index 71dc335e0..000000000 --- a/examples/blinky/test/test_Configure.c +++ /dev/null @@ -1,29 +0,0 @@ -#include "unity.h" -#include "Configure.h" -#include "stub_io.h" -#include "mock_stub_interrupt.h" - -void setUp(void) -{ -} - -void tearDown(void) -{ -} - -void test_Configure_should_setup_timer_and_port(void) - { - /* Ensure known test state */ - - /* Setup expected call chain */ - //these are defined into assembly instructions. - cli_Expect(); - sei_Expect(); - /* Call function under test */ - Configure(); - - /* Verify test results */ - TEST_ASSERT_EQUAL(3, TCCR0B); - TEST_ASSERT_EQUAL(1, TIMSK0); - TEST_ASSERT_EQUAL(0x20, DDRB); -} diff --git a/examples/blinky/test/test_main.c b/examples/blinky/test/test_main.c deleted file mode 100644 index 884f00731..000000000 --- a/examples/blinky/test/test_main.c +++ /dev/null @@ -1,60 +0,0 @@ -#include "unity.h" -#include "main.h" -#include "stub_io.h" -#include "mock_Configure.h" -#include "mock_BlinkTask.h" -void setUp(void) {} // every test file requires this function; - // setUp() is called by the generated runner before each test case function -void tearDown(void) {} // every test file requires this function; - // tearDown() is called by the generated runner before each test case function - -void test_AppMain_should_call_configure(void) -{ - /* Ensure known test state */ - BlinkTaskReady=0; - /* Setup expected call chain */ - Configure_Expect(); - /* Call function under test */ - AppMain(); - - /* Verify test results */ - TEST_ASSERT_EQUAL(0, BlinkTaskReady); -} -void test_AppMain_should_call_configure_and_BlinkTask(void) -{ - /* Ensure known test state */ - BlinkTaskReady=1; - /* Setup expected call chain */ - Configure_Expect(); - BlinkTask_Expect(); - /* Call function under test */ - AppMain(); - - /* Verify test results */ - TEST_ASSERT_EQUAL(0, BlinkTaskReady); -} -void test_ISR_should_increment_tick(void) -{ - /* Ensure known test state */ - tick = 0; - /* Setup expected call chain */ - - /* Call function under test */ - ISR(); - - /* Verify test results */ - TEST_ASSERT_EQUAL(1, tick); -} -void test_ISR_should_set_blinkReady_increment_tick(void) -{ - /* Ensure known test state */ - tick = 1000; - /* Setup expected call chain */ - - /* Call function under test */ - ISR(); - - /* Verify test results */ - TEST_ASSERT_EQUAL(1, tick); - TEST_ASSERT_EQUAL(1, BlinkTaskReady); -} diff --git a/examples/temp_sensor/README.md b/examples/temp_sensor/README.md new file mode 100644 index 000000000..b4557413a --- /dev/null +++ b/examples/temp_sensor/README.md @@ -0,0 +1,18 @@ +# Ceedling Temp-Sensor Example + +*Welcome to the Temp-Sensor Example!* This is a medium-sized example of how Ceedling +might work in your system. You can use it to verify you have all the tools installed. +You can use it as a starting point for your own code. You can build upon it for your +own creations, though honestly it's a bit of a mess. While it has some good ideas in +it, really it serves as a testing ground for some of Ceedling's features, and a place +for you to explore how Ceedling works. + +You'll find all the source files in the `src` folder. You'll find all the tests in +(you guessed it) the `test` folder (and its sub-folders). There are also some files +in the `test/support` folder, which demonstrate how you can create your own assertions. + +This project assumes you have `gcov` and `gcovr` installed for collecting coverage +information. If that's not true, you can just remove the `gcov` plugin from the +`plugins` list. Everything else will work fine. + +Have fun poking around! diff --git a/examples/temp_sensor/mixin/add_gcov.yml b/examples/temp_sensor/mixin/add_gcov.yml new file mode 100644 index 000000000..4cc54ed33 --- /dev/null +++ b/examples/temp_sensor/mixin/add_gcov.yml @@ -0,0 +1,51 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +--- + +# Enable gcov plugin +:plugins: + :enabled: + - gcov + +# Add -gcov to the plugins list to make sure of the gcov plugin +# You will need to have gcov and gcovr both installed to make it work. +# For more information on these options, see docs in plugins/gcov +:gcov: + :utilities: + - gcovr # Use gcovr to create the specified reports (default). + #- ReportGenerator # Use ReportGenerator to create the specified reports. + :reports: # Specify one or more reports to generate. + # Make an HTML summary report. + # - HtmlBasic + - HtmlDetailed + # - Text + # - Cobertura + # - SonarQube + # - JSON + # - HtmlInline + # - HtmlInlineAzure + # - HtmlInlineAzureDark + # - HtmlChart + # - MHtml + # - Badges + # - CsvSummary + # - Latex + # - LatexSummary + # - PngChart + # - TeamCitySummary + # - lcov + # - Xml + # - XmlSummary + :gcovr: + # :html_artifact_filename: TestCoverageReport.html + # :html_title: Test Coverage Report + :html_medium_threshold: 75 + :html_high_threshold: 90 + # :html_absolute_paths: TRUE + # :html_encoding: UTF-8 +... diff --git a/examples/temp_sensor/mixin/add_unity_helper.yml b/examples/temp_sensor/mixin/add_unity_helper.yml new file mode 100644 index 000000000..37571e1af --- /dev/null +++ b/examples/temp_sensor/mixin/add_unity_helper.yml @@ -0,0 +1,26 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +--- + +# Enable the unity helper's define to enable our custom assertion +:defines: + :test: + '*': + - TEST # Simple list option to add symbol 'TEST' to compilation of all files in all test executables + - TEST_CUSTOM_EXAMPLE_STRUCT_T + 'TestUsartIntegrated.c': + - TEST + - TEST_CUSTOM_EXAMPLE_STRUCT_T + - TEST_USART_INTEGRATED_STRING=\"It's Awesome Time!\n\" + :release: [] + +# Add the unity helper configuration to cmock +:cmock: + :unity_helper_path: + - test/support/UnityHelper.h +... diff --git a/examples/temp_sensor/project.yml b/examples/temp_sensor/project.yml index 3e9dedf54..4a7d4bd42 100644 --- a/examples/temp_sensor/project.yml +++ b/examples/temp_sensor/project.yml @@ -1,52 +1,150 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + --- +:project: + # how to use ceedling. If you're not sure, leave this as `gem` and `?` + :which_ceedling: gem + :ceedling_version: '?' -# 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. + # optional features. If you don't need them, keep them turned off for performance + :use_mocks: TRUE + :use_test_preprocessor: :all + :use_backtrace: :none -:project: - :use_exceptions: FALSE - :use_test_preprocessor: TRUE - :use_auxiliary_dependencies: TRUE + # tweak the way ceedling handles automatic tasks :build_root: build - # :release_build: TRUE :test_file_prefix: Test - :which_ceedling: gem - :ceedling_version: '?' + :default_tasks: + - test:all -#:release_build: -# :output: TempSensor.out -# :use_assembly: FALSE + # performance options. If your tools start giving mysterious errors, consider + # dropping this to 1 to force single-tasking + :test_threads: 8 + :compile_threads: 8 -:environment: [] + # enable release build (more details in release_build section below) + :release_build: FALSE + +# further details to configure the way Ceedling handles test code +:test_build: + :use_assembly: FALSE +# further details to configure the way Ceedling handles release code +:release_build: + :output: MyApp.out + :use_assembly: FALSE + :artifacts: [] + +# Specify where to find mixins and any that should be enabled automatically +:mixins: + :enabled: [] + :load_paths: + - mixin + +# Plugins are optional Ceedling features which can be enabled. Ceedling supports +# a variety of plugins which may effect the way things are compiled, reported, +# or may provide new command options. Refer to the readme in each plugin for +# details on how to use it. +:plugins: + :load_paths: [] + :enabled: + #- beep # beeps when finished, so you don't waste time waiting for ceedling + - module_generator # handy for quickly creating source, header, and test templates + #- gcov # test coverage using gcov. Requires gcc, gcov, and a coverage analyzer like gcovr + #- bullseye # test coverage using bullseye. Requires bullseye for your platform + #- command_hooks # write custom actions to be called at different points during the build process + #- compile_commands_json_db # generate a compile_commands.json file + #- dependencies # automatically fetch 3rd party libraries, etc. + #- subprojects # managing builds and test for static libraries + #- fake_function_framework # use FFF instead of CMock + + # Report options (You'll want to choose one stdout option, but may choose multiple stored options if desired) + #- report_build_warnings_log + #- report_tests_gtestlike_stdout + #- report_tests_ide_stdout + #- report_tests_log_factory + - report_tests_pretty_stdout + #- report_tests_raw_output_log + #- report_tests_teamcity_stdout + +# Specify which reports you'd like from the log factory +:report_tests_log_factory: + :reports: + - json + - junit + - cppunit + - html + +# override the default extensions for your system and toolchain :extension: + #:header: .h + #:source: .c + #:assembly: .s + #:dependencies: .d + #:object: .o :executable: .out + #:testpass: .pass + #:testfail: .fail + #:subprojects: .a +# This is where Ceedling should look for your source and test files. +# see documentation for the many options for specifying this. :paths: :test: - +:test/** - -:test/support :source: - src/** + :include: + - src/** :support: - test/support + :libraries: [] +# You can even specify specific files to add or remove from your test +# and release collections. Usually it's better to use paths and let +# Ceedling do the work for you! +:files: + :test: [] + :source: [] + +# Compilation symbols to be injected into builds +# See documentation for advanced options: +# - Test name matchers for different symbols per test executable build +# - Referencing symbols in multiple lists using advanced YAML +# - Specifiying symbols used during test preprocessing :defines: - :common: &common_defines [] :test: - - *common_defines - - TEST - :test_preprocess: - - *common_defines - - TEST + '*': + - TEST # Simple list option to add symbol 'TEST' to compilation of all files in all test executables + 'TestUsartIntegrated.c': + - TEST + - TEST_USART_INTEGRATED_STRING=\"It's Awesome Time!\n\" + :release: [] + + # Enable to inject name of a test as a unique compilation symbol into its respective executable build. + :use_test_definition: FALSE +# Configure additional command line flags provided to tools used in each build step +:flags: + :test: + :compile: + :TemperatureCalculator: + - '-DSUPPLY_VOLTAGE=3.0' + +# Configuration Options specific to CMock. See CMock docs for details :cmock: + :mock_prefix: Mock :when_no_prototypes: :warn :enforce_strict_ordering: TRUE :plugins: - :ignore + - :callback :treat_as: uint8: HEX8 uint16: HEX16 @@ -54,15 +152,210 @@ int8: INT8 bool: UINT8 +# You can optionally have ceedling create environment variables for you before +# performing the rest of its tasks. +:environment: [] +# :environment: +# # List enforces order allowing later to reference earlier with inline Ruby substitution +# - :var1: value +# - :var2: another value +# - :path: # Special PATH handling with platform-specific path separators +# - #{ENV['PATH']} # Environment variables can use inline Ruby substitution +# - /another/path/to/include + +# LIBRARIES +# These libraries are automatically injected into the build process. Those specified as +# common will be used in all types of builds. Otherwise, libraries can be injected in just +# tests or releases. These options are MERGED with the options in supplemental yaml files. :libraries: + :placement: :end + :flag: "-l${1}" + :path_flag: "-L ${1}" :system: - m + :test: [] + :release: [] -:plugins: - :load_paths: - - "#{Ceedling.load_path}" - :enabled: - - stdout_pretty_tests_report - - module_generator - - gcov +################################################################ +# PLUGIN CONFIGURATION +################################################################ + +# Add -gcov to the plugins list to make sure of the gcov plugin +# You will need to have gcov and gcovr both installed to make it work. +# For more information on these options, see docs in plugins/gcov +:gcov: + :utilities: + - gcovr # Use gcovr to create the specified reports (default). + #- ReportGenerator # Use ReportGenerator to create the specified reports. + :reports: # Specify one or more reports to generate. + # Make an HTML summary report. + - HtmlBasic + # - HtmlDetailed + # - Text + # - Cobertura + # - SonarQube + # - JSON + # - HtmlInline + # - HtmlInlineAzure + # - HtmlInlineAzureDark + # - HtmlChart + # - MHtml + # - Badges + # - CsvSummary + # - Latex + # - LatexSummary + # - PngChart + # - TeamCitySummary + # - lcov + # - Xml + # - XmlSummary + :gcovr: + # :html_artifact_filename: TestCoverageReport.html + # :html_title: Test Coverage Report + :html_medium_threshold: 75 + :html_high_threshold: 90 + # :html_absolute_paths: TRUE + # :html_encoding: UTF-8 + +# :module_generator: +# :project_root: ./ +# :source_root: source/ +# :inc_root: includes/ +# :test_root: tests/ +# :naming: :snake #options: :bumpy, :camel, :caps, or :snake +# :includes: +# :tst: [] +# :src: []:module_generator: +# :boilerplates: +# :src: "" +# :inc: "" +# :tst: "" + +# :dependencies: +# :libraries: +# - :name: WolfSSL +# :source_path: third_party/wolfssl/source +# :build_path: third_party/wolfssl/build +# :artifact_path: third_party/wolfssl/install +# :fetch: +# :method: :zip +# :source: \\shared_drive\third_party_libs\wolfssl\wolfssl-4.2.0.zip +# :environment: +# - CFLAGS+=-DWOLFSSL_DTLS_ALLOW_FUTURE +# :build: +# - "autoreconf -i" +# - "./configure --enable-tls13 --enable-singlethreaded" +# - make +# - make install +# :artifacts: +# :static_libraries: +# - lib/wolfssl.a +# :dynamic_libraries: +# - lib/wolfssl.so +# :includes: +# - include/** + +# :subprojects: +# :paths: +# - :name: libprojectA +# :source: +# - ./subprojectA/source +# :include: +# - ./subprojectA/include +# :build_root: ./subprojectA/build +# :defines: [] + +# :command_hooks: +# :pre_mock_preprocess: +# :post_mock_preprocess: +# :pre_test_preprocess: +# :post_test_preprocess: +# :pre_mock_generate: +# :post_mock_generate: +# :pre_runner_generate: +# :post_runner_generate: +# :pre_compile_execute: +# :post_compile_execute: +# :pre_link_execute: +# :post_link_execute: +# :pre_test_fixture_execute: +# :post_test_fixture_execute: +# :pre_test: +# :post_test: +# :pre_release: +# :post_release: +# :pre_build: +# :post_build: +# :post_error: + +################################################################ +# TOOLCHAIN CONFIGURATION +################################################################ + + +#: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 +# :tools: +# :test_compiler: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :test_linker: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :test_assembler: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :test_fixture: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :test_includes_preprocessor: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :test_file_preprocessor: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :test_file_preprocessor_directives: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :test_dependencies_generator: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :release_compiler: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :release_linker: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :release_assembler: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE +# :release_dependencies_generator: +# :executable: +# :arguments: [] +# :name: +# :optional: FALSE ... diff --git a/examples/temp_sensor/src/AdcConductor.c b/examples/temp_sensor/src/AdcConductor.c index 28d9d20cf..b18a2f47a 100644 --- a/examples/temp_sensor/src/AdcConductor.c +++ b/examples/temp_sensor/src/AdcConductor.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "Types.h" #include "AdcConductor.h" #include "AdcModel.h" diff --git a/examples/temp_sensor/src/AdcConductor.h b/examples/temp_sensor/src/AdcConductor.h index 867375be2..57f233efa 100644 --- a/examples/temp_sensor/src/AdcConductor.h +++ b/examples/temp_sensor/src/AdcConductor.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _ADCCONDUCTOR_H #define _ADCCONDUCTOR_H diff --git a/examples/temp_sensor/src/AdcHardware.c b/examples/temp_sensor/src/AdcHardware.c index 980764117..da418647a 100644 --- a/examples/temp_sensor/src/AdcHardware.c +++ b/examples/temp_sensor/src/AdcHardware.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "Types.h" #include "AdcHardware.h" #include "AdcHardwareConfigurator.h" diff --git a/examples/temp_sensor/src/AdcHardware.h b/examples/temp_sensor/src/AdcHardware.h index 224576250..0cd2eca27 100644 --- a/examples/temp_sensor/src/AdcHardware.h +++ b/examples/temp_sensor/src/AdcHardware.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _ADCHARDWARE_H #define _ADCHARDWARE_H diff --git a/examples/temp_sensor/src/AdcHardwareConfigurator.c b/examples/temp_sensor/src/AdcHardwareConfigurator.c index f7e08a239..5f6bd8228 100644 --- a/examples/temp_sensor/src/AdcHardwareConfigurator.c +++ b/examples/temp_sensor/src/AdcHardwareConfigurator.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "Types.h" #include "AdcHardwareConfigurator.h" #include "ModelConfig.h" diff --git a/examples/temp_sensor/src/AdcHardwareConfigurator.h b/examples/temp_sensor/src/AdcHardwareConfigurator.h index 78b9e9fcf..f283f8c2f 100644 --- a/examples/temp_sensor/src/AdcHardwareConfigurator.h +++ b/examples/temp_sensor/src/AdcHardwareConfigurator.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _ADCHARDWARECONFIGURATOR_H #define _ADCHARDWARECONFIGURATOR_H diff --git a/examples/temp_sensor/src/AdcModel.c b/examples/temp_sensor/src/AdcModel.c index ad9111d2c..1f3cb270c 100644 --- a/examples/temp_sensor/src/AdcModel.c +++ b/examples/temp_sensor/src/AdcModel.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "AdcModel.h" #include "TaskScheduler.h" #include "TemperatureCalculator.h" diff --git a/examples/temp_sensor/src/AdcModel.h b/examples/temp_sensor/src/AdcModel.h index 6b871fdbf..cab423093 100644 --- a/examples/temp_sensor/src/AdcModel.h +++ b/examples/temp_sensor/src/AdcModel.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _ADCMODEL_H #define _ADCMODEL_H diff --git a/examples/temp_sensor/src/AdcTemperatureSensor.c b/examples/temp_sensor/src/AdcTemperatureSensor.c index b2a3f2c13..9176fb7e1 100644 --- a/examples/temp_sensor/src/AdcTemperatureSensor.c +++ b/examples/temp_sensor/src/AdcTemperatureSensor.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "Types.h" #include "AdcTemperatureSensor.h" diff --git a/examples/temp_sensor/src/AdcTemperatureSensor.h b/examples/temp_sensor/src/AdcTemperatureSensor.h index bf2cc5b0d..c2a021af6 100644 --- a/examples/temp_sensor/src/AdcTemperatureSensor.h +++ b/examples/temp_sensor/src/AdcTemperatureSensor.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _ADCTEMPERATURESENSOR_H #define _ADCTEMPERATURESENSOR_H diff --git a/examples/temp_sensor/src/Executor.c b/examples/temp_sensor/src/Executor.c index 7e45c3e57..edc282d9d 100644 --- a/examples/temp_sensor/src/Executor.c +++ b/examples/temp_sensor/src/Executor.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "Types.h" #include "Executor.h" #include "Model.h" diff --git a/examples/temp_sensor/src/Executor.h b/examples/temp_sensor/src/Executor.h index 51a61a97e..ec3b42ecc 100644 --- a/examples/temp_sensor/src/Executor.h +++ b/examples/temp_sensor/src/Executor.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _EXECUTOR_H #define _EXECUTOR_H diff --git a/examples/temp_sensor/src/IntrinsicsWrapper.c b/examples/temp_sensor/src/IntrinsicsWrapper.c index 8b082aef4..79e1a438d 100644 --- a/examples/temp_sensor/src/IntrinsicsWrapper.c +++ b/examples/temp_sensor/src/IntrinsicsWrapper.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "IntrinsicsWrapper.h" #ifdef __ICCARM__ #include diff --git a/examples/temp_sensor/src/IntrinsicsWrapper.h b/examples/temp_sensor/src/IntrinsicsWrapper.h index 9273317cd..f9b9b55e3 100644 --- a/examples/temp_sensor/src/IntrinsicsWrapper.h +++ b/examples/temp_sensor/src/IntrinsicsWrapper.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _INTRINSICS_WRAPPER_H #define _INTRINSICS_WRAPPER_H diff --git a/examples/temp_sensor/src/Main.c b/examples/temp_sensor/src/Main.c index a784f4759..251d6af27 100644 --- a/examples/temp_sensor/src/Main.c +++ b/examples/temp_sensor/src/Main.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "Types.h" #include "IntrinsicsWrapper.h" diff --git a/examples/temp_sensor/src/Main.h b/examples/temp_sensor/src/Main.h index 6cbe5f43a..01604d231 100644 --- a/examples/temp_sensor/src/Main.h +++ b/examples/temp_sensor/src/Main.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _MAIN_H_ #define _MAIN_H_ diff --git a/examples/temp_sensor/src/Model.c b/examples/temp_sensor/src/Model.c index 5b34c40cd..5a29ad37e 100644 --- a/examples/temp_sensor/src/Model.c +++ b/examples/temp_sensor/src/Model.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "Model.h" #include "TaskScheduler.h" #include "TemperatureFilter.h" diff --git a/examples/temp_sensor/src/Model.h b/examples/temp_sensor/src/Model.h index d1309387d..1459ce7a6 100644 --- a/examples/temp_sensor/src/Model.h +++ b/examples/temp_sensor/src/Model.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _MODEL_H #define _MODEL_H diff --git a/examples/temp_sensor/src/ModelConfig.h b/examples/temp_sensor/src/ModelConfig.h index edc8e8d4b..4d2be93af 100644 --- a/examples/temp_sensor/src/ModelConfig.h +++ b/examples/temp_sensor/src/ModelConfig.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _MODELCONFIG_H #define _MODELCONFIG_H diff --git a/examples/temp_sensor/src/TaskScheduler.c b/examples/temp_sensor/src/TaskScheduler.c index bcc0e6436..9d5e72cbe 100644 --- a/examples/temp_sensor/src/TaskScheduler.c +++ b/examples/temp_sensor/src/TaskScheduler.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "Types.h" #include "TaskScheduler.h" diff --git a/examples/temp_sensor/src/TaskScheduler.h b/examples/temp_sensor/src/TaskScheduler.h index cc58342c5..ca088244f 100644 --- a/examples/temp_sensor/src/TaskScheduler.h +++ b/examples/temp_sensor/src/TaskScheduler.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _TASKSCHEDULER_H #define _TASKSCHEDULER_H diff --git a/examples/temp_sensor/src/TemperatureCalculator.c b/examples/temp_sensor/src/TemperatureCalculator.c index 04cec59d4..d1741c88e 100644 --- a/examples/temp_sensor/src/TemperatureCalculator.c +++ b/examples/temp_sensor/src/TemperatureCalculator.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "Types.h" #include "TemperatureCalculator.h" #include @@ -6,9 +13,13 @@ #define logl log #endif +#ifndef SUPPLY_VOLTAGE +#define SUPPLY_VOLTAGE 5.0 +#endif + float TemperatureCalculator_Calculate(uint16 millivolts) { - const double supply_voltage = 3.0; + const double supply_voltage = SUPPLY_VOLTAGE; const double series_resistance = 5000; const double coefficient_A = 316589.698; const double coefficient_B = -0.1382009; diff --git a/examples/temp_sensor/src/TemperatureCalculator.h b/examples/temp_sensor/src/TemperatureCalculator.h index 73f3df363..f9958ba3e 100644 --- a/examples/temp_sensor/src/TemperatureCalculator.h +++ b/examples/temp_sensor/src/TemperatureCalculator.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _TEMPERATURECALCULATOR_H #define _TEMPERATURECALCULATOR_H diff --git a/examples/temp_sensor/src/TemperatureFilter.c b/examples/temp_sensor/src/TemperatureFilter.c index 6fa6bab04..92c27b395 100644 --- a/examples/temp_sensor/src/TemperatureFilter.c +++ b/examples/temp_sensor/src/TemperatureFilter.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "Types.h" #include "TemperatureFilter.h" #include diff --git a/examples/temp_sensor/src/TemperatureFilter.h b/examples/temp_sensor/src/TemperatureFilter.h index 31413f491..4971be32f 100644 --- a/examples/temp_sensor/src/TemperatureFilter.h +++ b/examples/temp_sensor/src/TemperatureFilter.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _TEMPERATUREFILTER_H #define _TEMPERATUREFILTER_H diff --git a/examples/temp_sensor/src/TimerConductor.c b/examples/temp_sensor/src/TimerConductor.c index 569b489a0..60d932d0f 100644 --- a/examples/temp_sensor/src/TimerConductor.c +++ b/examples/temp_sensor/src/TimerConductor.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "Types.h" #include "TimerConductor.h" #include "TimerModel.h" diff --git a/examples/temp_sensor/src/TimerConductor.h b/examples/temp_sensor/src/TimerConductor.h index 7cd410970..3289b9dc9 100644 --- a/examples/temp_sensor/src/TimerConductor.h +++ b/examples/temp_sensor/src/TimerConductor.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _TIMERCONDUCTOR_H #define _TIMERCONDUCTOR_H diff --git a/examples/temp_sensor/src/TimerConfigurator.c b/examples/temp_sensor/src/TimerConfigurator.c index 996cedefb..5e1c4c8d5 100644 --- a/examples/temp_sensor/src/TimerConfigurator.c +++ b/examples/temp_sensor/src/TimerConfigurator.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "Types.h" #include "TimerConfigurator.h" #include "TimerInterruptConfigurator.h" diff --git a/examples/temp_sensor/src/TimerConfigurator.h b/examples/temp_sensor/src/TimerConfigurator.h index d078c54e6..19fc13bbb 100644 --- a/examples/temp_sensor/src/TimerConfigurator.h +++ b/examples/temp_sensor/src/TimerConfigurator.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _TIMERCONFIGURATOR_H #define _TIMERCONFIGURATOR_H diff --git a/examples/temp_sensor/src/TimerHardware.c b/examples/temp_sensor/src/TimerHardware.c index d5e983ff5..5ac45828c 100644 --- a/examples/temp_sensor/src/TimerHardware.c +++ b/examples/temp_sensor/src/TimerHardware.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "Types.h" #include "TimerHardware.h" #include "TimerConfigurator.h" diff --git a/examples/temp_sensor/src/TimerHardware.h b/examples/temp_sensor/src/TimerHardware.h index 92fa28710..778310be7 100644 --- a/examples/temp_sensor/src/TimerHardware.h +++ b/examples/temp_sensor/src/TimerHardware.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _TIMERHARDWARE_H #define _TIMERHARDWARE_H diff --git a/examples/temp_sensor/src/TimerInterruptConfigurator.c b/examples/temp_sensor/src/TimerInterruptConfigurator.c index fe603ef32..c9e0c1546 100644 --- a/examples/temp_sensor/src/TimerInterruptConfigurator.c +++ b/examples/temp_sensor/src/TimerInterruptConfigurator.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "Types.h" #include "TimerInterruptConfigurator.h" #include "TimerInterruptHandler.h" diff --git a/examples/temp_sensor/src/TimerInterruptConfigurator.h b/examples/temp_sensor/src/TimerInterruptConfigurator.h index bdf64718d..925c30994 100644 --- a/examples/temp_sensor/src/TimerInterruptConfigurator.h +++ b/examples/temp_sensor/src/TimerInterruptConfigurator.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _TIMERINTERRUPTCONFIGURATOR_H #define _TIMERINTERRUPTCONFIGURATOR_H diff --git a/examples/temp_sensor/src/TimerInterruptHandler.c b/examples/temp_sensor/src/TimerInterruptHandler.c index ebb543d44..95d99b711 100644 --- a/examples/temp_sensor/src/TimerInterruptHandler.c +++ b/examples/temp_sensor/src/TimerInterruptHandler.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "Types.h" #include "TimerInterruptHandler.h" #include "TimerInterruptConfigurator.h" diff --git a/examples/temp_sensor/src/TimerInterruptHandler.h b/examples/temp_sensor/src/TimerInterruptHandler.h index 29c0413bb..7c40b4469 100644 --- a/examples/temp_sensor/src/TimerInterruptHandler.h +++ b/examples/temp_sensor/src/TimerInterruptHandler.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _TIMERINTERRUPTHANDLER_H #define _TIMERINTERRUPTHANDLER_H diff --git a/examples/temp_sensor/src/TimerModel.c b/examples/temp_sensor/src/TimerModel.c index fcc9db9bd..6ff88ed37 100644 --- a/examples/temp_sensor/src/TimerModel.c +++ b/examples/temp_sensor/src/TimerModel.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "Types.h" #include "TimerModel.h" #include "TaskScheduler.h" diff --git a/examples/temp_sensor/src/TimerModel.h b/examples/temp_sensor/src/TimerModel.h index 54be21a47..030090984 100644 --- a/examples/temp_sensor/src/TimerModel.h +++ b/examples/temp_sensor/src/TimerModel.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _TIMERMODEL_H #define _TIMERMODEL_H diff --git a/examples/temp_sensor/src/Types.h b/examples/temp_sensor/src/Types.h index 8944c57fa..744a2839c 100644 --- a/examples/temp_sensor/src/Types.h +++ b/examples/temp_sensor/src/Types.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _MYTYPES_H_ #define _MYTYPES_H_ diff --git a/examples/temp_sensor/src/UsartBaudRateRegisterCalculator.c b/examples/temp_sensor/src/UsartBaudRateRegisterCalculator.c index f4ad1470f..f386b4e0a 100644 --- a/examples/temp_sensor/src/UsartBaudRateRegisterCalculator.c +++ b/examples/temp_sensor/src/UsartBaudRateRegisterCalculator.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "Types.h" #include "UsartBaudRateRegisterCalculator.h" diff --git a/examples/temp_sensor/src/UsartBaudRateRegisterCalculator.h b/examples/temp_sensor/src/UsartBaudRateRegisterCalculator.h index 70cd1189f..83cea58ef 100644 --- a/examples/temp_sensor/src/UsartBaudRateRegisterCalculator.h +++ b/examples/temp_sensor/src/UsartBaudRateRegisterCalculator.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _USARTBAUDRATEREGISTERCALCULATOR_H #define _USARTBAUDRATEREGISTERCALCULATOR_H diff --git a/examples/temp_sensor/src/UsartConductor.c b/examples/temp_sensor/src/UsartConductor.c index 3eeec3c1b..f87654a49 100644 --- a/examples/temp_sensor/src/UsartConductor.c +++ b/examples/temp_sensor/src/UsartConductor.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "Types.h" #include "UsartConductor.h" #include "UsartHardware.h" diff --git a/examples/temp_sensor/src/UsartConductor.h b/examples/temp_sensor/src/UsartConductor.h index f4207365f..de894f9fe 100644 --- a/examples/temp_sensor/src/UsartConductor.h +++ b/examples/temp_sensor/src/UsartConductor.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _USARTCONDUCTOR_H #define _USARTCONDUCTOR_H diff --git a/examples/temp_sensor/src/UsartConfigurator.c b/examples/temp_sensor/src/UsartConfigurator.c index b8c2cdc73..54224f14d 100644 --- a/examples/temp_sensor/src/UsartConfigurator.c +++ b/examples/temp_sensor/src/UsartConfigurator.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "Types.h" #include "UsartConfigurator.h" diff --git a/examples/temp_sensor/src/UsartConfigurator.h b/examples/temp_sensor/src/UsartConfigurator.h index 02bede2ab..938fc72de 100644 --- a/examples/temp_sensor/src/UsartConfigurator.h +++ b/examples/temp_sensor/src/UsartConfigurator.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _USARTCONFIGURATOR_H #define _USARTCONFIGURATOR_H diff --git a/examples/temp_sensor/src/UsartHardware.c b/examples/temp_sensor/src/UsartHardware.c index e37c2c606..5621a20db 100644 --- a/examples/temp_sensor/src/UsartHardware.c +++ b/examples/temp_sensor/src/UsartHardware.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "Types.h" #include "UsartHardware.h" #include "UsartConfigurator.h" diff --git a/examples/temp_sensor/src/UsartHardware.h b/examples/temp_sensor/src/UsartHardware.h index 041e28086..6606fa0e2 100644 --- a/examples/temp_sensor/src/UsartHardware.h +++ b/examples/temp_sensor/src/UsartHardware.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _USARTHARDWARE_H #define _USARTHARDWARE_H diff --git a/examples/temp_sensor/src/UsartModel.c b/examples/temp_sensor/src/UsartModel.c index d722a2f3e..7251481fe 100644 --- a/examples/temp_sensor/src/UsartModel.c +++ b/examples/temp_sensor/src/UsartModel.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "Types.h" #include "UsartModel.h" #include "ModelConfig.h" diff --git a/examples/temp_sensor/src/UsartModel.h b/examples/temp_sensor/src/UsartModel.h index 7d9485440..e3f1c3e24 100644 --- a/examples/temp_sensor/src/UsartModel.h +++ b/examples/temp_sensor/src/UsartModel.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _USARTMODEL_H #define _USARTMODEL_H diff --git a/examples/temp_sensor/src/UsartPutChar.c b/examples/temp_sensor/src/UsartPutChar.c index 9e3ce2c8b..d64868c3b 100644 --- a/examples/temp_sensor/src/UsartPutChar.c +++ b/examples/temp_sensor/src/UsartPutChar.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "Types.h" #include "UsartPutChar.h" #include "UsartTransmitBufferStatus.h" diff --git a/examples/temp_sensor/src/UsartPutChar.h b/examples/temp_sensor/src/UsartPutChar.h index 924446ab9..5bbb08d14 100644 --- a/examples/temp_sensor/src/UsartPutChar.h +++ b/examples/temp_sensor/src/UsartPutChar.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _USARTPUT_HAR_H #define _USARTPUT_HAR_H diff --git a/examples/temp_sensor/src/UsartTransmitBufferStatus.c b/examples/temp_sensor/src/UsartTransmitBufferStatus.c index 914b2e147..7474b95aa 100644 --- a/examples/temp_sensor/src/UsartTransmitBufferStatus.c +++ b/examples/temp_sensor/src/UsartTransmitBufferStatus.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "Types.h" #include "UsartTransmitBufferStatus.h" diff --git a/examples/temp_sensor/src/UsartTransmitBufferStatus.h b/examples/temp_sensor/src/UsartTransmitBufferStatus.h index b5925ba21..395c93b8f 100644 --- a/examples/temp_sensor/src/UsartTransmitBufferStatus.h +++ b/examples/temp_sensor/src/UsartTransmitBufferStatus.h @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _USARTTRANSMITBUFFERSTATUS_H #define _USARTTRANSMITBUFFERSTATUS_H diff --git a/examples/temp_sensor/test/TestExecutor.c b/examples/temp_sensor/test/TestExecutor.c index 8e4832620..83b11fc81 100644 --- a/examples/temp_sensor/test/TestExecutor.c +++ b/examples/temp_sensor/test/TestExecutor.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "Types.h" #include "Executor.h" @@ -32,5 +39,5 @@ void testRunShouldCallRunForEachConductorAndReturnTrueAlways(void) TimerConductor_Run_Expect(); AdcConductor_Run_Expect(); - TEST_ASSERT_EQUAL(TRUE, Executor_Run()); + TEST_ASSERT_TRUE(Executor_Run()); } diff --git a/examples/temp_sensor/test/TestMain.c b/examples/temp_sensor/test/TestMain.c index baf338290..a9c081c07 100644 --- a/examples/temp_sensor/test/TestMain.c +++ b/examples/temp_sensor/test/TestMain.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "Types.h" #include "MockExecutor.h" diff --git a/examples/temp_sensor/test/TestModel.c b/examples/temp_sensor/test/TestModel.c index 59dda1dc7..f37099d1d 100644 --- a/examples/temp_sensor/test/TestModel.c +++ b/examples/temp_sensor/test/TestModel.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "Types.h" #include "Model.h" diff --git a/examples/temp_sensor/test/TestTaskScheduler.c b/examples/temp_sensor/test/TestTaskScheduler.c index 29d1edf1d..529885806 100644 --- a/examples/temp_sensor/test/TestTaskScheduler.c +++ b/examples/temp_sensor/test/TestTaskScheduler.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "Types.h" #include "TaskScheduler.h" @@ -13,92 +20,92 @@ void tearDown(void) void testShouldScheduleUsartTaskAfter1000ms(void) { - TEST_ASSERT_EQUAL(FALSE, TaskScheduler_DoUsart()); + TEST_ASSERT_FALSE(TaskScheduler_DoUsart()); TaskScheduler_Update(999); - TEST_ASSERT_EQUAL(FALSE, TaskScheduler_DoUsart()); + TEST_ASSERT_FALSE(TaskScheduler_DoUsart()); TaskScheduler_Update(1000); - TEST_ASSERT_EQUAL(TRUE, TaskScheduler_DoUsart()); + TEST_ASSERT_TRUE(TaskScheduler_DoUsart()); } void testShouldClearUsartDoFlagAfterReported(void) { - TEST_ASSERT_EQUAL(FALSE, TaskScheduler_DoUsart()); + TEST_ASSERT_FALSE(TaskScheduler_DoUsart()); TaskScheduler_Update(1000); - TEST_ASSERT_EQUAL(TRUE, TaskScheduler_DoUsart()); - TEST_ASSERT_EQUAL(FALSE, TaskScheduler_DoUsart()); + TEST_ASSERT_TRUE(TaskScheduler_DoUsart()); + TEST_ASSERT_FALSE(TaskScheduler_DoUsart()); } void testShouldScheduleUsartTaskEvery1000ms(void) { - TEST_ASSERT_EQUAL(FALSE, TaskScheduler_DoUsart()); + TEST_ASSERT_FALSE(TaskScheduler_DoUsart()); TaskScheduler_Update(1300); - TEST_ASSERT_EQUAL(TRUE, TaskScheduler_DoUsart()); + TEST_ASSERT_TRUE(TaskScheduler_DoUsart()); TaskScheduler_Update(2000); - TEST_ASSERT_EQUAL(TRUE, TaskScheduler_DoUsart()); + TEST_ASSERT_TRUE(TaskScheduler_DoUsart()); TaskScheduler_Update(3100); - TEST_ASSERT_EQUAL(TRUE, TaskScheduler_DoUsart()); + TEST_ASSERT_TRUE(TaskScheduler_DoUsart()); } void testShouldScheduleUsartTaskOnlyOncePerPeriod(void) { - TEST_ASSERT_EQUAL(FALSE, TaskScheduler_DoUsart()); + TEST_ASSERT_FALSE(TaskScheduler_DoUsart()); TaskScheduler_Update(1000); - TEST_ASSERT_EQUAL(TRUE, TaskScheduler_DoUsart()); + TEST_ASSERT_TRUE(TaskScheduler_DoUsart()); TaskScheduler_Update(1001); - TEST_ASSERT_EQUAL(FALSE, TaskScheduler_DoUsart()); + TEST_ASSERT_FALSE(TaskScheduler_DoUsart()); TaskScheduler_Update(1999); - TEST_ASSERT_EQUAL(FALSE, TaskScheduler_DoUsart()); + TEST_ASSERT_FALSE(TaskScheduler_DoUsart()); TaskScheduler_Update(2000); - TEST_ASSERT_EQUAL(TRUE, TaskScheduler_DoUsart()); + TEST_ASSERT_TRUE(TaskScheduler_DoUsart()); } void testShouldScheduleAdcTaskAfter100ms(void) { - TEST_ASSERT_EQUAL(FALSE, TaskScheduler_DoAdc()); + TEST_ASSERT_FALSE(TaskScheduler_DoAdc()); TaskScheduler_Update(99); - TEST_ASSERT_EQUAL(FALSE, TaskScheduler_DoAdc()); + TEST_ASSERT_FALSE(TaskScheduler_DoAdc()); TaskScheduler_Update(100); - TEST_ASSERT_EQUAL(TRUE, TaskScheduler_DoAdc()); + TEST_ASSERT_TRUE(TaskScheduler_DoAdc()); } void testShouldClearAdcDoFlagAfterReported(void) { - TEST_ASSERT_EQUAL(FALSE, TaskScheduler_DoAdc()); + TEST_ASSERT_FALSE(TaskScheduler_DoAdc()); TaskScheduler_Update(100); - TEST_ASSERT_EQUAL(TRUE, TaskScheduler_DoAdc()); - TEST_ASSERT_EQUAL(FALSE, TaskScheduler_DoAdc()); + TEST_ASSERT_TRUE(TaskScheduler_DoAdc()); + TEST_ASSERT_FALSE(TaskScheduler_DoAdc()); } void testShouldScheduleAdcTaskEvery100ms(void) { - TEST_ASSERT_EQUAL(FALSE, TaskScheduler_DoAdc()); + TEST_ASSERT_FALSE(TaskScheduler_DoAdc()); TaskScheduler_Update(121); - TEST_ASSERT_EQUAL(TRUE, TaskScheduler_DoAdc()); + TEST_ASSERT_TRUE(TaskScheduler_DoAdc()); TaskScheduler_Update(200); - TEST_ASSERT_EQUAL(TRUE, TaskScheduler_DoAdc()); + TEST_ASSERT_TRUE(TaskScheduler_DoAdc()); TaskScheduler_Update(356); - TEST_ASSERT_EQUAL(TRUE, TaskScheduler_DoAdc()); + TEST_ASSERT_TRUE(TaskScheduler_DoAdc()); } void testShouldScheduleAdcTaskOnlyOncePerPeriod(void) { - TEST_ASSERT_EQUAL(FALSE, TaskScheduler_DoAdc()); + TEST_ASSERT_FALSE(TaskScheduler_DoAdc()); TaskScheduler_Update(100); - TEST_ASSERT_EQUAL(TRUE, TaskScheduler_DoAdc()); + TEST_ASSERT_TRUE(TaskScheduler_DoAdc()); TaskScheduler_Update(101); - TEST_ASSERT_EQUAL(FALSE, TaskScheduler_DoAdc()); + TEST_ASSERT_FALSE(TaskScheduler_DoAdc()); TaskScheduler_Update(199); - TEST_ASSERT_EQUAL(FALSE, TaskScheduler_DoAdc()); + TEST_ASSERT_FALSE(TaskScheduler_DoAdc()); TaskScheduler_Update(200); - TEST_ASSERT_EQUAL(TRUE, TaskScheduler_DoAdc()); + TEST_ASSERT_TRUE(TaskScheduler_DoAdc()); } diff --git a/examples/temp_sensor/test/TestTemperatureCalculator.c b/examples/temp_sensor/test/TestTemperatureCalculator.c index e54fbbfaa..a869ab739 100644 --- a/examples/temp_sensor/test/TestTemperatureCalculator.c +++ b/examples/temp_sensor/test/TestTemperatureCalculator.c @@ -1,8 +1,15 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "Types.h" #include -TEST_FILE("TemperatureCalculator.c") +TEST_SOURCE_FILE("TemperatureCalculator.c") extern float TemperatureCalculator_Calculate(uint16_t val); diff --git a/examples/temp_sensor/test/TestTemperatureFilter.c b/examples/temp_sensor/test/TestTemperatureFilter.c index 4d34f5d62..5047ef4c6 100644 --- a/examples/temp_sensor/test/TestTemperatureFilter.c +++ b/examples/temp_sensor/test/TestTemperatureFilter.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "Types.h" #include "TemperatureFilter.h" diff --git a/examples/temp_sensor/test/TestTimerConductor.c b/examples/temp_sensor/test/TestTimerConductor.c index 8064a8c51..f6652607b 100644 --- a/examples/temp_sensor/test/TestTimerConductor.c +++ b/examples/temp_sensor/test/TestTimerConductor.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "Types.h" #include "TimerConductor.h" diff --git a/examples/temp_sensor/test/TestTimerHardware.c b/examples/temp_sensor/test/TestTimerHardware.c index 16339d0ca..1b907068b 100644 --- a/examples/temp_sensor/test/TestTimerHardware.c +++ b/examples/temp_sensor/test/TestTimerHardware.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "Types.h" #include "TimerHardware.h" diff --git a/examples/temp_sensor/test/TestTimerIntegrated.c b/examples/temp_sensor/test/TestTimerIntegrated.c new file mode 100644 index 000000000..d607aeb66 --- /dev/null +++ b/examples/temp_sensor/test/TestTimerIntegrated.c @@ -0,0 +1,53 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#include "unity.h" +#include "Types.h" +#include "TimerConductor.h" +#include "TimerHardware.h" +#include "TimerModel.h" +#include "MockTimerConfigurator.h" +#include "MockTimerInterruptHandler.h" +#include "MockTaskScheduler.h" + +/* NOTE: we probably wouldn't actually perform this test on our own projects + but it's a good example of testing the same module(s) from multiple test + files, and therefore we like having it in this example. +*/ + +void setUp(void) +{ +} + +void tearDown(void) +{ +} + +void testInitShouldCallHardwareInit(void) +{ + Timer_EnablePeripheralClocks_Expect(); + Timer_Reset_Expect(); + Timer_ConfigureMode_Expect(); + Timer_ConfigurePeriod_Expect(); + Timer_EnableOutputPin_Expect(); + Timer_Enable_Expect(); + Timer_ConfigureInterruptHandler_Expect(); + Timer_Start_Expect(); + + TimerConductor_Init(); +} + +void testRunShouldGetSystemTimeAndPassOnToModelForEventScheduling(void) +{ + Timer_GetSystemTime_ExpectAndReturn(1230); + TaskScheduler_Update_Expect(1230); + TimerConductor_Run(); + + Timer_GetSystemTime_ExpectAndReturn(837460); + TaskScheduler_Update_Expect(837460); + TimerConductor_Run(); +} diff --git a/examples/temp_sensor/test/TestTimerModel.c b/examples/temp_sensor/test/TestTimerModel.c index e92a96aa8..55c606ee9 100644 --- a/examples/temp_sensor/test/TestTimerModel.c +++ b/examples/temp_sensor/test/TestTimerModel.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "Types.h" #include "TimerModel.h" diff --git a/examples/temp_sensor/test/TestUsartBaudRateRegisterCalculator.c b/examples/temp_sensor/test/TestUsartBaudRateRegisterCalculator.c index 08dc04591..f3151b25f 100644 --- a/examples/temp_sensor/test/TestUsartBaudRateRegisterCalculator.c +++ b/examples/temp_sensor/test/TestUsartBaudRateRegisterCalculator.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "Types.h" #include "UsartBaudRateRegisterCalculator.h" @@ -13,9 +20,9 @@ void tearDown(void) void testCalculateBaudRateRegisterSettingShouldCalculateRegisterSettingAppropriately(void) { // BaudRate = MCK / (CD x 16) - per datasheet section 30.6.1.2 "Baud Rate Calculation Example" - TEST_ASSERT_EQUAL(26, UsartModel_CalculateBaudRateRegisterSetting(48000000, 115200)); - TEST_ASSERT_EQUAL(6, UsartModel_CalculateBaudRateRegisterSetting(3686400, 38400)); - TEST_ASSERT_EQUAL(23, UsartModel_CalculateBaudRateRegisterSetting(14318180, 38400)); - TEST_ASSERT_EQUAL(20, UsartModel_CalculateBaudRateRegisterSetting(12000000, 38400)); - TEST_ASSERT_EQUAL(13, UsartModel_CalculateBaudRateRegisterSetting(12000000, 56800)); + TEST_ASSERT_EQUAL_INT(26, UsartModel_CalculateBaudRateRegisterSetting(48000000, 115200)); + TEST_ASSERT_EQUAL_INT(6, UsartModel_CalculateBaudRateRegisterSetting(3686400, 38400)); + TEST_ASSERT_EQUAL_INT(23, UsartModel_CalculateBaudRateRegisterSetting(14318180, 38400)); + TEST_ASSERT_EQUAL_INT(20, UsartModel_CalculateBaudRateRegisterSetting(12000000, 38400)); + TEST_ASSERT_EQUAL_INT(13, UsartModel_CalculateBaudRateRegisterSetting(12000000, 56800)); } diff --git a/examples/temp_sensor/test/TestUsartConductor.c b/examples/temp_sensor/test/TestUsartConductor.c index e23ef77fa..bc8d1ece9 100644 --- a/examples/temp_sensor/test/TestUsartConductor.c +++ b/examples/temp_sensor/test/TestUsartConductor.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "Types.h" #include "UsartConductor.h" diff --git a/examples/temp_sensor/test/TestUsartHardware.c b/examples/temp_sensor/test/TestUsartHardware.c index 5e8df758f..bddc206b1 100644 --- a/examples/temp_sensor/test/TestUsartHardware.c +++ b/examples/temp_sensor/test/TestUsartHardware.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "Types.h" #include "UsartHardware.h" diff --git a/examples/temp_sensor/test/TestUsartIntegrated.c b/examples/temp_sensor/test/TestUsartIntegrated.c new file mode 100644 index 000000000..08b12ab4a --- /dev/null +++ b/examples/temp_sensor/test/TestUsartIntegrated.c @@ -0,0 +1,63 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#include "unity.h" +#include "Types.h" +#include "UsartConductor.h" +#include "UsartModel.h" +#include "UsartHardware.h" +#include "ModelConfig.h" +#include "MockTaskScheduler.h" +#include "MockUsartConfigurator.h" +#include "MockUsartPutChar.h" +#include "MockTemperatureFilter.h" +#include "MockUsartBaudRateRegisterCalculator.h" +#include + +/* NOTE: we probably wouldn't actually perform this test on our own projects + but it's a good example of testing the same module(s) from multiple test + files, and therefore we like having it in this example. +*/ + +#ifndef TEST_USART_INTEGRATED_STRING +#define TEST_USART_INTEGRATED_STRING "THIS WILL FAIL" +#endif + +void setUp(void) +{ +} + +void tearDown(void) +{ +} + +void testShouldInitializeHardwareWhenInitCalled(void) +{ + size_t i; + const char* test_str = TEST_USART_INTEGRATED_STRING; + + UsartModel_CalculateBaudRateRegisterSetting_ExpectAndReturn(MASTER_CLOCK, USART0_BAUDRATE, 4); + Usart_ConfigureUsartIO_Expect(); + Usart_EnablePeripheralClock_Expect(); + Usart_Reset_Expect(); + Usart_ConfigureMode_Expect(); + Usart_SetBaudRateRegister_Expect(4); + Usart_Enable_Expect(); + for (i=0; i < strlen(test_str); i++) + { + Usart_PutChar_Expect(test_str[i]); + } + + UsartConductor_Init(); +} + +void testRunShouldNotDoAnythingIfSchedulerSaysItIsNotTimeYet(void) +{ + TaskScheduler_DoUsart_ExpectAndReturn(FALSE); + + UsartConductor_Run(); +} diff --git a/examples/temp_sensor/test/TestUsartModel.c b/examples/temp_sensor/test/TestUsartModel.c index 6ab23bc0f..62e3afa8b 100644 --- a/examples/temp_sensor/test/TestUsartModel.c +++ b/examples/temp_sensor/test/TestUsartModel.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "Types.h" #include "UsartModel.h" @@ -19,7 +26,7 @@ void testGetBaudRateRegisterSettingShouldReturnAppropriateBaudRateRegisterSettin uint8 dummyRegisterSetting = 17; UsartModel_CalculateBaudRateRegisterSetting_ExpectAndReturn(MASTER_CLOCK, USART0_BAUDRATE, dummyRegisterSetting); - TEST_ASSERT_EQUAL(dummyRegisterSetting, UsartModel_GetBaudRateRegisterSetting()); + TEST_ASSERT_EQUAL_UINT8(dummyRegisterSetting, UsartModel_GetBaudRateRegisterSetting()); } void testGetFormattedTemperatureFormatsTemperatureFromCalculatorAppropriately(void) diff --git a/examples/temp_sensor/test/TestAdcConductor.c b/examples/temp_sensor/test/adc/TestAdcConductor.c similarity index 90% rename from examples/temp_sensor/test/TestAdcConductor.c rename to examples/temp_sensor/test/adc/TestAdcConductor.c index a15d7d1b4..efeff8554 100644 --- a/examples/temp_sensor/test/TestAdcConductor.c +++ b/examples/temp_sensor/test/adc/TestAdcConductor.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "UnityHelper.h" #include "Types.h" diff --git a/examples/temp_sensor/test/TestAdcHardware.c b/examples/temp_sensor/test/adc/TestAdcHardware.c similarity index 73% rename from examples/temp_sensor/test/TestAdcHardware.c rename to examples/temp_sensor/test/adc/TestAdcHardware.c index 7aabaa759..0441a49d8 100644 --- a/examples/temp_sensor/test/TestAdcHardware.c +++ b/examples/temp_sensor/test/adc/TestAdcHardware.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "Types.h" #include "AdcHardware.h" @@ -40,5 +47,5 @@ void testGetSampleShouldDelegateToAdcTemperatureSensor(void) Adc_ReadTemperatureSensor_ExpectAndReturn(847); sample = AdcHardware_GetSample(); - TEST_ASSERT_EQUAL(847, sample); + TEST_ASSERT_EQUAL_INT(847, sample); } diff --git a/examples/temp_sensor/test/TestAdcModel.c b/examples/temp_sensor/test/adc/TestAdcModel.c similarity index 62% rename from examples/temp_sensor/test/TestAdcModel.c rename to examples/temp_sensor/test/adc/TestAdcModel.c index f1dcb4aae..141fa382d 100644 --- a/examples/temp_sensor/test/TestAdcModel.c +++ b/examples/temp_sensor/test/adc/TestAdcModel.c @@ -1,3 +1,10 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "Types.h" #include "AdcModel.h" @@ -16,13 +23,13 @@ void tearDown(void) void testDoGetSampleShouldReturn_FALSE_WhenTaskSchedulerReturns_FALSE(void) { TaskScheduler_DoAdc_ExpectAndReturn(FALSE); - TEST_ASSERT_EQUAL(FALSE, AdcModel_DoGetSample()); + TEST_ASSERT_FALSE(AdcModel_DoGetSample()); } void testDoGetSampleShouldReturn_TRUE_WhenTaskSchedulerReturns_TRUE(void) { TaskScheduler_DoAdc_ExpectAndReturn(TRUE); - TEST_ASSERT_EQUAL(TRUE, AdcModel_DoGetSample()); + TEST_ASSERT_TRUE(AdcModel_DoGetSample()); } void testProcessInputShouldDelegateToTemperatureCalculatorAndPassResultToFilter(void) diff --git a/examples/temp_sensor/test/support/UnityHelper.c b/examples/temp_sensor/test/support/UnityHelper.c index e60521fb3..3513ae070 100644 --- a/examples/temp_sensor/test/support/UnityHelper.c +++ b/examples/temp_sensor/test/support/UnityHelper.c @@ -1,12 +1,19 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #include "unity.h" #include "unity_internals.h" #include "UnityHelper.h" -#if 0 -void AssertEqualMyDataType(const MyDataType_T expected, const MyDataType_T actual, const unsigned short line) +#if TEST_CUSTOM_EXAMPLE_STRUCT_T +void AssertEqualEXAMPLE_STRUCT_T(const EXAMPLE_STRUCT_T expected, const EXAMPLE_STRUCT_T actual, const unsigned short line) { - UNITY_TEST_ASSERT_EQUAL_INT(expected.length, actual.length, line, "MyDataType_T.length check failed"); - UNITY_TEST_ASSERT_EQUAL_MEMORY(expected.buffer, actual.buffer, expected.length, line, "MyDataType_T.buffer check failed"); + UNITY_TEST_ASSERT_EQUAL_INT(expected.x, actual.x, line, "EXAMPLE_STRUCT_T.x check failed"); + UNITY_TEST_ASSERT_EQUAL_INT(expected.y, actual.y, line, "EXAMPLE_STRUCT_T.y check failed"); } #endif diff --git a/examples/temp_sensor/test/support/UnityHelper.h b/examples/temp_sensor/test/support/UnityHelper.h index 81667a3f2..bc536b0ed 100755 --- a/examples/temp_sensor/test/support/UnityHelper.h +++ b/examples/temp_sensor/test/support/UnityHelper.h @@ -1,12 +1,17 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + #ifndef _TESTHELPER_H #define _TESTHELPER_H -// #include "MyTypes.h" - -#if 0 -void AssertEqualMyDataType(const MyDataType_T expected, const MyDataType_T actual, const unsigned short line); - -#define UNITY_TEST_ASSERT_EQUAL_MyDataType_T(expected, actual, line, message) {AssertEqualMyDataType(expected, actual, line);} +#if TEST_CUSTOM_EXAMPLE_STRUCT_T +#include "Types.h" +void AssertEqualEXAMPLE_STRUCT_T(const EXAMPLE_STRUCT_T expected, const EXAMPLE_STRUCT_T actual, const unsigned short line); +#define UNITY_TEST_ASSERT_EQUAL_EXAMPLE_STRUCT_T(expected, actual, line, message) {AssertEqualEXAMPLE_STRUCT_T(expected, actual, line);} #endif #endif // _TESTHELPER_H diff --git a/lib/ceedling.rb b/lib/ceedling.rb index 7f3400236..28d271980 100644 --- a/lib/ceedling.rb +++ b/lib/ceedling.rb @@ -1,99 +1,14 @@ -## -# This module defines the interface for interacting with and loading a project -# with Ceedling. -module Ceedling - ## - # Returns the location where the gem is installed. - # === Return - # _String_ - The location where the gem lives. - def self.location - File.join( File.dirname(__FILE__), '..') - end - - ## - # Return the path to the "built-in" plugins. - # === Return - # _String_ - The path where the default plugins live. - def self.load_path - File.join( self.location, 'plugins') - end - - ## - # Return the path to the Ceedling Rakefile - # === Return - # _String_ - def self.rakefile - File.join( self.location, 'lib', 'ceedling', 'rakefile.rb' ) - end - - ## - # This method selects the project file that Ceedling will use by setting the - # CEEDLING_MAIN_PROJECT_FILE environment variable before loading the ceedling - # rakefile. A path supplied as an argument to this method will override the - # current value of the environment variable. If no path is supplied as an - # argument then the existing value of the environment variable is used. If - # the environment variable has not been set and no argument has been supplied - # then a default path of './project.yml' will be used. - # - # === Arguments - # +options+ _Hash_:: - # A hash containing the options for ceedling. Currently the following - # options are supported: - # * +config+ - The path to the project YAML configuration file. - # * +root+ - The root of the project directory. - # * +prefix+ - A prefix to prepend to plugin names in order to determine the - # corresponding gem name. - # * +plugins+ - The list of ceedling plugins to load - def self.load_project(options = {}) - # Make sure our path to the yaml file is setup - if options.has_key? :config - ENV['CEEDLING_MAIN_PROJECT_FILE'] = options[:config] - elsif ENV['CEEDLING_MAIN_PROJECT_FILE'].nil? - ENV['CEEDLING_MAIN_PROJECT_FILE'] = './project.yml' - end - - # Register the plugins - if options.has_key? :plugins - options[:plugins].each do |plugin| - register_plugin( plugin, options[:prefix] ) - end - end +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= - # Define the root of the project if specified - Object.const_set('PROJECT_ROOT', options[:root]) if options.has_key? :root - - # Load ceedling - load "#{self.rakefile}" - end - - ## - # Register a plugin for ceedling to use when a project is loaded. This method - # *must* be called prior to calling the _load_project_ method. - # - # This method is intended to be used for loading plugins distributed via the - # RubyGems mechanism. As such, the following gem structure is assumed for - # plugins. - # - # * The gem name must be prefixed with 'ceedling-' followed by the plugin - # name (ex. 'ceedling-bullseye') - # - # * The contents of the plugin must be isntalled into a subdirectory of - # the gem with the same name as the plugin (ex. 'bullseye/') - # - # === Arguments - # +name+ _String_:: The name of the plugin to load. - # +prefix+ _String_:: - # (optional, default = nil) The prefix to use for the full gem name. - def self.register_plugin(name, prefix=nil) - # Figure out the full name of the gem and location - prefix ||= 'ceedling-' - gem_name = prefix + name - gem_dir = Gem::Specification.find_by_name(gem_name).gem_dir() - - # Register the plugin with Ceedling - require 'ceedling/defaults' - DEFAULT_CEEDLING_CONFIG[:plugins][:enabled] << name - DEFAULT_CEEDLING_CONFIG[:plugins][:load_paths] << gem_dir - end -end +# This file exists solely for `require 'ceedling'` and any backwards compatibility. +# All path loading and related is handled from bin/ceedling. +# There is no shareable, library-worth code in lib/. +module Ceedling + # Emtpy +end \ No newline at end of file diff --git a/lib/ceedling/application.rb b/lib/ceedling/application.rb new file mode 100644 index 000000000..1b1dac169 --- /dev/null +++ b/lib/ceedling/application.rb @@ -0,0 +1,26 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +# As Rake is removed, more and more functionality and code entrypoints will migrate here + +class Application + + constructor :system_wrapper + + def setup + @failures = false + end + + def register_build_failure + @failures = true + end + + def build_succeeded? + return (!@failures) && @system_wrapper.ruby_success? + end + +end diff --git a/lib/ceedling/backtrace.gdb b/lib/ceedling/backtrace.gdb new file mode 100644 index 000000000..11ec159dc --- /dev/null +++ b/lib/ceedling/backtrace.gdb @@ -0,0 +1,5 @@ +if $_isvoid ($_exitcode) + call ((void(*)(int))fflush)(0) + backtrace + kill +end diff --git a/lib/ceedling/build_batchinator.rb b/lib/ceedling/build_batchinator.rb new file mode 100644 index 000000000..1227e958e --- /dev/null +++ b/lib/ceedling/build_batchinator.rb @@ -0,0 +1,113 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +class BuildBatchinator + + constructor :configurator, :loginator, :reportinator + + def setup + @queue = Queue.new + end + + # Neaten up a build step with progress message and some scope encapsulation + def build_step(msg, heading: true, &block) + if heading + msg = @reportinator.generate_heading( @loginator.decorate( msg, LogLabels::RUN ) ) + else # Progress message + msg = "\n" + @reportinator.generate_progress( @loginator.decorate( msg, LogLabels::RUN ) ) + end + + @loginator.log( msg ) + + yield # Execute build step block + end + + # Parallelize work to be done: + # - Enqueue things (thread-safe) + # - Spin up a number of worker threads within constraints of project file config and amount of work + # - Each worker thread consumes one item from queue and runs the block against its details + # - When the queue is empty, the worker threads wind down + def exec(workload:, things:, &block) + workers = 0 + + case workload + when :compile + workers = @configurator.project_compile_threads + when :test + workers = @configurator.project_test_threads + else + raise NameError.new("Unrecognized batch workload type: #{workload}") + end + + # Enqueue all the items the block will execute against + things.each { |thing| @queue << thing } + + # Choose lesser of max workers or number of things to process & redefine workers + # (It's neater and more efficient to avoid workers we won't use) + workers = [workers, things.size].min + + threads = (1..workers).collect do + thread = Thread.new do + Thread.handle_interrupt(Exception => :never) do + begin + Thread.handle_interrupt(Exception => :immediate) do + # Run tasks until there are no more enqueued + loop do + # pop(true) is non-blocking and raises ThreadError when queue is empty + yield @queue.pop(true) + end + end + + # First, handle thread exceptions (should always be due to empty queue) + rescue ThreadError => e + # Typical case: do nothing and allow thread to wind down + + # ThreadError outside scope of expected empty queue condition + unless e.message.strip.casecmp("queue empty") + # Shutdown all worker threads + shutdown_threads(threads) + # Raise exception again after intervening + raise(e) + end + + # Second, catch every other kind of exception so we can intervene with thread cleanup. + # Generally speaking, catching Exception is a no-no, but we must in this case. + # Raise the exception again so that: + # 1. Calling code knows something bad happened and handles appropriately + # 2. Ruby runtime can handle most serious problems + rescue Exception => e + # Shutdown all worker threads + shutdown_threads(threads) + # Raise exception again after intervening + raise(e) + end + end + end + + # Hand thread to Enumerable collect() routine + thread.abort_on_exception = true + thread + end + + # Hand worker threads to scheduler / wait for them to finish + threads.each { |thread| thread.join } + end + + ### Private ### + + private + + # Terminate worker threads other than ourselves (we're already winding down) + def shutdown_threads(workers) + workers.each do |thread| + next if thread == Thread.current + thread.terminate + end + end + +end + diff --git a/lib/ceedling/build_invoker_utils.rb b/lib/ceedling/build_invoker_utils.rb deleted file mode 100644 index 257c27a81..000000000 --- a/lib/ceedling/build_invoker_utils.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'ceedling/constants' - -## -# Utilities for raiser and reporting errors during building. -class BuildInvokerUtils - - constructor :configurator, :streaminator - - ## - # Processes exceptions and tries to display a useful message for the user. - # - # ==== Attributes - # - # * _exception_: The exception given by a rescue statement. - # * _context_: A symbol representing where in the build the exception - # occurs. - # * _test_build_: A bool to signify if the exception occurred while building - # from test or source. - # - def process_exception(exception, context, test_build=true) - if (exception.message =~ /Don't know how to build task '(.+)'/i) - error_header = "ERROR: Rake could not find file referenced in source" - error_header += " or test" if (test_build) - error_header += ": '#{$1}'. Possible stale dependency." - - @streaminator.stderr_puts( error_header ) - - if (@configurator.project_use_deep_dependencies) - help_message = "Try fixing #include statements or adding missing file. Then run '#{REFRESH_TASK_ROOT}#{context}' task and try again." - @streaminator.stderr_puts( help_message ) - end - - raise '' - else - raise exception - end - end - -end diff --git a/lib/ceedling/cacheinator.rb b/lib/ceedling/cacheinator.rb index 519a4aab4..57bff8ad8 100644 --- a/lib/ceedling/cacheinator.rb +++ b/lib/ceedling/cacheinator.rb @@ -1,3 +1,9 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= class Cacheinator diff --git a/lib/ceedling/cacheinator_helper.rb b/lib/ceedling/cacheinator_helper.rb index 14e8a6ef0..356762181 100644 --- a/lib/ceedling/cacheinator_helper.rb +++ b/lib/ceedling/cacheinator_helper.rb @@ -1,3 +1,9 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= class CacheinatorHelper diff --git a/lib/ceedling/cmock_builder.rb b/lib/ceedling/cmock_builder.rb deleted file mode 100644 index 4a74aa842..000000000 --- a/lib/ceedling/cmock_builder.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'cmock' - -class CmockBuilder - - attr_accessor :cmock - - def setup - @cmock = nil - end - - def manufacture(cmock_config) - @cmock = CMock.new(cmock_config) - end - -end diff --git a/lib/ceedling/config_matchinator.rb b/lib/ceedling/config_matchinator.rb new file mode 100644 index 000000000..d9708ab26 --- /dev/null +++ b/lib/ceedling/config_matchinator.rb @@ -0,0 +1,192 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'ceedling/exceptions' + +# :
: +# :: +# :: +# :: +# - +# - +# - ... + +class ConfigMatchinator + + constructor :configurator, :loginator, :reportinator + + def config_include?(primary:, secondary:, tertiary:nil) + # Create configurator accessor method + accessor = (primary.to_s + '_' + secondary.to_s).to_sym + + # If no entry in configuration for secondary in primary, bail out + return false if not @configurator.respond_to?( accessor ) + + # If tertiary undefined, we've progressed as far as we need and already know the config is present + return true if tertiary.nil? + + # Get element associated with this context + elem = @configurator.send( accessor ) + + # If [primary][secondary] is a simple array + if elem.is_a?(Array) + # A list instead of a hash, means [tertiary] is not present + return false + + # If [primary][secondary] is a hash + elsif elem.is_a?(Hash) + return elem.include?( tertiary ) + end + + # Otherwise, [primary][secondary] is something that cannot contain a [tertiary] sub-hash + return false + end + + def get_config(primary:, secondary:, tertiary:nil) + # Create configurator accessor method + accessor = (primary.to_s + '_' + secondary.to_s).to_sym + + # If no entry in configuration for secondary in primary, bail out + return nil if not @configurator.respond_to?( accessor ) + + # Get config element associated with this secondary + elem = @configurator.send( accessor ) + + # If [primary][secondary] is a simple array + if elem.class == Array + # If no tertiary specified, then a simple array makes sense + return elem if tertiary.nil? + + # Otherwise, if an tertiary is specified but we have an array, go boom + error = ":#{primary} ↳ :#{secondary} present in project configuration but does not contain :#{tertiary}." + raise CeedlingException.new( error ) + + # If [primary][secondary] is a hash + elsif elem.class == Hash + if not tertiary.nil? + # Bail out if we're looking for an [tertiary] sub-hash, but it's not present + return nil if not elem.include?( tertiary ) + + # Return array or hash at tertiary + return elem[tertiary] + + # If tertiary is not being queried, but we have a hash, return the hash + else + return elem + end + + # If [primary][secondary] is nothing we expect--something other than an array or hash + else + error = ":#{primary} ↳ :#{secondary} in project configuration is neither a list nor hash." + raise CeedlingException.new( error ) + end + + return nil + end + + # Note: This method only relevant if hash includes test filepath matching keys + def matches?(hash:, filepath:, section:, context:, operation:nil) + _values = [] + + # Sanity check + if filepath.nil? + path = generate_matcher_path(section, context, operation) + error = "#{path} ↳ #{matcher} matching provided nil #{filepath}" + raise CeedlingException.new(error) + end + + # Iterate through every hash touple [matcher key, values array] + # In prioritized order match test filepath against each matcher key. + # This order matches on special patterns first to ensure no funny business with simple substring matching + # 1. All files wildcard ('*') + # 2. Regex (/.../) + # 3. Wildcard filepath matching (e.g. 'name*') + # 4. Any filepath matching (substring matching) + # + # Each element of the collected _values array will be an array of values. + + hash.each do |matcher, values| + mtached = false + _matcher = matcher.to_s.strip + + # 1. Try gross wildcard matching -- return values for all test filepaths if '*' is the matching key + if ('*' == _matcher) + matched = true + + # 2. Try regular expression matching against all values matching keys that are regexes (ignore if not a valid regex) + # Note: We use logical AND here so that we get a meaningful fall-through condition. + # Nesting the actual regex matching beneath validity checking improperly catches unmatched regexes + elsif (regex?(_matcher)) and (!(form_regex(_matcher).match(filepath)).nil?) + matched = true + + # 3. Try wildcard matching -- return values for any test filepath that matches with '*' expansion + # Treat matcher as a regex: + # 1. Escape any regex characters (e.g. '-') + # 2. Convert any now escaped '\*'s into '.*' + # 3. Match filepath against regex-ified matcher + elsif (filepath =~ /#{Regexp.escape(matcher).gsub('\*', '.*')}/) + matched = true + + # 4. Try filepath literal matching (including substring matching) with each matching key + # Note: (3) will do this if the matcher key lacks a '*', but this is a just-in-case backup + elsif (filepath.include?(_matcher)) + matched = true + end + + if matched + _values += values + matched_notice(section:section, context:context, operation:operation, matcher:_matcher, filepath:filepath) + else # No match + path = generate_matcher_path(section, context, operation) + @loginator.log("#{path} ↳ `#{matcher}` did not match #{filepath}", Verbosity::DEBUG) + end + end + + # Flatten to handle list-nested YAML aliasing (should have already been flattened during validation) + return _values.flatten + end + + ### Private ### + + private + + def matched_notice(section:, context:, operation:, matcher:, filepath:) + path = generate_matcher_path(section, context, operation) + @loginator.log("#{path} ↳ #{matcher} matched #{filepath}", Verbosity::OBNOXIOUS) + end + + def generate_matcher_path(*keys) + return @reportinator.generate_config_walk(keys) + end + + # Assumes expr is a string and has been stripped + def regex?(expr) + valid = true + + if !expr.start_with?('/') + return false + end + + if !expr.end_with? ('/') + return false + end + + begin + Regexp.new(expr[1..-2]) + rescue RegexpError + valid = false + end + + return valid + end + + # Assumes expr is /.../ + def form_regex(expr) + return Regexp.new(expr[1..-2]) + end + +end diff --git a/lib/ceedling/config_walkinator.rb b/lib/ceedling/config_walkinator.rb new file mode 100644 index 000000000..f378cf7c9 --- /dev/null +++ b/lib/ceedling/config_walkinator.rb @@ -0,0 +1,35 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +class ConfigWalkinator + + def fetch_value(*keys, hash:, default:nil) + # Safe initial values + value = default + depth = 0 + + # Set walk variable + walk = hash + + # Walk into hash & extract value at requested key sequence + keys.each { |symbol| + # Validate that we can fetch something meaningful + if !walk.is_a?( Hash) or !symbol.is_a?( Symbol ) or walk[symbol].nil? + value = default + break + end + + # Walk into the hash one more level and update value + depth += 1 + walk = walk[symbol] + value = walk + } if !walk.nil? + + return value, depth + end + +end diff --git a/lib/ceedling/configurator.rb b/lib/ceedling/configurator.rb index 0ae4d04a8..5671da3e4 100644 --- a/lib/ceedling/configurator.rb +++ b/lib/ceedling/configurator.rb @@ -1,35 +1,47 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'ceedling/defaults' require 'ceedling/constants' require 'ceedling/file_path_utils' +require 'ceedling/exceptions' require 'deep_merge' - - class Configurator - attr_reader :project_config_hash, :script_plugins, :rake_plugins - attr_accessor :project_logging, :project_debug, :project_verbosity, :sanity_checks + attr_reader :project_config_hash, :programmatic_plugins, :rake_plugins + attr_accessor :project_logging, :sanity_checks, :include_test_case, :exclude_test_case - constructor(:configurator_setup, :configurator_builder, :configurator_plugins, :cmock_builder, :yaml_wrapper, :system_wrapper) do - @project_logging = false - @project_debug = false - @project_verbosity = Verbosity::NORMAL - @sanity_checks = TestResultsSanityChecks::NORMAL - end + constructor :configurator_setup, :configurator_builder, :configurator_plugins, :config_walkinator, :yaml_wrapper, :system_wrapper, :loginator, :reportinator + + def setup() + # Cmock config reference to provide to CMock for mock generation + @cmock_config = {} # Default empty hash, replaced by reference below - def setup - # special copy of cmock config to provide to cmock for construction - @cmock_config_hash = {} + # Runner config reference to provide to runner generation + @runner_config = {} # Default empty hash, replaced by reference below - # note: project_config_hash is an instance variable so constants and accessors created + # Note: project_config_hash is an instance variable so constants and accessors created # in eval() statements in build() have something of proper scope and persistence to reference @project_config_hash = {} - @project_config_hash_backup = {} - @script_plugins = [] + @programmatic_plugins = [] @rake_plugins = [] + + @project_logging = false + @sanity_checks = TestResultsSanityChecks::NORMAL end + # Override to prevent exception handling from walking & stringifying the object variables. + # Object variables are gigantic and produce a flood of output. + def inspect + # TODO: When identifying information is added to constructor, insert it into `inspect()` string + return this.class.name + end def replace_flattened_config(config) @project_config_hash.merge!(config) @@ -37,298 +49,596 @@ def replace_flattened_config(config) end - def store_config - @project_config_hash_backup = @project_config_hash.clone + # Set up essential flattened config related to verbosity. + # We do this because early config validation failures may need access to verbosity, + # but the accessors won't be available until after configuration is validated. + def set_verbosity(config) + # PROJECT_VERBOSITY and PROJECT_DEBUG set at command line processing before Ceedling is loaded + + if (!!defined?(PROJECT_DEBUG) and PROJECT_DEBUG) + eval("def project_debug() return true end", binding()) + else + eval("def project_debug() return false end", binding()) + end + + if !!defined?(PROJECT_VERBOSITY) + eval("def project_verbosity() return #{PROJECT_VERBOSITY} end", binding()) + end end - def restore_config - @project_config_hash = @project_config_hash_backup - @configurator_setup.build_constants_and_accessors(@project_config_hash, binding()) + # The default tools (eg. DEFAULT_TOOLS_TEST) are merged into default config hash + def merge_tools_defaults(config, default_config) + msg = @reportinator.generate_progress( 'Collecting default tool configurations' ) + @loginator.log( msg, Verbosity::OBNOXIOUS ) + + # config[:project] is guaranteed to exist / validated to exist but may not include elements referenced below + # config[:test_build] and config[:release_build] are optional in a user project configuration + + + release_build, _ = @config_walkinator.fetch_value( :project, :release_build, + hash:config, + default: DEFAULT_CEEDLING_PROJECT_CONFIG[:project][:release_build] + ) + + test_preprocessing, _ = @config_walkinator.fetch_value( :project, :use_test_preprocessor, + hash:config, + default: DEFAULT_CEEDLING_PROJECT_CONFIG[:project][:use_test_preprocessor] + ) + + backtrace, _ = @config_walkinator.fetch_value( :project, :use_backtrace, + hash:config, + default: DEFAULT_CEEDLING_PROJECT_CONFIG[:project][:use_backtrace] + ) + + release_assembly, _ = @config_walkinator.fetch_value( :release_build, :use_assembly, + hash:config, + default: DEFAULT_CEEDLING_PROJECT_CONFIG[:release_build][:use_assembly] + ) + + test_assembly, _ = @config_walkinator.fetch_value( :test_build, :use_assembly, + hash:config, + default: DEFAULT_CEEDLING_PROJECT_CONFIG[:test_build][:use_assembly] + ) + + default_config.deep_merge( DEFAULT_TOOLS_TEST.deep_clone() ) + + default_config.deep_merge( DEFAULT_TOOLS_TEST_PREPROCESSORS.deep_clone() ) if (test_preprocessing != :none) + default_config.deep_merge( DEFAULT_TOOLS_TEST_ASSEMBLER.deep_clone() ) if test_assembly + default_config.deep_merge( DEFAULT_TOOLS_TEST_GDB_BACKTRACE.deep_clone() ) if (backtrace == :gdb) + + default_config.deep_merge( DEFAULT_TOOLS_RELEASE.deep_clone() ) if release_build + default_config.deep_merge( DEFAULT_TOOLS_RELEASE_ASSEMBLER.deep_clone() ) if (release_build and release_assembly) + end + + + def populate_cmock_defaults(config, default_config) + # Cmock has its own internal defaults handling, but we need to set these specific values + # so they're guaranteed values and present for the Ceedling environment to access + + msg = @reportinator.generate_progress( 'Collecting CMock defaults' ) + @loginator.log( msg, Verbosity::OBNOXIOUS ) + + # Begin populating defaults with CMock defaults as set by Ceedling + default_cmock = default_config[:cmock] + + # Fill in default settings programmatically + default_cmock[:mock_path] = File.join(config[:project][:build_root], TESTS_BASE_PATH, 'mocks') + default_cmock[:verbosity] = project_verbosity() + end + + + def prepare_plugins_load_paths(plugins_load_path, config) + # Plugins must be loaded before generic path evaluation & magic that happen later. + # So, perform path magic here as discrete step. + config[:plugins][:load_paths].each do |path| + path.replace( @system_wrapper.module_eval( path ) ) if (path =~ RUBY_STRING_REPLACEMENT_PATTERN) + FilePathUtils::standardize( path ) + end + + # Add Ceedling's plugins path as load path so built-in plugins can be found + config[:plugins][:load_paths] << plugins_load_path + config[:plugins][:load_paths].uniq! + + return @configurator_plugins.process_aux_load_paths( config ) end - def reset_defaults(config) - [:test_compiler, - :test_linker, - :test_fixture, - :test_includes_preprocessor, - :test_file_preprocessor, - :test_file_preprocessor_directives, - :test_dependencies_generator, - :release_compiler, - :release_assembler, - :release_linker, - :release_dependencies_generator].each do |tool| - config[:tools].delete(tool) if (not (config[:tools][tool].nil?)) + def merge_plugins_defaults(paths_hash, config, default_config) + # Config YAML defaults plugins + plugin_yml_defaults = @configurator_plugins.find_plugin_yml_defaults( config, paths_hash ) + + # Config Ruby-based hash defaults plugins + plugin_hash_defaults = @configurator_plugins.find_plugin_hash_defaults( config, paths_hash ) + + + if !plugin_hash_defaults.empty? + msg = @reportinator.generate_progress( 'Collecting Plugin YAML defaults' ) + @loginator.log( msg, Verbosity::OBNOXIOUS ) + end + + # Load base configuration values (defaults) from YAML + plugin_yml_defaults.each do |plugin, defaults| + _defaults = @yaml_wrapper.load( defaults ) + + msg = " - #{plugin} >> " + _defaults.to_s() + @loginator.log( msg, Verbosity::DEBUG ) + + default_config.deep_merge( _defaults ) + end + + if !plugin_hash_defaults.empty? + msg = @reportinator.generate_progress( 'Collecting Plugin Ruby hash defaults' ) + @loginator.log( msg, Verbosity::OBNOXIOUS ) + end + + # Load base configuration values (defaults) as hash from Ruby + plugin_hash_defaults.each do |plugin, defaults| + msg = " - #{plugin} >> " + defaults.to_s() + @loginator.log( msg, Verbosity::DEBUG ) + + default_config.deep_merge( defaults ) end end - # The default values defined in defaults.rb (eg. DEFAULT_TOOLS_TEST) are populated - # into @param config - def populate_defaults(config) - new_config = DEFAULT_CEEDLING_CONFIG.deep_clone - new_config.deep_merge!(config) - config.replace(new_config) + def merge_ceedling_runtime_config(config, runtime_config) + # Merge Ceedling's internal runtime configuration settings + config.deep_merge( runtime_config ) + end - @configurator_builder.populate_defaults( config, DEFAULT_TOOLS_TEST ) - @configurator_builder.populate_defaults( config, DEFAULT_TOOLS_TEST_PREPROCESSORS ) if (config[:project][:use_test_preprocessor]) - @configurator_builder.populate_defaults( config, DEFAULT_TOOLS_TEST_DEPENDENCIES ) if (config[:project][:use_deep_dependencies]) + def populate_with_defaults( config_hash, defaults_hash ) + msg = @reportinator.generate_progress( 'Populating project configuration with collected default values' ) + @loginator.log( msg, Verbosity::OBNOXIOUS ) - @configurator_builder.populate_defaults( config, DEFAULT_TOOLS_RELEASE ) if (config[:project][:release_build]) - @configurator_builder.populate_defaults( config, DEFAULT_TOOLS_RELEASE_ASSEMBLER ) if (config[:project][:release_build] and config[:release_build][:use_assembly]) - @configurator_builder.populate_defaults( config, DEFAULT_TOOLS_RELEASE_DEPENDENCIES ) if (config[:project][:release_build] and config[:project][:use_deep_dependencies]) + @configurator_builder.populate_with_defaults( config_hash, defaults_hash ) end - def populate_unity_defaults(config) - unity = config[:unity] || {} - @runner_config = unity.merge(@runner_config || config[:test_runner] || {}) + def populate_unity_config(config) + msg = @reportinator.generate_progress( 'Processing Unity configuration' ) + @loginator.log( msg, Verbosity::OBNOXIOUS ) + + if config[:unity][:use_param_tests] + config[:unity][:defines] << 'UNITY_SUPPORT_TEST_CASES' + config[:unity][:defines] << 'UNITY_SUPPORT_VARIADIC_MACROS' + end + + @loginator.log( "Unity configuration >> #{config[:unity]}", Verbosity::DEBUG ) end - def populate_cmock_defaults(config) - # cmock has its own internal defaults handling, but we need to set these specific values - # so they're present for the build environment to access; - # note: these need to end up in the hash given to initialize cmock for this to be successful - cmock = config[:cmock] || {} - # yes, we're duplicating the default mock_prefix in cmock, but it's because we need CMOCK_MOCK_PREFIX always available in Ceedling's environment - cmock[:mock_prefix] = 'Mock' if (cmock[:mock_prefix].nil?) + def populate_cmock_config(config) + # Save CMock config reference + @cmock_config = config[:cmock] - # just because strict ordering is the way to go - cmock[:enforce_strict_ordering] = true if (cmock[:enforce_strict_ordering].nil?) + cmock = config[:cmock] - cmock[:mock_path] = File.join(config[:project][:build_root], TESTS_BASE_PATH, 'mocks') if (cmock[:mock_path].nil?) - cmock[:verbosity] = @project_verbosity if (cmock[:verbosity].nil?) + # Do no more prep if we're not using mocks + if !config[:project][:use_mocks] + @loginator.log( "CMock configuration >> #{cmock}", Verbosity::DEBUG ) + return + end + + msg = @reportinator.generate_progress( 'Processing CMock configuration' ) + @loginator.log( msg, Verbosity::OBNOXIOUS ) - cmock[:plugins] = [] if (cmock[:plugins].nil?) - cmock[:plugins].map! { |plugin| plugin.to_sym } - cmock[:plugins] << (:cexception) if (!cmock[:plugins].include?(:cexception) and (config[:project][:use_exceptions])) + # Plugins housekeeping + cmock[:plugins].map! { |plugin| plugin.to_sym() } cmock[:plugins].uniq! - cmock[:unity_helper] = false if (cmock[:unity_helper].nil?) + # Reformulate CMock helper path value as array of one element if it's a string in config + cmock[:unity_helper_path] = [cmock[:unity_helper_path]] if cmock[:unity_helper_path].is_a?( String ) - if (cmock[:unity_helper]) - cmock[:unity_helper] = [cmock[:unity_helper]] if cmock[:unity_helper].is_a? String - cmock[:includes] += cmock[:unity_helper].map{|helper| File.basename(helper) } - cmock[:includes].uniq! + # CMock Unity helper handling + cmock[:unity_helper_path].each do |path| + cmock[:includes] << File.basename( path ) end - @runner_config = cmock.merge(@runner_config || config[:test_runner] || {}) + cmock[:includes].uniq! - @cmock_builder.manufacture(cmock) + @loginator.log( "CMock configuration >> #{cmock}", Verbosity::DEBUG ) + end + + + def populate_test_runner_generation_config(config) + msg = @reportinator.generate_progress( 'Populating test runner generation settings' ) + @loginator.log( msg, Verbosity::OBNOXIOUS ) + + use_backtrace = config[:project][:use_backtrace] + + # Force command line argument option for any backtrace option + if use_backtrace != :none + config[:test_runner][:cmdline_args] = true + end + + # Copy CMock options used by test runner generation + config[:test_runner][:mock_prefix] = config[:cmock][:mock_prefix] + config[:test_runner][:mock_suffix] = config[:cmock][:mock_suffix] + config[:test_runner][:enforce_strict_ordering] = config[:cmock][:enforce_strict_ordering] + + # Merge Unity options used by test runner generation + config[:test_runner][:defines] += config[:unity][:defines] + config[:test_runner][:use_param_tests] = config[:unity][:use_param_tests] + + @runner_config = config[:test_runner] + + @loginator.log( "Test Runner configuration >> #{config[:test_runner]}", Verbosity::DEBUG ) + end + + + def populate_exceptions_config(config) + # Automagically set exception handling if CMock is configured for it + if config[:cmock][:plugins] && config[:cmock][:plugins].include?(:cexception) + msg = @reportinator.generate_progress( 'Enabling CException use based on CMock plugins settings' ) + @loginator.log( msg, Verbosity::OBNOXIOUS ) + + config[:project][:use_exceptions] = true + end + + @loginator.log( "CException configuration >> #{config[:cexception]}", Verbosity::DEBUG ) end def get_runner_config - @runner_config + # Clone because test runner generation is not thread-safe; + # The runner generator is manufactured for each use with configuration changes for each use. + return @runner_config.clone end - # grab tool names from yaml and insert into tool structures so available for error messages - # set up default values - def tools_setup(config) + def get_cmock_config + # Clone because test mock generation is not thread-safe; + # The mock generator is manufactured for each use with configuration changes for each use. + return @cmock_config.clone + end + + + # Process our tools + # - :tools entries + # - Insert missing names for + # - Handle needed defaults + # - Configure test runner from backtrace configuration + def populate_tools_config(config) + msg = @reportinator.generate_progress( 'Populating tool definition settings and expanding any string replacements' ) + @loginator.log( msg, Verbosity::OBNOXIOUS ) + config[:tools].each_key do |name| tool = config[:tools][name] - # populate name if not given - tool[:name] = name.to_s if (tool[:name].nil?) - - # handle inline ruby string substitution in executable - if (tool[:executable] =~ RUBY_STRING_REPLACEMENT_PATTERN) - tool[:executable].replace(@system_wrapper.module_eval(tool[:executable])) + if not tool.is_a?(Hash) + raise CeedlingException.new( "Expected configuration for tool :#{name} is a Hash but found #{tool.class}" ) end - # populate stderr redirect option - tool[:stderr_redirect] = StdErrRedirect::NONE if (tool[:stderr_redirect].nil?) + # Populate name if not given + tool[:name] = name.to_s if (tool[:name].nil?) - # populate background execution option - tool[:background_exec] = BackgroundExec::NONE if (tool[:background_exec].nil?) + # Populate $stderr redirect option + tool[:stderr_redirect] = StdErrRedirect::NONE if (tool[:stderr_redirect].nil?) - # populate optional option to control verification of executable in search paths + # Populate optional option to control verification of executable in search paths tool[:optional] = false if (tool[:optional].nil?) end end - def tools_supplement_arguments(config) - tools_name_prefix = 'tools_' - config[:tools].each_key do |name| - tool = @project_config_hash[(tools_name_prefix + name.to_s).to_sym] - - # smoosh in extra arguments if specified at top-level of config (useful for plugins & default gcc tools) - # arguments are squirted in at _end_ of list - top_level_tool = (tools_name_prefix + name.to_s).to_sym - if (not config[top_level_tool].nil?) - # adding and flattening is not a good idea: might over-flatten if there's array nesting in tool args - tool[:arguments].concat config[top_level_tool][:arguments] + # Process any tool definition shortcuts + # - Append extra arguments + # - Redefine executable + # + # :tools_ + # :arguments: [...] + # :executable: '...' + def populate_tools_shortcuts(config) + msg = @reportinator.generate_progress( 'Processing tool definition shortcuts' ) + @loginator.log( msg, Verbosity::OBNOXIOUS ) + + prefix = 'tools_' + config[:tools].each do |name, tool| + # Lookup shortcut tool definition (:tools_) + shortcut = (prefix + name.to_s).to_sym + + # Logging message to be built up + msg = '' + + # Try to lookup the executable from user config + executable, _ = @config_walkinator.fetch_value(shortcut, :executable, + hash:config + ) + + # Try to lookup arguments from user config + args_to_add, _ = @config_walkinator.fetch_value(shortcut, :arguments, + hash:config, + default: [] + ) + + # If either tool definition modification is happening, start building the logging message + if !args_to_add.empty? or !executable.nil? + msg += " > #{name}\n" end - end - end + # Log the executable and redefine the tool config + if !executable.nil? + msg += " executable: \"#{executable}\"\n" + tool[:executable] = executable + end - def find_and_merge_plugins(config) - # plugins must be loaded before generic path evaluation & magic that happen later; - # perform path magic here as discrete step - config[:plugins][:load_paths].each do |path| - path.replace(@system_wrapper.module_eval(path)) if (path =~ RUBY_STRING_REPLACEMENT_PATTERN) - FilePathUtils::standardize(path) - end + # Log the arguments and add to the tool config + if !args_to_add.empty? + msg += " arguments: " + args_to_add.map{|arg| "\"#{arg}\""}.join( ', ' ) + "\n" + tool[:arguments].concat( args_to_add ) + end - config[:plugins][:load_paths] << FilePathUtils::standardize(Ceedling.load_path) - config[:plugins][:load_paths].uniq! + # Log + @loginator.log( msg, Verbosity::DEBUG ) if !msg.empty? + end + end - paths_hash = @configurator_plugins.add_load_paths(config) - @rake_plugins = @configurator_plugins.find_rake_plugins(config, paths_hash) - @script_plugins = @configurator_plugins.find_script_plugins(config, paths_hash) - config_plugins = @configurator_plugins.find_config_plugins(config, paths_hash) - plugin_yml_defaults = @configurator_plugins.find_plugin_yml_defaults(config, paths_hash) - plugin_hash_defaults = @configurator_plugins.find_plugin_hash_defaults(config, paths_hash) + def discover_plugins(paths_hash, config) + msg = @reportinator.generate_progress( 'Discovering all plugins' ) + @loginator.log( msg, Verbosity::OBNOXIOUS ) - config_plugins.each do |plugin| - plugin_config = @yaml_wrapper.load(plugin) - config.deep_merge(plugin_config) + # Rake-based plugins + @rake_plugins = @configurator_plugins.find_rake_plugins( config, paths_hash ) + if !@configurator_plugins.rake_plugins.empty? + msg = " > Rake plugins: " + @configurator_plugins.rake_plugins.map{|p| p[:plugin]}.join( ', ' ) + @loginator.log( msg, Verbosity::DEBUG ) end - plugin_yml_defaults.each do |defaults| - @configurator_builder.populate_defaults( config, @yaml_wrapper.load(defaults) ) + # Ruby `Plugin` subclass programmatic plugins + @programmatic_plugins = @configurator_plugins.find_programmatic_plugins( config, paths_hash ) + if !@configurator_plugins.programmatic_plugins.empty? + msg = " > Programmatic plugins: " + @configurator_plugins.programmatic_plugins.map{|p| p[:plugin]}.join( ', ' ) + @loginator.log( msg, Verbosity::DEBUG ) end - - plugin_hash_defaults.each do |defaults| - @configurator_builder.populate_defaults( config, defaults ) + + # Config plugins + config_plugins = @configurator_plugins.find_config_plugins( config, paths_hash ) + if !@configurator_plugins.config_plugins.empty? + msg = " > Config plugins: " + @configurator_plugins.config_plugins.map{|p| p[:plugin]}.join( ', ' ) + @loginator.log( msg, Verbosity::DEBUG ) end + end - # special plugin setting for results printing + + def populate_plugins_config(paths_hash, config) + # Set special plugin setting for results printing if unset config[:plugins][:display_raw_test_results] = true if (config[:plugins][:display_raw_test_results].nil?) + # Add corresponding path to each plugin's configuration paths_hash.each_pair { |name, path| config[:plugins][name] = path } end - def merge_imports(config) - if config[:import] - if config[:import].is_a? Array - until config[:import].empty? - path = config[:import].shift - path = @system_wrapper.module_eval(path) if (path =~ RUBY_STRING_REPLACEMENT_PATTERN) - config.deep_merge!(@yaml_wrapper.load(path)) - end - else - config[:import].each_value do |path| - if !path.nil? - path = @system_wrapper.module_eval(path) if (path =~ RUBY_STRING_REPLACEMENT_PATTERN) - config.deep_merge!(@yaml_wrapper.load(path)) - end + def merge_config_plugins(config) + return if @configurator_plugins.config_plugins.empty? + + # Merge plugin configuration values (like Ceedling project file) + @configurator_plugins.config_plugins.each do |hash| + _config = @yaml_wrapper.load( hash[:path] ) + + msg = @reportinator.generate_progress( "Merging configuration from plugin #{hash[:plugin]}" ) + @loginator.log( msg, Verbosity::OBNOXIOUS ) + @loginator.log( _config.to_s, Verbosity::DEBUG ) + + # Special handling for plugin paths + if (_config.include?( :paths )) + _config[:paths].update( _config[:paths] ) do |k,v| + plugin_path = hash[:path].match( /(.*)[\/]config[\/]\w+\.yml/ )[1] + v.map {|vv| File.expand_path( vv.gsub!( /\$PLUGIN_PATH/, plugin_path) ) } end end + + config.deep_merge( _config ) end - config.delete(:import) end + # Process environment variables set in configuration file + # (Each entry within the :environment array is a hash) def eval_environment_variables(config) + return if config[:environment].nil? + + msg = @reportinator.generate_progress( 'Processing environment variables' ) + @loginator.log( msg, Verbosity::OBNOXIOUS ) + config[:environment].each do |hash| - key = hash.keys[0] - value = hash[key] + key = hash.keys[0] # Get first (should be only) environment variable entry + value = hash[key] # Get associated value items = [] - interstitial = ((key == :path) ? File::PATH_SEPARATOR : '') + # Special case handling for :path environment variable entry + # File::PATH_SEPARATOR => ':' (Unix-ish) or ';' (Windows) + interstitial = ((key == :path) ? File::PATH_SEPARATOR : ' ') + + # Create an array container for the value of this entry + # - If the value is an array, get it + # - Otherwise, place value in a single item array items = ((value.class == Array) ? hash[key] : [value]) + # Process value array items.each do |item| + # Process each item for Ruby string replacement if item.is_a? String and item =~ RUBY_STRING_REPLACEMENT_PATTERN item.replace( @system_wrapper.module_eval( item ) ) end end + + # Join any value items (become a flattened string) + # - With path separator if the key was :path + # - With space otherwise hash[key] = items.join( interstitial ) + # Set the environment variable for our session @system_wrapper.env_set( key.to_s.upcase, hash[key] ) + @loginator.log( " - #{key.to_s.upcase}: \"#{hash[key]}\"", Verbosity::DEBUG ) end end + # Eval config path lists (convert any strings to array of size 1) and handle any Ruby string replacement def eval_paths(config) - # [:plugins]:[load_paths] already handled + # :plugins ↳ :load_paths already handled - paths = [ # individual paths that don't follow convention processed below - config[:project][:build_root], - config[:release_build][:artifacts]] + msg = @reportinator.generate_progress( 'Processing path entries and expanding any string replacements' ) + @loginator.log( msg, Verbosity::OBNOXIOUS ) - eval_path_list( paths ) + eval_path_entries( config[:project][:build_root] ) + eval_path_entries( config[:release_build][:artifacts] ) - config[:paths].each_pair { |collection, paths| eval_path_list( paths ) } + config[:paths].each_pair do |entry, paths| + # :paths sub-entries (e.g. :test) could be a single string -> make array + reform_path_entries_as_lists( config[:paths], entry, paths ) + eval_path_entries( paths ) + end - config[:files].each_pair { |collection, files| eval_path_list( files ) } + config[:files].each_pair do |entry, files| + # :files sub-entries (e.g. :test) could be a single string -> make array + reform_path_entries_as_lists( config[:files], entry, files ) + eval_path_entries( files ) + end - # all other paths at secondary hash key level processed by convention: - # ex. [:toplevel][:foo_path] & [:toplevel][:bar_paths] are evaluated - config.each_pair { |parent, child| eval_path_list( collect_path_list( child ) ) } + # All other paths at secondary hash key level processed by convention (`_path`): + # ex. :toplevel ↳ :foo_path & :toplevel ↳ :bar_paths are evaluated + config.each_pair { |_, child| eval_path_entries( collect_path_list( child ) ) } + end + + + # Handle any Ruby string replacement for :flags string arrays + def eval_flags(config) + msg = @reportinator.generate_progress( 'Expanding any string replacements in :flags entries' ) + @loginator.log( msg, Verbosity::OBNOXIOUS ) + + # Descend down to array of command line flags strings regardless of depth in config block + traverse_hash_eval_string_arrays( config[:flags] ) + end + + + # Handle any Ruby string replacement for :defines string arrays + def eval_defines(config) + msg = @reportinator.generate_progress( 'Expanding any string replacements in :defines entries' ) + @loginator.log( msg, Verbosity::OBNOXIOUS ) + + # Descend down to array of #define strings regardless of depth in config block + traverse_hash_eval_string_arrays( config[:defines] ) end def standardize_paths(config) - # [:plugins]:[load_paths] already handled + msg = @reportinator.generate_progress( 'Standardizing all paths' ) + @loginator.log( msg, Verbosity::OBNOXIOUS ) - paths = [ # individual paths that don't follow convention processed below + # Individual paths that don't follow `_path` convention processed here + paths = [ config[:project][:build_root], - config[:release_build][:artifacts]] # cmock path in case it was explicitly set in config + config[:release_build][:artifacts] + ] paths.flatten.each { |path| FilePathUtils::standardize( path ) } config[:paths].each_pair do |collection, paths| - # ensure that list is an array (i.e. handle case of list being a single string, + # Ensure that list is an array (i.e. handle case of list being a single string, # or a multidimensional array) config[:paths][collection] = [paths].flatten.map{|path| FilePathUtils::standardize( path )} end - config[:files].each_pair { |collection, files| files.each{ |path| FilePathUtils::standardize( path ) } } + config[:files].each_pair { |_, files| files.each{ |path| FilePathUtils::standardize( path ) } } - config[:tools].each_pair { |tool, config| FilePathUtils::standardize( config[:executable] ) if (config.include? :executable) } + config[:tools].each_pair { |_, config| FilePathUtils::standardize( config[:executable] ) if (config.include? :executable) } - # all other paths at secondary hash key level processed by convention: - # ex. [:toplevel][:foo_path] & [:toplevel][:bar_paths] are standardized - config.each_pair do |parent, child| + # All other paths at secondary hash key level processed by convention (`_path`): + # ex. :toplevel ↳ :foo_path & :toplevel ↳ :bar_paths are standardized + config.each_pair do |_, child| collect_path_list( child ).each { |path| FilePathUtils::standardize( path ) } end end - def validate(config) - # collect felonies and go straight to jail - raise if (not @configurator_setup.validate_required_sections( config )) + def validate_essential(config) + # Collect all infractions, everybody on probation until final adjudication + blotter = true + + blotter &= @configurator_setup.validate_required_sections( config ) + blotter &= @configurator_setup.validate_required_section_values( config ) - # collect all misdemeanors, everybody on probation - blotter = [] - blotter << @configurator_setup.validate_required_section_values( config ) - blotter << @configurator_setup.validate_paths( config ) - blotter << @configurator_setup.validate_tools( config ) - blotter << @configurator_setup.validate_plugins( config ) + # Configuration sections can reference environment variables that are evaluated early on. + # So, we validate :environment early as an essential section. + blotter &= @configurator_setup.validate_environment_vars( config ) - raise if (blotter.include?( false )) + if !blotter + raise CeedlingException.new("Ceedling configuration failed validation") + end end - # create constants and accessors (attached to this object) from given hash - def build(config, *keys) - # create flattened & expanded configuration hash - built_config = @configurator_setup.build_project_config( config, @configurator_builder.flattenify( config ) ) + def validate_final(config, app_cfg) + # Collect all infractions, everybody on probation until final adjudication + blotter = true + blotter &= @configurator_setup.validate_paths( config ) + blotter &= @configurator_setup.validate_tools( config ) + blotter &= @configurator_setup.validate_test_runner_generation( + config, + app_cfg[:include_test_case], + app_cfg[:exclude_test_case] + ) + blotter &= @configurator_setup.validate_defines( config ) + blotter &= @configurator_setup.validate_flags( config ) + blotter &= @configurator_setup.validate_test_preprocessor( config ) + blotter &= @configurator_setup.validate_backtrace( config ) + blotter &= @configurator_setup.validate_threads( config ) + blotter &= @configurator_setup.validate_plugins( config ) + + if !blotter + raise CeedlingException.new( "Ceedling configuration failed validation" ) + end + end + + + # Create constants and accessors (attached to this object) from given hash + def build(ceedling_lib_path, logging_path, config, *keys) + flattened_config = @configurator_builder.flattenify( config ) + + @configurator_setup.build_project_config( ceedling_lib_path, logging_path, flattened_config ) + + @configurator_setup.build_directory_structure( flattened_config ) - @project_config_hash = built_config.clone - store_config() + # Copy Unity, CMock, CException into vendor directory within build directory + @configurator_setup.vendor_frameworks_and_support_files( ceedling_lib_path, flattened_config ) - @configurator_setup.build_constants_and_accessors(built_config, binding()) + @configurator_setup.build_project_collections( flattened_config ) - # top-level keys disappear when we flatten, so create global constants & accessors to any specified keys + @project_config_hash = flattened_config.clone + + @configurator_setup.build_constants_and_accessors( flattened_config, binding() ) + + # Top-level keys disappear when we flatten, so create global constants & accessors to any specified keys keys.each do |key| hash = { key => config[key] } - @configurator_setup.build_constants_and_accessors(hash, binding()) + @configurator_setup.build_constants_and_accessors( hash, binding() ) end end - # add to constants and accessors as post build step + def redefine_element(elem, value) + # Ensure elem is a symbol + elem = elem.to_sym if elem.class != Symbol + + # Ensure element already exists + if not @project_config_hash.include?(elem) + error = "Could not rederine #{elem} in configurator--element does not exist" + raise CeedlingException.new(error) + end + + # Update internal hash + @project_config_hash[elem] = value + + # Update global constant + @configurator_builder.build_global_constant(elem, value) + end + + + # Add to constants and accessors as post build step def build_supplement(config_base, config_more) # merge in our post-build additions to base configuration hash config_base.deep_merge!( config_more ) @@ -338,7 +648,6 @@ def build_supplement(config_base, config_more) # merge our flattened hash with built hash from previous build @project_config_hash.deep_merge!( config_more_flattened ) - store_config() # create more constants and accessors @configurator_setup.build_constants_and_accessors(config_more_flattened, binding()) @@ -352,31 +661,71 @@ def build_supplement(config_base, config_more) def insert_rake_plugins(plugins) - plugins.each do |plugin| - @project_config_hash[:project_rakefile_component_files] << plugin + plugins.each do |hash| + msg = @reportinator.generate_progress( "Adding plugin #{hash[:plugin]} to Rake load list" ) + @loginator.log( msg, Verbosity::OBNOXIOUS ) + + @project_config_hash[:project_rakefile_component_files] << hash[:path] end end - ### private ### + ### Private ### private + def reform_path_entries_as_lists( container, entry, value ) + container[entry] = [value] if value.kind_of?( String ) + end + + def collect_path_list( container ) paths = [] - container.each_key { |key| paths << container[key] if (key.to_s =~ /_path(s)?$/) } if (container.class == Hash) - return paths.flatten + + if (container.class == Hash) + container.each_key do |key| + paths << container[key] if (key.to_s =~ /_path(s)?$/) + end + end + + return paths.flatten() end - def eval_path_list( paths ) - if paths.kind_of?(Array) - paths = Array.new(paths) + + def eval_path_entries( container ) + paths = [] + + case(container) + when Array then paths = Array.new( container ).flatten() + when String then paths << container + else + return end - paths.flatten.each do |path| + paths.each do |path| path.replace( @system_wrapper.module_eval( path ) ) if (path =~ RUBY_STRING_REPLACEMENT_PATTERN) end end + # Traverse configuration tree recursively to find terminal leaf nodes that are a list of strings; + # expand in place any string with the Ruby string replacement pattern. + def traverse_hash_eval_string_arrays(config) + case config + + when Array + # If it's an array of strings, process it + if config.all? { |item| item.is_a?( String ) } + # Expand in place each string item in the array + config.each do |item| + item.replace( @system_wrapper.module_eval( item ) ) if (item =~ RUBY_STRING_REPLACEMENT_PATTERN) + end + end + + when Hash + # Recurse + config.each_value { |value| traverse_hash_eval_string_arrays( value ) } + end + end + end diff --git a/lib/ceedling/configurator_builder.rb b/lib/ceedling/configurator_builder.rb index 49482b6dc..306f3e1ab 100644 --- a/lib/ceedling/configurator_builder.rb +++ b/lib/ceedling/configurator_builder.rb @@ -1,31 +1,55 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'rubygems' -require 'rake' # for ext() method +require 'rake' # for ext() method require 'ceedling/file_path_utils' # for class methods require 'ceedling/defaults' require 'ceedling/constants' # for Verbosity constants class & base file paths +class ConfiguratorBuilder + constructor :file_path_collection_utils, :file_wrapper, :system_wrapper -class ConfiguratorBuilder - constructor :file_system_utils, :file_wrapper, :system_wrapper + def build_global_constant(elem, value) + # Convert key names to Ruby constant names + # Some key names can be C file names that can include dashes + # Upcase the key names to create consitency and Ruby constants by convention + # Replace dashes with underscores to match handling of Ruby accessor method names + formatted_key = elem.to_s.gsub('-','_').upcase + + # Undefine global constant if it already exists + Object.send(:remove_const, formatted_key.to_sym) if @system_wrapper.constants_include?(formatted_key) + + # Create global constant + Object.module_eval("#{formatted_key} = value") + end def build_global_constants(config) config.each_pair do |key, value| - formatted_key = key.to_s.upcase - # undefine global constant if it already exists - Object.send(:remove_const, formatted_key.to_sym) if @system_wrapper.constants_include?(formatted_key) - # create global constant - Object.module_eval("#{formatted_key} = value") + build_global_constant(key, value) end + + # TODO: This wants to go somewhere better + Object.module_eval("TOOLS_TEST_ASSEMBLER = {}") if (not config[:test_build_use_assembly]) && !defined?(TOOLS_TEST_ASSEMBLER) + Object.module_eval("TOOLS_RELEASE_ASSEMBLER = {}") if (not config[:release_build_use_assembly]) && !defined?(TOOLS_RELEASE_ASSEMBLER) end def build_accessor_methods(config, context) + # Fill configurator object with accessor methods config.each_pair do |key, value| - # fill configurator object with accessor methods - eval("def #{key.to_s.downcase}() return @project_config_hash[:#{key}] end", context) + # Convert key names to Ruby method names + # Some key names can be C file names that can include dashes; dashes are not allowed in Ruby method names + # Downcase the key names and replace any illegal dashes with legal underscores + # Downcased key names create consistency and ensure no method names become Ruby constants by accident + eval("def #{key.to_s.gsub('-','_').downcase}() return @project_config_hash[:#{key}] end", context) end end @@ -36,7 +60,7 @@ def flattenify(config) config.each_key do | parent | - # gracefully handle empty top-level entries + # Gracefully handle empty top-level entries next if (config[parent].nil?) case config[parent] @@ -45,12 +69,14 @@ def flattenify(config) key = "#{parent.to_s.downcase}_#{hash.keys[0].to_s.downcase}".to_sym new_hash[key] = hash[hash.keys[0]] end + when Hash config[parent].each_pair do | child, value | key = "#{parent.to_s.downcase}_#{child.to_s.downcase}".to_sym new_hash[key] = value end - # handle entries with no children, only values + + # Handle entries with no children, only values else new_hash["#{parent.to_s.downcase}".to_sym] = config[parent] end @@ -61,60 +87,67 @@ def flattenify(config) end - def populate_defaults(config, defaults) - defaults.keys.sort.each do |section| - defaults[section].keys.sort.each do |entry| - config[section] = {} if config[section].nil? - config[section][entry] = defaults[section][entry].deep_clone if (config[section][entry].nil?) + # If config lacks an entry present in defaults posseses, add the default entry + # Processes recursively + def populate_with_defaults(config, defaults) + defaults.each do |key, value| + # If config is missing the same key, copy in the default entry + if config[key].nil? + config[key] = value.is_a?(Hash) ? value.deep_clone : value + + # Continue recursively for hash entries + elsif config[key].is_a?(Hash) && value.is_a?(Hash) + populate_with_defaults(config[key], value) end end end - def clean(in_hash) - # ensure that include files inserted into test runners have file extensions & proper ones at that + def cleanup(in_hash) + # Ensure that include files inserted into test runners have file extensions & proper ones at that in_hash[:test_runner_includes].map!{|include| include.ext(in_hash[:extension_header])} end - def set_build_paths(in_hash) + def set_build_paths(in_hash, logging_path) out_hash = {} project_build_artifacts_root = File.join(in_hash[:project_build_root], 'artifacts') project_build_tests_root = File.join(in_hash[:project_build_root], TESTS_BASE_PATH) + project_build_vendor_root = File.join(in_hash[:project_build_root], 'vendor') project_build_release_root = File.join(in_hash[:project_build_root], RELEASE_BASE_PATH) paths = [ [:project_build_artifacts_root, project_build_artifacts_root, true ], [:project_build_tests_root, project_build_tests_root, true ], + [:project_build_vendor_root, project_build_vendor_root, true ], [:project_build_release_root, project_build_release_root, in_hash[:project_release_build] ], [:project_test_artifacts_path, File.join(project_build_artifacts_root, TESTS_BASE_PATH), true ], [:project_test_runners_path, File.join(project_build_tests_root, 'runners'), true ], [:project_test_results_path, File.join(project_build_tests_root, 'results'), true ], [:project_test_build_output_path, File.join(project_build_tests_root, 'out'), true ], - [:project_test_build_output_asm_path, File.join(project_build_tests_root, 'out', 'asm'), true ], - [:project_test_build_output_c_path, File.join(project_build_tests_root, 'out', 'c'), true ], [:project_test_build_cache_path, File.join(project_build_tests_root, 'cache'), true ], [:project_test_dependencies_path, File.join(project_build_tests_root, 'dependencies'), true ], + [:project_build_vendor_unity_path, File.join(project_build_vendor_root, 'unity', 'src'), true ], + [:project_build_vendor_cmock_path, File.join(project_build_vendor_root, 'cmock', 'src'), in_hash[:project_use_mocks] ], + [:project_build_vendor_cexception_path, File.join(project_build_vendor_root, 'c_exception', 'lib'), in_hash[:project_use_exceptions] ], + [:project_release_artifacts_path, File.join(project_build_artifacts_root, RELEASE_BASE_PATH), in_hash[:project_release_build] ], [:project_release_build_cache_path, File.join(project_build_release_root, 'cache'), in_hash[:project_release_build] ], [:project_release_build_output_path, File.join(project_build_release_root, 'out'), in_hash[:project_release_build] ], - [:project_release_build_output_asm_path, File.join(project_build_release_root, 'out', 'asm'), in_hash[:project_release_build] ], - [:project_release_build_output_c_path, File.join(project_build_release_root, 'out', 'c'), in_hash[:project_release_build] ], [:project_release_dependencies_path, File.join(project_build_release_root, 'dependencies'), in_hash[:project_release_build] ], - [:project_log_path, File.join(in_hash[:project_build_root], 'logs'), true ], - [:project_temp_path, File.join(in_hash[:project_build_root], 'temp'), true ], + [:project_log_path, logging_path, true ], - [:project_test_preprocess_includes_path, File.join(project_build_tests_root, 'preprocess/includes'), in_hash[:project_use_test_preprocessor] ], - [:project_test_preprocess_files_path, File.join(project_build_tests_root, 'preprocess/files'), in_hash[:project_use_test_preprocessor] ], + [:project_test_preprocess_includes_path, File.join(project_build_tests_root, 'preprocess/includes'), (in_hash[:project_use_test_preprocessor] != :none) ], + [:project_test_preprocess_files_path, File.join(project_build_tests_root, 'preprocess/files'), (in_hash[:project_use_test_preprocessor] != :none) ], ] out_hash[:project_build_paths] = [] - # fetch already set mock path + # Fetch already set mock path out_hash[:project_build_paths] << in_hash[:cmock_mock_path] if (in_hash[:project_use_mocks]) paths.each do |path| @@ -122,9 +155,9 @@ def set_build_paths(in_hash) build_path = path[1] build_path_add_condition = path[2] - # insert path into build paths if associated with true condition + # Insert path into build paths if associated with true condition out_hash[:project_build_paths] << build_path if build_path_add_condition - # set path symbol name and path for each entry in paths array + # Set path symbol name and path for each entry in paths array out_hash[build_path_name] = build_path end @@ -132,34 +165,18 @@ def set_build_paths(in_hash) end - def set_force_build_filepaths(in_hash) - out_hash = {} - - out_hash[:project_test_force_rebuild_filepath] = File.join( in_hash[:project_test_dependencies_path], 'force_build' ) - out_hash[:project_release_force_rebuild_filepath] = File.join( in_hash[:project_release_dependencies_path], 'force_build' ) if (in_hash[:project_release_build]) - - return out_hash - end - - - def set_rakefile_components(in_hash) + def set_rakefile_components(ceedling_lib_path, in_hash) out_hash = { - :project_rakefile_component_files => - [File.join(CEEDLING_LIB, 'ceedling', 'tasks_base.rake'), - File.join(CEEDLING_LIB, 'ceedling', 'tasks_filesystem.rake'), - File.join(CEEDLING_LIB, 'ceedling', 'tasks_tests.rake'), - File.join(CEEDLING_LIB, 'ceedling', 'tasks_vendor.rake'), - File.join(CEEDLING_LIB, 'ceedling', 'rules_tests.rake')]} - - out_hash[:project_rakefile_component_files] << File.join(CEEDLING_LIB, 'ceedling', 'rules_cmock.rake') if (in_hash[:project_use_mocks]) - out_hash[:project_rakefile_component_files] << File.join(CEEDLING_LIB, 'ceedling', 'rules_preprocess.rake') if (in_hash[:project_use_test_preprocessor]) - out_hash[:project_rakefile_component_files] << File.join(CEEDLING_LIB, 'ceedling', 'rules_tests_deep_dependencies.rake') if (in_hash[:project_use_deep_dependencies]) - out_hash[:project_rakefile_component_files] << File.join(CEEDLING_LIB, 'ceedling', 'tasks_tests_deep_dependencies.rake') if (in_hash[:project_use_deep_dependencies]) - - out_hash[:project_rakefile_component_files] << File.join(CEEDLING_LIB, 'ceedling', 'rules_release_deep_dependencies.rake') if (in_hash[:project_release_build] and in_hash[:project_use_deep_dependencies]) - out_hash[:project_rakefile_component_files] << File.join(CEEDLING_LIB, 'ceedling', 'rules_release.rake') if (in_hash[:project_release_build]) - out_hash[:project_rakefile_component_files] << File.join(CEEDLING_LIB, 'ceedling', 'tasks_release_deep_dependencies.rake') if (in_hash[:project_release_build] and in_hash[:project_use_deep_dependencies]) - out_hash[:project_rakefile_component_files] << File.join(CEEDLING_LIB, 'ceedling', 'tasks_release.rake') if (in_hash[:project_release_build]) + :project_rakefile_component_files => [ + File.join( ceedling_lib_path, 'tasks_base.rake' ), + File.join( ceedling_lib_path, 'tasks_filesystem.rake' ), + File.join( ceedling_lib_path, 'tasks_tests.rake' ), + File.join( ceedling_lib_path, 'rules_tests.rake' ) + ] + } + + out_hash[:project_rakefile_component_files] << File.join( ceedling_lib_path, 'rules_release.rake' ) if (in_hash[:project_release_build]) + out_hash[:project_rakefile_component_files] << File.join( ceedling_lib_path, 'tasks_release.rake' ) if (in_hash[:project_release_build]) return out_hash end @@ -179,16 +196,65 @@ def set_release_target(in_hash) end - def collect_project_options(in_hash) - options = [] + def set_build_thread_counts(in_hash) + require 'etc' + + auto_thread_count = (Etc.nprocessors + 4) - in_hash[:project_options_paths].each do |path| - options << @file_wrapper.directory_listing( File.join(path, '*.yml') ) + compile_threads = in_hash[:project_compile_threads] + test_threads = in_hash[:project_test_threads] + + case compile_threads + when Integer + # Do nothing--value already validated + when Symbol + # If a symbol, it's already been validated as legal + compile_threads = auto_thread_count if compile_threads == :auto + end + + case test_threads + when Integer + # Do nothing--value already validated + when Symbol + # If a symbol, it's already been validated as legal + test_threads = auto_thread_count if test_threads == :auto end return { - :collection_project_options => options.flatten - } + :project_compile_threads => compile_threads, + :project_test_threads => test_threads + } + end + + + def set_test_preprocessor_accessors(in_hash) + accessors = {} + + # :project_use_test_preprocessor already validated + case in_hash[:project_use_test_preprocessor] + when :none + accessors = { + :project_use_test_preprocessor_tests => false, + :project_use_test_preprocessor_mocks => false + } + when :all + accessors = { + :project_use_test_preprocessor_tests => true, + :project_use_test_preprocessor_mocks => true + } + when :tests + accessors = { + :project_use_test_preprocessor_tests => true, + :project_use_test_preprocessor_mocks => false + } + when :mocks + accessors = { + :project_use_test_preprocessor_tests => false, + :project_use_test_preprocessor_mocks => true + } + end + + return accessors end @@ -201,9 +267,9 @@ def expand_all_path_globs(in_hash) path_keys << key end - # sorted to provide assured order of traversal in test calls on mocks - path_keys.sort.each do |key| - out_hash["collection_#{key}".to_sym] = @file_system_utils.collect_paths( in_hash[key] ) + path_keys.each do |key| + _collection = "collection_#{key}".to_sym + out_hash[_collection] = @file_path_collection_utils.collect_paths( in_hash[key] ) end return out_hash @@ -214,14 +280,14 @@ def collect_source_and_include_paths(in_hash) return { :collection_paths_source_and_include => ( in_hash[:collection_paths_source] + - in_hash[:collection_paths_include] ).select {|x| File.directory?(x)} + in_hash[:collection_paths_include] ) } end def collect_source_include_vendor_paths(in_hash) extra_paths = [] - extra_paths << File.join(in_hash[:cexception_vendor_path], CEXCEPTION_LIB_PATH) if (in_hash[:project_use_exceptions]) + extra_paths << in_hash[:project_build_vendor_cexception_path] if (in_hash[:project_use_exceptions]) return { :collection_paths_source_include_vendor => @@ -234,10 +300,10 @@ def collect_source_include_vendor_paths(in_hash) def collect_test_support_source_include_paths(in_hash) return { :collection_paths_test_support_source_include => - (in_hash[:collection_paths_test] + - in_hash[:collection_paths_support] + - in_hash[:collection_paths_source] + - in_hash[:collection_paths_include] ).select {|x| File.directory?(x)} + ( in_hash[:collection_paths_test] + + in_hash[:collection_paths_support] + + in_hash[:collection_paths_source] + + in_hash[:collection_paths_include] ) } end @@ -263,9 +329,13 @@ def collect_tests(in_hash) all_tests.include( File.join(path, "#{in_hash[:project_test_file_prefix]}*#{in_hash[:extension_source]}") ) end - @file_system_utils.revise_file_list( all_tests, in_hash[:files_test] ) + # Force Rake::FileList to expand patterns to ensure it happens (FileList is a bit unreliable) + all_tests.resolve() - return {:collection_all_tests => all_tests} + return { + # Add / subtract files via :files ↳ :test + :collection_all_tests => @file_path_collection_utils.revise_filelist( all_tests, in_hash[:files_test] ) + } end @@ -284,25 +354,31 @@ def collect_assembly(in_hash) all_assembly.include( File.join(path, "*#{in_hash[:extension_assembly]}") ) end - # Also add files that we are explicitly adding via :files:assembly: section - @file_system_utils.revise_file_list( all_assembly, in_hash[:files_assembly] ) + # Force Rake::FileList to expand patterns to ensure it happens (FileList is a bit unreliable) + all_assembly.resolve() + + return { + # Add / subtract files via :files ↳ :assembly + :collection_all_assembly => @file_path_collection_utils.revise_filelist( all_assembly, in_hash[:files_assembly] ) + } - return {:collection_all_assembly => all_assembly} end def collect_source(in_hash) all_source = @file_wrapper.instantiate_file_list + in_hash[:collection_paths_source].each do |path| - if File.exist?(path) and not File.directory?(path) - all_source.include( path ) - else - all_source.include( File.join(path, "*#{in_hash[:extension_source]}") ) - end + all_source.include( File.join(path, "*#{in_hash[:extension_source]}") ) end - @file_system_utils.revise_file_list( all_source, in_hash[:files_source] ) - return {:collection_all_source => all_source} + # Force Rake::FileList to expand patterns to ensure it happens (FileList is a bit unreliable) + all_source.resolve() + + return { + # Add / subtract files via :files ↳ :source + :collection_all_source => @file_path_collection_utils.revise_filelist( all_source, in_hash[:files_source] ) + } end @@ -312,166 +388,169 @@ def collect_headers(in_hash) paths = in_hash[:collection_paths_test] + in_hash[:collection_paths_support] + - in_hash[:collection_paths_source] + in_hash[:collection_paths_include] paths.each do |path| - all_headers.include( File.join(path, "**/*#{in_hash[:extension_header]}") ) + all_headers.include( File.join(path, "*#{in_hash[:extension_header]}") ) end - @file_system_utils.revise_file_list( all_headers, in_hash[:files_include] ) + # Force Rake::FileList to expand patterns to ensure it happens (FileList is a bit unreliable) + all_headers.resolve() - return {:collection_all_headers => all_headers} + return { + # Add / subtract files via :files ↳ :include + :collection_all_headers => @file_path_collection_utils.revise_filelist( all_headers, in_hash[:files_include] ) + } end - def collect_release_existing_compilation_input(in_hash) + def collect_release_build_input(in_hash) release_input = @file_wrapper.instantiate_file_list - paths = - in_hash[:collection_paths_source] + - in_hash[:collection_paths_include] - - paths << File.join(in_hash[:cexception_vendor_path], CEXCEPTION_LIB_PATH) if (in_hash[:project_use_exceptions]) + paths = [] + paths << in_hash[:project_build_vendor_cexception_path] if (in_hash[:project_use_exceptions]) + # Collect vendor framework code files paths.each do |path| - release_input.include( File.join(path, "*#{in_hash[:extension_header]}") ) - if File.exist?(path) and not File.directory?(path) - release_input.include( path ) - else - release_input.include( File.join(path, "*#{in_hash[:extension_source]}") ) - end + release_input.include( File.join(path, '*' + EXTENSION_CORE_SOURCE) ) + end + + # Collect source files + in_hash[:collection_paths_source].each do |path| + release_input.include( File.join(path, "*#{in_hash[:extension_source]}") ) + release_input.include( File.join(path, "*#{in_hash[:extension_assembly]}") ) if in_hash[:release_build_use_assembly] end - @file_system_utils.revise_file_list( release_input, in_hash[:files_source] ) - @file_system_utils.revise_file_list( release_input, in_hash[:files_include] ) - # finding assembly files handled explicitly through other means + # Add / subtract files via :files ↳ :source & :files ↳ :assembly + revisions = in_hash[:files_source] + revisions += in_hash[:files_assembly] if in_hash[:release_build_use_assembly] + + # Force Rake::FileList to expand patterns to ensure it happens (FileList is a bit unreliable) + release_input.resolve() - return {:collection_release_existing_compilation_input => release_input} + return { + :collection_release_build_input => @file_path_collection_utils.revise_filelist( release_input, revisions ) + } end - def collect_all_existing_compilation_input(in_hash) + # Collect all test build code that exists in the configured paths (runners and mocks are handled at build time) + def collect_existing_test_build_input(in_hash) all_input = @file_wrapper.instantiate_file_list + # Vendor paths for frameworks + paths = [] + paths << in_hash[:project_build_vendor_unity_path] + paths << in_hash[:project_build_vendor_cexception_path] if (in_hash[:project_use_exceptions]) + paths << in_hash[:project_build_vendor_cmock_path] if (in_hash[:project_use_mocks]) + + # Collect vendor framework code files + paths.each do |path| + all_input.include( File.join(path, '*' + EXTENSION_CORE_SOURCE) ) + end + paths = in_hash[:collection_paths_test] + in_hash[:collection_paths_support] + - in_hash[:collection_paths_source] + - in_hash[:collection_paths_include] + - [File.join(in_hash[:unity_vendor_path], UNITY_LIB_PATH)] - - paths << File.join(in_hash[:cexception_vendor_path], CEXCEPTION_LIB_PATH) if (in_hash[:project_use_exceptions]) - paths << File.join(in_hash[:cmock_vendor_path], CMOCK_LIB_PATH) if (in_hash[:project_use_mocks]) + in_hash[:collection_paths_source] + # Collect code files paths.each do |path| - all_input.include( File.join(path, "*#{in_hash[:extension_header]}") ) - if File.exist?(path) and not File.directory?(path) - all_input.include( path ) - else - all_input.include( File.join(path, "*#{in_hash[:extension_source]}") ) - all_input.include( File.join(path, "*#{in_hash[:extension_assembly]}") ) if (defined?(TEST_BUILD_USE_ASSEMBLY) && TEST_BUILD_USE_ASSEMBLY) - end + all_input.include( File.join(path, "*#{in_hash[:extension_source]}") ) + all_input.include( File.join(path, "*#{in_hash[:extension_assembly]}") ) if in_hash[:test_build_use_assembly] end - @file_system_utils.revise_file_list( all_input, in_hash[:files_test] ) - @file_system_utils.revise_file_list( all_input, in_hash[:files_support] ) - @file_system_utils.revise_file_list( all_input, in_hash[:files_source] ) - @file_system_utils.revise_file_list( all_input, in_hash[:files_include] ) - # finding assembly files handled explicitly through other means + # Add / subtract files via :files entries + revisions = in_hash[:files_test] + revisions += in_hash[:files_support] + revisions += in_hash[:files_source] + revisions += in_hash[:files_assembly] if in_hash[:test_build_use_assembly] - return {:collection_all_existing_compilation_input => all_input} - end + # Force Rake::FileList to expand patterns to ensure it happens (FileList is a bit unreliable) + all_input.resolve() - - def get_vendor_defines(in_hash) - defines = in_hash[:unity_defines].clone - defines.concat(in_hash[:cmock_defines]) if (in_hash[:project_use_mocks]) - defines.concat(in_hash[:cexception_defines]) if (in_hash[:project_use_exceptions]) - - return defines + return { + :collection_existing_test_build_input => @file_path_collection_utils.revise_filelist( all_input, revisions ) + } end - def collect_vendor_defines(in_hash) - return {:collection_defines_vendor => get_vendor_defines(in_hash)} - end - + def collect_release_artifact_extra_link_objects(in_hash) + objects = [] - def collect_test_and_vendor_defines(in_hash) - defines = in_hash[:defines_test].clone + # no build paths here so plugins can remap if necessary (i.e. path mapping happens at runtime) + objects << CEXCEPTION_C_FILE.ext( in_hash[:extension_object] ) if (in_hash[:project_use_exceptions]) - require_relative 'unity_utils.rb' - cmd_line_define = UnityUtils.update_defines_if_args_enables(in_hash) + return {:collection_release_artifact_extra_link_objects => objects} + end - vendor_defines = get_vendor_defines(in_hash) - defines.concat(vendor_defines) if vendor_defines - defines.concat(cmd_line_define) if cmd_line_define - return {:collection_defines_test_and_vendor => defines} - end + def collect_test_fixture_extra_link_objects(in_hash) + sources = [] + support = @file_wrapper.instantiate_file_list() + paths = in_hash[:collection_paths_support] - def collect_release_and_vendor_defines(in_hash) - release_defines = in_hash[:defines_release].clone + # Collect code files + paths.each do |path| + support.include( File.join(path, "*#{in_hash[:extension_source]}") ) + support.include( File.join(path, "*#{in_hash[:extension_assembly]}") ) if in_hash[:test_build_use_assembly] + end - release_defines.concat(in_hash[:cexception_defines]) if (in_hash[:project_use_exceptions]) + # Force Rake::FileList to expand patterns to ensure it happens (FileList is a bit unreliable) + support.resolve() - return {:collection_defines_release_and_vendor => release_defines} - end + support = @file_path_collection_utils.revise_filelist( support, in_hash[:files_support] ) + support.each { |file| sources << file } - def collect_release_artifact_extra_link_objects(in_hash) - objects = [] + # Create object files from all the sources + objects = sources.map { |file| File.basename(file) } - # no build paths here so plugins can remap if necessary (i.e. path mapping happens at runtime) - objects << CEXCEPTION_C_FILE.ext( in_hash[:extension_object] ) if (in_hash[:project_use_exceptions]) + # No build paths here so plugins can remap if necessary (i.e. path mapping happens at runtime) + objects.map! { |object| object.ext(in_hash[:extension_object]) } - return {:collection_release_artifact_extra_link_objects => objects} + return { + :collection_all_support => sources, + :collection_test_fixture_extra_link_objects => objects + } end - def collect_test_fixture_extra_link_objects(in_hash) - # Note: Symbols passed to compiler at command line can change Unity and CException behavior / configuration; - # we also handle those dependencies elsewhere in compilation dependencies + # .c files without path + def collect_vendor_framework_sources(in_hash) + sources = [] + filelist = @file_wrapper.instantiate_file_list() - sources = [UNITY_C_FILE] + # Vendor paths for frameworks + paths = get_vendor_paths(in_hash) - in_hash[:files_support].each { |file| sources << file } + # Collect vendor framework code files + paths.each do |path| + filelist.include( File.join(path, '*' + EXTENSION_CORE_SOURCE) ) + end - # we don't include paths here because use of plugins or mixing different compilers may require different build paths - sources << CEXCEPTION_C_FILE if (in_hash[:project_use_exceptions]) - sources << CMOCK_C_FILE if (in_hash[:project_use_mocks]) + # Force Rake::FileList to expand patterns to ensure it happens (FileList is a bit unreliable) + filelist.resolve() - # if we're using mocks & a unity helper is defined & that unity helper includes a source file component (not only a header of macros), - # then link in the unity_helper object file too - if ( in_hash[:project_use_mocks] and in_hash[:cmock_unity_helper] ) - in_hash[:cmock_unity_helper].each do |helper| - if @file_wrapper.exist?(helper.ext(in_hash[:extension_source])) - sources << helper - end - end + # Extract just source file names + filelist.each do |filepath| + sources << File.basename(filepath) end - # create object files from all the sources - objects = sources.map { |file| File.basename(file) } + return {:collection_vendor_framework_sources => sources} + end - # no build paths here so plugins can remap if necessary (i.e. path mapping happens at runtime) - objects.map! { |object| object.ext(in_hash[:extension_object]) } - return { :collection_all_support => sources, - :collection_test_fixture_extra_link_objects => objects - } - end + ### Private ### private def get_vendor_paths(in_hash) vendor_paths = [] - vendor_paths << File.join(in_hash[:unity_vendor_path], UNITY_LIB_PATH) - vendor_paths << File.join(in_hash[:cexception_vendor_path], CEXCEPTION_LIB_PATH) if (in_hash[:project_use_exceptions]) - vendor_paths << File.join(in_hash[:cmock_vendor_path], CMOCK_LIB_PATH) if (in_hash[:project_use_mocks]) - vendor_paths << in_hash[:cmock_mock_path] if (in_hash[:project_use_mocks]) + vendor_paths << in_hash[:project_build_vendor_unity_path] + vendor_paths << in_hash[:project_build_vendor_cmock_path] if (in_hash[:project_use_mocks]) + vendor_paths << in_hash[:project_build_vendor_cexception_path] if (in_hash[:project_use_exceptions]) return vendor_paths end diff --git a/lib/ceedling/configurator_plugins.rb b/lib/ceedling/configurator_plugins.rb index 75bcd982d..b5c316722 100644 --- a/lib/ceedling/configurator_plugins.rb +++ b/lib/ceedling/configurator_plugins.rb @@ -1,33 +1,65 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'ceedling/constants' class ConfiguratorPlugins - constructor :stream_wrapper, :file_wrapper, :system_wrapper - attr_reader :rake_plugins, :script_plugins + constructor :file_wrapper, :system_wrapper + + attr_reader :rake_plugins, :programmatic_plugins, :config_plugins, :plugin_yml_defaults, :plugin_hash_defaults def setup - @rake_plugins = [] - @script_plugins = [] + @rake_plugins = [] + @programmatic_plugins = [] + @config_plugins = [] + @plugin_yml_defaults = [] + @plugin_hash_defaults = [] + end + + + # Override to prevent exception handling from walking & stringifying the object variables. + # Object variables are gigantic and produce a flood of output. + def inspect + # TODO: When identifying information is added to constructor, insert it into `inspect()` string + return this.class.name end - def add_load_paths(config) + def process_aux_load_paths(config) plugin_paths = {} + # Add any base load path to Ruby's load path collection + config[:plugins][:load_paths].each do |path| + @system_wrapper.add_load_path( path ) + end + + # If a load path contains an actual Ceedling plugin, load its subdirectories by convention config[:plugins][:enabled].each do |plugin| config[:plugins][:load_paths].each do |root| path = File.join(root, plugin) - is_script_plugin = ( not @file_wrapper.directory_listing( File.join( path, 'lib', '*.rb' ) ).empty? ) - is_rake_plugin = ( not @file_wrapper.directory_listing( File.join( path, '*.rake' ) ).empty? ) + # Ceedling Ruby-based hash defaults plugin (or config for Ceedling programmatic plugin) + is_config_plugin = ( not @file_wrapper.directory_listing( File.join( path, 'config', '*.rb' ) ).empty? ) + + # Ceedling programmatic plugin + is_programmatic_plugin = ( not @file_wrapper.directory_listing( File.join( path, 'lib', '*.rb' ) ).empty? ) - if is_script_plugin or is_rake_plugin + # Ceedling Rake plugin + is_rake_plugin = ( not @file_wrapper.directory_listing( File.join( path, '*.rake' ) ).empty? ) + + if (is_config_plugin or is_programmatic_plugin or is_rake_plugin) plugin_paths[(plugin + '_path').to_sym] = path - if is_script_plugin - @system_wrapper.add_load_path( File.join( path, 'lib') ) - @system_wrapper.add_load_path( File.join( path, 'config') ) - end + # Add paths to Ruby load paths that contain *.rb files + @system_wrapper.add_load_path( File.join( path, 'config') ) if is_config_plugin + @system_wrapper.add_load_path( File.join( path, 'lib') ) if is_programmatic_plugin + + # We found load_path/ + / path that exists, skip ahead break end end @@ -37,7 +69,7 @@ def add_load_paths(config) end - # gather up and return .rake filepaths that exist on-disk + # Gather up and return .rake filepaths that exist in plugin paths def find_rake_plugins(config, plugin_paths) @rake_plugins = [] plugins_with_path = [] @@ -45,63 +77,64 @@ def find_rake_plugins(config, plugin_paths) config[:plugins][:enabled].each do |plugin| if path = plugin_paths[(plugin + '_path').to_sym] rake_plugin_path = File.join(path, "#{plugin}.rake") - if (@file_wrapper.exist?(rake_plugin_path)) - plugins_with_path << rake_plugin_path - @rake_plugins << plugin + if @file_wrapper.exist?( rake_plugin_path ) + @rake_plugins << {:plugin => plugin, :path => rake_plugin_path} end end end - return plugins_with_path + return @rake_plugins end - # gather up and return just names of .rb classes that exist on-disk - def find_script_plugins(config, plugin_paths) - @script_plugins = [] + # Gather up names of .rb `Plugin` subclasses and root paths that exist in plugin paths + lib/ + def find_programmatic_plugins(config, plugin_paths) + @programmatic_plugins = [] config[:plugins][:enabled].each do |plugin| if path = plugin_paths[(plugin + '_path').to_sym] - script_plugin_path = File.join(path, "lib", "#{plugin}.rb") + plugin_path = File.join( path, "lib", "#{plugin}.rb" ) - if @file_wrapper.exist?(script_plugin_path) - @script_plugins << plugin + if @file_wrapper.exist?( plugin_path ) + @programmatic_plugins << {:plugin => plugin, :root_path => path} end end end - return @script_plugins + return @programmatic_plugins end - # gather up and return configuration .yml filepaths that exist on-disk + # Gather up and return config .yml filepaths that exist in plugin paths + config/ def find_config_plugins(config, plugin_paths) + @config_plugins = [] plugins_with_path = [] config[:plugins][:enabled].each do |plugin| if path = plugin_paths[(plugin + '_path').to_sym] config_plugin_path = File.join(path, "config", "#{plugin}.yml") - if @file_wrapper.exist?(config_plugin_path) - plugins_with_path << config_plugin_path + if @file_wrapper.exist?( config_plugin_path ) + @config_plugins << {:plugin => plugin, :path => config_plugin_path} end end end - return plugins_with_path + return @config_plugins end - # gather up and return default .yml filepaths that exist on-disk + # Gather up and return default .yml filepaths that exist on-disk def find_plugin_yml_defaults(config, plugin_paths) - defaults_with_path = [] + defaults_with_path = {} config[:plugins][:enabled].each do |plugin| if path = plugin_paths[(plugin + '_path').to_sym] default_path = File.join(path, 'config', 'defaults.yml') - if @file_wrapper.exist?(default_path) - defaults_with_path << default_path + if @file_wrapper.exist?( default_path ) + defaults_with_path[plugin.to_sym] = default_path + @plugin_yml_defaults << plugin end end end @@ -109,18 +142,19 @@ def find_plugin_yml_defaults(config, plugin_paths) return defaults_with_path end - # gather up and return + # Gather up and return defaults generated by Ruby code in plugin paths + config/ def find_plugin_hash_defaults(config, plugin_paths) - defaults_hash= [] + defaults_hash = {} config[:plugins][:enabled].each do |plugin| if path = plugin_paths[(plugin + '_path').to_sym] default_path = File.join(path, "config", "defaults_#{plugin}.rb") - if @file_wrapper.exist?(default_path) - @system_wrapper.require_file( "defaults_#{plugin}.rb") + if @file_wrapper.exist?( default_path ) + @system_wrapper.require_file( "defaults_#{plugin}.rb" ) object = eval("get_default_config()") - defaults_hash << object + defaults_hash[plugin.to_sym()] = object + @plugin_hash_defaults << plugin end end end diff --git a/lib/ceedling/configurator_setup.rb b/lib/ceedling/configurator_setup.rb index c43bb5c12..934d3e179 100644 --- a/lib/ceedling/configurator_setup.rb +++ b/lib/ceedling/configurator_setup.rb @@ -1,5 +1,14 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= -# add sort-ability to symbol so we can order keys array in hash for test-ability +require 'ceedling/constants' +require 'ceedling/exceptions' + +# Add sort-ability to symbol so we can order keys array in hash for test-ability class Symbol include Comparable @@ -11,39 +20,93 @@ def <=>(other) class ConfiguratorSetup - constructor :configurator_builder, :configurator_validator, :configurator_plugins, :stream_wrapper - - - def build_project_config(config, flattened_config) - ### flesh out config - @configurator_builder.clean(flattened_config) - - ### add to hash values we build up from configuration & file system contents - flattened_config.merge!(@configurator_builder.set_build_paths(flattened_config)) - flattened_config.merge!(@configurator_builder.set_force_build_filepaths(flattened_config)) - flattened_config.merge!(@configurator_builder.set_rakefile_components(flattened_config)) - flattened_config.merge!(@configurator_builder.set_release_target(flattened_config)) - flattened_config.merge!(@configurator_builder.collect_project_options(flattened_config)) - - ### iterate through all entries in paths section and expand any & all globs to actual paths - flattened_config.merge!(@configurator_builder.expand_all_path_globs(flattened_config)) - - flattened_config.merge!(@configurator_builder.collect_vendor_paths(flattened_config)) - flattened_config.merge!(@configurator_builder.collect_source_and_include_paths(flattened_config)) - flattened_config.merge!(@configurator_builder.collect_source_include_vendor_paths(flattened_config)) - flattened_config.merge!(@configurator_builder.collect_test_support_source_include_paths(flattened_config)) - flattened_config.merge!(@configurator_builder.collect_test_support_source_include_vendor_paths(flattened_config)) - flattened_config.merge!(@configurator_builder.collect_tests(flattened_config)) - flattened_config.merge!(@configurator_builder.collect_assembly(flattened_config)) - flattened_config.merge!(@configurator_builder.collect_source(flattened_config)) - flattened_config.merge!(@configurator_builder.collect_headers(flattened_config)) - flattened_config.merge!(@configurator_builder.collect_release_existing_compilation_input(flattened_config)) - flattened_config.merge!(@configurator_builder.collect_all_existing_compilation_input(flattened_config)) - flattened_config.merge!(@configurator_builder.collect_vendor_defines(flattened_config)) - flattened_config.merge!(@configurator_builder.collect_test_and_vendor_defines(flattened_config)) - flattened_config.merge!(@configurator_builder.collect_release_and_vendor_defines(flattened_config)) - flattened_config.merge!(@configurator_builder.collect_release_artifact_extra_link_objects(flattened_config)) - flattened_config.merge!(@configurator_builder.collect_test_fixture_extra_link_objects(flattened_config)) + constructor :configurator_builder, :configurator_validator, :configurator_plugins, :loginator, :reportinator, :file_wrapper + + + # Override to prevent exception handling from walking & stringifying the object variables. + # Object variables are gigantic and produce a flood of output. + def inspect + # TODO: When identifying information is added to constructor, insert it into `inspect()` string + return this.class.name + end + + def build_project_config(ceedling_lib_path, logging_path, flattened_config) + # Housekeeping + @configurator_builder.cleanup( flattened_config ) + + # Add to hash values we build up from configuration & file system contents + flattened_config.merge!( @configurator_builder.set_build_paths( flattened_config, logging_path ) ) + flattened_config.merge!( @configurator_builder.set_rakefile_components( ceedling_lib_path, flattened_config ) ) + flattened_config.merge!( @configurator_builder.set_release_target( flattened_config ) ) + flattened_config.merge!( @configurator_builder.set_build_thread_counts( flattened_config ) ) + flattened_config.merge!( @configurator_builder.set_test_preprocessor_accessors( flattened_config ) ) + + return flattened_config + end + + def build_directory_structure(flattened_config) + msg = "Build paths:" + flattened_config[:project_build_paths].each do |path| + msg += "\n - #{(path.nil? or path.empty?) ? '' : path}" + end + @loginator.log( msg, Verbosity::DEBUG ) + + flattened_config[:project_build_paths].each do |path| + if path.nil? or path.empty? + raise CeedlingException.new( "An internal project build path subdirectory path is unexpectedly blank" ) + end + + @file_wrapper.mkdir( path ) + end + end + + def vendor_frameworks_and_support_files(ceedling_lib_path, flattened_config) + # Copy Unity C files into build/vendor directory structure + @file_wrapper.cp_r( + # '/.' to cause cp_r to copy directory contents + File.join( flattened_config[:unity_vendor_path], UNITY_LIB_PATH, '/.' ), + flattened_config[:project_build_vendor_unity_path] + ) + + # Copy CMock C files into build/vendor directory structure + @file_wrapper.cp_r( + # '/.' to cause cp_r to copy directory contents + File.join( flattened_config[:cmock_vendor_path], CMOCK_LIB_PATH, '/.' ), + flattened_config[:project_build_vendor_cmock_path] + ) if flattened_config[:project_use_mocks] + + # Copy CException C files into build/vendor directory structure + @file_wrapper.cp_r( + # '/.' to cause cp_r to copy directory contents + File.join( flattened_config[:cexception_vendor_path], CEXCEPTION_LIB_PATH, '/.' ), + flattened_config[:project_build_vendor_cexception_path] + ) if flattened_config[:project_use_exceptions] + + # Copy backtrace debugging script into build/test directory structure + @file_wrapper.cp_r( + File.join( ceedling_lib_path, BACKTRACE_GDB_SCRIPT_FILE ), + flattened_config[:project_build_tests_root] + ) if flattened_config[:project_use_backtrace] == :gdb + end + + def build_project_collections(flattened_config) + # Iterate through all entries in paths section and expand any & all globs to actual paths + flattened_config.merge!( @configurator_builder.expand_all_path_globs( flattened_config ) ) + + flattened_config.merge!( @configurator_builder.collect_vendor_paths( flattened_config ) ) + flattened_config.merge!( @configurator_builder.collect_source_and_include_paths( flattened_config ) ) + flattened_config.merge!( @configurator_builder.collect_source_include_vendor_paths( flattened_config ) ) + flattened_config.merge!( @configurator_builder.collect_test_support_source_include_paths( flattened_config ) ) + flattened_config.merge!( @configurator_builder.collect_test_support_source_include_vendor_paths( flattened_config ) ) + flattened_config.merge!( @configurator_builder.collect_tests( flattened_config ) ) + flattened_config.merge!( @configurator_builder.collect_assembly( flattened_config ) ) + flattened_config.merge!( @configurator_builder.collect_source( flattened_config ) ) + flattened_config.merge!( @configurator_builder.collect_headers( flattened_config ) ) + flattened_config.merge!( @configurator_builder.collect_release_build_input( flattened_config ) ) + flattened_config.merge!( @configurator_builder.collect_existing_test_build_input( flattened_config ) ) + flattened_config.merge!( @configurator_builder.collect_release_artifact_extra_link_objects( flattened_config ) ) + flattened_config.merge!( @configurator_builder.collect_test_fixture_extra_link_objects( flattened_config ) ) + flattened_config.merge!( @configurator_builder.collect_vendor_framework_sources( flattened_config ) ) return flattened_config end @@ -64,6 +127,7 @@ def validate_required_sections(config) return true end + def validate_required_section_values(config) validation = [] validation << @configurator_validator.exists?(config, :project, :build_root) @@ -74,52 +138,601 @@ def validate_required_section_values(config) return true end - def validate_paths(config) - validation = [] - if config[:cmock][:unity_helper] - config[:cmock][:unity_helper].each do |path| - validation << @configurator_validator.validate_filepath_simple( path, :cmock, :unity_helper ) - end - end + def validate_paths(config) + valid = true - config[:project][:options_paths].each do |path| - validation << @configurator_validator.validate_filepath_simple( path, :project, :options_paths ) + # Ceedling ensures [:unity_helper_path] is an array + config[:cmock][:unity_helper_path].each do |path| + valid &= @configurator_validator.validate_filepath_simple( path, :cmock, :unity_helper_path ) end config[:plugins][:load_paths].each do |path| - validation << @configurator_validator.validate_filepath_simple( path, :plugins, :load_paths ) + valid &= @configurator_validator.validate_filepath_simple( path, :plugins, :load_paths ) end config[:paths].keys.sort.each do |key| - validation << @configurator_validator.validate_path_list(config, :paths, key) + valid &= @configurator_validator.validate_path_list(config, :paths, key) + valid &= @configurator_validator.validate_paths_entries(config, key) end - return false if (validation.include?(false)) - return true + config[:files].keys.sort.each do |key| + valid &= @configurator_validator.validate_path_list(config, :files, key) + valid &= @configurator_validator.validate_files_entries(config, key) + end + + return valid end def validate_tools(config) - validation = [] + valid = true - config[:tools].keys.sort.each do |key| - validation << @configurator_validator.exists?(config, :tools, key, :executable) - validation << @configurator_validator.validate_executable_filepath(config, :tools, key, :executable) if (not config[:tools][key][:optional]) - validation << @configurator_validator.validate_tool_stderr_redirect(config, :tools, key) + config[:tools].keys.sort.each do |tool| + valid &= @configurator_validator.validate_tool( config:config, key:tool ) + end + + return valid + end + + def validate_test_runner_generation(config, include_test_case, exclude_test_case) + cmdline_args = config[:test_runner][:cmdline_args] + + # Test case filters in use + test_case_filters = !include_test_case.empty? || !exclude_test_case.empty? + + # Test case filters are in use but test runner command line arguments are not enabled + if (test_case_filters and !cmdline_args) + msg = 'Test case filters cannot be used -- enable :test_runner ↳ :cmdline_args in your project configuration' + @loginator.log( msg, Verbosity::ERRORS ) + return false end - return false if (validation.include?(false)) return true end + + def validate_defines(_config) + defines = _config[:defines] + + return true if defines.nil? + + # Ensure config[:defines] is a hash + if defines.class != Hash + msg = ":defines must contain key / value pairs, not #{defines.class.to_s.downcase} (see docs for examples)" + @loginator.log( msg, Verbosity::ERRORS ) + return false + end + + valid = true + + # Validate that each context contains only a list of symbols or a matcher hash for :test / :preprocess context + # + # :defines: + # :: + # - FOO + # - BAR + # + # or + # + # :defines: + # :test: + # :: + # - FOO + # - BAR + # :preprocess: + # :: + # - FOO + # - BAR + + defines.each_pair do |context, config| + walk = @reportinator.generate_config_walk( [:defines, context] ) + + # Special handling for configuration setting, not a hash context container + next if context == :use_test_definition + + # Matcher contexts (only contexts that support matcher hashes) + if context == :test or context == :preprocess + if config.class != Array and config.class != Hash + msg = "#{walk} entry '#{config}' must be a list or matcher, not #{config.class.to_s.downcase} (see docs for examples)" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + end + + # All other (simple) contexts + else + # Handle the (probably) common case of trying to use matchers for any context other than :test or :preprocess + if config.class == Hash + msg = "#{walk} entry '#{config}' must be a list--matcher hashes only availalbe for :test & :preprocess contexts (see docs for details)" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + # Catchall for any oddball entries + elsif config.class != Array + msg = "#{walk} entry '#{config}' must be a list, not #{config.class.to_s.downcase} (see docs for examples)" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + end + end + end + + # Validate simple option of lists applied across an entire context of any name + # :defines: + # :: # :test, :release, etc. + # - FOO + # - BAR + + defines.each_pair do |context, config| + # Only validate lists of compilation symbols in this block (look for matchers in next block) + next if config.class != Array + + # Handle any YAML alias referencing causing a nested array + config.flatten!() + + # Ensure each item in list is a string + config.each do |symbol| + if symbol.class != String + walk = @reportinator.generate_config_walk( [:defines, context] ) + msg = "#{walk} list entry #{symbol} must be a string, not #{symbol.class.to_s.downcase} (see docs for examples)" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + end + end + end + + # Validate :test / :preprocess context matchers (hash) if they exist + # :defines: + # :test: + # :: # Can be wildcard, substring, or regular expression in a string or symbol + # - FOO + # - BAR + # :preprocess: + # :: # Can be wildcard, substring, or regular expression in a string or symbol + # - FOO + # - BAR + + contexts = [:test, :preprocess] + + contexts.each do |context| + matchers = defines[context] + + # Skip processing if context isn't present or is present but is not a matcher hash + next if matchers.nil? or matchers.class != Hash + + # Inspect each test matcher + matchers.each_pair do |matcher, symbols| + + walk = @reportinator.generate_config_walk( [:defines, context, matcher] ) + + # Ensure container associated with matcher is a list + if symbols.class != Array + msg = "#{walk} entry '#{symbols}' is not a list of compilation symbols but a #{symbols.class.to_s.downcase}" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + + # Skip further validation if matcher value is not a list of symbols + next + end + + # Handle any YAML alias nesting in array + symbols.flatten!() + + # Ensure matcher itself is a Ruby symbol or string + if matcher.class != Symbol and matcher.class != String + msg = "#{walk} matcher is not a string or symbol" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + + # Skip further validation if matcher key is not a symbol + next + end + + walk = @reportinator.generate_config_walk( [:defines, context, matcher] ) + + # Ensure each item in compilation symbols list for matcher is a string + symbols.each do |symbol| + if symbol.class != String + msg = "#{walk} entry '#{symbol}' is not a string" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + end + end + + begin + @configurator_validator.validate_matcher( matcher.to_s.strip() ) + rescue Exception => ex + msg = "Matcher #{walk} contains #{ex.message}" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + end + + end + end + + return valid + end + + + def validate_flags(_config) + flags = _config[:flags] + + return true if flags.nil? + + # Ensure config[:flags] is a hash + if flags.class != Hash + msg = ":flags must contain key / value pairs, not #{flags.class.to_s.downcase} (see docs for examples)" + @loginator.log( msg, Verbosity::ERRORS ) + # Immediately bail out + return false + end + + valid = true + + # Validate that each context has an operation hash + # :flags + # :: # :test, :release, etc. + # :: # :compile, :link, etc. + # ... + + flags.each_pair do |context, operations| + walk = @reportinator.generate_config_walk( [:flags, context] ) + + if operations.nil? + msg = "#{walk} operations key / value pairs are missing" + @loginator.log( msg, Verbosity::ERRORS ) + + valid = false + next + end + + if operations.class != Hash + example = @reportinator.generate_config_walk( [:flags, context, :compile] ) + msg = "#{walk} context must contain : key / value pairs, not #{operations.class.to_s.downcase} '#{operations}' (ex. #{example})" + @loginator.log( msg, Verbosity::ERRORS ) + + # Immediately bail out + return false + end + end + + if !!flags[:release] and !!flags[:release][:preprocess] + walk = @reportinator.generate_config_walk( [:flags, :release, :preprocess] ) + msg = "Preprocessing configured at #{walk} is only supported in the :test context" + @loginator.log( msg, Verbosity::ERRORS, LogLabels::WARNING ) + end + + # Validate that each <:context> ↳ <:operation> contains only a list of flags or that :test ↳ <:operation> optionally contains a matcher hash + # + # :flags: + # :: # :test or :release + # :: # :compile, :link, or :assemble (plus :preprocess for :test context) + # - --flag + # + # or + # + # :flags: + # :test: + # :: + # :: + # - --flag + + flags.each_pair do |context, operations| + operations.each_pair do |operation, config| + walk = @reportinator.generate_config_walk( [:flags, context, operation] ) + + if config.nil? + msg = "#{walk} is missing a list or matcher hash" + @loginator.log( msg, Verbosity::ERRORS ) + + valid = false + next + end + + # :test context operations with lists or matchers (hashes) + if context == :test + if config.class != Array and config.class != Hash + msg = "#{walk} entry '#{config}' must be a list or matcher hash, not #{config.class.to_s.downcase} (see docs for examples)" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + end + + # Other (simple) contexts + else + # Handle the (probably) common case of trying to use matchers for operations in any context other than :test + if config.class == Hash + msg = "#{walk} entry '#{config}' must be a list--matcher hashes only availalbe for :test context (see docs for details)" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + # Catchall for any oddball entries + elsif config.class != Array + msg = "#{walk} entry '#{config}' must be a list, not #{config.class.to_s.downcase} (see docs for examples)" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + end + end + end + end + + # Validate simple option of lists of flags (strings) for <:context> ↳ <:operation> + # :flags + # :: + # :: + # - --flag + + flags.each_pair do |context, operations| + operations.each_pair do |operation, flags| + + # Only validate lists of flags in this block (look for matchers in next block) + next if flags.class != Array + + # Handle any YAML alias referencing causing a nested array + flags.flatten!() + + # Ensure each item in list is a string + flags.each do |flag| + if flag.class != String + walk = @reportinator.generate_config_walk( [:flags, context, operation] ) + msg = "#{walk} simple list entry '#{flag}' must be a string, not #{flag.class.to_s.downcase} (see docs for examples)" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + end + end + end + end + + # Validate :test ↳ <:operation> matchers (hash) if they exist + # :flags: + # :test: + # :: # :preprocess, :compile, :assemble, :link + # :: # Can be wildcard, substring, or regular expression as a Ruby string or symbol + # - FOO + # - BAR + + # If there's no test context, we're done + test_context = flags[:test] + return valid if test_context.nil? + + matchers_present = false + test_context.each_pair do |operation, matchers| + if matchers.class == Hash + matchers_present = true + break + end + end + + # If there's no matchers for :test ↳ <:operation>, we're done + return valid if !matchers_present + + # Inspect each :test ↳ <:operation> matcher + test_context.each_pair do |operation, matchers| + # Only validate matchers (skip simple lists of flags) + next if matchers.class != Hash + + matchers.each_pair do |matcher, flags| + # Ensure matcher itself is a Ruby symbol or string + if matcher.class != Symbol and matcher.class != String + walk = @reportinator.generate_config_walk( [:flags, :test, operation] ) + msg = "#{walk} entry '#{matcher}' is not a string or symbol" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + + # Skip further validation if matcher key is not a string or symbol + next + end + + walk = @reportinator.generate_config_walk( [:flags, :test, operation, matcher] ) + + # Ensure container associated with matcher is a list + if flags.class != Array + msg = "#{walk} entry '#{flags}' is not a list of command line flags but a #{flags.class.to_s.downcase}" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + + # Skip further validation if matcher value is not a list of flags + next + end + + # Handle any YAML alias nesting in array + flags.flatten!() + + # Ensure each item in flags list for matcher is a string + flags.each do |flag| + if flag.class != String + msg = "#{walk} entry '#{flag}' is not a string" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + end + end + + begin + @configurator_validator.validate_matcher( matcher.to_s.strip() ) + rescue Exception => ex + msg = "Matcher #{walk} contains #{ex.message}" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + end + + end + end + + return valid + end + + + def validate_test_preprocessor(config) + valid = true + + options = [:none, :all, :tests, :mocks] + + use_test_preprocessor = config[:project][:use_test_preprocessor] + + if !options.include?( use_test_preprocessor ) + walk = @reportinator.generate_config_walk( [:project, :use_test_preprocessor] ) + msg = "#{walk} is :'#{use_test_preprocessor}' but must be one of #{options.map{|o| ':' + o.to_s()}.join(', ')}" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + end + + return valid + end + + + def validate_environment_vars(config) + environment = config[:environment] + + return true if environment.nil? + + # Ensure config[:environment] is an array (of simple hashes--validated below) + if environment.class != Array + msg = ":environment must contain a list of key / value pairs, not #{environment.class.to_s.downcase} (see docs for examples)" + @loginator.log( msg, Verbosity::ERRORS ) + return false + end + + valid = true + keys = [] + + # Ensure a hash for each entry + environment.each do |entry| + if entry.class != Hash + msg = ":environment list entry #{entry} is not a key / value pair (ex. :var: value)" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + end + end + + # Only end processing if an entry wasn't a hash + return valid if !valid + + # Validate each hash entry + environment.each do |entry| + key_length = entry.keys.length() + + # Ensure entry is a hash with just a single key / value pair + if key_length != 1 + msg = ":environment entry #{entry} does not specify exactly one key (see docs for examples)" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + end + + key = entry.keys[0] # Get first (should be only) environment variable entry + value = entry[key] # Get associated value + + # Remember key for later duplication check + keys << key.to_s.downcase + + # Ensure entry key is a symbol or string + if key.class != Symbol and key.class != String + msg = ":environment entry '#{key}' must be a symbol or string (:#{key})" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + + # Skip validation of value if key is not a symbol or string + next + end + + # Ensure entry value is a string or list + if not (value.class == String or value.class == Array) + msg = ":environment entry #{key} is associated with #{value.class.to_s.downcase}, not a string or list (see docs for details)" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + end + + # If path is a list, ensure it's all strings + if value.class == Array + value.each do |item| + if item.class != String + msg = ":environment entry #{key} contains a list element '#{item}' (#{item.class.to_s.downcase}) that is not a string" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + end + end + end + end + + # Find any duplicate keys + dups = keys.uniq.select { |k| keys.count( k ) > 1 } + + if !dups.empty? + msg = "Duplicate :environment entr#{dups.length() == 1 ? 'y' : 'ies'} #{dups.map{|d| ':' + d.to_s}.join( ', ' )} found" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + end + + return valid + end + + + def validate_backtrace(config) + valid = true + + options = [:none, :simple, :gdb] + + use_backtrace = config[:project][:use_backtrace] + + if !options.include?( use_backtrace ) + walk = @reportinator.generate_config_walk( [:project, :use_backtrace] ) + + msg = "#{walk} is :'#{use_backtrace}' but must be one of #{options.map{|o| ':' + o.to_s()}.join(', ')}" + @loginator.log( msg, Verbosity::ERRORS ) + valid = false + end + + return valid + end + + def validate_threads(config) + valid = true + + compile_threads = config[:project][:compile_threads] + test_threads = config[:project][:test_threads] + + walk = @reportinator.generate_config_walk( [:project, :compile_threads] ) + + case compile_threads + when Integer + if compile_threads < 1 + @loginator.log( "#{walk} must be greater than 0", Verbosity::ERRORS ) + valid = false + end + when Symbol + if compile_threads != :auto + @loginator.log( "#{walk} is neither an integer nor :auto", Verbosity::ERRORS ) + valid = false + end + else + @loginator.log( "#{walk} is neither an integer nor :auto", Verbosity::ERRORS ) + valid = false + end + + walk = @reportinator.generate_config_walk( [:project, :test_threads] ) + + case test_threads + when Integer + if test_threads < 1 + @loginator.log( "#{walk} must be greater than 0", Verbosity::ERRORS ) + valid = false + end + when Symbol + if test_threads != :auto + @loginator.log( "#{walk} is neither an integer nor :auto", Verbosity::ERRORS ) + valid = false + end + else + @loginator.log( "#{walk} is neither an integer nor :auto", Verbosity::ERRORS ) + valid = false + end + + return valid + end + def validate_plugins(config) missing_plugins = Set.new( config[:plugins][:enabled] ) - Set.new( @configurator_plugins.rake_plugins ) - - Set.new( @configurator_plugins.script_plugins ) + Set.new( @configurator_plugins.programmatic_plugins.map {|p| p[:plugin]} ) missing_plugins.each do |plugin| - @stream_wrapper.stderr_puts("ERROR: Ceedling plugin '#{plugin}' contains no rake or ruby class entry point. (Misspelled or missing files?)") + message = "Plugin '#{plugin}' not found in built-in or project Ruby load paths. Check load paths and plugin naming and path conventions." + @loginator.log( message, Verbosity::ERRORS ) end return ( (missing_plugins.size > 0) ? false : true ) diff --git a/lib/ceedling/configurator_validator.rb b/lib/ceedling/configurator_validator.rb index f362f6baa..8001ae78a 100644 --- a/lib/ceedling/configurator_validator.rb +++ b/lib/ceedling/configurator_validator.rb @@ -1,50 +1,52 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'rubygems' require 'rake' # for ext() require 'ceedling/constants' -require 'ceedling/tool_executor' # for argument replacement pattern require 'ceedling/file_path_utils' # for glob handling class methods class ConfiguratorValidator - constructor :file_wrapper, :stream_wrapper, :system_wrapper + constructor :config_walkinator, :file_wrapper, :loginator, :system_wrapper, :reportinator, :tool_validator - # walk into config hash verify existence of data at key depth + # Walk into config hash verify existence of data at key depth def exists?(config, *keys) - hash = retrieve_value(config, keys) - exist = !hash[:value].nil? + hash, _ = @config_walkinator.fetch_value( *keys, hash:config ) + exist = !hash.nil? if (not exist) - # no verbosity checking since this is lowest level anyhow & verbosity checking depends on configurator - @stream_wrapper.stderr_puts("ERROR: Required config file entry #{format_key_sequence(keys, hash[:depth])} does not exist.") + walk = @reportinator.generate_config_walk( keys ) + @loginator.log( "Required config file entry #{walk} does not exist.", Verbosity::ERRORS ) end return exist end - - # walk into config hash. verify directory path(s) at given key depth + # Walk into config hash. verify existence of path(s) at given key depth. + # Paths are either full simple paths or a simple portion of a path up to a glob. def validate_path_list(config, *keys) - hash = retrieve_value(config, keys) - list = hash[:value] + exist = true + list, depth = @config_walkinator.fetch_value( *keys, hash:config ) - # return early if we couldn't walk into hash and find a value + # Return early if we couldn't walk into hash and find a value return false if (list.nil?) - - path_list = [] - exist = true - case list - when String then path_list << list - when Array then path_list = list - end - - path_list.each do |path| - base_path = FilePathUtils::extract_path(path) # lop off add/subtract notation & glob specifiers - - if (not @file_wrapper.exist?(base_path)) - # no verbosity checking since this is lowest level anyhow & verbosity checking depends on configurator - @stream_wrapper.stderr_puts("ERROR: Config path #{format_key_sequence(keys, hash[:depth])}['#{base_path}'] does not exist on disk.") + list.each do |path| + # Trim add/subtract notation & glob specifiers + _path = FilePathUtils::no_decorators( path ) + + next if _path.empty? # Path begins with or is entirely a glob, skip it + + # If (partial) path does not exist, complain + if (not @file_wrapper.exist?( _path )) + walk = @reportinator.generate_config_walk( keys, depth ) + @loginator.log( "Config path #{walk} => '#{_path}' does not exist in the filesystem.", Verbosity::ERRORS ) exist = false end end @@ -52,142 +54,150 @@ def validate_path_list(config, *keys) return exist end - - # simple path verification - def validate_filepath_simple(path, *keys) - validate_path = path - - if (not @file_wrapper.exist?(validate_path)) - # no verbosity checking since this is lowest level anyhow & verbosity checking depends on configurator - @stream_wrapper.stderr_puts("ERROR: Config path '#{validate_path}' associated with #{format_key_sequence(keys, keys.size)} does not exist on disk.") - return false - end - - return true - end - - # walk into config hash. verify specified file exists. - def validate_filepath(config, *keys) - hash = retrieve_value(config, keys) - filepath = hash[:value] - # return early if we couldn't walk into hash and find a value - return false if (filepath.nil?) + # Validate :paths entries, exercising each entry as Ceedling directory glob (variation of Ruby glob) + def validate_paths_entries(config, key) + valid = true + keys = [:paths, key] + walk = @reportinator.generate_config_walk( keys ) + + list, _ = @config_walkinator.fetch_value( *keys, hash:config ) - # skip everything if we've got an argument replacement pattern - return true if (filepath =~ TOOL_EXECUTOR_ARGUMENT_REPLACEMENT_PATTERN) + # Return early if we couldn't walk into hash and find a value + return false if (list.nil?) - if (not @file_wrapper.exist?(filepath)) - - # See if we can deal with it internally. - if GENERATED_DIR_PATH.include?(filepath) - # we already made this directory before let's make it again. - FileUtils.mkdir_p File.join(File.dirname(__FILE__), filepath) - @stream_wrapper.stderr_puts("WARNING: Generated filepath #{format_key_sequence(keys, hash[:depth])}['#{filepath}'] does not exist on disk. Recreating") - - else - # no verbosity checking since this is lowest level anyhow & verbosity checking depends on configurator - @stream_wrapper.stderr_puts("ERROR: Config filepath #{format_key_sequence(keys, hash[:depth])}['#{filepath}'] does not exist on disk.") - return false - end - end + list.each do |path| + dirs = [] # Working list - return true - end + # Trim add/subtract notation + _path = FilePathUtils::no_aggregation_decorators( path ) - # walk into config hash. verify specified file exists. - def validate_executable_filepath(config, *keys) - exe_extension = config[:extension][:executable] - hash = retrieve_value(config, keys) - filepath = hash[:value] + if @file_wrapper.exist?( _path ) and !@file_wrapper.directory?( _path ) + # Path is a simple filepath (not a directory) + warning = "#{walk} => '#{_path}' is a filepath and will be ignored (FYI :paths is directory-oriented while :files is file-oriented)" + @loginator.log( warning, Verbosity::COMPLAIN ) - # return early if we couldn't walk into hash and find a value - return false if (filepath.nil?) + next # Skip to next path + end - # skip everything if we've got an argument replacement pattern - return true if (filepath =~ TOOL_EXECUTOR_ARGUMENT_REPLACEMENT_PATTERN) - - # if there's no path included, verify file exists somewhere in system search paths - if (not filepath.include?('/')) - exists = false - - @system_wrapper.search_paths.each do |path| - if (@file_wrapper.exist?( File.join(path, filepath)) ) - exists = true - break - end - - if (@file_wrapper.exist?( (File.join(path, filepath)).ext( exe_extension ) )) - exists = true - break - elsif (@system_wrapper.windows? and @file_wrapper.exist?( (File.join(path, filepath)).ext( EXTENSION_WIN_EXE ) )) - exists = true - break - end + # Expand paths using Ruby's Dir.glob() + # - A simple path will yield that path + # - A path glob will expand to one or more paths + _reformed = FilePathUtils::reform_subdirectory_glob( _path ) + @file_wrapper.directory_listing( _reformed ).each do |entry| + # For each result, add it to the working list *if* it's a directory + dirs << entry if @file_wrapper.directory?(entry) end - if (not exists) - # no verbosity checking since this is lowest level anyhow & verbosity checking depends on configurator - @stream_wrapper.stderr_puts("ERROR: Config filepath #{format_key_sequence(keys, hash[:depth])}['#{filepath}'] does not exist in system search paths.") - return false + # Handle edge case of subdirectories glob but not subdirectories + # (Containing parent directory will still exist) + next if dirs.empty? and _path =~ /\/\*{1,2}$/ + + # Path did not work -- must be malformed glob or glob referencing path that does not exist. + # (An earlier step validates all simple directory paths). + if dirs.empty? + error = "#{walk} => '#{_path}' yielded no directories -- matching glob is malformed or directories do not exist" + @loginator.log( error, Verbosity::ERRORS ) + valid = false end - - # if there is a path included, check that explicit filepath exists - else - if (not @file_wrapper.exist?(filepath)) - # no verbosity checking since this is lowest level anyhow & verbosity checking depends on configurator - @stream_wrapper.stderr_puts("ERROR: Config filepath #{format_key_sequence(keys, hash[:depth])}['#{filepath}'] does not exist on disk.") - return false - end end - - return true + + return valid end - - def validate_tool_stderr_redirect(config, tools, tool) - redirect = config[tools][tool][:stderr_redirect] - if (redirect.class == Symbol) - # map constants and force to array of strings for runtime universality across ruby versions - if (not StdErrRedirect.constants.map{|constant| constant.to_s}.include?(redirect.to_s.upcase)) - error = "ERROR: [:#{tools}][:#{tool}][:stderr_redirect][:#{redirect}] is not a recognized option " + - "{#{StdErrRedirect.constants.map{|constant| ':' + constant.to_s.downcase}.join(', ')}}." - @stream_wrapper.stderr_puts(error) - return false - end + + + # Validate :files entries, exercising each entry as FileList glob + def validate_files_entries(config, key) + valid = true + keys = [:files, key] + walk = @reportinator.generate_config_walk( keys ) + + list, _ = @config_walkinator.fetch_value( *keys, hash:config ) + + # Return early if we couldn't walk into hash and find a value + return false if (list.nil?) + + list.each do |path| + # Trim add/subtract notation + _path = FilePathUtils::no_aggregation_decorators( path ) + + if @file_wrapper.exist?( _path ) and @file_wrapper.directory?( _path ) + # Path is a simple directory path (and is naturally ignored by FileList without a glob pattern) + warning = "#{walk} => '#{_path}' is a directory path and will be ignored (FYI :files is file-oriented while :paths is directory-oriented)" + @loginator.log( warning, Verbosity::COMPLAIN ) + + next # Skip to next path + end + + filelist = @file_wrapper.instantiate_file_list(_path) + + # If file list is empty, complain + if (filelist.size == 0) + error = "#{walk} => '#{_path}' yielded no files -- matching glob is malformed or files do not exist" + @loginator.log( error, Verbosity::ERRORS ) + valid = false + end end + return valid + end + + + # Simple path verification + def validate_filepath_simple(path, *keys) + validate_path = path + + if (not @file_wrapper.exist?(validate_path)) + walk = @reportinator.generate_config_walk( keys, keys.size ) + @loginator.log("Config path '#{validate_path}' associated with #{walk} does not exist in the filesystem.", Verbosity::ERRORS ) + return false + end + return true end + + def validate_tool(config:, key:, respect_optional:true) + # Get tool + walk = [:tools, key] + tool, _ = @config_walkinator.fetch_value( *walk, hash:config ) + + arg_hash = { + tool: tool, + name: @reportinator.generate_config_walk( walk ), + extension: config[:extension][:executable], + respect_optional: respect_optional + } - private ######################################### - - - def retrieve_value(config, keys) - value = nil - hash = config - depth = 0 - - # walk into hash & extract value at requested key sequence - keys.each do |symbol| - depth += 1 - if (not hash[symbol].nil?) - hash = hash[symbol] - value = hash - else - value = nil - break - end - end - - return {:value => value, :depth => depth} + return @tool_validator.validate( **arg_hash ) end - def format_key_sequence(keys, depth) - walked_keys = keys.slice(0, depth) - formatted_keys = walked_keys.map{|key| "[:#{key}]"} + def validate_matcher(matcher) + case matcher - return formatted_keys.join + # Handle regex-based matcher + when /\/.+\// + # Ensure regex is well-formed by trying to compile it + begin + Regexp.compile( matcher[1..-2] ) + rescue Exception => ex + # Re-raise with our own message formatting + raise "invalid regular expression:: #{ex.message}" + end + + # Handle wildcard / substring matchers + else + # Strip out allowed characters + invalid = matcher.gsub( /[a-z0-9 \/\.\-_\*]/i, '' ) + + # If there's any characters left, then we found invalid characters + if invalid.length() > 0 + # Format invalid characters into a printable list with no duplicates + _invalid = invalid.chars.uniq.map{|c| "'#{c}'"}.join( ', ') + + raise "invalid substring or wilcard characters #{_invalid}" + end + end end - + end diff --git a/lib/ceedling/constants.rb b/lib/ceedling/constants.rb index 19484f063..74a9006de 100644 --- a/lib/ceedling/constants.rb +++ b/lib/ceedling/constants.rb @@ -1,13 +1,55 @@ - +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +# Logging verbosity levels class Verbosity - SILENT = 0 # as silent as possible (though there are some messages that must be spit out) - ERRORS = 1 # only errors - COMPLAIN = 2 # spit out errors and warnings/notices - NORMAL = 3 # errors, warnings/notices, standard status messages - OBNOXIOUS = 4 # all messages including extra verbose output (used for lite debugging / verification) - DEBUG = 5 # special extra verbose output for hardcore debugging + SILENT = 0 # As silent as possible (though there are some messages that must be spit out) + ERRORS = 1 # Only errors + COMPLAIN = 2 # Spit out errors and warnings/notices + NORMAL = 3 # Errors, warnings/notices, standard status messages + OBNOXIOUS = 4 # All messages including extra verbose output (used for lite debugging / verification) + DEBUG = 5 # Special extra verbose output for hardcore debugging end +VERBOSITY_OPTIONS = { + :silent => Verbosity::SILENT, + :errors => Verbosity::ERRORS, + :warnings => Verbosity::COMPLAIN, + :normal => Verbosity::NORMAL, + :obnoxious => Verbosity::OBNOXIOUS, + :debug => Verbosity::DEBUG, +}.freeze() + +# Label + decorator options for logging +class LogLabels + NONE = 0 # Override logic and settings with no label and no decoration + AUTO = 1 # Default labeling and decorators + NOTICE = 2 # decorator + 'NOTICE:' + WARNING = 3 # decorator + 'WARNING:' + ERROR = 4 # decorator + 'ERROR:' + EXCEPTION = 5 # decorator + 'EXCEPTION:' + CONSTRUCT = 6 # decorator only + RUN = 7 # decorator only + CRASH = 8 # decorator only + PASS = 9 # decorator only + FAIL = 10 # decorator only + TITLE = 11 # decorator only + + # Verbosity levels ERRORS – DEBUG default to certain labels or lack thereof + # The above label constants are available to override Loginator's default AUTO level as needed + # See Loginator comments +end + +class DurationCounts + DAY_MS = (24 * 60 * 60 * 1000) + HOUR_MS = (60 * 60 * 1000) + MINUTE_MS = (60 * 1000) + SECOND_MS = (1000) +end class TestResultsSanityChecks NONE = 0 # no sanity checking of test results @@ -15,7 +57,6 @@ class TestResultsSanityChecks THOROUGH = 2 # perform checks that require inside knowledge of system workings end - class StdErrRedirect NONE = :none AUTO = :auto @@ -24,70 +65,78 @@ class StdErrRedirect TCSH = :tcsh end +GIT_COMMIT_SHA_FILENAME = 'GIT_COMMIT_SHA' -class BackgroundExec - NONE = :none - AUTO = :auto - WIN = :win - UNIX = :unix -end +# Escaped newline literal (literally double-slash-n) for "encoding" multiline strings as single string +NEWLINE_TOKEN = '\\n' -unless defined?(PROJECT_ROOT) - PROJECT_ROOT = Dir.pwd() -end +DEFAULT_PROJECT_FILENAME = 'project.yml' +DEFAULT_BUILD_LOGS_PATH = 'logs' GENERATED_DIR_PATH = [['vendor', 'ceedling'], 'src', "test", ['test', 'support'], 'build'].each{|p| File.join(*p)} -EXTENSION_WIN_EXE = '.exe' -EXTENSION_NONWIN_EXE = '.out' +EXTENSION_WIN_EXE = '.exe' +EXTENSION_NONWIN_EXE = '.out' +# Vendor frameworks, generated mocks, generated runners are always .c files +EXTENSION_CORE_SOURCE = '.c' +PREPROCESS_SYM = :preprocess +CEXCEPTION_SYM = :cexception CEXCEPTION_ROOT_PATH = 'c_exception' CEXCEPTION_LIB_PATH = "#{CEXCEPTION_ROOT_PATH}/lib" CEXCEPTION_C_FILE = 'CException.c' CEXCEPTION_H_FILE = 'CException.h' +UNITY_SYM = :unity UNITY_ROOT_PATH = 'unity' UNITY_LIB_PATH = "#{UNITY_ROOT_PATH}/src" UNITY_C_FILE = 'unity.c' UNITY_H_FILE = 'unity.h' UNITY_INTERNALS_H_FILE = 'unity_internals.h' +# Do-nothing macros defined in unity.h for extra build context to be used by build tools like Ceedling +UNITY_TEST_SOURCE_FILE = 'TEST_SOURCE_FILE' +UNITY_TEST_INCLUDE_PATH = 'TEST_INCLUDE_PATH' + +RUNNER_BUILD_CMDLINE_ARGS_DEFINE = 'UNITY_USE_COMMAND_LINE_ARGS' + +CMOCK_SYM = :cmock CMOCK_ROOT_PATH = 'cmock' CMOCK_LIB_PATH = "#{CMOCK_ROOT_PATH}/src" CMOCK_C_FILE = 'cmock.c' CMOCK_H_FILE = 'cmock.h' +DEFAULT_CEEDLING_LOGFILE = 'ceedling.log' -DEFAULT_CEEDLING_MAIN_PROJECT_FILE = 'project.yml' unless defined?(DEFAULT_CEEDLING_MAIN_PROJECT_FILE) # main project file -DEFAULT_CEEDLING_USER_PROJECT_FILE = 'user.yml' unless defined?(DEFAULT_CEEDLING_USER_PROJECT_FILE) # supplemental user config file +BACKTRACE_GDB_SCRIPT_FILE = 'backtrace.gdb' -INPUT_CONFIGURATION_CACHE_FILE = 'input.yml' unless defined?(INPUT_CONFIGURATION_CACHE_FILE) # input configuration file dump -DEFINES_DEPENDENCY_CACHE_FILE = 'defines_dependency.yml' unless defined?(DEFINES_DEPENDENCY_CACHE_FILE) # preprocessor definitions for files +INPUT_CONFIGURATION_CACHE_FILE = 'input.yml' unless defined?(INPUT_CONFIGURATION_CACHE_FILE) # input configuration file dump +DEFINES_DEPENDENCY_CACHE_FILE = 'defines_dependency.yml' unless defined?(DEFINES_DEPENDENCY_CACHE_FILE) # preprocessor definitions for files TEST_ROOT_NAME = 'test' unless defined?(TEST_ROOT_NAME) TEST_TASK_ROOT = TEST_ROOT_NAME + ':' unless defined?(TEST_TASK_ROOT) -TEST_SYM = TEST_ROOT_NAME.to_sym unless defined?(TEST_SYM) +TEST_SYM = :test RELEASE_ROOT_NAME = 'release' unless defined?(RELEASE_ROOT_NAME) RELEASE_TASK_ROOT = RELEASE_ROOT_NAME + ':' unless defined?(RELEASE_TASK_ROOT) RELEASE_SYM = RELEASE_ROOT_NAME.to_sym unless defined?(RELEASE_SYM) -REFRESH_ROOT_NAME = 'refresh' unless defined?(REFRESH_ROOT_NAME) -REFRESH_TASK_ROOT = REFRESH_ROOT_NAME + ':' unless defined?(REFRESH_TASK_ROOT) -REFRESH_SYM = REFRESH_ROOT_NAME.to_sym unless defined?(REFRESH_SYM) - UTILS_ROOT_NAME = 'utils' unless defined?(UTILS_ROOT_NAME) UTILS_TASK_ROOT = UTILS_ROOT_NAME + ':' unless defined?(UTILS_TASK_ROOT) UTILS_SYM = UTILS_ROOT_NAME.to_sym unless defined?(UTILS_SYM) -OPERATION_COMPILE_SYM = :compile unless defined?(OPERATION_COMPILE_SYM) -OPERATION_ASSEMBLE_SYM = :assemble unless defined?(OPERATION_ASSEMBLE_SYM) -OPERATION_LINK_SYM = :link unless defined?(OPERATION_LINK_SYM) +OPERATION_PREPROCESS_SYM = :preprocess unless defined?(OPERATION_PREPROCESS_SYM) +OPERATION_COMPILE_SYM = :compile unless defined?(OPERATION_COMPILE_SYM) +OPERATION_ASSEMBLE_SYM = :assemble unless defined?(OPERATION_ASSEMBLE_SYM) +OPERATION_LINK_SYM = :link unless defined?(OPERATION_LINK_SYM) +PREPROCESS_FULL_EXPANSION_DIR = 'full_expansion' +PREPROCESS_DIRECTIVES_ONLY_DIR = 'directives_only' +# Match presence of any glob pattern characters +GLOB_PATTERN = /[\*\?\{\}\[\]]/ RUBY_STRING_REPLACEMENT_PATTERN = /#\{.+\}/ -RUBY_EVAL_REPLACEMENT_PATTERN = /^\{(.+)\}$/ TOOL_EXECUTOR_ARGUMENT_REPLACEMENT_PATTERN = /(\$\{(\d+)\})/ TEST_STDOUT_STATISTICS_PATTERN = /\n-+\s*(\d+)\s+Tests\s+(\d+)\s+Failures\s+(\d+)\s+Ignored\s+(OK|FAIL)\s*/i @@ -97,3 +146,16 @@ class BackgroundExec RELEASE_BASE_PATH = RELEASE_ROOT_NAME VENDORS_FILES = %w(unity UnityHelper cmock CException).freeze + +# Ruby Here +UNITY_TEST_RESULTS_TEMPLATE = <<~UNITY_TEST_RESULTS + %{output} + + ----------------------- + %{total} Tests %{failed} Failures %{ignored} Ignored + %{result} +UNITY_TEST_RESULTS + + + + diff --git a/lib/ceedling/defaults.rb b/lib/ceedling/defaults.rb index d7ac05bbd..3f448835b 100644 --- a/lib/ceedling/defaults.rb +++ b/lib/ceedling/defaults.rb @@ -1,26 +1,29 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'ceedling/constants' require 'ceedling/system_wrapper' require 'ceedling/file_path_utils' -#this should be defined already, but not always during system specs -CEEDLING_VENDOR = File.expand_path(File.dirname(__FILE__) + '/../../vendor') unless defined? CEEDLING_VENDOR +# Assign a default value for system testing where CEEDLING_APPCFG may not be present +# TODO: Create code config & test structure that does not internalize a test path like this +CEEDLING_VENDOR = defined?( CEEDLING_APPCFG ) ? CEEDLING_APPCFG[:ceedling_vendor_path] : File.expand_path( File.dirname(__FILE__) + '/../../vendor' ) + CEEDLING_PLUGINS = [] unless defined? CEEDLING_PLUGINS DEFAULT_TEST_COMPILER_TOOL = { - :executable => ENV['CC'].nil? ? FilePathUtils.os_executable_ext('gcc').freeze : ENV['CC'].split[0], + :executable => FilePathUtils.os_executable_ext('gcc').freeze, :name => 'default_test_compiler'.freeze, - :stderr_redirect => StdErrRedirect::NONE.freeze, - :background_exec => BackgroundExec::NONE.freeze, :optional => false.freeze, :arguments => [ - ENV['CC'].nil? ? "" : ENV['CC'].split[1..-1], - ENV['CPPFLAGS'].nil? ? "" : ENV['CPPFLAGS'].split, - {"-I\"$\"" => 'COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR'}.freeze, - {"-I\"$\"" => 'COLLECTION_PATHS_TEST_TOOLCHAIN_INCLUDE'}.freeze, - {"-D$" => 'COLLECTION_DEFINES_TEST_AND_VENDOR'}.freeze, - "-DGNU_COMPILER".freeze, + "-I\"${5}\"".freeze, # Per-test executable search paths + "-D\"${6}\"".freeze, # Per-test executable defines + "-DGNU_COMPILER".freeze, # OSX clang "-g".freeze, - ENV['CFLAGS'].nil? ? "" : ENV['CFLAGS'].split, "-c \"${1}\"".freeze, "-o \"${2}\"".freeze, # gcc's list file output options are complex; no use of ${3} parameter in default config @@ -29,95 +32,110 @@ ].freeze } +DEFAULT_TEST_ASSEMBLER_TOOL = { + :executable => FilePathUtils.os_executable_ext('as').freeze, + :name => 'default_test_assembler'.freeze, + :optional => false.freeze, + :arguments => [ + "-I\"${3}\"".freeze, # Search paths + # Any defines (${4}) are not included since GNU assembler ignores them + "\"${1}\"".freeze, + "-o \"${2}\"".freeze, + ].freeze + } + DEFAULT_TEST_LINKER_TOOL = { - :executable => ENV['CCLD'].nil? ? FilePathUtils.os_executable_ext('gcc').freeze : ENV['CCLD'].split[0], + :executable => FilePathUtils.os_executable_ext('gcc').freeze, :name => 'default_test_linker'.freeze, - :stderr_redirect => StdErrRedirect::NONE.freeze, - :background_exec => BackgroundExec::NONE.freeze, :optional => false.freeze, :arguments => [ - ENV['CCLD'].nil? ? "" : ENV['CCLD'].split[1..-1], - ENV['CFLAGS'].nil? ? "" : ENV['CFLAGS'].split, - ENV['LDFLAGS'].nil? ? "" : ENV['LDFLAGS'].split, - "\"${1}\"".freeze, + "${1}".freeze, "${5}".freeze, "-o \"${2}\"".freeze, "".freeze, "${4}".freeze, - ENV['LDLIBS'].nil? ? "" : ENV['LDLIBS'].split ].freeze } DEFAULT_TEST_FIXTURE_TOOL = { - :executable => '${1}'.freeze, + :executable => '${1}'.freeze, # Unity test runner executable :name => 'default_test_fixture'.freeze, - :stderr_redirect => StdErrRedirect::AUTO.freeze, - :background_exec => BackgroundExec::NONE.freeze, :optional => false.freeze, :arguments => [].freeze } -DEFAULT_TEST_INCLUDES_PREPROCESSOR_TOOL = { - :executable => ENV['CC'].nil? ? FilePathUtils.os_executable_ext('gcc').freeze : ENV['CC'].split[0], - :name => 'default_test_includes_preprocessor'.freeze, - :stderr_redirect => StdErrRedirect::NONE.freeze, - :background_exec => BackgroundExec::NONE.freeze, +DEFAULT_TEST_FIXTURE_SIMPLE_BACKTRACE_TOOL = { + :executable => '${1}'.freeze, # Unity test runner executable + :name => 'default_test_fixture_simple_backtrace'.freeze, :optional => false.freeze, :arguments => [ - ENV['CC'].nil? ? "" : ENV['CC'].split[1..-1], - ENV['CPPFLAGS'].nil? ? "" : ENV['CPPFLAGS'].split, - '-E'.freeze, # OSX clang - '-MM'.freeze, - '-MG'.freeze, - # avoid some possibility of deep system lib header file complications by omitting vendor paths - # if cpp is run on *nix system, escape spaces in paths; if cpp on windows just use the paths collection as is - # {"-I\"$\"" => "{SystemWrapper.windows? ? COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE : COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE.map{|path| path.gsub(\/ \/, \'\\\\ \') }}"}.freeze, - {"-I\"$\"" => 'COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR'}.freeze, - {"-I\"$\"" => 'COLLECTION_PATHS_TEST_TOOLCHAIN_INCLUDE'}.freeze, - {"-D$" => 'COLLECTION_DEFINES_TEST_AND_VENDOR'}.freeze, - {"-D$" => 'DEFINES_TEST_PREPROCESS'}.freeze, + '-n ${2}'.freeze # Exact test case name matching flag + ].freeze + } + +DEFAULT_TEST_SHALLOW_INCLUDES_PREPROCESSOR_TOOL = { + :executable => FilePathUtils.os_executable_ext('gcc').freeze, + :name => 'default_test_shallow_includes_preprocessor'.freeze, + :optional => false.freeze, + :arguments => [ + '-E'.freeze, # Run only through preprocessor stage with its output + '-MM'.freeze, # Output make rule + suppress header files found in system header directories + '-MG'.freeze, # Assume missing header files are generated files (do not discard) + '-MP'.freeze, # Create make "phony" rules for each include dependency + "-D\"${2}\"".freeze, # Per-test executable defines "-DGNU_COMPILER".freeze, # OSX clang - # '-nostdinc'.freeze, # disabled temporarily due to stdio access violations on OSX + '-nostdinc'.freeze, # Ignore standard include paths + "-x c".freeze, # Force C language "\"${1}\"".freeze ].freeze } -DEFAULT_TEST_FILE_PREPROCESSOR_TOOL = { - :executable => ENV['CC'].nil? ? FilePathUtils.os_executable_ext('gcc').freeze : ENV['CC'].split[0], - :name => 'default_test_file_preprocessor'.freeze, - :stderr_redirect => StdErrRedirect::NONE.freeze, - :background_exec => BackgroundExec::NONE.freeze, +DEFAULT_TEST_NESTED_INCLUDES_PREPROCESSOR_TOOL = { + :executable => FilePathUtils.os_executable_ext('gcc').freeze, + :name => 'default_test_nested_includes_preprocessor'.freeze, + :optional => false.freeze, + :arguments => [ + '-E'.freeze, # Run only through preprocessor stage with its output + '-MM'.freeze, # Output make rule + suppress header files found in system header directories + '-MG'.freeze, # Assume missing header files are generated files (do not discard) + '-H'.freeze, # Also output #include list with depth + "-I\"${2}\"".freeze, # Per-test executable search paths + "-D\"${3}\"".freeze, # Per-test executable defines + "-DGNU_COMPILER".freeze, # OSX clang + '-nostdinc'.freeze, # Ignore standard include paths + "-x c".freeze, # Force C language + "\"${1}\"".freeze + ].freeze + } + +DEFAULT_TEST_FILE_FULL_PREPROCESSOR_TOOL = { + :executable => FilePathUtils.os_executable_ext('gcc').freeze, + :name => 'default_test_file_full_preprocessor'.freeze, :optional => false.freeze, :arguments => [ - ENV['CC'].nil? ? "" : ENV['CC'].split[1..-1], - ENV['CPPFLAGS'].nil? ? "" : ENV['CPPFLAGS'].split, '-E'.freeze, - {"-I\"$\"" => 'COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR'}.freeze, - {"-I\"$\"" => 'COLLECTION_PATHS_TEST_TOOLCHAIN_INCLUDE'}.freeze, - {"-D$" => 'COLLECTION_DEFINES_TEST_AND_VENDOR'}.freeze, - {"-D$" => 'DEFINES_TEST_PREPROCESS'}.freeze, - "-DGNU_COMPILER".freeze, + "-I\"${4}\"".freeze, # Per-test executable search paths + "-D\"${3}\"".freeze, # Per-test executable defines + "-DGNU_COMPILER".freeze, # OSX clang # '-nostdinc'.freeze, # disabled temporarily due to stdio access violations on OSX + "-x c".freeze, # Force C language "\"${1}\"".freeze, "-o \"${2}\"".freeze ].freeze } -DEFAULT_TEST_FILE_PREPROCESSOR_DIRECTIVES_TOOL = { +DEFAULT_TEST_FILE_DIRECTIVES_ONLY_PREPROCESSOR_TOOL = { :executable => FilePathUtils.os_executable_ext('gcc').freeze, - :name => 'default_test_file_preprocessor_directives'.freeze, - :stderr_redirect => StdErrRedirect::NONE.freeze, - :background_exec => BackgroundExec::NONE.freeze, + :name => 'default_test_file_directives_only_preprocessor'.freeze, :optional => false.freeze, :arguments => [ '-E'.freeze, - {"-I\"$\"" => 'COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR'}.freeze, - {"-I\"$\"" => 'COLLECTION_PATHS_TEST_TOOLCHAIN_INCLUDE'}.freeze, - {"-D$" => 'COLLECTION_DEFINES_TEST_AND_VENDOR'}.freeze, - {"-D$" => 'DEFINES_TEST_PREPROCESS'}.freeze, - "-DGNU_COMPILER".freeze, - '-fdirectives-only'.freeze, + "-I\"${4}\"".freeze, # Per-test executable search paths + "-D\"${3}\"".freeze, # Per-test executable defines + "-DGNU_COMPILER".freeze, # OSX clang # '-nostdinc'.freeze, # disabled temporarily due to stdio access violations on OSX + "-x c".freeze, # Force C language + "-fdirectives-only", # Only preprocess directives "\"${1}\"".freeze, "-o \"${2}\"".freeze ].freeze @@ -131,39 +149,30 @@ end DEFAULT_TEST_DEPENDENCIES_GENERATOR_TOOL = { - :executable => ENV['CC'].nil? ? FilePathUtils.os_executable_ext('gcc').freeze : ENV['CC'].split[0], + :executable => FilePathUtils.os_executable_ext('gcc').freeze, :name => 'default_test_dependencies_generator'.freeze, - :stderr_redirect => StdErrRedirect::NONE.freeze, - :background_exec => BackgroundExec::NONE.freeze, :optional => false.freeze, :arguments => [ - ENV['CC'].nil? ? "" : ENV['CC'].split[1..-1], - ENV['CPPFLAGS'].nil? ? "" : ENV['CPPFLAGS'].split, '-E'.freeze, - {"-I\"$\"" => 'COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR'}.freeze, - {"-I\"$\"" => 'COLLECTION_PATHS_TEST_TOOLCHAIN_INCLUDE'}.freeze, - {"-D$" => 'COLLECTION_DEFINES_TEST_AND_VENDOR'}.freeze, - {"-D$" => 'DEFINES_TEST_PREPROCESS'}.freeze, + "-I\"${5}\"".freeze, # Per-test executable search paths + "-D\"${4}\"".freeze, # Per-test executable defines "-DGNU_COMPILER".freeze, "-MT \"${3}\"".freeze, '-MM'.freeze, MD_FLAG.freeze, '-MG'.freeze, "-MF \"${2}\"".freeze, + "-x c".freeze, # Force C language "-c \"${1}\"".freeze, # '-nostdinc'.freeze, ].freeze } DEFAULT_RELEASE_DEPENDENCIES_GENERATOR_TOOL = { - :executable => ENV['CC'].nil? ? FilePathUtils.os_executable_ext('gcc').freeze : ENV['CC'].split[0], + :executable => FilePathUtils.os_executable_ext('gcc').freeze, :name => 'default_release_dependencies_generator'.freeze, - :stderr_redirect => StdErrRedirect::NONE.freeze, - :background_exec => BackgroundExec::NONE.freeze, :optional => false.freeze, :arguments => [ - ENV['CC'].nil? ? "" : ENV['CC'].split[1..-1], - ENV['CPPFLAGS'].nil? ? "" : ENV['CPPFLAGS'].split, '-E'.freeze, {"-I\"$\"" => 'COLLECTION_PATHS_SOURCE_INCLUDE_VENDOR'}.freeze, {"-I\"$\"" => 'COLLECTION_PATHS_RELEASE_TOOLCHAIN_INCLUDE'}.freeze, @@ -175,26 +184,20 @@ MD_FLAG.freeze, '-MG'.freeze, "-MF \"${2}\"".freeze, + "-x c".freeze, # Force C language "-c \"${1}\"".freeze, # '-nostdinc'.freeze, ].freeze } - DEFAULT_RELEASE_COMPILER_TOOL = { - :executable => ENV['CC'].nil? ? FilePathUtils.os_executable_ext('gcc').freeze : ENV['CC'].split[0], + :executable => FilePathUtils.os_executable_ext('gcc').freeze, :name => 'default_release_compiler'.freeze, - :stderr_redirect => StdErrRedirect::NONE.freeze, - :background_exec => BackgroundExec::NONE.freeze, :optional => false.freeze, :arguments => [ - ENV['CC'].nil? ? "" : ENV['CC'].split[1..-1], - ENV['CPPFLAGS'].nil? ? "" : ENV['CPPFLAGS'].split, - {"-I\"$\"" => 'COLLECTION_PATHS_SOURCE_INCLUDE_VENDOR'}.freeze, - {"-I\"$\"" => 'COLLECTION_PATHS_RELEASE_TOOLCHAIN_INCLUDE'}.freeze, - {"-D$" => 'COLLECTION_DEFINES_RELEASE_AND_VENDOR'}.freeze, + "-I\"${5}\"".freeze, # Search paths + "-D\"${6}\"".freeze, # Defines "-DGNU_COMPILER".freeze, - ENV['CFLAGS'].nil? ? "" : ENV['CFLAGS'].split, "-c \"${1}\"".freeze, "-o \"${2}\"".freeze, # gcc's list file output options are complex; no use of ${3} parameter in default config @@ -204,53 +207,72 @@ } DEFAULT_RELEASE_ASSEMBLER_TOOL = { - :executable => ENV['AS'].nil? ? FilePathUtils.os_executable_ext('as').freeze : ENV['AS'].split[0], + :executable => FilePathUtils.os_executable_ext('as').freeze, :name => 'default_release_assembler'.freeze, - :stderr_redirect => StdErrRedirect::NONE.freeze, - :background_exec => BackgroundExec::NONE.freeze, :optional => false.freeze, :arguments => [ - ENV['AS'].nil? ? "" : ENV['AS'].split[1..-1], - ENV['ASFLAGS'].nil? ? "" : ENV['ASFLAGS'].split, - {"-I\"$\"" => 'COLLECTION_PATHS_SOURCE_AND_INCLUDE'}.freeze, + "-I\"${3}\"".freeze, # Search paths + "-D\"${4}\"".freeze, # Defines (FYI--allowed with GNU assembler but ignored) "\"${1}\"".freeze, "-o \"${2}\"".freeze, ].freeze } DEFAULT_RELEASE_LINKER_TOOL = { - :executable => ENV['CCLD'].nil? ? FilePathUtils.os_executable_ext('gcc').freeze : ENV['CCLD'].split[0], + :executable => FilePathUtils.os_executable_ext('gcc').freeze, :name => 'default_release_linker'.freeze, - :stderr_redirect => StdErrRedirect::NONE.freeze, - :background_exec => BackgroundExec::NONE.freeze, :optional => false.freeze, :arguments => [ - ENV['CCLD'].nil? ? "" : ENV['CCLD'].split[1..-1], - ENV['CFLAGS'].nil? ? "" : ENV['CFLAGS'].split, - ENV['LDFLAGS'].nil? ? "" : ENV['LDFLAGS'].split, "\"${1}\"".freeze, "${5}".freeze, "-o \"${2}\"".freeze, "".freeze, "${4}".freeze, - ENV['LDLIBS'].nil? ? "" : ENV['LDLIBS'].split ].freeze } +DEFAULT_TEST_BACKTRACE_GDB_TOOL = { + :executable => FilePathUtils.os_executable_ext('gdb').freeze, + :name => 'default_test_backtrace_gdb'.freeze, + :optional => false.freeze, + :arguments => [ + '-q'.freeze, + '--batch'.freeze, + '--eval-command run'.freeze, + "--command \"${1}\"".freeze, # Debug script file to run + '--args'.freeze, + '${2}'.freeze, # Test executable + '-n ${3}'.freeze # Exact test case name matching flag + ].freeze + } DEFAULT_TOOLS_TEST = { :tools => { :test_compiler => DEFAULT_TEST_COMPILER_TOOL, :test_linker => DEFAULT_TEST_LINKER_TOOL, :test_fixture => DEFAULT_TEST_FIXTURE_TOOL, + :test_fixture_simple_backtrace => DEFAULT_TEST_FIXTURE_SIMPLE_BACKTRACE_TOOL + } + } + +DEFAULT_TOOLS_TEST_GDB_BACKTRACE = { + :tools => { + :test_backtrace_gdb => DEFAULT_TEST_BACKTRACE_GDB_TOOL + } + } + +DEFAULT_TOOLS_TEST_ASSEMBLER = { + :tools => { + :test_assembler => DEFAULT_TEST_ASSEMBLER_TOOL, } } DEFAULT_TOOLS_TEST_PREPROCESSORS = { :tools => { - :test_includes_preprocessor => DEFAULT_TEST_INCLUDES_PREPROCESSOR_TOOL, - :test_file_preprocessor => DEFAULT_TEST_FILE_PREPROCESSOR_TOOL, - :test_file_preprocessor_directives => DEFAULT_TEST_FILE_PREPROCESSOR_DIRECTIVES_TOOL, + :test_shallow_includes_preprocessor => DEFAULT_TEST_SHALLOW_INCLUDES_PREPROCESSOR_TOOL, + :test_nested_includes_preprocessor => DEFAULT_TEST_NESTED_INCLUDES_PREPROCESSOR_TOOL, + :test_file_full_preprocessor => DEFAULT_TEST_FILE_FULL_PREPROCESSOR_TOOL, + :test_file_directives_only_preprocessor => DEFAULT_TEST_FILE_DIRECTIVES_ONLY_PREPROCESSOR_TOOL, } } @@ -260,7 +282,6 @@ } } - DEFAULT_TOOLS_RELEASE = { :tools => { :release_compiler => DEFAULT_RELEASE_COMPILER_TOOL, @@ -283,131 +304,140 @@ DEFAULT_RELEASE_TARGET_NAME = 'project' -DEFAULT_CEEDLING_CONFIG = { - :project => { - # :build_root must be set by user - :use_exceptions => true, - :use_mocks => true, - :compile_threads => 1, - :test_threads => 1, - :use_test_preprocessor => false, - :use_preprocessor_directives => false, - :use_deep_dependencies => false, - :generate_deep_dependencies => true, # only applicable if use_deep_dependencies is true - :auto_link_deep_dependencies => false, - :test_file_prefix => 'test_', - :options_paths => [], - :release_build => false, - :use_backtrace_gdb_reporter => false, +DEFAULT_CEEDLING_PROJECT_CONFIG = { + :project => { + # :build_root must be set by user + :use_mocks => false, + :use_exceptions => false, + :compile_threads => 1, + :test_threads => 1, + :use_test_preprocessor => :none, + :test_file_prefix => 'test_', + :release_build => false, + :use_backtrace => :simple }, - :release_build => { - # :output is set while building configuration -- allows smart default system-dependent file extension handling - :use_assembly => false, - :artifacts => [], + :release_build => { + # :output is set while building configuration -- allows smart default system-dependent file extension handling + :use_assembly => false, + :artifacts => [] }, - :paths => { - :test => [], # must be populated by user - :source => [], # must be populated by user - :support => [], - :include => [], - :libraries => [], - :test_toolchain_include => [], - :release_toolchain_include => [], + :test_build => { + :use_assembly => false }, - :files => { - :test => [], - :source => [], - :assembly => [], - :support => [], - :include => [], + # Unlike other top-level entries, :environment is an array (of hashes) to preserve order + :environment => [], + + :paths => { + :test => [], # Must be populated by user + :source => [], # Should be populated by user but TEST_INCLUDE_PATH() could be used exclusively instead + :support => [], + :include => [], # Must be populated by user + :libraries => [], + :test_toolchain_include => [], + :release_toolchain_include => [], }, - # unlike other top-level entries, environment's value is an array to preserve order - :environment => [ - # when evaluated, this provides wider text field for rake task comments - {:rake_columns => '120'}, - ], - - :defines => { - :test => [], - :test_preprocess => [], - :release => [], - :release_preprocess => [], - :use_test_definition => false, + :files => { + :test => [], + :source => [], + :assembly => [], + :support => [], + :include => [], }, - :libraries => { - :flag => '-l${1}', - :path_flag => '-L ${1}', - :test => [], - :test_preprocess => [], - :release => [], - :release_preprocess => [], + :defines => { + :use_test_definition => false, + :test => [], # List of symbols or matcher hashes with test executables as keys + # :preprocess is identical to :test but lacks a default here as missing vs. empty values have additional meaning + :release => [] # List of symbols only }, - :flags => {}, - - :extension => { - :header => '.h', - :source => '.c', - :assembly => '.s', - :object => '.o', - :libraries => ['.a','.so'], - :executable => ( SystemWrapper.windows? ? EXTENSION_WIN_EXE : EXTENSION_NONWIN_EXE ), - :map => '.map', - :list => '.lst', - :testpass => '.pass', - :testfail => '.fail', - :dependencies => '.d', + :flags => { + # Test & release flags are validated for presence--empty flags causes an error + # :test => {}, # hash/sub-hash of operations containing lists of flags or matcher hashes with test executables as keys + # :release => {} # hash/sub-hashes of operations containing lists of flags }, - :unity => { - :vendor_path => CEEDLING_VENDOR, - :defines => [] + :libraries => { + :flag => '-l${1}', + :path_flag => '-L ${1}', + :test => [], + :release => [] }, - :cmock => { - :vendor_path => CEEDLING_VENDOR, - :defines => [], - :includes => [] + :extension => { + :header => '.h', + :source => '.c', + :assembly => '.s', + :object => '.o', + :libraries => ['.a','.so'], + :executable => ( SystemWrapper.windows? ? EXTENSION_WIN_EXE : EXTENSION_NONWIN_EXE ), + :map => '.map', + :list => '.lst', + :testpass => '.pass', + :testfail => '.fail', + :dependencies => '.d', + :yaml => '.yml' }, - :cexception => { - :vendor_path => CEEDLING_VENDOR, - :defines => [] + :unity => { + :defines => [], + :use_param_tests => false }, - :test_runner => { - :includes => [], - :file_suffix => '_runner', + :cmock => { + :includes => [], + :defines => [], + :plugins => [], + :unity_helper_path => [], + # Yes, we're duplicating these defaults in CMock, but it's because: + # (A) We always need CMOCK_MOCK_PREFIX in Ceedling's environment + # (B) Test runner generator uses these same configuration values + :mock_prefix => 'Mock', + :mock_suffix => '', + # CMock's default duplicated here. + # We need a value present so preprocessing logic can safely reference it. + :treat_inlines => :exclude, + # Just because strict ordering is the way to go + :enforce_strict_ordering => true }, - # all tools populated while building up config structure - :tools => {}, + :cexception => { + :defines => [] + }, - # empty argument lists for default tools - # (these can be overridden in project file to add arguments to tools without totally redefining tools) - :test_compiler => { :arguments => [] }, - :test_linker => { :arguments => [] }, - :test_fixture => { - :arguments => [], - :link_objects => [], # compiled object files to always be linked in (e.g. cmock.o if using mocks) + :test_runner => { + :cmdline_args => false, + :includes => [], + :defines => [], + :file_suffix => '_runner', }, - :test_includes_preprocessor => { :arguments => [] }, - :test_file_preprocessor => { :arguments => [] }, - :test_file_preprocessor_directives => { :arguments => [] }, - :test_dependencies_generator => { :arguments => [] }, - :release_compiler => { :arguments => [] }, - :release_linker => { :arguments => [] }, - :release_assembler => { :arguments => [] }, - :release_dependencies_generator => { :arguments => [] }, - - :plugins => { - :load_paths => CEEDLING_PLUGINS, - :enabled => [], + + # All tools populated while building up config / defaults structure + :tools => {}, + + }.freeze + + +CEEDLING_RUNTIME_CONFIG = { + :unity => { + :vendor_path => CEEDLING_VENDOR + }, + + :cmock => { + :vendor_path => CEEDLING_VENDOR + }, + + :cexception => { + :vendor_path => CEEDLING_VENDOR + }, + + :plugins => { + :load_paths => [], + :enabled => CEEDLING_PLUGINS, } }.freeze diff --git a/lib/ceedling/defineinator.rb b/lib/ceedling/defineinator.rb new file mode 100644 index 000000000..954088661 --- /dev/null +++ b/lib/ceedling/defineinator.rb @@ -0,0 +1,88 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +# :defines: +# :test: +# :*: # Define TEST during compilation of all files for all test executables +# - TEST +# :Model: # Define PLATFORM_B during compilation of any test executable with Model in its filename +# - -PLATFORM_B +# :unity: +# - UNITY_INCLUDE_PRINT_FORMATTED # Define Unity configuration symbols during all test compilation +# - UNITY_FLOAT_PRECISION=0.001f # ... +# :release: +# - COM=Serial # Define COM for compilation of all files during release build + +# :defines: +# :test: # Equivalent to [test]['*'] -- i.e. same defines for all test executables +# - TEST +# - PLATFORM_B + +class Defineinator + + constructor :configurator, :loginator, :config_matchinator + + def setup + @topkey = :defines + end + + def defines_defined?(context:) + return @config_matchinator.config_include?(primary:@topkey, secondary:context) + end + + # Defaults to inspecting configurations beneath top-level :defines + # (But, we can also lookup defines symbol lists within framework configurations--:unity, :cmock, :cexception) + def defines(topkey:@topkey, subkey:, filepath:nil, default:[]) + defines = @config_matchinator.get_config(primary:topkey, secondary:subkey) + + if defines == nil then return default + # Flatten to handle list-nested YAML aliasing (should have already been flattened during validation) + elsif defines.is_a?(Array) then return defines.flatten + elsif defines.is_a?(Hash) + arg_hash = { + hash: defines, + filepath: filepath, + section: topkey, + context: subkey + } + + return @config_matchinator.matches?(**arg_hash) + end + + # Handle unexpected config element type + return [] + end + + # Optionally create a command line compilation symbol that is a test file's sanitized/converted name + def generate_test_definition(filepath:) + defines = [] + + if @configurator.defines_use_test_definition + # Get filename with no path or extension + test_def = File.basename(filepath, '.*').strip + + # Replace any non-ASCII characters with underscores + test_def = test_def.encode("ASCII", "UTF-8", invalid: :replace, undef: :replace, replace: "_") + + # Replace all non-alphanumeric characters (including spaces/punctuation but excluding underscores) with underscores + test_def.gsub!(/[^0-9a-z_]/i, '_') + + # Convert to all caps + test_def.upcase! + + # Add leading and trailiing underscores unless they already exist + test_def = test_def.start_with?('_') ? test_def : ('_' + test_def) + test_def = test_def.end_with?('_') ? test_def : (test_def + '_') + + # Add the test filename as a #define symbol to the array + defines << test_def + end + + return defines + end + +end diff --git a/lib/ceedling/dependinator.rb b/lib/ceedling/dependinator.rb index 669744b80..0483215e2 100644 --- a/lib/ceedling/dependinator.rb +++ b/lib/ceedling/dependinator.rb @@ -1,13 +1,13 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= class Dependinator - constructor :configurator, :project_config_manager, :test_includes_extractor, :file_path_utils, :rake_wrapper, :file_wrapper - - def touch_force_rebuild_files - @file_wrapper.touch( @configurator.project_test_force_rebuild_filepath ) - @file_wrapper.touch( @configurator.project_release_force_rebuild_filepath ) if (@configurator.project_release_build) - end - + constructor :configurator, :test_context_extractor, :file_path_utils, :rake_wrapper, :file_wrapper def load_release_object_deep_dependencies(dependencies_list) @@ -19,14 +19,6 @@ def load_release_object_deep_dependencies(dependencies_list) end - def enhance_release_file_dependencies(files) - files.each do |filepath| - @rake_wrapper[filepath].enhance( [@configurator.project_release_force_rebuild_filepath] ) if (@project_config_manager.release_config_changed) - end - end - - - def load_test_object_deep_dependencies(files_list) dependencies_list = @file_path_utils.form_test_dependencies_filelist(files_list) dependencies_list.each do |dependencies_file| @@ -36,62 +28,4 @@ def load_test_object_deep_dependencies(files_list) end end - - def enhance_runner_dependencies(runner_filepath) - @rake_wrapper[runner_filepath].enhance( [@configurator.project_test_force_rebuild_filepath] ) if (@project_config_manager.test_config_changed || - @project_config_manager.test_defines_changed) - end - - - def enhance_shallow_include_lists_dependencies(include_lists) - include_lists.each do |include_list_filepath| - @rake_wrapper[include_list_filepath].enhance( [@configurator.project_test_force_rebuild_filepath] ) if (@project_config_manager.test_config_changed || - @project_config_manager.test_defines_changed) - end - end - - - def enhance_preprocesed_file_dependencies(files) - files.each do |filepath| - @rake_wrapper[filepath].enhance( [@configurator.project_test_force_rebuild_filepath] ) if (@project_config_manager.test_config_changed || - @project_config_manager.test_defines_changed) - end - end - - - def enhance_mock_dependencies(mocks_list) - # if input configuration or ceedling changes, make sure these guys get rebuilt - mocks_list.each do |mock_filepath| - @rake_wrapper[mock_filepath].enhance( [@configurator.project_test_force_rebuild_filepath] ) if (@project_config_manager.test_config_changed || - @project_config_manager.test_defines_changed) - @rake_wrapper[mock_filepath].enhance( @configurator.cmock_unity_helper ) if (@configurator.cmock_unity_helper) - end - end - - - def enhance_dependencies_dependencies(dependencies) - dependencies.each do |dependencies_filepath| - @rake_wrapper[dependencies_filepath].enhance( [@configurator.project_test_force_rebuild_filepath] ) if (@project_config_manager.test_config_changed || - @project_config_manager.test_defines_changed) - end - end - - - def enhance_test_build_object_dependencies(objects) - objects.each do |object_filepath| - @rake_wrapper[object_filepath].enhance( [@configurator.project_test_force_rebuild_filepath] ) if (@project_config_manager.test_config_changed || - @project_config_manager.test_defines_changed) - end - end - - - def enhance_results_dependencies(result_filepath) - @rake_wrapper[result_filepath].enhance( [@configurator.project_test_force_rebuild_filepath] ) if @project_config_manager.test_config_changed - end - - - def enhance_test_executable_dependencies(test, objects) - @rake_wrapper[ @file_path_utils.form_test_executable_filepath(test) ].enhance( objects ) - end - end diff --git a/lib/ceedling/erb_wrapper.rb b/lib/ceedling/erb_wrapper.rb index 77c458b5a..814e02307 100644 --- a/lib/ceedling/erb_wrapper.rb +++ b/lib/ceedling/erb_wrapper.rb @@ -1,3 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'erb' class ErbWrapper diff --git a/lib/ceedling/exceptions.rb b/lib/ceedling/exceptions.rb new file mode 100644 index 000000000..08f73232f --- /dev/null +++ b/lib/ceedling/exceptions.rb @@ -0,0 +1,47 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'ceedling/constants' + + +class CeedlingException < RuntimeError + # Nothing at the moment +end + + +class ShellException < CeedlingException + + attr_reader :shell_result + + def initialize(shell_result:{}, name:, message:'') + @shell_result = shell_result + + _message = '' + + # Most shell exceptions will be from build compilation and linking. + # The formatting of these messages should place the tool output on its own + # lines without any other surrounding characters. + # This formatting maximizes the ability of IDEs to parse, highlight, and make + # actionable the build errors that appear within their terminal windows. + + # If shell results exist, report the exit code... + if !shell_result.empty? + _message = "#{name} terminated with exit code [#{shell_result[:exit_code]}]" + + if !shell_result[:output].empty? + _message += " and output >>\n#{shell_result[:output].strip()}" + end + + # Otherwise, just report the exception message + else + _message = "#{name} encountered an error with output >>\n#{message}" + end + + # Hand the message off to parent Exception + super( _message ) + end +end diff --git a/lib/ceedling/file_finder.rb b/lib/ceedling/file_finder.rb index e0a73bf6a..b4166c060 100644 --- a/lib/ceedling/file_finder.rb +++ b/lib/ceedling/file_finder.rb @@ -1,146 +1,183 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'rubygems' require 'rake' # for adding ext() method to string +require 'ceedling/exceptions' + class FileFinder - SEMAPHORE = Mutex.new constructor :configurator, :file_finder_helper, :cacheinator, :file_path_utils, :file_wrapper, :yaml_wrapper - def prepare_search_sources - @all_test_source_and_header_file_collection = - @configurator.collection_all_tests + - @configurator.collection_all_source + - @configurator.collection_all_headers - end + def find_header_input_for_mock(mock_name) + # Mock name =>
+ # Examples: 'Mockfoo' or 'mock_Bar' + # Note: In some rare cases, a mock name may include a dot (ex. Sensor.44) because of versioning file naming convention + # Be careful about assuming the end of the name has any sort of file extension - def find_header_file(mock_file) - header = File.basename(mock_file).sub(/#{@configurator.cmock_mock_prefix}/, '').ext(@configurator.extension_header) + header = mock_name.sub(/#{@configurator.cmock_mock_prefix}/, '') + @configurator.extension_header - found_path = @file_finder_helper.find_file_in_collection(header, @configurator.collection_all_headers, :error) + found_path = @file_finder_helper.find_file_in_collection(header, @configurator.collection_all_headers, :error, mock_name) return found_path end - def find_header_input_for_mock_file(mock_file) - found_path = find_header_file(mock_file) - mock_input = found_path - - if (@configurator.project_use_test_preprocessor) - mock_input = @cacheinator.diff_cached_test_file( @file_path_utils.form_preprocessed_file_filepath( found_path ) ) - end + # Find test filepath from another filepath (e.g. test executable with same base name, a/path/test_foo.exe) + def find_test_file_from_filepath(filepath) + # Strip filepath down to filename and remove file extension + name = File.basename( filepath ).ext('') - return mock_input + return find_test_file_from_name( name ) end - def find_source_from_test(test, complain) - test_prefix = @configurator.project_test_file_prefix - source_paths = @configurator.collection_all_source - - source = File.basename(test).sub(/#{test_prefix}/, '') - - # we don't blow up if a test file has no corresponding source file - return @file_finder_helper.find_file_in_collection(source, source_paths, complain) - end - + # Find test filepath from only the base name of a test file (e.g. 'test_foo') + def find_test_file_from_name(name) + test_file = name + @configurator.extension_source - def find_test_from_runner_path(runner_path) - extension_source = @configurator.extension_source - - test_file = File.basename(runner_path).sub(/#{@configurator.test_runner_file_suffix}#{'\\'+extension_source}/, extension_source) - - found_path = @file_finder_helper.find_file_in_collection(test_file, @configurator.collection_all_tests, :error) + found_path = @file_finder_helper.find_file_in_collection(test_file, @configurator.collection_all_tests, :error, name) return found_path end - def find_test_input_for_runner_file(runner_path) - found_path = find_test_from_runner_path(runner_path) - runner_input = found_path - - if (@configurator.project_use_test_preprocessor) - runner_input = @cacheinator.diff_cached_test_file( @file_path_utils.form_preprocessed_file_filepath( found_path ) ) - end - - return runner_input - end - + def find_build_input_file(filepath:, complain: :error, context:) + release = (context == RELEASE_SYM) - def find_test_from_file_path(file_path) - test_file = File.basename(file_path).ext(@configurator.extension_source) + found_file = nil - found_path = @file_finder_helper.find_file_in_collection(test_file, @configurator.collection_all_tests, :error) + # Strip off file extension + source_file = File.basename(filepath).ext('') - return found_path - end + # We only collect files that already exist when we start up. + # FileLists can produce undesired results for dynamically generated files depending on when they're accessed. + # So collect mocks and runners separately and right now. + # Assume that project configuration options will have already filtered out any files that should not be searched for. + + # Note: We carefully add file extensions below with string addition instead of using .ext() + # Some legacy files can include naming conventions like .##. for versioning. + # If we use .ext() below we'll clobber the dotted portion of the filename + + # Generated test runners + if (!release) and (source_file =~ /^#{@configurator.project_test_file_prefix}.+#{@configurator.test_runner_file_suffix}$/) + _source_file = source_file + EXTENSION_CORE_SOURCE + found_file = + @file_finder_helper.find_file_in_collection( + _source_file, + @file_wrapper.directory_listing( File.join(@configurator.project_test_runners_path, '*') ), + complain, + filepath) + + # Generated mocks + elsif (!release) and (source_file =~ /^#{@configurator.cmock_mock_prefix}/) + _source_file = source_file + EXTENSION_CORE_SOURCE + found_file = + @file_finder_helper.find_file_in_collection( + _source_file, + @file_wrapper.directory_listing( File.join(@configurator.cmock_mock_path, '**/*') ), + complain, + filepath) + + # Vendor framework sources (unity.c, cmock.c, cexception.c, etc.) + # Note: Taking a small chance by mixing test and release frameworks without smart checks on test/release build + elsif (@configurator.collection_vendor_framework_sources.include?(source_file.ext(EXTENSION_CORE_SOURCE))) + _source_file = source_file + EXTENSION_CORE_SOURCE + found_file = + @file_finder_helper.find_file_in_collection( + _source_file, + @configurator.collection_existing_test_build_input, + complain, + filepath) + end - def find_test_or_source_or_header_file(file_path) - file = File.basename(file_path) - return @file_finder_helper.find_file_in_collection(file, @all_test_source_and_header_file_collection, :error) - end + if !found_file.nil? + return found_file + end + # + # Above we can confidently rely on the complain parameter passed to file_finder_helper because + # we know the specific type of file being searched for. + # + # Below we ignore file misses because of lgoical complexities of searching for potentially either + # assmebly or C files, including C files that may not exist (counterparts to header files by convention). + # We save the existence handling until the end. + # + + # Assembly files for release build + if release and @configurator.release_build_use_assembly + _source_file = source_file + @configurator.extension_assembly + found_file = + @file_finder_helper.find_file_in_collection( + _source_file, + @configurator.collection_release_build_input, + :ignore, + filepath) + + # Assembly files for test build + elsif (!release) and @configurator.test_build_use_assembly + _source_file = source_file + @configurator.extension_assembly + found_file = + @file_finder_helper.find_file_in_collection( + _source_file, + @configurator.collection_existing_test_build_input, + :ignore, + filepath) + end - def find_compilation_input_file(file_path, complain=:error, release=false) - found_file = nil + if !found_file.nil? + return found_file + end - source_file = File.basename(file_path).ext(@configurator.extension_source) + # Release build C files + if release + _source_file = source_file + @configurator.extension_source + found_file = + @file_finder_helper.find_file_in_collection( + _source_file, + @configurator.collection_release_build_input, + :ignore, + filepath) + + # Test build C files + else + _source_file = source_file + @configurator.extension_source + found_file = + @file_finder_helper.find_file_in_collection( + _source_file, + @configurator.collection_existing_test_build_input, + :ignore, + filepath) + end - # We only collect files that already exist when we start up. - # FileLists can produce undesired results for dynamically generated files depending on when they're accessed. - # So collect mocks and runners separately and right now. + if found_file.nil? + _source_file += " or #{_source_file.ext(@configurator.extension_assembly)}" if @configurator.release_build_use_assembly + @file_finder_helper.handle_missing_file(_source_file, complain) + end - SEMAPHORE.synchronize { - - if (source_file =~ /#{@configurator.test_runner_file_suffix}/) - found_file = - @file_finder_helper.find_file_in_collection( - source_file, - @file_wrapper.directory_listing( File.join(@configurator.project_test_runners_path, '*') ), - complain) - - elsif (@configurator.project_use_mocks and (source_file =~ /#{@configurator.cmock_mock_prefix}/)) - found_file = - @file_finder_helper.find_file_in_collection( - source_file, - @file_wrapper.directory_listing( File.join(@configurator.cmock_mock_path, '**/*.*') ), - complain) - - elsif release - found_file = - @file_finder_helper.find_file_in_collection( - source_file, - @configurator.collection_release_existing_compilation_input, - complain) - else - temp_complain = (defined?(TEST_BUILD_USE_ASSEMBLY) && TEST_BUILD_USE_ASSEMBLY) ? :ignore : complain - found_file = - @file_finder_helper.find_file_in_collection( - source_file, - @configurator.collection_all_existing_compilation_input, - temp_complain) - found_file ||= find_assembly_file(file_path, false) if (defined?(TEST_BUILD_USE_ASSEMBLY) && TEST_BUILD_USE_ASSEMBLY) - end - } return found_file end - def find_source_file(file_path, complain) - source_file = File.basename(file_path).ext(@configurator.extension_source) - return @file_finder_helper.find_file_in_collection(source_file, @configurator.collection_all_source, complain) + def find_source_file(filepath, complain = :error) + source_file = File.basename(filepath).ext(@configurator.extension_source) + return @file_finder_helper.find_file_in_collection(source_file, @configurator.collection_all_source, complain, filepath) end - def find_assembly_file(file_path, complain = :error) - assembly_file = File.basename(file_path).ext(@configurator.extension_assembly) - return @file_finder_helper.find_file_in_collection(assembly_file, @configurator.collection_all_assembly, complain) + def find_assembly_file(filepath, complain = :error) + assembly_file = File.basename(filepath).ext(@configurator.extension_assembly) + return @file_finder_helper.find_file_in_collection(assembly_file, @configurator.collection_all_assembly, complain, filepath) end - def find_file_from_list(file_path, file_list, complain) - return @file_finder_helper.find_file_in_collection(file_path, file_list, complain) + def find_file_from_list(filepath, file_list, complain) + return @file_finder_helper.find_file_in_collection(filepath, file_list, complain, filepath) end end diff --git a/lib/ceedling/file_finder_helper.rb b/lib/ceedling/file_finder_helper.rb index a168e5cb5..a95ae694a 100644 --- a/lib/ceedling/file_finder_helper.rb +++ b/lib/ceedling/file_finder_helper.rb @@ -1,54 +1,100 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'fileutils' require 'ceedling/constants' # for Verbosity enumeration +require 'ceedling/exceptions' class FileFinderHelper - constructor :streaminator + constructor :loginator - def find_file_in_collection(file_name, file_list, complain, extra_message="") - file_to_find = nil + def find_file_in_collection(filename, file_list, complain, original_filepath="") + # Search our collection for the specified base filename + matches = file_list.find_all {|v| File.basename(v) == filename } - file_list.each do |item| - base_file = File.basename(item) - - # case insensitive comparison - if (base_file.casecmp(file_name) == 0) - # case sensitive check - if (base_file == file_name) - file_to_find = item - break - else - blow_up(file_name, "However, a filename having different capitalization was found: '#{item}'.") + case matches.length + when 0 + matches = file_list.find_all {|v| v =~ /(?:\\|\/|^)#{filename}$/i} + if (matches.length > 0) + blow_up(filename, "However, a filename having different capitalization was found: '#{matches[0]}'.") end - end - + + return handle_missing_file(filename, complain) + when 1 + return matches[0] + else + # Determine the closest match by looking for matching path segments, especially paths ENDING the same + best_match_index = 0 + best_match_value = 0 + reverse_original_pieces = original_filepath.split(/(?:\\|\/)/).reverse + matches.each_with_index do |m,i| + reverse_match_pieces = m.split(/(?:\\|\/)/).reverse + + num = reverse_original_pieces.zip(reverse_match_pieces).inject(0){|s,v| v[0] == v[1] ? s+3 : s} + num = reverse_original_pieces.inject(num){|s,v| reverse_match_pieces.include?(v) ? s+1 : s} + if num > best_match_value + best_match_index = i + best_match_value = num + end + end + return matches[best_match_index] end - - if file_to_find.nil? - case (complain) - when :error then blow_up(file_name, extra_message) - when :warn then gripe(file_name, extra_message) - #when :ignore then + + return nil + end + + def find_best_path_in_collection(pathname, path_list, complain) + # search our collection for the specified exact path + raise "No path list provided for search" if path_list.nil? + return pathname if path_list.include?(pathname) + + # Determine the closest match by looking for matching path segments, especially paths ENDING the same + best_match_index = 0 + best_match_value = 0 + reverse_original_pieces = pathname.split(/(?:\\|\/)/).reverse + path_list.each_with_index do |p,i| + reverse_match_pieces = p.split(/(?:\\|\/)/).reverse + # + num = reverse_original_pieces.zip(reverse_match_pieces).inject(0){|s,v| v[0] == v[1] ? s+3 : s} + num = reverse_original_pieces.inject(num){|s,v| reverse_match_pieces.include?(v) ? s+1 : s} + if num > best_match_value + best_match_index = i + best_match_value = num end end - - return file_to_find + return path_list[best_match_index] + end + + def handle_missing_file(filename, complain) + case (complain) + when :error then blow_up(filename) + when :warn + gripe(filename) + return nil + when :ignore then return nil + end + + return nil end + ### Private ### + private - - def blow_up(file_name, extra_message="") - error = "ERROR: Found no file '#{file_name}' in search paths." - error += ' ' if (extra_message.length > 0) - @streaminator.stderr_puts(error + extra_message, Verbosity::ERRORS) - raise + + def blow_up(filename, extra_message="") + error = ["Found no file `#{filename}` in search paths.", extra_message].join(' ').strip + raise CeedlingException.new( error ) end - - def gripe(file_name, extra_message="") - warning = "WARNING: Found no file '#{file_name}' in search paths." - warning += ' ' if (extra_message.length > 0) - @streaminator.stderr_puts(warning + extra_message, Verbosity::COMPLAIN) + + def gripe(filename, extra_message="") + warning = ["Found no file `#{filename}` in search paths.", extra_message].join(' ').strip + @loginator.log( warning + extra_message, Verbosity::COMPLAIN ) end end diff --git a/lib/ceedling/file_path_collection_utils.rb b/lib/ceedling/file_path_collection_utils.rb new file mode 100644 index 000000000..1c01d3ee6 --- /dev/null +++ b/lib/ceedling/file_path_collection_utils.rb @@ -0,0 +1,137 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'set' +require 'pathname' +require 'fileutils' +require 'ceedling/file_path_utils' +require 'ceedling/exceptions' + + +class FilePathCollectionUtils + + constructor :file_wrapper + + def setup() + # TODO: Update Dir.pwd() to use a project root once it has been figured out + @working_dir_path = Pathname.new( Dir.pwd() ) + end + + # Build up a directory path list from one or more strings or arrays of (+:/-:) simple paths & globs + def collect_paths(paths) + plus = Set.new # All real, expanded directory paths to add + minus = Set.new # All real, expanded paths to exclude + + # Iterate each path possibly decorated with aggregation modifiers and/or containing glob characters + paths.each do |path| + dirs = [] # Working list for evaluated directory paths + + # Get path stripped of any +:/-: aggregation modifier + _path = FilePathUtils.no_aggregation_decorators( path ) + + # If it's a glob, modify it for Ceedling's recursive subdirectory convention + _reformed = FilePathUtils::reform_subdirectory_glob( _path ) + + # Expand paths using Ruby's Dir.glob() + # - A simple path will yield that path + # - A path glob will expand to one or more paths + # Note: `sort()` becuase of Github Issue #860 + @file_wrapper.directory_listing( _reformed ).sort.each do |entry| + # For each result, add it to the working list *only* if it's a directory + # Previous validation has already made warnings about filepaths in the list + dirs << entry if @file_wrapper.directory?(entry) + end + + # For recursive directory glob at end of a path, collect parent directories too. + # Ceedling's recursive glob convention includes parent directories (unlike Ruby's glob). + if path.end_with?('/**') or path.end_with?('/*') + parents = [] + + dirs.each {|dir| parents << File.join(dir, '..')} + + # Handle edge case of subdirectory glob but no subdirectories and therefore no parents + # (Containing parent directory still exists) + parents << FilePathUtils.no_decorators( _path ) if dirs.empty? + + dirs += parents + end + + # Based on aggregation modifiers, add entries to plus and minus sets. + # Use full, absolute paths to ensure logical paths are compared properly. + # './' is logically equivalent to '' but is not equivalent as strings. + # Because plus and minus are sets, each insertion eliminates any duplicates + # (such as the parent directories for each directory as added above). + dirs.each do |dir| + abs_path = File.expand_path( dir ) + if FilePathUtils.add_path?( path ) + plus << abs_path + else + minus << abs_path + end + end + end + + # Use Set subtraction operator to remove any excluded paths + paths = (plus - minus).to_a + paths.map! {|path| shortest_path_from_working(path) } + + return paths + end + + + # Given a file list, add to it or remove from it considering (+:/-:) aggregation operators. + # Rake's FileList does not robustly handle relative filepaths and patterns. + # So, we rebuild the FileList ourselves and return it. + # TODO: Replace FileList with our own, better version. + def revise_filelist(list, revisions) + plus = Set.new # All real, expanded directory paths to add + minus = Set.new # All real, expanded paths to exclude + + # Build base plus set for revised path + list.each do |path| + # Start with expanding all list entries to absolute paths + plus << File.expand_path( path ) + end + + revisions.each do |revision| + # Include or exclude revisions in file list + path = FilePathUtils.no_aggregation_decorators( revision ) + + # Working list of revisions + filepaths = [] + + # Expand path by pattern as needed and add only filepaths to working list + @file_wrapper.directory_listing( path ).each do |entry| + filepaths << File.expand_path( entry ) if !@file_wrapper.directory?( entry ) + end + + # Handle +: / -: revisions + if FilePathUtils.add_path?( revision ) + plus.merge( filepaths ) + else + minus.merge( filepaths ) + end + end + + # Use Set subtraction operator to remove any excluded paths + paths = (plus - minus).to_a + paths.map! {|path| shortest_path_from_working(path) } + + return FileList.new( paths ) + end + + def shortest_path_from_working(path) + begin + # Reform path from full absolute to nice, neat relative path instead + (Pathname.new( path ).relative_path_from( @working_dir_path )).to_s + rescue StandardError + # If we can't form a relative path between these paths, use the absolute + path + end + end + +end diff --git a/lib/ceedling/file_path_utils.rb b/lib/ceedling/file_path_utils.rb index 3f5489be1..bf1e1f3e5 100644 --- a/lib/ceedling/file_path_utils.rb +++ b/lib/ceedling/file_path_utils.rb @@ -1,7 +1,15 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'rubygems' require 'rake' # for ext() require 'fileutils' require 'ceedling/system_wrapper' +require 'ceedling/constants' # global utility methods (for plugins, project files, etc.) def ceedling_form_filepath(destination_path, original_filepath, new_extension=nil) @@ -12,14 +20,12 @@ def ceedling_form_filepath(destination_path, original_filepath, new_extension=ni class FilePathUtils - GLOB_MATCHER = /[\*\?\{\}\[\]]/ - constructor :configurator, :file_wrapper - ######### class methods ########## + ######### Class methods ########## - # standardize path to use '/' path separator & have no trailing path separator + # Standardize path to use '/' path separator & have no trailing path separator def self.standardize(path) if path.is_a? String path.strip! @@ -34,52 +40,59 @@ def self.os_executable_ext(executable) return executable end - # extract directory path from between optional add/subtract aggregation modifiers and up to glob specifiers - # note: slightly different than File.dirname in that /files/foo remains /files/foo and does not become /files - def self.extract_path(path) - path = path.sub(/^(\+|-):/, '') + # Extract path from between optional aggregation modifiers + # and up to last path separator before glob specifiers. + # Examples: + # - '+:foo/bar/baz/' => 'foo/bar/baz' + # - 'foo/bar/ba?' => 'foo/bar' + # - 'foo/bar/baz/' => 'foo/bar/baz' + # - 'foo/bar/baz/file.x' => 'foo/bar/baz/file.x' + # - 'foo/bar/baz/file*.x' => 'foo/bar/baz' + def self.no_decorators(path) + path = self.no_aggregation_decorators(path) + + # Find first occurrence of glob specifier: *, ?, {, }, [, ] + find_index = (path =~ GLOB_PATTERN) - # find first occurrence of path separator followed by directory glob specifier: *, ?, {, }, [, ] - find_index = (path =~ GLOB_MATCHER) + # Return empty path if first character is part of a glob + return '' if find_index == 0 - # no changes needed (lop off final path separator) + # If path contains no glob, clean it up and return whole path return path.chomp('/') if (find_index.nil?) - # extract up to first glob specifier + # Extract up to first glob specifier path = path[0..(find_index-1)] - # lop off everything up to and including final path separator + # Keep everything from start of path string up to and + # including final path separator before glob character find_index = path.rindex('/') return path[0..(find_index-1)] if (not find_index.nil?) - # return string up to first glob specifier if no path separator found - return path + # Otherwise, return empty string + # (Not enough of a usable path exists free of glob operators) + return '' end - # return whether the given path is to be aggregated (no aggregation modifier defaults to same as +:) + # Return whether the given path is to be aggregated (no aggregation modifier defaults to same as +:) def self.add_path?(path) - return (path =~ /^-:/).nil? + return !path.strip.start_with?('-:') end - # get path (and glob) lopping off optional +: / -: prefixed aggregation modifiers - def self.extract_path_no_aggregation_operators(path) - return path.sub(/^(\+|-):/, '') + # Get path (and glob) lopping off optional +: / -: prefixed aggregation modifiers + def self.no_aggregation_decorators(path) + return path.sub(/^(\+|-):/, '').strip() end - # all the globs that may be in a path string work fine with one exception; - # to recurse through all subdirectories, the glob is dir/**/** but our paths use - # convention of only dir/** - def self.reform_glob(path) - return path if (path =~ /\/\*\*$/).nil? - return path + '/**' + # To recurse through all subdirectories, the RUby glob is /**/**, but our paths use + # convenience convention of only /** at tail end of a path. + def self.reform_subdirectory_glob(path) + return path if path.end_with?( '/**/**' ) + return path + '/**' if path.end_with?( '/**' ) + return path end ######### instance methods ########## - def form_temp_path(filepath, prefix='') - return File.join( @configurator.project_temp_path, prefix + File.basename(filepath) ) - end - ### release ### def form_release_build_cache_path(filepath) return File.join( @configurator.project_release_build_cache_path, File.basename(filepath) ) @@ -89,31 +102,20 @@ def form_release_dependencies_filepath(filepath) return File.join( @configurator.project_release_dependencies_path, File.basename(filepath).ext(@configurator.extension_dependencies) ) end - def form_release_build_c_object_filepath(filepath) - return File.join( @configurator.project_release_build_output_c_path, File.basename(filepath).ext(@configurator.extension_object) ) - end - - def form_release_build_asm_object_filepath(filepath) - return File.join( @configurator.project_release_build_output_asm_path, File.basename(filepath).ext(@configurator.extension_object) ) - end - - def form_release_build_c_objects_filelist(files) - return (@file_wrapper.instantiate_file_list(files)).pathmap("#{@configurator.project_release_build_output_c_path}/%n#{@configurator.extension_object}") - end - - def form_release_build_asm_objects_filelist(files) - return (@file_wrapper.instantiate_file_list(files)).pathmap("#{@configurator.project_release_build_output_asm_path}/%n#{@configurator.extension_object}") + def form_release_build_objects_filelist(files) + return (@file_wrapper.instantiate_file_list(files)).pathmap("#{@configurator.project_release_build_output_path}/%n#{@configurator.extension_object}") end - def form_release_build_c_list_filepath(filepath) - return File.join( @configurator.project_release_build_output_c_path, File.basename(filepath).ext(@configurator.extension_list) ) + def form_release_build_list_filepath(filepath) + return File.join( @configurator.project_release_build_output_path, File.basename(filepath).ext(@configurator.extension_list) ) end def form_release_dependencies_filelist(files) return (@file_wrapper.instantiate_file_list(files)).pathmap("#{@configurator.project_release_dependencies_path}/%n#{@configurator.extension_dependencies}") end - ### tests ### + ### Tests ### + def form_test_build_cache_path(filepath) return File.join( @configurator.project_test_build_cache_path, File.basename(filepath) ) end @@ -122,85 +124,57 @@ def form_test_dependencies_filepath(filepath) return File.join( @configurator.project_test_dependencies_path, File.basename(filepath).ext(@configurator.extension_dependencies) ) end - def form_pass_results_filepath(filepath) - return File.join( @configurator.project_test_results_path, File.basename(filepath).ext(@configurator.extension_testpass) ) + def form_pass_results_filepath(build_output_path, filepath) + return File.join( build_output_path, File.basename(filepath).ext(@configurator.extension_testpass) ) end - def form_fail_results_filepath(filepath) - return File.join( @configurator.project_test_results_path, File.basename(filepath).ext(@configurator.extension_testfail) ) + def form_fail_results_filepath(build_output_path, filepath) + return File.join( build_output_path, File.basename(filepath).ext(@configurator.extension_testfail) ) end def form_runner_filepath_from_test(filepath) - return File.join( @configurator.project_test_runners_path, File.basename(filepath, @configurator.extension_source)) + @configurator.test_runner_file_suffix + @configurator.extension_source + return File.join( @configurator.project_test_runners_path, File.basename(filepath, @configurator.extension_source)) + @configurator.test_runner_file_suffix + EXTENSION_CORE_SOURCE end def form_test_filepath_from_runner(filepath) return filepath.sub(/#{TEST_RUNNER_FILE_SUFFIX}/, '') end - def form_runner_object_filepath_from_test(filepath) - return (form_test_build_c_object_filepath(filepath)).sub(/(#{@configurator.extension_object})$/, "#{@configurator.test_runner_file_suffix}\\1") - end - - def form_test_build_c_object_filepath(filepath) - return File.join( @configurator.project_test_build_output_c_path, File.basename(filepath).ext(@configurator.extension_object) ) - end - - def form_test_build_asm_object_filepath(filepath) - return File.join( @configurator.project_test_build_output_asm_path, File.basename(filepath).ext(@configurator.extension_object) ) + def form_test_executable_filepath(build_output_path, filepath) + return File.join( build_output_path, File.basename(filepath).ext(@configurator.extension_executable) ) end - def form_test_executable_filepath(filepath) - return File.join( @configurator.project_test_build_output_path, File.basename(filepath).ext(@configurator.extension_executable) ) - end - - def form_test_build_map_filepath(filepath) - return File.join( @configurator.project_test_build_output_path, File.basename(filepath).ext(@configurator.extension_map) ) + def form_test_build_map_filepath(build_output_path, filepath) + return File.join( build_output_path, File.basename(filepath).ext(@configurator.extension_map) ) end def form_test_build_list_filepath(filepath) return File.join( @configurator.project_test_build_output_path, File.basename(filepath).ext(@configurator.extension_list) ) end - def form_preprocessed_file_filepath(filepath) - return File.join( @configurator.project_test_preprocess_files_path, File.basename(filepath) ) + def form_preprocessed_includes_list_filepath(filepath, subdir) + return File.join( @configurator.project_test_preprocess_includes_path, subdir, File.basename(filepath) + @configurator.extension_yaml ) end - def form_preprocessed_includes_list_filepath(filepath) - return File.join( @configurator.project_test_preprocess_includes_path, File.basename(filepath) ) + def form_preprocessed_file_filepath(filepath, subdir) + return File.join( @configurator.project_test_preprocess_files_path, subdir, File.basename(filepath) ) end - def form_test_build_objects_filelist(sources) - return (@file_wrapper.instantiate_file_list(sources)).pathmap("#{@configurator.project_test_build_output_c_path}/%n#{@configurator.extension_object}") + def form_preprocessed_file_full_expansion_filepath(filepath, subdir) + return File.join( @configurator.project_test_preprocess_files_path, subdir, PREPROCESS_FULL_EXPANSION_DIR, File.basename(filepath) ) end - def form_folder_for_mock(mock) - path = File.dirname(mock).sub(/^#{@configurator.cmock_mock_path}[\\\/]?/, '') - - # If we dont have a path then File.dirname() returns '.' however, we cannot return this because it would break - # dependency handling. - return '' if path == '.' - - if path != '' - path = File.join(path, "") - end - return path + def form_preprocessed_file_directives_only_filepath(filepath, subdir) + return File.join( @configurator.project_test_preprocess_files_path, subdir, PREPROCESS_DIRECTIVES_ONLY_DIR, File.basename(filepath) ) end - def form_preprocessed_mockable_headers_filelist(mocks) - list = @file_wrapper.instantiate_file_list(mocks) - headers = list.map do |file| - path_name = form_folder_for_mock(file) - module_name = File.basename(file).sub(/^#{@configurator.cmock_mock_prefix}/, '').sub(/\.[a-zA-Z]+$/,'') - "#{@configurator.project_test_preprocess_files_path}/#{path_name}#{module_name}#{@configurator.extension_header}" - end - return headers + def form_test_build_objects_filelist(path, sources) + return (@file_wrapper.instantiate_file_list(sources)).pathmap("#{path}/%n#{@configurator.extension_object}") end - def form_mocks_source_filelist(mocks) + def form_mocks_source_filelist(path, mocks) list = (@file_wrapper.instantiate_file_list(mocks)) - sources = list.map{|file| "#{@configurator.cmock_mock_path}/#{form_folder_for_mock(file)}#{File.basename(file)}#{@configurator.extension_source}"} - return sources + return list.map{ |file| File.join(path, File.basename(file).ext(@configurator.extension_source)) } end def form_test_dependencies_filelist(files) diff --git a/lib/ceedling/file_system_utils.rb b/lib/ceedling/file_system_utils.rb deleted file mode 100644 index bf29650c0..000000000 --- a/lib/ceedling/file_system_utils.rb +++ /dev/null @@ -1,69 +0,0 @@ -require 'rubygems' -require 'rake' -require 'set' -require 'fileutils' -require 'ceedling/file_path_utils' - - -class FileSystemUtils - - constructor :file_wrapper - - # build up path list from input of one or more strings or arrays of (+/-) paths & globs - def collect_paths(*paths) - raw = [] # all paths and globs - plus = Set.new # all paths to expand and add - minus = Set.new # all paths to remove from plus set - - # assemble all globs and simple paths, reforming our glob notation to ruby globs - paths.each do |paths_container| - case (paths_container) - when String then raw << (FilePathUtils::reform_glob(paths_container)) - when Array then paths_container.each {|path| raw << (FilePathUtils::reform_glob(path))} - else raise "Don't know how to handle #{paths_container.class}" - end - end - - # iterate through each path and glob - raw.each do |path| - - dirs = [] # container for only (expanded) paths - - # if a glob, expand it and slurp up all non-file paths - if path.include?('*') - # grab base directory only if globs are snug up to final path separator - if (path =~ /\/\*+$/) - dirs << FilePathUtils.extract_path(path) - end - - # grab expanded sub-directory globs - expanded = @file_wrapper.directory_listing( FilePathUtils.extract_path_no_aggregation_operators(path) ) - expanded.each do |entry| - dirs << entry if @file_wrapper.directory?(entry) - end - - # else just grab simple path - # note: we could just run this through glob expansion but such an - # approach doesn't handle a path not yet on disk) - else - dirs << FilePathUtils.extract_path_no_aggregation_operators(path) - end - - # add dirs to the appropriate set based on path aggregation modifier if present - FilePathUtils.add_path?(path) ? plus.merge(dirs) : minus.merge(dirs) - end - - return (plus - minus).to_a.uniq.sort - end - - - # given a file list, add to it or remove from it - def revise_file_list(list, revisions) - revisions.each do |revision| - # include or exclude file or glob to file list - file = FilePathUtils.extract_path_no_aggregation_operators( revision ) - FilePathUtils.add_path?(revision) ? list.include(file) : list.exclude(file) - end - end - -end diff --git a/lib/ceedling/file_system_wrapper.rb b/lib/ceedling/file_system_wrapper.rb index 807cbd23f..6389ae7fb 100644 --- a/lib/ceedling/file_system_wrapper.rb +++ b/lib/ceedling/file_system_wrapper.rb @@ -1,3 +1,9 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= class FileSystemWrapper diff --git a/lib/ceedling/file_wrapper.rb b/lib/ceedling/file_wrapper.rb index 9e5a909b4..814114471 100644 --- a/lib/ceedling/file_wrapper.rb +++ b/lib/ceedling/file_wrapper.rb @@ -1,6 +1,14 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'rubygems' require 'rake' # for FileList require 'fileutils' +require 'pathname' require 'ceedling/constants' @@ -20,16 +28,27 @@ def exist?(filepath) return File.exist?(filepath) end + def extname(filepath) + return File.extname(filepath) + end + + # Is path a directory and does it exist? def directory?(path) return File.directory?(path) end + def relative?(path) + return Pathname.new( path).relative? + end + def dirname(path) return File.dirname(path) end def directory_listing(glob) - return Dir.glob(glob, File::FNM_PATHNAME) + # Note: `sort()` to ensure platform-independent directory listings (Github Issue #860) + # FNM_PATHNAME => Case insensitive globs + return Dir.glob(glob, File::FNM_PATHNAME).sort() end def rm_f(filepath, options={}) @@ -40,22 +59,42 @@ def rm_r(filepath, options={}) FileUtils.rm_r(filepath, **options={}) end + def rm_rf(path, options={}) + FileUtils.rm_rf(path, **options={}) + end + def cp(source, destination, options={}) FileUtils.cp(source, destination, **options) end + def cp_r(source, destination, options={}) + FileUtils.cp_r(source, destination, **options) + end + + def mv(source, destination, options={}) + FileUtils.mv(source, destination, **options) + end + def compare(from, to) return FileUtils.compare_file(from, to) end + # Is filepath A newer than B? + def newer?(filepathA, filepathB) + return false unless File.exist?(filepathA) + return false unless File.exist?(filepathB) + + return (File.mtime(filepathA) > File.mtime(filepathB)) + end + def open(filepath, flags) File.open(filepath, flags) do |file| yield(file) end end - def read(filepath) - return File.read(filepath) + def read(filepath, length=nil) + return File.read(filepath, length) end def touch(filepath, options={}) diff --git a/lib/ceedling/flaginator.rb b/lib/ceedling/flaginator.rb index 0b305e623..209848250 100644 --- a/lib/ceedling/flaginator.rb +++ b/lib/ceedling/flaginator.rb @@ -1,74 +1,65 @@ -require 'rubygems' -require 'rake' # for ext() -require 'fileutils' -require 'ceedling/constants' - +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= # :flags: -# :release: +# :test: # :compile: -# :'test_.+' -# - -pedantic # add '-pedantic' to every test file -# :*: # add '-foo' to compilation of all files not main.c +# :*: # Add '-foo' to compilation of all files for all test executables # - -foo -# :main: # add '-Wall' to compilation of main.c +# :Model: # Add '-Wall' to compilation of any test executable with Model in its filename # - -Wall -# :test: # :link: -# :test_main: # add '--bar --baz' to linking of test_main.exe +# :tests/comm/TestUsart.c: # Add '--bar --baz' to link step of TestUsart executable # - --bar # - --baz +# :release: +# - -std=c99 -def partition(hash, &predicate) - hash.partition(&predicate).map(&:to_h) -end +# :flags: +# :test: +# :compile: # Equivalent to [test][compile]['*'] -- i.e. same extra flags for all test executables +# - -foo +# - -Wall +# :link: # Equivalent to [test][link]['*'] -- i.e. same flags for all test executables +# - --bar +# - --baz class Flaginator - constructor :configurator + constructor :configurator, :loginator, :config_matchinator - def get_flag(hash, file_name) - file_key = file_name.to_sym - - # 1. try literals - literals, magic = partition(hash) { |k, v| k.to_s =~ /^\w+$/ } - return literals[file_key] if literals.include?(file_key) - - any, regex = partition(magic) { |k, v| (k == :'*') || (k == :'.*') } # glob or regex wild card - - # 2. try regexes - find_res = regex.find { |k, v| file_name =~ /^#{k}$/ } - return find_res[1] if find_res - - # 3. try anything - find_res = any.find { |k, v| file_name =~ /.*/ } - return find_res[1] if find_res - - # 4. well, we've tried - return [] + def setup + @section = :flags end - - def flag_down( operation, context, file ) - # create configurator accessor method - accessor = ('flags_' + context.to_s).to_sym - # create simple filename key from whatever filename provided - file_name = File.basename( file ).ext('') - file_key = File.basename( file ).ext('').to_sym - - # if no entry in configuration for flags for this context, bail out - return [] if not @configurator.respond_to?( accessor ) + def flags_defined?(context:, operation:nil) + return @config_matchinator.config_include?(primary:@section, secondary:context, tertiary:operation) + end - # get flags sub hash associated with this context - flags = @configurator.send( accessor ) + def flag_down(context:, operation:, filepath:nil, default:[]) + flags = @config_matchinator.get_config(primary:@section, secondary:context, tertiary:operation) - # if operation not represented in flags hash, bail out - return [] if not flags.include?( operation ) + if flags == nil then return default + # Flatten to handle list-nested YAML aliasing (should have already been flattened during validation) + elsif flags.is_a?(Array) then return flags.flatten + elsif flags.is_a?(Hash) + arg_hash = { + hash: flags, + filepath: filepath, + section: @section, + context: context, + operation: operation + } - # redefine flags to sub hash associated with the operation - flags = flags[operation] + return @config_matchinator.matches?(**arg_hash) + end - return get_flag(flags, file_name) + # Handle unexpected config element type + return [] end end diff --git a/lib/ceedling/generator.rb b/lib/ceedling/generator.rb index 511aa8c81..9b327ef54 100644 --- a/lib/ceedling/generator.rb +++ b/lib/ceedling/generator.rb @@ -1,109 +1,236 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'ceedling/constants' +require 'ceedling/exceptions' require 'ceedling/file_path_utils' +require 'rake' class Generator constructor :configurator, :generator_helper, :preprocessinator, - :cmock_builder, - :generator_test_runner, + :generator_mocks, :generator_test_results, - :flaginator, - :test_includes_extractor, + :generator_test_results_backtrace, + :test_context_extractor, :tool_executor, :file_finder, :file_path_utils, - :streaminator, + :reportinator, + :loginator, :plugin_manager, :file_wrapper, - :unity_utils + :test_runner_manager - def generate_shallow_includes_list(context, file) - @streaminator.stdout_puts("Generating include list for #{File.basename(file)}...", Verbosity::NORMAL) - @preprocessinator.preprocess_shallow_includes(file) + def setup() + # Aliases + @helper = @generator_helper + @backtrace = @generator_test_results_backtrace end - def generate_preprocessed_file(context, file) - @streaminator.stdout_puts("Preprocessing #{File.basename(file)}...", Verbosity::NORMAL) - @preprocessinator.preprocess_file(file) + def generate_mock(context:, mock:, test:, input_filepath:, output_path:) + arg_hash = { + :header_file => input_filepath, + :test => test, + :context => context, + :output_path => output_path } + + @plugin_manager.pre_mock_generate( arg_hash ) + + begin + # Below is a workaround that instantiates CMock anew: + # 1. To allow dfferent output path per mock + # 2. To avoid any thread safety complications + + # TODO: + # - Add option to CMock to generate mock to any destination path + # - Make CMock thread-safe + + # Get default config created by Ceedling and customize it + config = @generator_mocks.build_configuration( output_path ) + + # Generate mock + msg = @reportinator.generate_module_progress( + operation: "Generating mock for", + module_name: test, + filename: File.basename(input_filepath) + ) + @loginator.log( msg ) + + cmock = @generator_mocks.manufacture( config ) + cmock.setup_mocks( arg_hash[:header_file] ) + rescue StandardError => ex + # Re-raise execption but decorate it with CMock to better identify it + raise( ex, "CMock >> #{ex.message}", ex.backtrace ) + ensure + @plugin_manager.post_mock_generate( arg_hash ) + end end - def generate_dependencies_file(tool, context, source, object, dependencies) - @streaminator.stdout_puts("Generating dependencies for #{File.basename(source)}...", Verbosity::NORMAL) + def generate_test_runner(context:, mock_list:, includes_list:, test_filepath:, input_filepath:, runner_filepath:) + arg_hash = { + :context => context, + :test_file => test_filepath, + :input_file => input_filepath, + :runner_file => runner_filepath} - command = - @tool_executor.build_command_line( - tool, - [], # extra per-file command line parameters - source, - dependencies, - object) + @plugin_manager.pre_runner_generate( arg_hash ) - @tool_executor.exec( command[:line], command[:options] ) - end + # Collect info we need + module_name = File.basename( arg_hash[:test_file] ) - def generate_mock(context, mock) - arg_hash = {:header_file => mock.source, :context => context} - @plugin_manager.pre_mock_generate( arg_hash ) + msg = @reportinator.generate_progress("Generating runner for #{module_name}") + @loginator.log( msg ) + + unity_test_runner_generator = + @test_context_extractor.lookup_test_runner_generator( test_filepath ) + if unity_test_runner_generator.nil? + msg = "Could not find test runner generator for #{test_filepath}" + raise CeedlingException.new( msg ) + end + + # Build runner file begin - folder = @file_path_utils.form_folder_for_mock(mock.name) - if folder == '' - folder = nil - end - @cmock_builder.cmock.setup_mocks( arg_hash[:header_file], folder ) - rescue - raise + unity_test_runner_generator.generate( + module_name: module_name, + runner_filepath: runner_filepath, + mock_list: mock_list, + test_file_includes: includes_list, + header_extension: @configurator.extension_header + ) + rescue StandardError => ex + # Re-raise execption but decorate it to better identify it in Ceedling output + raise( ex, "Unity Runner Generator >> #{ex.message}", ex.backtrace ) ensure - @plugin_manager.post_mock_generate( arg_hash ) + @plugin_manager.post_runner_generate(arg_hash) end end - # test_filepath may be either preprocessed test file or original test file - def generate_test_runner(context, test_filepath, runner_filepath) - arg_hash = {:context => context, :test_file => test_filepath, :runner_file => runner_filepath} - @plugin_manager.pre_runner_generate(arg_hash) + def generate_object_file_c( + tool:, + module_name:, + context:, + source:, + object:, + search_paths:[], + flags:[], + defines:[], + list:'', + dependencies:'', + msg:nil + ) + + shell_result = {} + arg_hash = { :tool => tool, + :module_name => module_name, + :operation => OPERATION_COMPILE_SYM, + :context => context, + :source => source, + :object => object, + :search_paths => search_paths, + :flags => flags, + :defines => defines, + :list => list, + :dependencies => dependencies, + :msg => String(msg) + } - # collect info we need - module_name = File.basename(arg_hash[:test_file]) - test_cases = @generator_test_runner.find_test_cases( @file_finder.find_test_from_runner_path(runner_filepath) ) - mock_list = @test_includes_extractor.lookup_raw_mock_list(arg_hash[:test_file]) + @plugin_manager.pre_compile_execute(arg_hash) - @streaminator.stdout_puts("Generating runner for #{module_name}...", Verbosity::NORMAL) + msg = arg_hash[:msg] + msg = @reportinator.generate_module_progress( + operation: "Compiling", + module_name: module_name, + filename: File.basename(arg_hash[:source]) + ) if msg.empty? + @loginator.log( msg ) - test_file_includes = [] # Empty list for now, since apparently unused + command = + @tool_executor.build_command_line( + arg_hash[:tool], + arg_hash[:flags], + arg_hash[:source], + arg_hash[:object], + arg_hash[:list], + arg_hash[:dependencies], + arg_hash[:search_paths], + arg_hash[:defines] + ) - # build runner file begin - @generator_test_runner.generate(module_name, runner_filepath, test_cases, mock_list, test_file_includes) - rescue - raise + shell_result = @tool_executor.exec( command ) + rescue ShellException => ex + shell_result = ex.shell_result + raise ex ensure - @plugin_manager.post_runner_generate(arg_hash) + arg_hash[:shell_command] = command[:line] + arg_hash[:shell_result] = shell_result + @plugin_manager.post_compile_execute(arg_hash) end end - def generate_object_file(tool, operation, context, source, object, list='', dependencies='') + def generate_object_file_asm( + tool:, + module_name:, + context:, + source:, + object:, + search_paths:[], + flags:[], + defines:[], + list:'', + dependencies:'', + msg:nil + ) + shell_result = {} - arg_hash = {:tool => tool, :operation => operation, :context => context, :source => source, :object => object, :list => list, :dependencies => dependencies} + + arg_hash = { :tool => tool, + :module_name => module_name, + :operation => OPERATION_ASSEMBLE_SYM, + :context => context, + :source => source, + :object => object, + :search_paths => search_paths, + :flags => flags, + :defines => defines, + :list => list, + :dependencies => dependencies + } + @plugin_manager.pre_compile_execute(arg_hash) - @streaminator.stdout_puts("Compiling #{File.basename(arg_hash[:source])}...", Verbosity::NORMAL) - command = - @tool_executor.build_command_line( arg_hash[:tool], - @flaginator.flag_down( operation, context, source ), - arg_hash[:source], - arg_hash[:object], - arg_hash[:list], - arg_hash[:dependencies]) + msg = String(msg) + msg = @reportinator.generate_module_progress( + operation: "Assembling", + module_name: module_name, + filename: File.basename(arg_hash[:source]) + ) if msg.empty? + @loginator.log( msg ) - @streaminator.stdout_puts("Command: #{command}", Verbosity::DEBUG) + command = + @tool_executor.build_command_line( + arg_hash[:tool], + arg_hash[:flags], + arg_hash[:source], + arg_hash[:object], + arg_hash[:search_paths], + arg_hash[:defines], + arg_hash[:list], + arg_hash[:dependencies] + ) begin - shell_result = @tool_executor.exec( command[:line], command[:options] ) - rescue ShellExecutionException => ex + shell_result = @tool_executor.exec( command ) + rescue ShellException => ex shell_result = ex.shell_result raise ex ensure @@ -113,11 +240,12 @@ def generate_object_file(tool, operation, context, source, object, list='', depe end end - def generate_executable_file(tool, context, objects, executable, map='', libraries=[], libpaths=[]) + def generate_executable_file(tool, context, objects, flags, executable, map='', libraries=[], libpaths=[]) shell_result = {} arg_hash = { :tool => tool, :context => context, :objects => objects, + :flags => flags, :executable => executable, :map => map, :libraries => libraries, @@ -126,77 +254,108 @@ def generate_executable_file(tool, context, objects, executable, map='', librari @plugin_manager.pre_link_execute(arg_hash) - @streaminator.stdout_puts("Linking #{File.basename(arg_hash[:executable])}...", Verbosity::NORMAL) + msg = @reportinator.generate_progress("Linking #{File.basename(arg_hash[:executable])}") + @loginator.log( msg ) + command = - @tool_executor.build_command_line( arg_hash[:tool], - @flaginator.flag_down( OPERATION_LINK_SYM, context, executable ), - arg_hash[:objects], - arg_hash[:executable], - arg_hash[:map], - arg_hash[:libraries], - arg_hash[:libpaths] - ) - @streaminator.stdout_puts("Command: #{command}", Verbosity::DEBUG) + @tool_executor.build_command_line( + arg_hash[:tool], + arg_hash[:flags], + arg_hash[:objects], + arg_hash[:executable], + arg_hash[:map], + arg_hash[:libraries], + arg_hash[:libpaths] + ) begin - shell_result = @tool_executor.exec( command[:line], command[:options] ) - rescue ShellExecutionException => ex - notice = "\n" + - "NOTICE: If the linker reports missing symbols, the following may be to blame:\n" + - " 1. Test lacks #include statements corresponding to needed source files.\n" + - " 2. Project search paths do not contain source files corresponding to #include statements in the test.\n" - - if (@configurator.project_use_mocks) - notice += " 3. Test does not #include needed mocks.\n\n" - else - notice += "\n" - end - - @streaminator.stderr_puts(notice, Verbosity::COMPLAIN) + shell_result = @tool_executor.exec( command ) + rescue ShellException => ex shell_result = ex.shell_result - raise '' + raise ex ensure + arg_hash[:shell_command] = command[:line] arg_hash[:shell_result] = shell_result @plugin_manager.post_link_execute(arg_hash) end end - def generate_test_results(tool, context, executable, result) - arg_hash = {:tool => tool, :context => context, :executable => executable, :result_file => result} - @plugin_manager.pre_test_fixture_execute(arg_hash) - - @streaminator.stdout_puts("Running #{File.basename(arg_hash[:executable])}...", Verbosity::NORMAL) - - # Unity's exit code is equivalent to the number of failed tests, so we tell @tool_executor not to fail out if there are failures - # so that we can run all tests and collect all results - command = @tool_executor.build_command_line(arg_hash[:tool], [], arg_hash[:executable]) - command[:line] += @unity_utils.collect_test_runner_additional_args - @streaminator.stdout_puts("Command: #{command}", Verbosity::DEBUG) + def generate_test_results(tool:, context:, test_name:, test_filepath:, executable:, result:) + arg_hash = { + :tool => tool, + :context => context, + :test_name => test_name, + :test_filepath => test_filepath, + :executable => executable, + :result_file => result + } + + @plugin_manager.pre_test_fixture_execute( arg_hash ) + + msg = @reportinator.generate_progress( "Running #{File.basename(arg_hash[:executable])}" ) + @loginator.log( msg ) + + # Unity's exit code is equivalent to the number of failed tests. + # We tell @tool_executor not to fail out if there are failures + # so that we can run all tests and collect all results. + command = + @tool_executor.build_command_line( + arg_hash[:tool], + # Apply additional test case filters + @test_runner_manager.collect_cmdline_args(), + arg_hash[:executable] + ) + + # Run the test executable itself + # We allow it to fail without an exception. + # We'll analyze its results apart from tool_executor command[:options][:boom] = false - shell_result = @tool_executor.exec( command[:line], command[:options] ) - - shell_result[:exit_code] = 0 - # Add extra collecting backtrace - # if use_backtrace_gdb_reporter is set to true - if @configurator.project_config_hash[:project_use_backtrace_gdb_reporter] and (shell_result[:output] =~ /\s*Segmentation\sfault.*/) - gdb_file_name = FilePathUtils.os_executable_ext('gdb').freeze - gdb_exec_cmd = "#{gdb_file_name} -q #{command[:line]} --eval-command run --eval-command backtrace --batch" - crash_result = @tool_executor.exec(gdb_exec_cmd, command[:options] ) - shell_result[:output] = crash_result[:output] - else - # Don't Let The Failure Count Make Us Believe Things Aren't Working - @generator_helper.test_results_error_handler(executable, shell_result) + shell_result = @tool_executor.exec( command ) + + # Handle crashes + if @helper.test_crash?( shell_result ) + @helper.log_test_results_crash( test_name, executable, shell_result ) + + filename = File.basename( test_filepath ) + + # Lookup test cases and filter based on any matchers specified for the build task + test_cases = @test_context_extractor.lookup_test_cases( test_filepath ) + test_cases = @generator_test_results.filter_test_cases( test_cases ) + + case @configurator.project_config_hash[:project_use_backtrace] + # If we have the options and tools to learn more, dig into the details + when :gdb + shell_result = + @backtrace.do_gdb( filename, executable, shell_result, test_cases ) + + # Simple test-case-by-test-case exercise + when :simple + shell_result = + @backtrace.do_simple( filename, executable, shell_result, test_cases ) + + else # :none + # Otherwise, call a crash a single failure so it shows up in the report + shell_result = @generator_test_results.create_crash_failure( + filename, + shell_result, + test_cases + ) + end end - processed = @generator_test_results.process_and_write_results( shell_result, - arg_hash[:result_file], - @file_finder.find_test_from_file_path(arg_hash[:executable]) ) + processed = @generator_test_results.process_and_write_results( + executable, + shell_result, + arg_hash[:result_file], + @file_finder.find_test_file_from_filepath( arg_hash[:executable] ) + ) arg_hash[:result_file] = processed[:result_file] arg_hash[:results] = processed[:results] - arg_hash[:shell_result] = shell_result # for raw output display if no plugins for formatted display + # For raw output display if no plugins enabled for nice display + arg_hash[:shell_result] = shell_result - @plugin_manager.post_test_fixture_execute(arg_hash) + @plugin_manager.post_test_fixture_execute( arg_hash ) end end diff --git a/lib/ceedling/generator_helper.rb b/lib/ceedling/generator_helper.rb index 343156092..bbb635f9f 100644 --- a/lib/ceedling/generator_helper.rb +++ b/lib/ceedling/generator_helper.rb @@ -1,39 +1,66 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'ceedling/constants' +require 'ceedling/exceptions' class GeneratorHelper - constructor :streaminator + constructor :loginator + def test_crash?(shell_result) + return true if (shell_result[:output] =~ /\s*Segmentation\sfault.*/i) - def test_results_error_handler(executable, shell_result) - notice = '' - error = false - + # Unix Signal 11 ==> SIGSEGV + # Applies to Unix-like systems including MSYS on Windows + return true if (shell_result[:status].termsig == 11) + + # No test results found in test executable output + return true if (shell_result[:output] =~ TEST_STDOUT_STATISTICS_PATTERN).nil? + + return false + end + + def log_test_results_crash(test_name, executable, shell_result) + runner = File.basename(executable) + + notice = "Test executable `#{runner}` seems to have crashed" + @loginator.log( notice, Verbosity::ERRORS, LogLabels::CRASH ) + + log = false + + # Check for empty output if (shell_result[:output].nil? or shell_result[:output].strip.empty?) - error = true - # mirror style of generic tool_executor failure output - notice = "\n" + - "ERROR: Test executable \"#{File.basename(executable)}\" failed.\n" + - "> Produced no output to $stdout.\n" + # Mirror style of generic tool_executor failure output + notice = "Test executable `#{runner}` failed.\n" + + "> Produced no output\n" + + log = true + + # Check for no test results elsif ((shell_result[:output] =~ TEST_STDOUT_STATISTICS_PATTERN).nil?) - error = true - # mirror style of generic tool_executor failure output - notice = "\n" + - "ERROR: Test executable \"#{File.basename(executable)}\" failed.\n" + - "> Produced no final test result counts in $stdout:\n" + - "#{shell_result[:output].strip}\n" + # Mirror style of generic tool_executor failure output + notice = "Test executable `#{runner}` failed.\n" + + "> Output contains no test result counts\n" + + log = true end - if (error) - # since we told the tool executor to ignore the exit code, handle it explicitly here - notice += "> And exited with status: [#{shell_result[:exit_code]}] (count of failed tests).\n" if (shell_result[:exit_code] != nil) - notice += "> And then likely crashed.\n" if (shell_result[:exit_code] == nil) + if (log) + if (shell_result[:exit_code] != nil) + notice += "> And terminated with exit code: [#{shell_result[:exit_code]}] (failed test case count).\n" + end - notice += "> This is often a symptom of a bad memory access in source or test code.\n\n" + notice += "> Causes can include a bad memory access, stack overflow, heap error, or bad branch in source or test code.\n" - @streaminator.stderr_puts(notice, Verbosity::COMPLAIN) - raise + @loginator.log( '', Verbosity::OBNOXIOUS ) + @loginator.log( notice, Verbosity::OBNOXIOUS, LogLabels::ERROR ) + @loginator.log( '', Verbosity::OBNOXIOUS ) end end diff --git a/lib/ceedling/generator_mocks.rb b/lib/ceedling/generator_mocks.rb new file mode 100644 index 000000000..5b57b83f7 --- /dev/null +++ b/lib/ceedling/generator_mocks.rb @@ -0,0 +1,41 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'cmock' + +class GeneratorMocks + + constructor :configurator + + def manufacture(config) + return CMock.new(config) + end + + def build_configuration( output_path ) + config = @configurator.get_cmock_config + config[:mock_path] = output_path + + # Verbosity management for logging messages + verbosity = @configurator.project_verbosity + + # Default to errors and warnings only so we can customize messages inside Ceedling + config[:verbosity] = 1 + + # Extreme ends of verbosity scale case handling + if (verbosity == Verbosity::SILENT) + # CMock is silent + config[:verbosity] = 0 + + elsif (verbosity == Verbosity::DEBUG) + # CMock max verbosity + config[:verbosity] = 3 + end + + return config + end + +end diff --git a/lib/ceedling/generator_test_results.rb b/lib/ceedling/generator_test_results.rb index 20e551469..8331e4979 100644 --- a/lib/ceedling/generator_test_results.rb +++ b/lib/ceedling/generator_test_results.rb @@ -1,12 +1,100 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'rubygems' require 'rake' # for .ext() require 'ceedling/constants' +require 'ceedling/exceptions' + + +## +## Sample Unity Test Executable Output +## =================================== +## +## - Output is line-oriented. Anything outside the recognized lines is assumed to be from `printf()` +## or equivalent calls and collected for presentation as a collection of $stdout lines. +## - Multiline output (i.e. failure messages) can be achieved by "encoding" newlines as literal +## "\n"s (slash-n). `extract_line_elements()` handles converting newline markers into real newlines. +## - :PASS has no trailing message unless Unity's test case execution duration feature is enabled. +## If enabled, a numeric value with 'ms' as a units signifier trails, ":PASS 1.2 ms". +## - :IGNORE optionally can include a trailing message. +## - :FAIL has a trailing message that relays an assertion failure or crash condition. +## - The statistics line always has the same format with only the count values varying. +## - If there are no failed test cases, the final line is 'OK'. Otherwise, it is 'FAIL'. +## +## $stdout: +## ----------------------------------------------------------------------------------------------------- +## TestUsartModel.c:24:testGetBaudRateRegisterSettingShouldReturnAppropriateBaudRateRegisterSetting:PASS +## TestUsartModel.c:34:testIgnore:IGNORE +## TestUsartModel.c:39:testFail:FAIL: Expected 2 Was 3 +## TestUsartModel.c:49:testGetFormattedTemperatureFormatsTemperatureFromCalculatorAppropriately:PASS +## TestUsartModel.c:55:testShouldReturnErrorMessageUponInvalidTemperatureValue:PASS +## TestUsartModel.c:61:testShouldReturnWakeupMessage:PASS +## +## ----------------------- +## 6 Tests 1 Failures 1 Ignored +## FAIL + +## +## Sample Test Results Output File (YAML) +## ====================================== +## The following corresponds to the test executable output above. +## +## TestUsartModel.fail: +## --- +## :source: +## :file: test/TestUsartModel.c +## :dirname: test +## :basename: TestUsartModel.c +## :successes: +## - :test: testGetBaudRateRegisterSettingShouldReturnAppropriateBaudRateRegisterSetting +## :line: 24 +## :message: '' +## :unity_test_time: 0 +## - :test: testGetFormattedTemperatureFormatsTemperatureFromCalculatorAppropriately +## :line: 49 +## :message: '' +## :unity_test_time: 0 +## - :test: testShouldReturnErrorMessageUponInvalidTemperatureValue +## :line: 55 +## :message: '' +## :unity_test_time: 0 +## - :test: testShouldReturnWakeupMessage +## :line: 61 +## :message: '' +## :unity_test_time: 0 +## :failures: +## - :test: testFail +## :line: 39 +## :message: Expected 2 Was 3 +## :unity_test_time: 0 +## :ignores: +## - :test: testIgnore +## :line: 34 +## :message: '' +## :unity_test_time: 0 +## :counts: +## :total: 6 +## :passed: 4 +## :failed: 1 +## :ignored: 1 +## :stdout: [] +## :time: 0.006512000225484371 class GeneratorTestResults constructor :configurator, :generator_test_results_sanity_checker, :yaml_wrapper - def process_and_write_results(unity_shell_result, results_file, test_file) + def setup() + # Aliases + @sanity_checker = @generator_test_results_sanity_checker + end + + def process_and_write_results(executable, unity_shell_result, results_file, test_file) output_file = results_file results = get_results_structure @@ -16,69 +104,115 @@ def process_and_write_results(unity_shell_result, results_file, test_file) results[:source][:file] = test_file results[:time] = unity_shell_result[:time] unless unity_shell_result[:time].nil? - # process test statistics + # Process test statistics if (unity_shell_result[:output] =~ TEST_STDOUT_STATISTICS_PATTERN) - results[:counts][:total] = $1.to_i - results[:counts][:failed] = $2.to_i + results[:counts][:total] = $1.to_i + results[:counts][:failed] = $2.to_i results[:counts][:ignored] = $3.to_i results[:counts][:passed] = (results[:counts][:total] - results[:counts][:failed] - results[:counts][:ignored]) else - if @configurator.project_config_hash[:project_use_backtrace_gdb_reporter] - # Accessing this code block we expect failure during test execution - # which should be connected with SIGSEGV - results[:counts][:total] = 1 # Set to one as the amount of test is unknown in segfault, and one of the test is failing - results[:counts][:failed] = 1 # Set to one as the one of tests is failing with segfault - results[:counts][:ignored] = 0 - results[:counts][:passed] = 0 - - #Collect function name which cause issue and line number - if unity_shell_result[:output] =~ /\s"(.*)",\sline_num=(\d*)/ - results[:failures] << { :test => $1, :line =>$2, :message => unity_shell_result[:output], :unity_test_time => unity_shell_result[:time]} - else - #In case if regex fail write default values - results[:failures] << { :test => '??', :line =>-1, :message => unity_shell_result[:output], :unity_test_time => unity_shell_result[:time]} - end - end + raise CeedlingException.new( "Could not parse output for `#{executable}`: \"#{unity_shell_result[:output]}\"" ) end - # remove test statistics lines - output_string = unity_shell_result[:output].sub(TEST_STDOUT_STATISTICS_PATTERN, '') + # Remove test statistics lines + output_string = unity_shell_result[:output].sub( TEST_STDOUT_STATISTICS_PATTERN, '' ) + # Process test executable results line-by-line output_string.lines do |line| - # process unity output - case line.chomp! + # Process Unity test executable output + case line.chomp when /(:IGNORE)/ - elements = extract_line_elements(line, results[:source][:file]) - results[:ignores] << elements[0] + elements = extract_line_elements( executable, line, results[:source][:file] ) + results[:ignores] << elements[0] results[:stdout] << elements[1] if (!elements[1].nil?) + when /(:PASS$)/ - elements = extract_line_elements(line, results[:source][:file]) - results[:successes] << elements[0] + elements = extract_line_elements( executable, line, results[:source][:file] ) + results[:successes] << elements[0] results[:stdout] << elements[1] if (!elements[1].nil?) + when /(:PASS \(.* ms\)$)/ - elements = extract_line_elements(line, results[:source][:file]) - results[:successes] << elements[0] + elements = extract_line_elements( executable, line, results[:source][:file] ) + results[:successes] << elements[0] results[:stdout] << elements[1] if (!elements[1].nil?) + when /(:FAIL)/ - elements = extract_line_elements(line, results[:source][:file]) + elements = extract_line_elements( executable, line, results[:source][:file] ) results[:failures] << elements[0] results[:stdout] << elements[1] if (!elements[1].nil?) - else # collect up all other - if !@configurator.project_config_hash[:project_use_backtrace_gdb_reporter] - results[:stdout] << line.chomp - end + + # Collect up all other output + else + results[:stdout] << line.chomp # Ignores blank lines end end - @generator_test_results_sanity_checker.verify(results, unity_shell_result[:exit_code]) + @sanity_checker.verify( results, unity_shell_result[:exit_code] ) - output_file = results_file.ext(@configurator.extension_testfail) if (results[:counts][:failed] > 0) + output_file = results_file.ext( @configurator.extension_testfail ) if (results[:counts][:failed] > 0) @yaml_wrapper.dump(output_file, results) return { :result_file => output_file, :result => results } end + # Filter list of test cases: + # --test_case + # --exclude_test_case + # + # @return Array - list of the test_case hashses {:test, :line_number} + def filter_test_cases(test_cases) + _test_cases = test_cases.clone + + # Filter tests which contain test_case_name passed by `--test_case` argument + if !@configurator.include_test_case.empty? + _test_cases.delete_if { |i| !(i[:test] =~ /#{@configurator.include_test_case}/) } + end + + # Filter tests which contain test_case_name passed by `--exclude_test_case` argument + if !@configurator.exclude_test_case.empty? + _test_cases.delete_if { |i| i[:test] =~ /#{@configurator.exclude_test_case}/ } + end + + return _test_cases + end + + def create_crash_failure(source, shell_result, test_cases) + count = test_cases.size() + + output = [] + test_cases.each do |test_case| + output << "#{source}:#{test_case[:line_number]}:#{test_case[:test]}:FAIL: Test executable crashed" + end + + shell_result[:output] = + regenerate_test_executable_stdout( + total: count, + failed: count, + ignored: 0, + output: output + ) + + shell_result[:exit_code] = count + + return shell_result + end + + # Fill out a template to mimic Unity's test executable output + def regenerate_test_executable_stdout(total:, failed:, ignored:, output:[]) + values = { + :total => total, + :failed => failed, + :ignored => ignored, + :output => output.map {|line| line.strip()}.join("\n"), + :result => (failed > 0) ? 'FAIL' : 'OK' + } + + return UNITY_TEST_RESULTS_TEMPLATE % values + end + + ### Private ### + private def get_results_structure @@ -93,10 +227,10 @@ def get_results_structure } end - def extract_line_elements(line, filename) - # handle anything preceding filename in line as extra output to be collected + def extract_line_elements(executable, line, filename) + # Handle anything preceding filename in line as extra output to be collected stdout = nil - stdout_regex = /(.+)#{Regexp.escape(filename)}.+/i + stdout_regex = /(.+)#{Regexp.escape(filename)}:[0-9]+:(PASS|IGNORE|FAIL).+/i unity_test_time = 0 if (line =~ stdout_regex) @@ -104,17 +238,33 @@ def extract_line_elements(line, filename) line.sub!(/#{Regexp.escape(stdout)}/, '') end - # collect up test results minus and extra output + # Collect up test results minus any extra output elements = (line.strip.split(':'))[1..-1] - # find timestamp if available + # Find timestamp if available if (elements[-1] =~ / \((\d*(?:\.\d*)?) ms\)/) unity_test_time = $1.to_f / 1000 elements[-1].sub!(/ \((\d*(?:\.\d*)?) ms\)/, '') end - return {:test => elements[1], :line => elements[0].to_i, :message => (elements[3..-1].join(':')).strip, :unity_test_time => unity_test_time}, stdout if elements.size >= 3 - return {:test => '???', :line => -1, :message => nil, :unity_test_time => unity_test_time} #fallback safe option. TODO better handling + if elements[3..-1] + message = (elements[3..-1].join(':')).strip + else + message = nil + end + + components = { + :test => elements[1], + :line => elements[0].to_i, + # Decode any multline strings + :message => message.nil? ? nil : message.gsub( NEWLINE_TOKEN, "\n" ), + :unity_test_time => unity_test_time + } + + return components, stdout if elements.size >= 3 + + # Fall through failure case + raise CeedlingException.new( "Could not parse results output line \"line\" for `#{executable}`" ) end end diff --git a/lib/ceedling/generator_test_results_backtrace.rb b/lib/ceedling/generator_test_results_backtrace.rb new file mode 100644 index 000000000..a83b1bc08 --- /dev/null +++ b/lib/ceedling/generator_test_results_backtrace.rb @@ -0,0 +1,215 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +class GeneratorTestResultsBacktrace + + constructor :configurator, :tool_executor, :generator_test_results + + def setup() + @RESULTS_COLLECTOR = Struct.new( :passed, :failed, :ignored, :output, keyword_init:true ) + end + + def do_simple(filename, executable, shell_result, test_cases) + # Clean stats tracker + test_case_results = @RESULTS_COLLECTOR.new( passed:0, failed:0, ignored:0, output:[] ) + + # Reset time + shell_result[:time] = 0 + + # Iterate on test cases + test_cases.each do |test_case| + # Build the test fixture to run with our test case of interest + command = @tool_executor.build_command_line( + @configurator.tools_test_fixture_simple_backtrace, [], + executable, + test_case[:test] + ) + # Things are gonna go boom, so ignore booms to get output + command[:options][:boom] = false + + crash_result = @tool_executor.exec( command ) + + # Sum execution time for each test case + # Note: Running tests serpatately increases total execution time) + shell_result[:time] += crash_result[:time].to_f() + + # Process single test case stats + case crash_result[:output] + # Success test case + when /(^#{filename}.+:PASS\s*$)/ + test_case_results[:passed] += 1 + test_output = $1 # Grab regex match + + # Ignored test case + when /(^#{filename}.+:IGNORE\s*$)/ + test_case_results[:ignored] += 1 + test_output = $1 # Grab regex match + + when /(^#{filename}.+:FAIL(:.+)?\s*$)/ + test_case_results[:failed] += 1 + test_output = $1 # Grab regex match + + else # Crash failure case + test_case_results[:failed] += 1 + test_output = "#{filename}}:#{test_case[:line_number]}:#{test_case[:test]}:FAIL: Test case crashed" + end + + # Collect up real and stand-in test results output + test_case_results[:output] << test_output + end + + # Reset shell result exit code and output + shell_result[:exit_code] = test_case_results[:failed] + shell_result[:output] = + @generator_test_results.regenerate_test_executable_stdout( + total: test_cases.size(), + ignored: test_case_results[:ignored], + failed: test_case_results[:failed], + output: test_case_results[:output] + ) + + return shell_result + end + + def do_gdb(filename, executable, shell_result, test_cases) + gdb_script_filepath = File.join( @configurator.project_build_tests_root, BACKTRACE_GDB_SCRIPT_FILE ) + + # Clean stats tracker + test_case_results = @RESULTS_COLLECTOR.new( passed:0, failed:0, ignored:0, output:[] ) + + # Reset time + shell_result[:time] = 0 + + # Iterate on test cases + test_cases.each do |test_case| + # Build the test fixture to run with our test case of interest + command = @tool_executor.build_command_line( + @configurator.tools_test_backtrace_gdb, [], + gdb_script_filepath, + executable, + test_case[:test] + ) + # Things are gonna go boom, so ignore booms to get output + command[:options][:boom] = false + + crash_result = @tool_executor.exec( command ) + + # Sum execution time for each test case + # Note: Running tests serpatately increases total execution time) + shell_result[:time] += crash_result[:time].to_f() + + test_output = '' + + # Process single test case stats + case crash_result[:output] + # Success test case + when /(^#{filename}.+:PASS\s*$)/ + test_case_results[:passed] += 1 + test_output = $1 # Grab regex match + + # Ignored test case + when /(^#{filename}.+:IGNORE\s*$)/ + test_case_results[:ignored] += 1 + test_output = $1 # Grab regex match + + when /(^#{filename}.+:FAIL(:.+)?\s*$)/ + test_case_results[:failed] += 1 + test_output = $1 # Grab regex match + + else # Crash failure case + test_case_results[:failed] += 1 + + # Collect file_name and line in which crash occurred + matched = crash_result[:output].match( /#{test_case[:test]}\s*\(\)\sat.+#{filename}:(\d+)\n/ ) + + # If we found an error report line containing `test_case() at filename.c:###` in `gdb` output + if matched + # Line number + line_number = matched[1] + + # Filter the `gdb` $stdout report to find most important lines of text + crash_report = filter_gdb_test_report( crash_result[:output], test_case[:test], filename ) + + # Unity’s test executable output is line oriented. + # Multi-line output is not possible (it looks like random `printf()` statements to the results parser) + # "Encode" newlines in multiline string to be handled by the test results parser. + test_output = crash_report.gsub( "\n", NEWLINE_TOKEN ) + + test_output = "#{filename}:#{line_number}:#{test_case[:test]}:FAIL: Test case crashed >> #{test_output}" + + # Otherwise communicate that `gdb` failed to produce a usable report + else + test_output = "#{filename}:#{test_case[:line_number]}:#{test_case[:test]}:FAIL: Test case crashed (no usable `gdb` report)" + end + end + + test_case_results[:output] << test_output + end + + # Reset shell result exit code and output + shell_result[:exit_code] = test_case_results[:failed] + shell_result[:output] = + @generator_test_results.regenerate_test_executable_stdout( + total: test_cases.size(), + ignored: test_case_results[:ignored], + failed: test_case_results[:failed], + output: test_case_results[:output] + ) + + return shell_result + end + + ### Private ### + private + + def filter_gdb_test_report(report, test_case, filename) + # Sample `gdb` backtrace output + # ============================= + # [Thread debugging using libthread_db enabled] + # Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". + # + # [1] > Program received signal SIGSEGV, Segmentation fault. + # 0x0000555999f661fb in testCrash () at test/TestUsartModel.c:40 + # [2] > 40 uint32_t i = *nullptr; + # #0 0x0000555999f661fb in testCrash () at test/TestUsartModel.c:40 + # #1 0x0000555999f674de in run_test (func=0x555999f661e7 , name=0x555999f6b2e0 "testCrash", line_num=37) at build/test/runners/TestUsartModel_runner.c:76 + # #2 0x0000555999f6766b in main (argc=3, argv=0x7fff917e2c98) at build/test/runners/TestUsartModel_runner.c:117 + + lines = report.split( "\n" ) + + # Safe defaults to extract entire report + report_start_index = 0 # [1] + report_end_index = (lines.size()-1) # [2] + + # Find line preceding last ` () at `, [2]; + # it is the offending line of code. + # We don't need the rest of the call trace -- it's just from the runner + # up to the crashed test case. + lines.each_with_index do |line, index| + if line =~ /#{test_case}.+at.+#{filename}/ + report_end_index = (index - 1) unless (index == 0) + end + end + + # Work up from [2] to find top line of the containing text block, [1]. + # Go until we find a blank line; then increment index back down a line. + # This lops off any unneeded preamble. + report_end_index.downto(0).to_a().each do |index| + if lines[index].empty? + # Look for a blank line and adjust index to next line of text + report_start_index = (index + 1) + break + end + end + + length = (report_end_index - report_start_index) + 1 + + # Reconstitute the report from the extracted lines + return lines[report_start_index, length].join( "\n" ) + end + +end diff --git a/lib/ceedling/generator_test_results_sanity_checker.rb b/lib/ceedling/generator_test_results_sanity_checker.rb index 0b518325b..f40cec48b 100644 --- a/lib/ceedling/generator_test_results_sanity_checker.rb +++ b/lib/ceedling/generator_test_results_sanity_checker.rb @@ -1,17 +1,25 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'rubygems' require 'rake' # for ext() method require 'ceedling/constants' +require 'ceedling/exceptions' class GeneratorTestResultsSanityChecker - constructor :configurator, :streaminator + constructor :configurator, :loginator def verify(results, unity_exit_code) # do no sanity checking if it's disabled return if (@configurator.sanity_checks == TestResultsSanityChecks::NONE) - raise "results nil or empty" if results.nil? || results.empty? + raise CeedlingException.new( "Test results nil or empty" ) if results.nil? || results.empty? ceedling_ignores_count = results[:ignores].size ceedling_failures_count = results[:failures].size @@ -48,17 +56,14 @@ def verify(results, unity_exit_code) def sanity_check_warning(file, message) unless defined?(CEEDLING_IGNORE_SANITY_CHECK) - notice = "\n" + - "ERROR: Internal sanity check for test fixture '#{file.ext(@configurator.extension_executable)}' finds that #{message}\n" + + notice = "Internal sanity check for test fixture '#{file.ext(@configurator.extension_executable)}' finds that #{message}\n" + " Possible causes:\n" + " 1. Your test + source dereferenced a null pointer.\n" + " 2. Your test + source indexed past the end of a buffer.\n" + " 3. Your test + source committed a memory access violation.\n" + " 4. Your test fixture produced an exit code of 0 despite execution ending prematurely.\n" + - " Sanity check failures of test results are usually a symptom of interrupted test execution.\n\n" - - @streaminator.stderr_puts( notice ) - raise + " Sanity check failures of test results are usually a symptom of interrupted test execution.\n\n" + raise CeedlingException.new( notice ) end end diff --git a/lib/ceedling/generator_test_runner.rb b/lib/ceedling/generator_test_runner.rb index 0dcacb2ff..ef444b741 100644 --- a/lib/ceedling/generator_test_runner.rb +++ b/lib/ceedling/generator_test_runner.rb @@ -1,60 +1,84 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'generate_test_runner' # Unity's test runner generator class GeneratorTestRunner - constructor :configurator, :file_path_utils, :file_wrapper + attr_accessor :test_cases + + # + # This class is not within any DIY context. + # It is instantiated on demand for each test file processed in a build. + # + + def initialize(config:, test_file_contents:, preprocessed_file_contents:nil) + @unity_runner_generator = UnityTestRunnerGenerator.new( config ) + + # Reduced information set + @test_cases = [] - def find_test_cases(test_file) + # Full information set used for runner generation + @test_cases_internal = [] + + parse_test_file( test_file_contents, preprocessed_file_contents ) + end - #Pull in Unity's Test Runner Generator - require 'generate_test_runner.rb' - @test_runner_generator ||= UnityTestRunnerGenerator.new( @configurator.get_runner_config ) + def generate(module_name:, runner_filepath:, mock_list:, test_file_includes:, header_extension:) + # Actually build the test runner using Unity's test runner generator. + @unity_runner_generator.generate( + module_name, + runner_filepath, + @test_cases_internal, + mock_list.map{ |mock| mock + header_extension }, + test_file_includes.map{|f| File.basename(f,'.*') + header_extension} + ) + end - if (@configurator.project_use_test_preprocessor) + ### Private ### - #redirect to use the preprocessor file if we're doing that sort of thing - pre_test_file = @file_path_utils.form_preprocessed_file_filepath(test_file) + private - #actually look for the tests using Unity's test runner generator - contents = @file_wrapper.read(pre_test_file) - tests_and_line_numbers = @test_runner_generator.find_tests(contents) - @test_runner_generator.find_setup_and_teardown(contents) + def parse_test_file(test_file_contents, preprocessed_file_contents) + # If there's a preprocessed file, align test case line numbers with original file contents + if (!preprocessed_file_contents.nil?) + # Save the test case structure to be used in generation + @test_cases_internal = @unity_runner_generator.find_tests( preprocessed_file_contents ) + + # Configure the runner generator around `setUp()` and `tearDown()` + @unity_runner_generator.find_setup_and_teardown( preprocessed_file_contents ) - #look up the line numbers in the original file - source_lines = @file_wrapper.read(test_file).split("\n") + # Modify line numbers to match the original, non-preprocessed file + source_lines = test_file_contents.split("\n") source_index = 0; - tests_and_line_numbers.size.times do |i| + @test_cases_internal.size.times do |i| source_lines[source_index..-1].each_with_index do |line, index| - if (line =~ /#{tests_and_line_numbers[i][:test]}/) + if (line =~ /#{@test_cases_internal[i][:test]}/) source_index += index - tests_and_line_numbers[i][:line_number] = source_index + 1 + @test_cases_internal[i][:line_number] = source_index + 1 break end end end + + # Just look for the test cases within the original test file else - #Just look for the tests using Unity's test runner generator - contents = @file_wrapper.read(test_file) - tests_and_line_numbers = @test_runner_generator.find_tests(contents) - @test_runner_generator.find_setup_and_teardown(contents) + # Save the test case structure to be used in generation + @test_cases_internal = @unity_runner_generator.find_tests( test_file_contents ) + + # Configure the runner generator around `setUp()` and `tearDown()` + @unity_runner_generator.find_setup_and_teardown( test_file_contents ) end - return tests_and_line_numbers - end + # Unity's `find_tests()` produces an array of hashes with the following keys... + # { test:, args:, call:, params:, line_number: } - def generate(module_name, runner_filepath, test_cases, mock_list, test_file_includes=[]) - require 'generate_test_runner.rb' - - header_extension = @configurator.extension_header - - #actually build the test runner using Unity's test runner generator - #(there is no need to use preprocessor here because we've already looked up test cases and are passing them in here) - @test_runner_generator ||= UnityTestRunnerGenerator.new( @configurator.get_runner_config ) - @test_runner_generator.generate( module_name, - runner_filepath, - test_cases, - mock_list.map{ - |f| @file_path_utils.form_folder_for_mock(f) + File.basename(f,'.*') + header_extension - }, - test_file_includes.map{|f| File.basename(f,'.*')+header_extension}) + # For external use of test case names and line numbers, keep only those pieces of info + @test_cases = @test_cases_internal.map {|hash| hash.slice( :test, :line_number )} end + end diff --git a/lib/ceedling/include_pathinator.rb b/lib/ceedling/include_pathinator.rb new file mode 100644 index 000000000..9390b8e86 --- /dev/null +++ b/lib/ceedling/include_pathinator.rb @@ -0,0 +1,83 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'pathname' +require 'ceedling/exceptions' + +class IncludePathinator + + constructor :configurator, :test_context_extractor, :loginator, :file_wrapper + + def setup + # TODO: When Ceedling's base project path handling is resolved, update this value to automatically + # modify TEST_INCLUDE_PATH() locations relative to the working directory or project file location + # @base_path = '.' + + # Alias for brevity + @extractor = @test_context_extractor + end + + def validate_test_build_directive_paths + @extractor.inspect_include_paths do |test_filepath, include_paths| + include_paths.each do |path| + + # TODO: When Ceedling's base project path handling is resolved, enable this path redefinition + # path = File.join( @base_path, path ) + unless @file_wrapper.exist?(path) + error = "'#{path}' specified by #{UNITY_TEST_INCLUDE_PATH}() within #{test_filepath} not found" + raise CeedlingException.new( error ) + end + end + end + end + + + def validate_header_files_collection + # Get existing, possibly minimal header file collection + headers = @configurator.collection_all_headers + + # Get all paths specified by TEST_INCLUDE_PATH() directive in test files + directive_paths = @extractor.lookup_all_include_paths + + # Add to collection of headers (Rake FileList) with directive paths and shallow wildcard matching on header file extension + headers += @file_wrapper.instantiate_file_list( directive_paths.map { |path| File.join(path, '*' + EXTENSION_HEADER) } ) + headers.resolve() + + headers.uniq! + + if headers.length == 0 + error = "No header files found in project.\n" + + "Add search paths to :paths ↳ :include in your project file and/or use #{UNITY_TEST_INCLUDE_PATH}() in your test files.\n" + + "Verify header files with `ceedling paths:include` and\\or `ceedling files:include`." + @loginator.log( error, Verbosity::COMPLAIN ) + end + + return headers + end + + def augment_environment_header_files(headers) + @configurator.redefine_element(:collection_all_headers, headers) + end + + def lookup_test_directive_include_paths(filepath) + # TODO: When Ceedling's base project path handling is resolved, enable this path redefinition + # return @extractor.lookup_include_paths_list(filepath).map { |path| File.join( @base_path, path) } + return @extractor.lookup_include_paths_list(filepath) + end + + # Gather together [:paths][:test] that actually contain .h files + def collect_test_include_paths + paths = [] + @configurator.collection_paths_test.each do |path| + headers = @file_wrapper.directory_listing( File.join( path, '*' + @configurator.extension_header ) ) + paths << path if headers.length > 0 + end + + return paths + end + +end diff --git a/lib/ceedling/loginator.rb b/lib/ceedling/loginator.rb index 92276e1df..d949c7c72 100644 --- a/lib/ceedling/loginator.rb +++ b/lib/ceedling/loginator.rb @@ -1,31 +1,326 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'ceedling/constants' + +# Loginator handles console and file output of logging statements class Loginator - constructor :configurator, :project_file_loader, :project_config_manager, :file_wrapper, :system_wrapper + attr_reader :project_logging + attr_writer :decorators + constructor :verbosinator, :file_wrapper, :system_wrapper - def setup_log_filepath - config_files = [] - config_files << @project_file_loader.main_file - config_files << @project_file_loader.user_file - config_files.concat( @project_config_manager.options_files ) - config_files.compact! - config_files.map! { |file| file.ext('') } - - log_name = config_files.join( '_' ) + def setup() + $loginator = self + @decorators = false - @project_log_filepath = File.join( @configurator.project_log_path, log_name.ext('.log') ) - end + # Friendly robustification for certain testing scenarios + unless @system_wrapper.nil? + # Automatic setting of console printing decorations based on platform + @decorators = !@system_wrapper.windows?() + # Environment variable takes precedence over automatic setting + _env = @system_wrapper.env_get('CEEDLING_DECORATORS') if !@system_wrapper.nil? + if !_env.nil? + if (_env == '1' or _env.strip().downcase() == 'true') + @decorators = true + elsif (_env == '0' or _env.strip().downcase() == 'false') + @decorators = false + end + end + end - def log(string, heading=nil) - return if (not @configurator.project_logging) + @replace = { + # Problematic characters pattern => Simple characters + /↳/ => '>>', # Config sub-entry notation + /‱/ => '*', # Bulleted lists + } + + @project_logging = false + @log_filepath = nil - output = "\n[#{@system_wrapper.time_now}]" - output += " :: #{heading}" if (not heading.nil?) - output += "\n#{string.strip}\n" + @queue = Queue.new + @worker = Thread.new do + # Run tasks until there are no more enqueued + @done = false + while !@done do + Thread.handle_interrupt(Exception => :never) do + begin + Thread.handle_interrupt(Exception => :immediate) do + # pop(false) is blocking and should just hang here and wait for next message + item = @queue.pop(false) + if (item.nil?) + @done = true + next + end - @file_wrapper.write(@project_log_filepath, output, 'a') + # pick out the details + message = item[:message] + label = item[:label] + verbosity = item[:verbosity] + stream = item[:stream] + + # Write to log as though Verbosity::DEBUG (no filtering at all) but without fun characters + if @project_logging + file_msg = message.dup() # Copy for safe inline modifications + + # Add labels + file_msg = format( file_msg, verbosity, label, false ) + + # Note: In practice, file-based logging only works with trailing newlines (i.e. `log()` calls) + # `out()` calls will be a little ugly in the log file, but these are typically only + # used for console logging anyhow. + logfile( sanitize( file_msg, false ), extract_stream_name( stream ) ) + end + + # Only output to console when message reaches current verbosity level + if !stream.nil? && (@verbosinator.should_output?( verbosity )) + # Add labels and fun characters + console_msg = format( message, verbosity, label, @decorators ) + + # Write to output stream after optionally removing any problematic characters + stream.print( sanitize( console_msg, @decorators ) ) + end + end + rescue ThreadError + @done = true + rescue Exception => e + puts e.inspect + end + end + end + end end - + + def wrapup + begin + @queue.close + @worker.join + rescue + #If we failed at this point, just give up on it + end + end + + def set_logfile( log_filepath ) + if !log_filepath.empty? + @project_logging = true + @log_filepath = log_filepath + end + end + + + # log() + # ----- + # + # Write the given string to an optional log file and to the console + # - Logging statements to a file are always at the highest verbosity + # - Console logging is controlled by the verbosity level + # + # For default label of LogLabels::AUTO + # - If verbosity ERRORS, add ERROR: heading + # - If verbosity COMPLAIN, added WARNING: heading + # - All other verbosity levels default to no heading + # + # By setting a label: + # - A heading begins a message regardless of verbosity level, except NONE + # - NONE forcibly presents headings and emoji decorators + # + # If decoration is enabled: + # - Add fun emojis before all headings, except TITLE + # - TITLE log level adds seedling emoji alone + # + # If decoration is disabled: + # - No emojis are added to label + # - Any problematic console characters in a message are replaced with + # simpler variants + + def log(message="\n", verbosity=Verbosity::NORMAL, label=LogLabels::AUTO, stream=nil) + # Choose appropriate console stream + stream = get_stream( verbosity, stream ) + + # Flatten if needed + message = message.flatten.join("\n") if (message.class == Array) + + # Message contatenated with "\n" (unless it aready ends with a newline) + message += "\n" unless message.end_with?( "\n" ) + + # Add item to the queue + item = { + :message => message, + :verbosity => verbosity, + :label => label, + :stream => stream + } + @queue << item + end + + + def log_debug_backtrace(exception) + log( "\nDebug Backtrace ==>", Verbosity::DEBUG ) + + # Send backtrace to debug logging, formatted almost identically to how Ruby does it. + # Don't log the exception message itself in the first `log()` call as it will already be logged elsewhere + log( "#{exception.backtrace.first}: (#{exception.class})", Verbosity::DEBUG ) + log( exception.backtrace.drop(1).map{|s| "\t#{s}"}.join("\n"), Verbosity::DEBUG ) + end + + + def decorate(str, label=LogLabels::NONE) + return str if !@decorators + + prepend = '' + + case label + when LogLabels::NOTICE + prepend = 'â„č ' + when LogLabels::WARNING + prepend = '⚠ ' + when LogLabels::ERROR + prepend = 'đŸȘČ ' + when LogLabels::EXCEPTION + prepend = '🧹 ' + when LogLabels::CONSTRUCT + prepend = '🚧 ' + when LogLabels::CRASH + prepend = '☠ ' + when LogLabels::RUN + prepend = '👟 ' + when LogLabels::PASS + prepend = '✅ ' + when LogLabels::FAIL + prepend = '❌ ' + when LogLabels::TITLE + prepend = 'đŸŒ± ' + end + + return prepend + str + end + + + def sanitize(string, decorate=nil) + # Redefine `decorate` to @decorators value by default, + # otherwise use the argument as an override value + decorate = @decorators if decorate.nil? + + # Remove problematic console characters in-place if decoration disabled + @replace.each_pair {|k,v| string.gsub!( k, v) } if (decorate == false) + return string + end + + ### Private ### + + private + + def get_stream(verbosity, stream) + # If no stream has been specified, choose one based on the verbosity level of the prompt + if stream.nil? + if verbosity <= Verbosity::ERRORS + return $stderr + else + return $stdout + end + end + + return stream + end + + + def format(string, verbosity, label, decorate) + prepend = '' + + # Force no automatic label / decorator + return string if label == LogLabels::NONE + + # Add decorators if enabled + if decorate + case label + when LogLabels::AUTO + if verbosity == Verbosity::ERRORS + prepend = 'đŸȘČ ' + elsif verbosity == Verbosity::COMPLAIN + prepend = '⚠ ' + end + # Otherwise, no decorators for verbosity levels + else + # If not auto, go get us a decorator + prepend = decorate('', label) + end + end + + # Add headings + case label + when LogLabels::AUTO + if verbosity == Verbosity::ERRORS + prepend += 'ERROR: ' + elsif verbosity == Verbosity::COMPLAIN + prepend += 'WARNING: ' + end + # Otherwise, no headings + when LogLabels::NOTICE + prepend += 'NOTICE: ' + when LogLabels::WARNING + prepend += 'WARNING: ' + when LogLabels::ERROR + prepend += 'ERROR: ' + when LogLabels::EXCEPTION + prepend += 'EXCEPTION: ' + when LogLabels::CRASH + prepend += 'ERROR: ' + # Otherwise no headings for decorator-only messages + end + + return prepend + string + end + + + def extract_stream_name(stream) + name = case (stream.fileno) + when 0 then '' + when 1 then '' + when 2 then '' + else stream.inspect + end + + return name + end + + + def logfile(string, stream='') + # Ex: '# May 1 22:20:41 2024 | ' + header = "#{stream} #{@system_wrapper.time_now('%b %e %H:%M:%S %Y')} | " + + # Split any multiline strings so we can align them with the header + lines = string.strip.split("\n") + + # Build output string with the first (can be only) line + output = "#{header}#{lines[0]}\n" + + # If additional lines in a multiline string, pad them on the left + # to align with the header + if lines.length > 1 + lines[1..-1].each {|line| output += ((' ' * header.length) + line + "\n")} + end + + # Example output: + # + # May 1 22:20:40 2024 | Determining Artifacts to Be Built... + # May 1 22:20:40 2024 | Building Objects + # ---------------- + # May 1 22:20:40 2024 | Compiling TestUsartModel.c... + # May 1 22:20:40 2024 | Compiling TestUsartModel::unity.c... + # May 1 22:20:40 2024 | Compiling TestUsartModel::cmock.c... + + @file_wrapper.write( @log_filepath, output, 'a' ) + end + end + +END { + $loginator.wrapup unless $loginator.nil? +} \ No newline at end of file diff --git a/lib/ceedling/makefile.rb b/lib/ceedling/makefile.rb index c3d7496d2..e63345000 100644 --- a/lib/ceedling/makefile.rb +++ b/lib/ceedling/makefile.rb @@ -1,3 +1,9 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= # modified version of Rake's provided make-style dependency loader # customizations: diff --git a/lib/ceedling/objects.yml b/lib/ceedling/objects.yml index 86acf56f5..1e6ec7e6a 100644 --- a/lib/ceedling/objects.yml +++ b/lib/ceedling/objects.yml @@ -1,17 +1,25 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +application: + compose: + - system_wrapper file_wrapper: file_system_wrapper: -stream_wrapper: - rake_wrapper: yaml_wrapper: system_wrapper: -cmock_builder: +config_walkinator: reportinator: @@ -28,26 +36,11 @@ file_path_utils: - configurator - file_wrapper -file_system_utils: - compose: file_wrapper - -project_file_loader: +file_path_collection_utils: compose: - - yaml_wrapper - - stream_wrapper - - system_wrapper - file_wrapper -unity_utils: - compose: - - configurator - -project_config_manager: - compose: - - cacheinator - - configurator - - yaml_wrapper - - file_wrapper +test_runner_manager: cacheinator: compose: @@ -65,75 +58,77 @@ tool_executor: compose: - configurator - tool_executor_helper - - streaminator + - loginator + - verbosinator - system_wrapper tool_executor_helper: compose: - - streaminator + - loginator - system_utils - system_wrapper + - verbosinator + +tool_validator: + compose: + - file_wrapper + - loginator + - system_wrapper + - reportinator configurator: compose: - configurator_setup - configurator_plugins - configurator_builder - - cmock_builder + - config_walkinator - yaml_wrapper - system_wrapper + - loginator + - reportinator configurator_setup: compose: - configurator_builder - configurator_validator - configurator_plugins - - stream_wrapper + - loginator + - reportinator + - file_wrapper configurator_plugins: compose: - - stream_wrapper - file_wrapper - system_wrapper configurator_validator: compose: + - config_walkinator - file_wrapper - - stream_wrapper + - loginator - system_wrapper + - reportinator + - tool_validator configurator_builder: compose: - - file_system_utils + - file_path_collection_utils - file_wrapper - system_wrapper loginator: compose: - - configurator - - project_file_loader - - project_config_manager + - verbosinator - file_wrapper - system_wrapper -streaminator: - compose: - - streaminator_helper - - verbosinator - - loginator - - stream_wrapper - -streaminator_helper: - setupinator: -plugin_builder: - plugin_manager: compose: - configurator - plugin_manager_helper - - streaminator + - loginator - reportinator - system_wrapper @@ -148,12 +143,12 @@ plugin_reportinator: plugin_reportinator_helper: compose: - configurator - - streaminator + - loginator - yaml_wrapper - file_wrapper verbosinator: - compose: configurator + # compose: configurator file_finder: compose: @@ -165,46 +160,67 @@ file_finder: - yaml_wrapper file_finder_helper: - compose: streaminator + compose: loginator -test_includes_extractor: +test_context_extractor: compose: - configurator - - yaml_wrapper + - file_wrapper + - loginator + +include_pathinator: + compose: + - configurator + - test_context_extractor + - loginator - file_wrapper task_invoker: compose: - dependinator + - build_batchinator - rake_utils - rake_wrapper - - project_config_manager + +config_matchinator: + compose: + - configurator + - loginator + - reportinator flaginator: compose: - configurator + - loginator + - config_matchinator + +defineinator: + compose: + - configurator + - loginator + - config_matchinator generator: compose: - configurator - generator_helper - preprocessinator - - cmock_builder - - generator_test_runner + - generator_mocks - generator_test_results - - flaginator - - test_includes_extractor + - test_context_extractor - tool_executor - file_finder - file_path_utils - - streaminator + - reportinator + - loginator - plugin_manager - file_wrapper - - unity_utils + - test_runner_manager + - generator_test_results_backtrace generator_helper: compose: - - streaminator + - loginator generator_test_results: compose: @@ -212,54 +228,53 @@ generator_test_results: - generator_test_results_sanity_checker - yaml_wrapper +generator_test_results_backtrace: + compose: + - configurator + - tool_executor + - generator_test_results + generator_test_results_sanity_checker: compose: - configurator - - streaminator + - loginator -generator_test_runner: +generator_mocks: compose: - configurator - - file_path_utils - - file_wrapper dependinator: compose: - configurator - - project_config_manager - - test_includes_extractor + - test_context_extractor - file_path_utils - rake_wrapper - file_wrapper preprocessinator: compose: - - preprocessinator_helper - preprocessinator_includes_handler - preprocessinator_file_handler - task_invoker + - file_finder - file_path_utils + - file_wrapper - yaml_wrapper - - project_config_manager - - configurator - -preprocessinator_helper: - compose: + - plugin_manager - configurator - - test_includes_extractor - - task_invoker - - file_finder - - file_path_utils + - test_context_extractor + - loginator + - reportinator + - rake_wrapper preprocessinator_includes_handler: compose: - configurator - tool_executor - - task_invoker - - file_path_utils + - test_context_extractor - yaml_wrapper - - file_wrapper - - file_finder + - loginator + - reportinator preprocessinator_file_handler: compose: @@ -268,37 +283,55 @@ preprocessinator_file_handler: - tool_executor - file_path_utils - file_wrapper + - loginator preprocessinator_extractor: +build_batchinator: + compose: + - configurator + - loginator + - reportinator + test_invoker: compose: + - application - configurator - test_invoker_helper - plugin_manager - - streaminator + - build_batchinator + - reportinator + - loginator - preprocessinator - task_invoker - - dependinator - - project_config_manager - - build_invoker_utils + - generator + - test_context_extractor - file_path_utils + - file_finder - file_wrapper + - verbosinator test_invoker_helper: compose: - configurator + - loginator + - build_batchinator - task_invoker - - test_includes_extractor + - test_context_extractor + - include_pathinator + - preprocessinator + - defineinator + - flaginator - file_finder - file_path_utils - file_wrapper + - generator + - test_runner_manager release_invoker: compose: - configurator - release_invoker_helper - - build_invoker_utils - dependinator - task_invoker - file_path_utils @@ -310,9 +343,4 @@ release_invoker_helper: - dependinator - task_invoker -build_invoker_utils: - compose: - - configurator - - streaminator - erb_wrapper: diff --git a/lib/ceedling/par_map.rb b/lib/ceedling/par_map.rb deleted file mode 100644 index 98198a2ce..000000000 --- a/lib/ceedling/par_map.rb +++ /dev/null @@ -1,19 +0,0 @@ - - -def par_map(n, things, &block) - queue = Queue.new - things.each { |thing| queue << thing } - threads = (1..n).collect do - Thread.new do - begin - while true - yield queue.pop(true) - end - rescue ThreadError - - end - end - end - threads.each { |t| t.join } -end - diff --git a/lib/ceedling/plugin.rb b/lib/ceedling/plugin.rb index f20b3a3b2..b41a4692d 100644 --- a/lib/ceedling/plugin.rb +++ b/lib/ceedling/plugin.rb @@ -1,79 +1,29 @@ - -class String - # reformat a multiline string to have given number of whitespace columns; - # helpful for formatting heredocs - def left_margin(margin=0) - non_whitespace_column = 0 - new_lines = [] - - # find first line with non-whitespace and count left columns of whitespace - self.each_line do |line| - if (line =~ /^\s*\S/) - non_whitespace_column = $&.length - 1 - break - end - end - - # iterate through each line, chopping off leftmost whitespace columns and add back the desired whitespace margin - self.each_line do |line| - columns = [] - margin.times{columns << ' '} - # handle special case of line being narrower than width to be lopped off - if (non_whitespace_column < line.length) - new_lines << "#{columns.join}#{line[non_whitespace_column..-1]}" - else - new_lines << "\n" - end - end - - return new_lines.join - end -end +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= class Plugin attr_reader :name, :environment attr_accessor :plugin_objects - def initialize(system_objects, name) + def initialize(system_objects, name, root_path) @environment = [] @ceedling = system_objects + @plugin_root_path = root_path @name = name self.setup end - def setup; end - - # mock generation - def pre_mock_generate(arg_hash); end - def post_mock_generate(arg_hash); end - - # test runner generation - def pre_runner_generate(arg_hash); end - def post_runner_generate(arg_hash); end - - # compilation (test or source) - def pre_compile_execute(arg_hash); end - def post_compile_execute(arg_hash); end - - # linking (test or source) - def pre_link_execute(arg_hash); end - def post_link_execute(arg_hash); end - - # test fixture execution - def pre_test_fixture_execute(arg_hash); end - def post_test_fixture_execute(arg_hash); end - - # test task - def pre_test(test); end - def post_test(test); end - - # release task - def pre_release; end - def post_release; end + # Override to prevent exception handling from walking & stringifying the object variables. + # Plugin's object variables are gigantic and produce a flood of output. + def inspect + return this.class.name + end - # whole shebang (any use of Ceedling) - def pre_build; end - def post_build; end + def setup; end def summary; end diff --git a/lib/ceedling/plugin_builder.rb b/lib/ceedling/plugin_builder.rb deleted file mode 100644 index 8d5530467..000000000 --- a/lib/ceedling/plugin_builder.rb +++ /dev/null @@ -1,55 +0,0 @@ -require 'ceedling/plugin' -require 'ceedling/yaml_wrapper' - -class PluginBuilder - - attr_accessor :plugin_objects - - def construct_plugin(plugin_name, object_map_yaml, system_objects) - # @streaminator.stdout_puts("Constructing plugin #{plugin_name}...", Verbosity::OBNOXIOUS) - object_map = {} - @plugin_objects = {} - @system_objects = system_objects - - if object_map_yaml - ym = YamlMapper.new - @object_map = ym.load_string(object_map_yaml) - @object_map.each_key do |obj| - construct_object(obj) - end - else - raise "Invalid object map for plugin #{plugin_name}!" - end - - return @plugin_objects - end - - private - - def camelize(underscored_name) - return underscored_name.gsub(/(_|^)([a-z0-9])/) {$2.upcase} - end - - def construct_object(obj) - if @plugin_objects[obj].nil? - if @object_map[obj] && @object_map[obj]['compose'] - @object_map[obj]['compose'].each do |dep| - construct_object(dep) - end - end - build_object(obj) - end - end - - def build_object(new_object) - if @plugin_objects[new_object.to_sym].nil? - # @streaminator.stdout_puts("Building plugin object #{new_object}", Verbosity::OBNOXIOUS) - require new_object - class_name = camelize(new_object) - new_instance = eval("#{class_name}.new(@system_objects, class_name.to_s)") - new_instance.plugin_objects = @plugin_objects - @plugin_objects[new_object.to_sym] = new_instance - end - end - -end \ No newline at end of file diff --git a/lib/ceedling/plugin_manager.rb b/lib/ceedling/plugin_manager.rb index 0468f2fc4..ca6c50857 100644 --- a/lib/ceedling/plugin_manager.rb +++ b/lib/ceedling/plugin_manager.rb @@ -1,31 +1,47 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'ceedling/constants' class PluginManager - constructor :configurator, :plugin_manager_helper, :streaminator, :reportinator, :system_wrapper + constructor :configurator, :plugin_manager_helper, :loginator, :reportinator, :system_wrapper def setup @build_fail_registry = [] - @plugin_objects = [] # so we can preserve order + @plugin_objects = [] # List so we can preserve order end - def load_plugin_scripts(script_plugins, system_objects) + def load_programmatic_plugins(plugins, system_objects) environment = [] - script_plugins.each do |plugin| - # protect against instantiating object multiple times due to processing config multiple times (option files, etc) - next if (@plugin_manager_helper.include?(@plugin_objects, plugin)) + plugins.each do |hash| + # Protect against instantiating object multiple times due to processing config multiple times (option files, etc) + next if (@plugin_manager_helper.include?( @plugin_objects, hash[:plugin] ) ) + + msg = @reportinator.generate_progress( "Instantiating plugin class #{camelize( hash[:plugin] )}" ) + @loginator.log( msg, Verbosity::OBNOXIOUS ) + begin - @system_wrapper.require_file( "#{plugin}.rb" ) - object = @plugin_manager_helper.instantiate_plugin_script( camelize(plugin), system_objects, plugin ) + @system_wrapper.require_file( "#{hash[:plugin]}.rb" ) + object = @plugin_manager_helper.instantiate_plugin( + camelize( hash[:plugin] ), + system_objects, + hash[:plugin], + hash[:root_path] + ) @plugin_objects << object environment += object.environment - # add plugins to hash of all system objects - system_objects[plugin.downcase.to_sym] = object - rescue - puts "Exception raised while trying to load plugin: #{plugin}" - raise + # Add plugins to hash of all system objects + system_objects[hash[:plugin].downcase().to_sym()] = object + rescue + @loginator.log( "Exception raised while trying to load plugin: #{hash[:plugin]}", Verbosity::ERRORS, LogLabels::EXCEPTION ) + raise # Raise again for backtrace, etc. end end @@ -46,7 +62,7 @@ def print_plugin_failures report += "\n" - @streaminator.stderr_puts(report, Verbosity::ERRORS) + @loginator.log( report, Verbosity::ERRORS, LogLabels::NONE ) end end @@ -56,6 +72,12 @@ def register_build_failure(message) #### execute all plugin methods #### + def pre_mock_preprocess(arg_hash); execute_plugins(:pre_mock_preprocess, arg_hash); end + def post_mock_preprocess(arg_hash); execute_plugins(:post_mock_preprocess, arg_hash); end + + def pre_test_preprocess(arg_hash); execute_plugins(:pre_test_preprocess, arg_hash); end + def post_test_preprocess(arg_hash); execute_plugins(:post_test_preprocess, arg_hash); end + def pre_mock_generate(arg_hash); execute_plugins(:pre_mock_generate, arg_hash); end def post_mock_generate(arg_hash); execute_plugins(:post_mock_generate, arg_hash); end @@ -70,8 +92,8 @@ def post_link_execute(arg_hash); execute_plugins(:post_link_execute, arg_hash); def pre_test_fixture_execute(arg_hash); execute_plugins(:pre_test_fixture_execute, arg_hash); end def post_test_fixture_execute(arg_hash) - # special arbitration: raw test results are printed or taken over by plugins handling the job - @streaminator.stdout_puts(arg_hash[:shell_result][:output]) if (@configurator.plugins_display_raw_test_results) + # Special arbitration: Raw test results are printed or taken over by plugins handling the job + @loginator.log( arg_hash[:shell_result][:output] ) if @configurator.plugins_display_raw_test_results execute_plugins(:post_test_fixture_execute, arg_hash) end @@ -96,10 +118,14 @@ def camelize(underscored_name) def execute_plugins(method, *args) @plugin_objects.each do |plugin| begin - plugin.send(method, *args) if plugin.respond_to?(method) + if plugin.respond_to?(method) + message = @reportinator.generate_progress( "Plugin | #{camelize( plugin.name )} > :#{method}" ) + @loginator.log( message, Verbosity::OBNOXIOUS ) + plugin.send(method, *args) + end rescue - puts "Exception raised in plugin: #{plugin.name}, in method #{method}" - raise + @loginator.log( "Exception raised in plugin `#{plugin.name}` within build hook :#{method}", Verbosity::ERRORS ) + raise # Raise again for backtrace, etc. end end end diff --git a/lib/ceedling/plugin_manager_helper.rb b/lib/ceedling/plugin_manager_helper.rb index b18248a65..89dd19f3b 100644 --- a/lib/ceedling/plugin_manager_helper.rb +++ b/lib/ceedling/plugin_manager_helper.rb @@ -1,3 +1,9 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= class PluginManagerHelper @@ -12,8 +18,8 @@ def include?(plugins, name) return include end - def instantiate_plugin_script(plugin, system_objects, name) - return eval("#{plugin}.new(system_objects, name)") + def instantiate_plugin(plugin, system_objects, name, root_path) + return eval( "#{plugin}.new(system_objects, name, root_path)" ) end end diff --git a/lib/ceedling/plugin_reportinator.rb b/lib/ceedling/plugin_reportinator.rb index 8d83727ba..7c1c23dea 100644 --- a/lib/ceedling/plugin_reportinator.rb +++ b/lib/ceedling/plugin_reportinator.rb @@ -1,5 +1,13 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'ceedling/constants' require 'ceedling/defaults' +require 'ceedling/exceptions' class PluginReportinator @@ -9,25 +17,74 @@ def setup @test_results_template = nil end - + def register_test_results_template(template) + @test_results_template = template + end + def set_system_objects(system_objects) @plugin_reportinator_helper.ceedling = system_objects end - def fetch_results(results_path, test, options={:boom => false}) return @plugin_reportinator_helper.fetch_results( File.join(results_path, test), options ) end - def generate_banner(message) return @reportinator.generate_banner(message) end - + def generate_heading(message) + return @reportinator.generate_heading(message) + end + + ## + ## Sample Test Results Output File (YAML) + ## ====================================== + ## + ## TestUsartModel.fail: + ## --- + ## :source: + ## :file: test/TestUsartModel.c + ## :dirname: test + ## :basename: TestUsartModel.c + ## :successes: + ## - :test: testGetBaudRateRegisterSettingShouldReturnAppropriateBaudRateRegisterSetting + ## :line: 24 + ## :message: '' + ## :unity_test_time: 0 + ## - :test: testGetFormattedTemperatureFormatsTemperatureFromCalculatorAppropriately + ## :line: 49 + ## :message: '' + ## :unity_test_time: 0 + ## - :test: testShouldReturnErrorMessageUponInvalidTemperatureValue + ## :line: 55 + ## :message: '' + ## :unity_test_time: 0 + ## - :test: testShouldReturnWakeupMessage + ## :line: 61 + ## :message: '' + ## :unity_test_time: 0 + ## :failures: + ## - :test: testFail + ## :line: 39 + ## :message: Expected 2 Was 3 + ## :unity_test_time: 0 + ## :ignores: + ## - :test: testIgnore + ## :line: 34 + ## :message: '' + ## :unity_test_time: 0 + ## :counts: + ## :total: 6 + ## :passed: 4 + ## :failed: 1 + ## :ignored: 1 + ## :stdout: [] + ## :time: 0.006512000225484371 + def assemble_test_results(results_list, options={:boom => false}) - aggregated_results = get_results_structure - + aggregated_results = new_results() + results_list.each do |result_path| results = @plugin_reportinator_helper.fetch_results( result_path, options ) @plugin_reportinator_helper.process_results(aggregated_results, results) @@ -35,41 +92,47 @@ def assemble_test_results(results_list, options={:boom => false}) return aggregated_results end - - - def register_test_results_template(template) - @test_results_template = template if (@test_results_template.nil?) - end - - + def run_test_results_report(hash, verbosity=Verbosity::NORMAL, &block) - run_report( $stdout, - ((@test_results_template.nil?) ? DEFAULT_TESTS_RESULTS_REPORT_TEMPLATE : @test_results_template), - hash, - verbosity, - &block ) + if @test_results_template.nil? + raise CeedlingException.new( "No test results report template has been set." ) + end + + run_report( + @test_results_template, + hash, + verbosity, + &block + ) end - - def run_report(stream, template, hash=nil, verbosity=Verbosity::NORMAL) + def run_report(template, hash=nil, verbosity=Verbosity::NORMAL) failure = nil failure = yield() if block_given? @plugin_manager.register_build_failure( failure ) - - @plugin_reportinator_helper.run_report( stream, template, hash, verbosity ) + + # Set verbosity to error level if there were failures + verbosity = failure ? Verbosity::ERRORS : Verbosity::NORMAL + + @plugin_reportinator_helper.run_report( template, hash, verbosity ) end - private ############################### + # + # Private + # + + private - def get_results_structure + def new_results() return { - :successes => [], - :failures => [], - :ignores => [], - :stdout => [], - :counts => {:total => 0, :passed => 0, :failed => 0, :ignored => 0, :stdout => 0}, - :time => 0.0 + :times => {}, + :successes => [], + :failures => [], + :ignores => [], + :stdout => [], + :counts => {:total => 0, :passed => 0, :failed => 0, :ignored => 0, :stdout => 0}, + :total_time => 0.0 } end diff --git a/lib/ceedling/plugin_reportinator_helper.rb b/lib/ceedling/plugin_reportinator_helper.rb index ea3a9dcb9..b25f0f252 100644 --- a/lib/ceedling/plugin_reportinator_helper.rb +++ b/lib/ceedling/plugin_reportinator_helper.rb @@ -1,51 +1,84 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'erb' require 'rubygems' require 'rake' # for ext() require 'ceedling/constants' +require 'ceedling/exceptions' class PluginReportinatorHelper attr_writer :ceedling - constructor :configurator, :streaminator, :yaml_wrapper, :file_wrapper + constructor :configurator, :loginator, :yaml_wrapper, :file_wrapper def fetch_results(results_path, options) - pass_path = File.join(results_path.ext( @configurator.extension_testpass )) - fail_path = File.join(results_path.ext( @configurator.extension_testfail )) - - if (@file_wrapper.exist?(fail_path)) - return @yaml_wrapper.load(fail_path) - elsif (@file_wrapper.exist?(pass_path)) - return @yaml_wrapper.load(pass_path) - else - if (options[:boom]) - @streaminator.stderr_puts("Could find no test results for '#{File.basename(results_path).ext(@configurator.extension_source)}'", Verbosity::ERRORS) - raise + # Create the results filepaths + pass_path = results_path.ext( @configurator.extension_testpass ) + fail_path = results_path.ext( @configurator.extension_testfail ) + + # Collect whether the results file(s) exists + pass_exists = ( @file_wrapper.exist?( pass_path ) ? true : false ) + fail_exists = ( @file_wrapper.exist?( fail_path ) ? true : false ) + + # Handle if neither file exists + if !fail_exists and !pass_exists + + if options[:boom] + # Complain loudly + error = "Could find no test results for '#{File.basename(results_path).ext(@configurator.extension_source)}'" + raise CeedlingException.new(error) + + else + # Otherwise simply return empty results + return {} end end + + # Handle if both files exists and return the newer results + if pass_exists and fail_exists + if @file_wrapper.newer?( pass_path, fail_path ) + return @yaml_wrapper.load( pass_path ) + else + return @yaml_wrapper.load( fail_path ) + end + end + + # Return success results + return @yaml_wrapper.load(pass_path) if pass_exists + + # Return fail results + return @yaml_wrapper.load(fail_path) if fail_exists + # Safety fall-through (flow control should never get here) return {} end - - def process_results(aggregate_results, results) + def process_results(aggregate, results) return if (results.empty?) - aggregate_results[:successes] << { :source => results[:source].clone, :collection => results[:successes].clone } if (results[:successes].size > 0) - aggregate_results[:failures] << { :source => results[:source].clone, :collection => results[:failures].clone } if (results[:failures].size > 0) - aggregate_results[:ignores] << { :source => results[:source].clone, :collection => results[:ignores].clone } if (results[:ignores].size > 0) - aggregate_results[:stdout] << { :source => results[:source].clone, :collection => results[:stdout].clone } if (results[:stdout].size > 0) - aggregate_results[:counts][:total] += results[:counts][:total] - aggregate_results[:counts][:passed] += results[:counts][:passed] - aggregate_results[:counts][:failed] += results[:counts][:failed] - aggregate_results[:counts][:ignored] += results[:counts][:ignored] - aggregate_results[:counts][:stdout] += results[:stdout].size - aggregate_results[:time] += results[:time] + aggregate[:times][(results[:source][:file])] = results[:time] + aggregate[:successes] << { :source => results[:source].clone, :collection => results[:successes].clone } if (results[:successes].size > 0) + aggregate[:failures] << { :source => results[:source].clone, :collection => results[:failures].clone } if (results[:failures].size > 0) + aggregate[:ignores] << { :source => results[:source].clone, :collection => results[:ignores].clone } if (results[:ignores].size > 0) + aggregate[:stdout] << { :source => results[:source].clone, :collection => results[:stdout].clone } if (results[:stdout].size > 0) + aggregate[:counts][:total] += results[:counts][:total] + aggregate[:counts][:passed] += results[:counts][:passed] + aggregate[:counts][:failed] += results[:counts][:failed] + aggregate[:counts][:ignored] += results[:counts][:ignored] + aggregate[:counts][:stdout] += results[:stdout].size + aggregate[:total_time] += results[:time] end + def run_report(template, hash, verbosity) + output = ERB.new( template, trim_mode: "%<>" ) - def run_report(stream, template, hash, verbosity) - output = ERB.new(template, trim_mode: "%<>") - @streaminator.stream_puts(stream, output.result(binding()), verbosity) + # Run the report template and log result with no log level heading + @loginator.log( output.result(binding()), verbosity, LogLabels::NONE ) end end diff --git a/lib/ceedling/preprocessinator.rb b/lib/ceedling/preprocessinator.rb index 52d82ca29..af121cc6a 100644 --- a/lib/ceedling/preprocessinator.rb +++ b/lib/ceedling/preprocessinator.rb @@ -1,56 +1,229 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= class Preprocessinator - constructor :preprocessinator_helper, :preprocessinator_includes_handler, :preprocessinator_file_handler, :task_invoker, :file_path_utils, :yaml_wrapper, :project_config_manager, :configurator + constructor :preprocessinator_includes_handler, + :preprocessinator_file_handler, + :task_invoker, + :file_finder, + :file_path_utils, + :file_wrapper, + :yaml_wrapper, + :plugin_manager, + :configurator, + :test_context_extractor, + :loginator, + :reportinator, + :rake_wrapper def setup - # fashion ourselves callbacks @preprocessinator_helper can use - @preprocess_includes_proc = Proc.new { |filepath| self.preprocess_shallow_includes(filepath) } - @preprocess_mock_file_proc = Proc.new { |filepath| self.preprocess_file(filepath) } - @preprocess_test_file_directives_proc = Proc.new { |filepath| self.preprocess_file_directives(filepath) } - @preprocess_test_file_proc = Proc.new { |filepath| self.preprocess_file(filepath) } + # Aliases + @includes_handler = @preprocessinator_includes_handler + @file_handler = @preprocessinator_file_handler end - def preprocess_shallow_source_includes(test) - @preprocessinator_helper.preprocess_source_includes(test) - end - def preprocess_test_and_invoke_test_mocks(test) - @preprocessinator_helper.preprocess_includes(test, @preprocess_includes_proc) + def preprocess_includes(filepath:, test:, flags:, include_paths:, defines:) + includes_list_filepath = @file_path_utils.form_preprocessed_includes_list_filepath( filepath, test ) + + includes = [] - mocks_list = @preprocessinator_helper.assemble_mocks_list(test) + # If existing YAML file of includes is newer than the file we're processing, skip preprocessing + if @file_wrapper.newer?( includes_list_filepath, filepath ) + msg = @reportinator.generate_module_progress( + operation: "Loading #include statement listing file for", + module_name: test, + filename: File.basename(filepath) + ) + @loginator.log( msg, Verbosity::NORMAL ) + + # Note: It's possible empty YAML content returns nil + includes = @yaml_wrapper.load( includes_list_filepath ) - @project_config_manager.process_test_defines_change(mocks_list) + msg = "Loaded existing #include list from #{includes_list_filepath}:" - @preprocessinator_helper.preprocess_mockable_headers(mocks_list, @preprocess_mock_file_proc) + if includes.nil? or includes.empty? + # Ensure includes defaults to emtpy array to prevent external iteration problems + includes = [] + msg += ' ' + else + includes.each { |include| msg += "\n - #{include}" } + end - @task_invoker.invoke_test_mocks(mocks_list) + @loginator.log( msg, Verbosity::DEBUG ) + @loginator.log( '', Verbosity::DEBUG ) - if (@configurator.project_use_preprocessor_directives) - @preprocessinator_helper.preprocess_test_file(test, @preprocess_test_file_directives_proc) + # Full preprocessing-based #include extraction with saving to YAML file else - @preprocessinator_helper.preprocess_test_file(test, @preprocess_test_file_proc) + includes = @includes_handler.extract_includes( + filepath: filepath, + test: test, + flags: flags, + include_paths: include_paths, + defines: defines + ) + + msg = "Extracted #include list from #{filepath}:" + + if includes.nil? or includes.empty? + # Ensure includes defaults to emtpy array to prevent external iteration problems + includes = [] + msg += ' ' + else + includes.each { |include| msg += "\n - #{include}" } + end + + @loginator.log( msg, Verbosity::DEBUG ) + @loginator.log( '', Verbosity::DEBUG ) + + @includes_handler.write_includes_list( includes_list_filepath, includes ) end - return mocks_list + return includes end - def preprocess_shallow_includes(filepath) - includes = @preprocessinator_includes_handler.extract_includes(filepath) - @preprocessinator_includes_handler.write_shallow_includes_list( - @file_path_utils.form_preprocessed_includes_list_filepath(filepath), includes) + def preprocess_mockable_header_file(filepath:, test:, flags:, include_paths:, defines:) + preprocessed_filepath = @file_path_utils.form_preprocessed_file_filepath( filepath, test ) + + plugin_arg_hash = { + header_file: filepath, + preprocessed_header_file: preprocessed_filepath, + test: test, + flags: flags, + include_paths: include_paths, + defines: defines + } + + # Trigger pre_mock_preprocessing plugin hook + @plugin_manager.pre_mock_preprocess( plugin_arg_hash ) + + arg_hash = { + filepath: filepath, + test: test, + flags: flags, + include_paths: include_paths, + defines: defines + } + + # Extract includes & log progress and details + includes = preprocess_file_common( **arg_hash ) + + arg_hash = { + source_filepath: filepath, + test: test, + flags: flags, + include_paths: include_paths, + defines: defines, + extras: (@configurator.cmock_treat_inlines == :include) + } + + # `contents` & `extras` are arrays of text strings to be assembled in generating a new header file. + # `extras` are macro definitions, pragmas, etc. needed for the special case of mocking `inline` function declarations. + # `extras` are empty for any cases other than mocking `inline` function declarations + # (We don't want to increase our chances of a badly generated file--extracting extras could fail in complex files.) + contents, extras = @file_handler.collect_header_file_contents( **arg_hash ) + + arg_hash = { + filename: File.basename( filepath ), + preprocessed_filepath: preprocessed_filepath, + contents: contents, + extras: extras, + includes: includes + } + + # Create a reconstituted header file from preprocessing expansion and preserving any extras + @file_handler.assemble_preprocessed_header_file( **arg_hash ) + + # Trigger post_mock_preprocessing plugin hook + @plugin_manager.post_mock_preprocess( plugin_arg_hash ) + + return preprocessed_filepath end - def preprocess_file(filepath) - @preprocessinator_includes_handler.invoke_shallow_includes_list(filepath) - @preprocessinator_file_handler.preprocess_file( filepath, @yaml_wrapper.load(@file_path_utils.form_preprocessed_includes_list_filepath(filepath)) ) + + def preprocess_test_file(filepath:, test:, flags:, include_paths:, defines:) + preprocessed_filepath = @file_path_utils.form_preprocessed_file_filepath( filepath, test ) + + plugin_arg_hash = { + test_file: filepath, + preprocessed_test_file: preprocessed_filepath, + test: test, + flags: flags, + include_paths: include_paths, + defines: defines + } + + # Trigger pre_test_preprocess plugin hook + @plugin_manager.pre_test_preprocess( plugin_arg_hash ) + + arg_hash = { + filepath: filepath, + test: test, + flags: flags, + include_paths: include_paths, + defines: defines + } + + # Extract includes & log progress and info + includes = preprocess_file_common( **arg_hash ) + + arg_hash = { + source_filepath: filepath, + test: test, + flags: flags, + include_paths: include_paths, + defines: defines + } + + # `contents` & `extras` are arrays of text strings to be assembled in generating a new test file. + # `extras` are test build directives TEST_SOURCE_FILE() and TEST_INCLUDE_PATH(). + contents, extras = @file_handler.collect_test_file_contents( **arg_hash ) + + arg_hash = { + filename: File.basename( filepath ), + preprocessed_filepath: preprocessed_filepath, + contents: contents, + extras: extras, + includes: includes + } + + # Create a reconstituted test file from preprocessing expansion and preserving any extras + @file_handler.assemble_preprocessed_test_file( **arg_hash ) + + # Trigger pre_mock_preprocessing plugin hook + @plugin_manager.post_test_preprocess( plugin_arg_hash ) + + return preprocessed_filepath end - def preprocess_file_directives(filepath) - @preprocessinator_includes_handler.invoke_shallow_includes_list( filepath ) - @preprocessinator_file_handler.preprocess_file_directives( filepath, - @yaml_wrapper.load( @file_path_utils.form_preprocessed_includes_list_filepath( filepath ) ) ) + ### Private ### + private + + def preprocess_file_common(filepath:, test:, flags:, include_paths:, defines:) + msg = @reportinator.generate_module_progress( + operation: "Preprocessing", + module_name: test, + filename: File.basename(filepath) + ) + + @loginator.log( msg, Verbosity::NORMAL ) + + # Extract includes + includes = preprocess_includes( + filepath: filepath, + test: test, + flags: flags, + include_paths: include_paths, + defines: defines) + + return includes end + end diff --git a/lib/ceedling/preprocessinator_extractor.rb b/lib/ceedling/preprocessinator_extractor.rb index 62026e15a..6bb48f03f 100644 --- a/lib/ceedling/preprocessinator_extractor.rb +++ b/lib/ceedling/preprocessinator_extractor.rb @@ -1,55 +1,241 @@ -class PreprocessinatorExtractor - def extract_base_file_from_preprocessed_expansion(filepath) - # preprocessing by way of toolchain preprocessor expands macros, eliminates - # comments, strips out #ifdef code, etc. however, it also expands in place - # each #include'd file. so, we must extract only the lines of the file - # that belong to the file originally preprocessed - - # iterate through all lines and alternate between extract and ignore modes - # all lines between a '#'line containing file name of our filepath and the - # next '#'line should be extracted - - base_name = File.basename(filepath) - not_pragma = /^#(?!pragma\b)/ # preprocessor directive that's not a #pragma - pattern = /^#.*(\s|\/|\\|\")#{Regexp.escape(base_name)}/ - found_file = false # have we found the file we care about? +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'ceedling/constants' + +class PreprocessinatorExtractor + + ## + ## Preprocessor Expansion Output Handling + ## ====================================== + ## + ## Preprocessing expands macros, eliminates comments, strips out #ifdef code, etc. + ## However, it also expands in place each #include'd file. So, we must extract + ## only the lines of the file that belong to the file originally preprocessed. + ## + ## We do this by examininig each line and ping-ponging between extracting and + ## ignoring text based on preprocessor statements referencing the file we're + ## seeking to reassemble. + ## + ## Note that the same text handling approach applies to full preprocessor + ## expansion as directives only expansion. + ## + ## Example preprocessed expansion output + ## -------------------------------------- + ## + ## # 14 "test/TestUsartModel.c" 2 + ## + ## void setUp(void) + ## { + ## } + ## + ## void tearDown(void) + ## { + ## } + ## + ## void testUsartModelInit(void) + ## { + ## TemperatureFilter_Init_CMockExpect(26); + ## + ## UsartModel_Init(); + ## } + ## # 55 "test/TestUsartModel.c" + ## void testGetBaudRateRegisterSettingShouldReturnAppropriateBaudRateRegisterSetting(void) + ## { + ## uint8 dummyRegisterSetting = 17; + ## UsartModel_CalculateBaudRateRegisterSetting_CMockExpectAndReturn(58, 48054857, 115200, dummyRegisterSetting); + ## + ## UnityAssertEqualNumber((UNITY_INT)(UNITY_UINT8 )((dummyRegisterSetting)), (UNITY_INT)(UNITY_UINT8 )((UsartModel_GetBaudRateRegisterSetting())), (((void*)0)), (UNITY_UINT)(60), UNITY_DISPLAY_STYLE_UINT8); + ## } + ## + ## void testIgnore(void) + ## { + ## UnityIgnore( (((void*)0)), (UNITY_UINT)(65)); + ## } + ## # 75 "test/TestUsartModel.c" + ## void testGetFormattedTemperatureFormatsTemperatureFromCalculatorAppropriately(void) + ## { + ## TemperatureFilter_GetTemperatureInCelcius_CMockExpectAndReturn(77, 25.0f); + ## UnityAssertEqualString((const char*)(("25.0 C\n")), (const char*)((UsartModel_GetFormattedTemperature())), (((void*)0)), (UNITY_UINT)(78)); + ## } + ## + ## void testShouldReturnErrorMessageUponInvalidTemperatureValue(void) + ## { + ## TemperatureFilter_GetTemperatureInCelcius_CMockExpectAndReturn(83, -__builtin_huge_valf()); + ## UnityAssertEqualString((const char*)(("Temperature sensor failure!\n")), (const char*)((UsartModel_GetFormattedTemperature())), (((void*)0)), (UNITY_UINT)(84)); + ## } + ## + ## void testShouldReturnWakeupMessage(void) + ## { + ## UnityAssertEqualString((const char*)(("It's Awesome Time!\n")), (const char*)((UsartModel_GetWakeupMessage())), (((void*)0)), (UNITY_UINT)(89)); + ## } + + # `input` must have the interface of IO -- StringIO for testing or File in typical use + def extract_file_as_array_from_expansion(input, filepath) + + # Iterate through all lines and alternate between extract and ignore modes. + # All lines between a '#' line containing the file name of our filepath and the + # next '#' line should be extracted. + # + # Notes: + # 1. Successive blocks can all be from the same source text file without a different, intervening '#' line. + # Multiple back-to-back blocks could all begin with '# 99 "path/file.c"'. + # 2. The first line of the file could start a text block we care about. + # 3. End of file could end a text block. + + base_name = File.basename( filepath ) + # Preprocessor output blocks take the form of '# [optional digits]' + directive = /^# \d+ \"/ + # Preprocessor output blocks for the file we care about take the form of '# "path/filename.ext" [optional digits]' + marker = /^# \d+ \".*#{Regexp.escape(base_name)}\"/ + # Boolean to ping pong between line-by-line extract/ignore + extract = false lines = [] - File.readlines(filepath).each do |line| - line.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '') - if found_file and not line =~ not_pragma - lines << line + + # Use `each_line()` instead of `readlines()` (chomp removes newlines). + # `each_line()` processes the IO buffer one line at a time instead of ingesting lines in an array. + # At large buffer sizes needed for potentially lengthy preprocessor output this is far more memory efficient and faster. + input.each_line( chomp:true ) do |line| + + # Clean up any oddball characters in an otherwise ASCII document + encoding_options = { + :invalid => :replace, # Replace invalid byte sequences + :undef => :replace, # Replace anything not defined in ASCII + :replace => '', # Use a blank for those replacements + :universal_newline => true # Always break lines with \n + } + line = line.encode("ASCII", **encoding_options).encode('UTF-8') + + # Handle extraction if the line is not a preprocessor directive + if extract and not line =~ directive + # Strip a line so we can omit useless blank lines + _line = line.strip() + # Restore text with left-side whitespace if previous stripping left some text + _line = line.rstrip() if !_line.empty? + lines << _line + + # Otherwise the line contained a preprocessor directive; drop out of extract mode else - found_file = false + extract = false end - found_file = true if line =~ pattern + # Enter extract mode if the line is a preprocessor directive with filename of interest + extract = true if line =~ marker end return lines end - def extract_base_file_from_preprocessed_directives(filepath) - # preprocessing by way of toolchain preprocessor eliminates directives only - # like #ifdef's and leave other code - # iterate through all lines and only get last chunk of file after a last - # '#'line containing file name of our filepath + # Simple variation of preceding that returns file contents as single string + def extract_file_as_string_from_expansion(input, filepath) + return extract_file_as_array_from_expansion(input, filepath).join( "\n" ) + end - base_name = File.basename(filepath) - pattern = /^#.*(\s|\/|\\|\")#{Regexp.escape(base_name)}/ - found_file = false # have we found the file we care about? - lines = [] - File.readlines(filepath).each do |line| - line.encode!('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '') - lines << line + # Extract all test directive macros as a list from a file as string + def extract_test_directive_macro_calls(file_contents) + # Look for TEST_SOURCE_FILE("...") and TEST_INCLUDE_PATH("...") in a string (i.e. a file's contents as a string) + + regexes = [ + /#{UNITY_TEST_SOURCE_FILE}.+?"\)/, + /#{UNITY_TEST_INCLUDE_PATH}.+?"\)/ + ] + + return extract_tokens_by_regex_list( file_contents, *regexes ) + end + + + # Extract all pragmas as a list from a file as string + def extract_pragmas(file_contents) + return extract_multiline_directives( file_contents, 'pragma' ) + end + + + # Find include guard in file contents as string + def extract_include_guard(file_contents) + # Look for first occurrence of #ifndef followed by #define + regex = /#\s*ifndef\s+(\w+)(?:\s*\n)+\s*#\s*define\s+(\w+)/ + matches = file_contents.match( regex ) + + # Return if no match results + return nil if matches.nil? + + # Return if match results are not expected size + return nil if matches.size != 3 + + # Return if #ifndef does not match #define + return nil if matches[1] != matches[2] + + # Return string in common + return matches[1] + end + + + # Extract all macro definitions as a list from a file as string + def extract_macro_defs(file_contents, include_guard) + macro_definitions = extract_multiline_directives( file_contents, 'define' ) + + # Remove an include guard if provided + macro_definitions.reject! {|macro| macro.include?( include_guard ) } if !include_guard.nil? + + return macro_definitions + end + + ### Private ### + + private + + def extract_multiline_directives(file_contents, directive) + results = [] + + # Output from the GCC preprocessor directives-only mode is the intended input to be processed here. + # The GCC preprpocessor smooshes multiline directives into a single line. + # We process both single and multiline directives here in case this is ever not true or we need + # to extract directives from files that have not been preprocessed. + + # This regex captures any single or multiline preprocessor directive definition: + # - Looks for any string that begins with '#' ('#' and '' may be separated by spaces per C spec). + # - Captures all text (non-greedily) after '#' on a first line through 0 or more line continuations up to a final newline. + # - Line continuations comprise a final '\' on a given line followed by whitespace & newline, wrapping to the next + # line up to a final '\' on that next line. + regex = /(#\s*#{directive}\s+.*?(\\\s*\n.*?)*)\n/ - if line =~ pattern - lines = [] + tokens = extract_tokens_by_regex_list( file_contents, regex ) + + tokens.each do |token| + # Get the full text string from `scan() results` and split it at any newlines + lines = token[0].split( "\n" ) + # Lop off any trailing whitespace (mostly to simplify unit testing) + lines.map! {|line| line.rstrip()} + + # If the result of splitting is just a single string, add it to the results array as a single string + if lines.size == 1 + results << lines[0] + # Otherwise, add the array of split strings to the results as a sub-array + else + results << lines end end - return lines + return results end + + + def extract_tokens_by_regex_list(file_contents, *regexes) + tokens = [] + + # For each regex provided, extract all matches from the source string + regexes.each do |regex| + tokens += file_contents.scan( regex ) + end + + return tokens + end + end diff --git a/lib/ceedling/preprocessinator_file_handler.rb b/lib/ceedling/preprocessinator_file_handler.rb index 978fa0d05..b2c15f630 100644 --- a/lib/ceedling/preprocessinator_file_handler.rb +++ b/lib/ceedling/preprocessinator_file_handler.rb @@ -1,34 +1,229 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= +require 'rake' # for ext() method class PreprocessinatorFileHandler - constructor :preprocessinator_extractor, :configurator, :tool_executor, :file_path_utils, :file_wrapper + constructor :preprocessinator_extractor, :configurator, :tool_executor, :file_path_utils, :file_wrapper, :loginator + def collect_header_file_contents(source_filepath:, test:, flags:, defines:, include_paths:, extras:) + contents = [] - def preprocess_file(filepath, includes) - preprocessed_filepath = @file_path_utils.form_preprocessed_file_filepath(filepath) + # Our extra file content to be preserved + # Leave these empty if :extras is false + pragmas = [] + macro_defs = [] - command = @tool_executor.build_command_line(@configurator.tools_test_file_preprocessor, [], filepath, preprocessed_filepath) - @tool_executor.exec(command[:line], command[:options]) + preprocessed_filepath = @file_path_utils.form_preprocessed_file_full_expansion_filepath( source_filepath, test ) - contents = @preprocessinator_extractor.extract_base_file_from_preprocessed_expansion(preprocessed_filepath) + # Run GCC with full preprocessor expansion + command = @tool_executor.build_command_line( + @configurator.tools_test_file_full_preprocessor, + flags, + source_filepath, + preprocessed_filepath, + defines, + include_paths + ) + @tool_executor.exec( command ) - includes.each{|include| contents.unshift("#include \"#{include}\"")} + @file_wrapper.open( preprocessed_filepath, 'r' ) do |file| + contents = @preprocessinator_extractor.extract_file_as_array_from_expansion( file, preprocessed_filepath ) + end - @file_wrapper.write(preprocessed_filepath, contents.join("\n")) + # Bail out, skipping directives-only preprocessing if no extras are required + return contents, (pragmas + macro_defs) if !extras + + preprocessed_filepath = @file_path_utils.form_preprocessed_file_directives_only_filepath( source_filepath, test ) + + # Run GCC with directives-only preprocessor expansion + command = @tool_executor.build_command_line( + @configurator.tools_test_file_directives_only_preprocessor, + flags, + source_filepath, + preprocessed_filepath, + defines, + include_paths + ) + @tool_executor.exec( command ) + + # Try to find an #include guard in the first 2k of the file text. + # An #include guard is one macro from the original file we don't want to preserve if we can help it. + # We create our own #include guard in the header file we create. + # It's possible preserving the macro from the original file's #include guard could trip something up. + # Of course, it's also possible some header conditional compilation feature is dependent on it. + # ÂŻ\_(ツ)_/ÂŻ + include_guard = @preprocessinator_extractor.extract_include_guard( @file_wrapper.read( source_filepath, 2048 ) ) + + @file_wrapper.open( preprocessed_filepath, 'r' ) do |file| + # Get code contents of preprocessed directives-only file as a string + # TODO: Modify to process line-at-a-time for memory savings & performance boost + _contents = @preprocessinator_extractor.extract_file_as_string_from_expansion( file, preprocessed_filepath ) + + # Extract pragmas and macros from + pragmas = @preprocessinator_extractor.extract_pragmas( _contents ) + macro_defs = @preprocessinator_extractor.extract_macro_defs( _contents, include_guard ) + end + + return contents, (pragmas + macro_defs) + end + + + def assemble_preprocessed_header_file(filename:, preprocessed_filepath:, contents:, extras:, includes:) + _contents = [] + + # Add #include guards for header files + # Note: These aren't truly needed as preprocessed header files are only ingested by CMock. + # They're created for sake of completeness and just in case... + # ---------------------------------------------------- + # abc-XYZ.h --> _ABC_XYZ_H_ + guardname = '_' + filename.gsub(/\W/, '_').upcase + '_' + + forward_guards = [ + "#ifndef #{guardname} // Ceedling-generated include guard", + "#define #{guardname}", + '' + ] + + # Insert Ceedling notice + # ---------------------------------------------------- + comment = "// CEEDLING NOTICE: This generated file only to be consumed by CMock" + _contents += [comment, ''] + + # Add guards to beginning of file contents + _contents += forward_guards + + # Blank line + _contents << '' + + # Reinsert #include statements into stripped down file + includes.each{ |include| _contents << "#include \"#{include}\"" } + + # Blank line + _contents << '' + + # Add in any macro defintions or prgamas + extras.each do |ex| + if ex.class == String + _contents << ex + + elsif ex.class == Array + _contents += ex + end + + # Blank line + _contents << '' + end + + _contents += contents + + _contents += ['', "#endif // #{guardname}", ''] # Rear guard + + # Write file, collapsing any repeated blank lines + # ---------------------------------------------------- + _contents = _contents.join("\n") + _contents.gsub!( /(\h*\n){3,}/, "\n\n" ) + + # Remove paths from expanded #include directives + # ---------------------------------------------------- + # - We rely on search paths at compilation rather than explicit #include paths + # - Match (#include ")((path/)+)(file") and reassemble string using first and last matching groups + _contents.gsub!( /(#include\s+")(([^\/]+\/)+)(.+")/, '\1\4' ) + + # Write contents of final preprocessed file + @file_wrapper.write( preprocessed_filepath, _contents ) + end + + + def collect_test_file_contents(source_filepath:, test:, flags:, defines:, include_paths:) + contents = [] + # TEST_SOURCE_FILE() and TEST_INCLUDE_PATH() + test_directives = [] + + preprocessed_filepath = @file_path_utils.form_preprocessed_file_full_expansion_filepath( source_filepath, test ) + + # Run GCC with full preprocessor expansion + command = @tool_executor.build_command_line( + @configurator.tools_test_file_full_preprocessor, + flags, + source_filepath, + preprocessed_filepath, + defines, + include_paths + ) + @tool_executor.exec( command ) + + @file_wrapper.open( preprocessed_filepath, 'r' ) do |file| + contents = @preprocessinator_extractor.extract_file_as_array_from_expansion( file, preprocessed_filepath ) + end + + preprocessed_filepath = @file_path_utils.form_preprocessed_file_directives_only_filepath( source_filepath, test ) + + # Run GCC with directives-only preprocessor expansion + command = @tool_executor.build_command_line( + @configurator.tools_test_file_directives_only_preprocessor, + flags, + source_filepath, + preprocessed_filepath, + defines, + include_paths + ) + @tool_executor.exec( command ) + + @file_wrapper.open( preprocessed_filepath, 'r' ) do |file| + # Get code contents of preprocessed directives-only file as a string + # TODO: Modify to process line-at-a-time for memory savings & performance boost + _contents = @preprocessinator_extractor.extract_file_as_string_from_expansion( file, preprocessed_filepath ) + + # Extract TEST_SOURCE_FILE() and TEST_INCLUDE_PATH() + test_directives = @preprocessinator_extractor.extract_test_directive_macro_calls( _contents ) + end + + return contents, test_directives end - def preprocess_file_directives(filepath, includes) - preprocessed_filepath = @file_path_utils.form_preprocessed_file_filepath(filepath) - command = @tool_executor.build_command_line(@configurator.tools_test_file_preprocessor_directives, [], filepath, preprocessed_filepath) - @tool_executor.exec(command[:line], command[:options]) + def assemble_preprocessed_test_file(filename:, preprocessed_filepath:, contents:, extras:, includes:) + _contents = [] + + # Insert Ceedling notice + # ---------------------------------------------------- + comment = "// CEEDLING NOTICE: This generated file only to be consumed for test runner creation" + _contents += [comment, ''] + + # Blank line + _contents << '' + + # Reinsert #include statements into stripped down file + includes.each{ |include| _contents << "#include \"#{include}\"" } + + # Blank line + _contents << '' + + # Add in test directive macro calls + extras.each {|ex| _contents << ex} + + # Blank line + _contents << '' - contents = @preprocessinator_extractor.extract_base_file_from_preprocessed_directives(preprocessed_filepath) + _contents += contents - includes.each{|include| contents.unshift("#include \"#{include}\"")} + # Write file, doing some prettyifying along the way + # ---------------------------------------------------- + _contents = _contents.join("\n") + _contents.gsub!( /^\s*;/, '' ) # Drop blank lines with semicolons left over from macro expansion + trailing semicolon + _contents.gsub!( /\)\s+\{/, ")\n{" ) # Collapse any unnecessary white space between closing argument paren and opening function bracket + _contents.gsub!( /\{(\n){2,}/, "{\n" ) # Collapse any unnecessary white space between opening function bracket and code + _contents.gsub!( /(\n){2,}\}/, "\n}" ) # Collapse any unnecessary white space between code and closing function bracket + _contents.gsub!( /(\h*\n){3,}/, "\n\n" ) # Collapse repeated blank lines - @file_wrapper.write(preprocessed_filepath, contents.join("\n")) + # Write contents of final preprocessed file + @file_wrapper.write( preprocessed_filepath, _contents ) end end diff --git a/lib/ceedling/preprocessinator_helper.rb b/lib/ceedling/preprocessinator_helper.rb deleted file mode 100644 index 4bbda67fc..000000000 --- a/lib/ceedling/preprocessinator_helper.rb +++ /dev/null @@ -1,50 +0,0 @@ - - -class PreprocessinatorHelper - - constructor :configurator, :test_includes_extractor, :task_invoker, :file_finder, :file_path_utils - - - def preprocess_includes(test, preprocess_includes_proc) - if (@configurator.project_use_test_preprocessor) - preprocessed_includes_list = @file_path_utils.form_preprocessed_includes_list_filepath(test) - preprocess_includes_proc.call( @file_finder.find_test_from_file_path(preprocessed_includes_list) ) - @test_includes_extractor.parse_includes_list(preprocessed_includes_list) - else - @test_includes_extractor.parse_test_file(test) - end - end - - def preprocess_source_includes(test) - @test_includes_extractor.parse_test_file_source_include(test) - end - - def assemble_mocks_list(test) - return @file_path_utils.form_mocks_source_filelist( @test_includes_extractor.lookup_raw_mock_list(test) ) - end - - def preprocess_mockable_headers(mock_list, preprocess_file_proc) - if (@configurator.project_use_test_preprocessor) - preprocess_files_smartly( - @file_path_utils.form_preprocessed_mockable_headers_filelist(mock_list), - preprocess_file_proc ) { |file| @file_finder.find_header_file(file) } - end - end - - def preprocess_test_file(test, preprocess_file_proc) - return if (!@configurator.project_use_test_preprocessor) - - preprocess_file_proc.call(test) - end - - private ############################ - - def preprocess_files_smartly(file_list, preprocess_file_proc) - if (@configurator.project_use_deep_dependencies) - @task_invoker.invoke_test_preprocessed_files(file_list) - else - file_list.each { |file| preprocess_file_proc.call( yield(file) ) } - end - end - -end diff --git a/lib/ceedling/preprocessinator_includes_handler.rb b/lib/ceedling/preprocessinator_includes_handler.rb index 8b89c0b3a..5f4815894 100644 --- a/lib/ceedling/preprocessinator_includes_handler.rb +++ b/lib/ceedling/preprocessinator_includes_handler.rb @@ -1,189 +1,394 @@ - +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= class PreprocessinatorIncludesHandler - constructor :configurator, :tool_executor, :task_invoker, :file_path_utils, :yaml_wrapper, :file_wrapper, :file_finder - @@makefile_cache = {} + constructor :configurator, :tool_executor, :test_context_extractor, :yaml_wrapper, :loginator, :reportinator - # shallow includes: only those headers a source file explicitly includes + ## + ## Includes Extraction Overview + ## ============================ + ## + ## BACKGROUND + ## -------- + ## #include extraction is hard to do. In simple cases a regex approach suffices, but nested header files, + ## clever macros, and conditional preprocessing statements easily introduce high complexity. + ## + ## Unfortunately, there's no readily available cross-platform C parsing tool that provides a simple means + ## to extract the #include statements directly embedded in a given file. Even the gcc preprocessor itself + ## only comes close to providing this information externally. + ## + ## APPROACH + ## -------- + ## (Full details including fallback options are in the extensive code comments among the methods below.) + ## + ## Sadly, we can't preprocess a file with full search paths and defines and ask for the #include statements + ## embedded in a file. We get far more #includes than we want with no way to discern which are at the depth + ## of the file being processed. + ## + ## Instead, we try our best to use some educated guessing to get as close as possible to the desired list. + ## + ## I. Try to extract shallow defines with no crawling out into other header files. This conservative approach + ## gives us a reference point on possible directly included files. The results may be incomplete, though. + ## They also may mistakenly list #includes that should not be in the list--because of #ifndef defaults or + ## because of system headers or #include <...> statements and differences among gcc implementations. + ## + ## II. Extract a full list of #includes by spidering out into nested headers and processing all macros, etc. + ## This is the greedy approach. + ## + ## III. Find #includes common to (I) and (II). The results of (I) should limit the potentially lengthy + ## results of (II). The complete and accurate list of (II) should cut out any mistaken entries in (I). + ## + ## IV. I–III are not foolproof. A purely greedy approach or a purely conservative approach will cause symbol + ## conflicts, missing symbols, etc. The blended and balanced approach should come quite close to an + ## accurate list of shallow includes. Edge cases and gaps will cause trouble. Other Ceedling features + ## should provide the tools to intervene. + ## + + def extract_includes(filepath:, test:, flags:, include_paths:, defines:) + msg = @reportinator.generate_module_progress( + operation: "Extracting #include statements via preprocessor from", + module_name: test, + filename: File.basename(filepath) + ) + @loginator.log(msg, Verbosity::NORMAL) + + # Extract shallow includes with preprocessor and fallback regex + shallow = extract_shallow_includes( + test: test, + filepath: filepath, + flags: flags, + defines: defines + ) - def invoke_shallow_includes_list(filepath) - @task_invoker.invoke_test_shallow_include_lists( [@file_path_utils.form_preprocessed_includes_list_filepath(filepath)] ) + # Extract nested includes but optionally act in fallback mode + nested = extract_nested_includes( + filepath: filepath, + include_paths: include_paths, + flags: flags, + defines: defines, + # If no shallow results, fall back to only depth 1 results of nested discovery + shallow: shallow.empty? + ) + + # Combine shallow and nested include knowledge of mocks + mocks = combine_mocks(shallow, nested) + + # Redefine shallow and nested results without any mocks + shallow = remove_mocks( shallow ) + nested = remove_mocks( nested ) + + # Return + # - Includes common to shallow and nested results, with paths from nested + # - Add mocks back in (may be empty if mocking not enabled) + return common_includes(shallow:shallow, nested:nested) + mocks end - ## - # Ask the preprocessor for a make-style dependency rule of only the headers - # the source file immediately includes. - # - # === Arguments - # +filepath+ _String_:: Path to the test file to process. - # - # === Return - # _String_:: The text of the dependency rule generated by the preprocessor. - def form_shallow_dependencies_rule(filepath) - if @@makefile_cache.has_key?(filepath) - return @@makefile_cache[filepath] + # Write to disk a yaml representation of a list of includes + def write_includes_list(filepath, list) + @yaml_wrapper.dump(filepath, list) + end + + ### Private ### + private + + def extract_shallow_includes(test:, filepath:, flags:, defines:) + # Shallow includes extraction, first attempt with preprocessor + success, shallow = + extract_shallow_includes_preprocessor( + test: test, + filepath: filepath, + flags: flags, + defines: defines + ) + + # Shallow includes extraction, second attempt with file read + regex + if not success + shallow = extract_shallow_includes_regex( + test: test, + filepath: filepath, + flags: flags, + defines: defines + ) end - # change filename (prefix of '_') to prevent preprocessor from finding - # include files in temp directory containing file it's scanning - temp_filepath = @file_path_utils.form_temp_path(filepath, '_') - - # read the file and replace all include statements with a decorated version - # (decorating the names creates file names that don't exist, thus preventing - # the preprocessor from snaking out and discovering the entire include path - # that winds through the code). The decorated filenames indicate files that - # are included directly by the test file. - contents = @file_wrapper.read(filepath) - - if !contents.valid_encoding? - contents = contents.encode("UTF-16be", :invalid=>:replace, :replace=>"?").encode('UTF-8') + + return shallow + end + + def extract_shallow_includes_preprocessor(test:, filepath:, flags:, defines:) + ## + ## Preprocessor Make Rule Handling + ## =============================== + ## + ## Creation: + ## - This output is created with the -MM -MG -MP command line options. + ## - No search paths are used towards extracting only the #include statements of the file. + ## The intent is to minimize the list of .h -> .c module matches to, in turn, minimize + ## unnecessary compilation when extracting includes from a test file. + ## - Note: This approach can have gaps with complex macros / conditional statements. + ## Gaps can be minimized with proper defines in the project file. + ## However, needed / complex macros located in other header files can still gum + ## up the works. + ## + ## Format: + ## - First line is .o file followed by colon and dependencies (on one or more lines). + ## - "Phony" make rules follow that conveniently list each #include, one per line. + ## + ## Notes: + ## - Many errors can occur but may not necessarily prevent usable results. + ## - A file with no includes will create the first line with self-referential .h file path. + ## - Make rule formation assumes any files not found in a search path will be generated. + ## - Since we're not using search paths, the preprocessor largely assumes all #include + ## files are generated (and include no paths). + ## - The exception is #include files that exist in the same directory as the file + ## being processed. + ## + ## Approach: + ## 1. Disable exceptions for tool execution as errors are likely. + ## - We may still have usable output. + ## - We do not want to stop execution on fatal error; instead use a fallback method. + ## 2. The only true error is no make rule present--check for this first. + ## - A make rule may be present but not depedencies if the file has no #includes. + ## 3. Extract includes from "phony" make rules that follow opening rule line. + ## - These may be .h or .c files. + ## + ## Example output follows + ## ----------------------------------------------------------------------------------------- + ## os.o: ../../src/app/task/os/os.h fstd_types.h FreeRTOS.h queue.h + ## fstd_types.h: + ## FreeRTOS.h: + ## queue.h: + ## ../../src/app/task/os/os.h:72:21: error: no include path in which to search for stdbool.h + ## 72 | #include + ## | ^ + ## ../../src/app/task/os/os.h:73:20: error: no include path in which to search for stdint.h + ## 73 | #include + ## | ^ + ## + + # Matcher for the first line of the make rule output + make_rule_matcher = /^\S+\.o:\s+.+$/ # .o: + + # Matcher for the “phony“ make rule output lines for each #include dependency (.h, .c, etc.) + # Capture file name before the colon + include_matcher = /^(\S+\.\S+):\s*$/ # .: + + command = + @tool_executor.build_command_line( + @configurator.tools_test_shallow_includes_preprocessor, + flags, + filepath, + defines + ) + + # Assume possible errors so we have best shot at extracting results from preprocessing. + # Full code compilation will catch any breaking code errors + command[:options][:boom] = false + shell_result = @tool_executor.exec( command ) + + make_rules = shell_result[:output] + + # Do not check exit code for success. In some error conditions we still get usable output. + # Look for the first line of the make rule output. + if not make_rules =~ make_rule_matcher + msg = "Preprocessor #include extraction failed: #{shell_result[:output]}" + @loginator.log(msg, Verbosity::DEBUG) + + return false, [] end - contents.gsub!( /^\s*#include\s+[\"<]\s*(\S+)\s*[\">]/, "#include \"\\1\"\n#include \"@@@@\\1\"" ) - contents.gsub!( /^\s*TEST_FILE\(\s*\"\s*(\S+)\s*\"\s*\)/, "#include \"\\1\"\n#include \"@@@@\\1\"") - @file_wrapper.write( temp_filepath, contents ) + includes = [] - # extract the make-style dependency rule telling the preprocessor to - # ignore the fact that it can't find the included files - command = @tool_executor.build_command_line(@configurator.tools_test_includes_preprocessor, [], temp_filepath) - shell_result = @tool_executor.exec(command[:line], command[:options]) + # Extract the #include dependencies from the "phony" make rules, one per line + includes = make_rules.scan( include_matcher ) + includes.flatten! # Regex results can be nested arrays becuase of paren captures - @@makefile_cache[filepath] = shell_result[:output] - return shell_result[:output] + return true, includes.uniq end - ## - # Extract the headers that are directly included by a source file using the - # provided, annotated Make dependency rule. - # - # === Arguments - # +filepath+ _String_:: C source or header file to extract includes for. - # - # === Return - # _Array_ of _String_:: Array of the direct dependencies for the source file. - def extract_includes(filepath) - to_process = [filepath] - ignore_list = [] - list = [] - all_mocks = [] - - include_paths = @configurator.project_config_hash[:collection_paths_include] - include_paths = [] if include_paths.nil? - include_paths.map! {|path| File.expand_path(path)} - - while to_process.length > 0 - target = to_process.shift() - ignore_list << target - new_deps, new_to_process, all_mocks = extract_includes_helper(target, include_paths, ignore_list, all_mocks) - list += new_deps - to_process += new_to_process - if !@configurator.project_config_hash[:project_auto_link_deep_dependencies] - break - else - list = list.uniq() - to_process = to_process.uniq() - end + def extract_shallow_includes_regex(test:, filepath:, flags:, defines:) + msg = @reportinator.generate_module_progress( + operation: "Using fallback regex #include extraction for", + module_name: test, + filename: File.basename( filepath ) + ) + @loginator.log(msg, Verbosity::NORMAL) + + # Use abilities of @test_context_extractor to extract the #includes via regex on the file + includes = [] + @file_wrapper.open( filepath, 'r' ) do |file| + includes = @test_context_extractor.extract_includes( file ) end - return list + return includes end - def extract_includes_helper(filepath, include_paths, ignore_list, mocks) - # Extract the dependencies from the make rule - make_rule = self.form_shallow_dependencies_rule(filepath) - target_file = make_rule.split[0].gsub(':', '').gsub('\\','/') - base = File.basename(target_file, File.extname(target_file)) - make_rule_dependencies = make_rule.gsub(/.*\b#{Regexp.escape(base)}\S*/, '').gsub(/\\$/, '') - - # Extract the headers dependencies from the make rule - hdr_ext = @configurator.extension_header - headers_dependencies = make_rule_dependencies.split.find_all {|path| path.end_with?(hdr_ext) }.uniq - headers_dependencies.map! {|hdr| hdr.gsub('\\','/') } - full_path_headers_dependencies = extract_full_path_dependencies(headers_dependencies) - - # Extract the sources dependencies from the make rule - src_ext = @configurator.extension_source - sources_dependencies = make_rule_dependencies.split.find_all {|path| path.end_with?(src_ext) }.uniq - sources_dependencies.map! {|src| src.gsub('\\','/') } - full_path_sources_dependencies = extract_full_path_dependencies(sources_dependencies) - - list = full_path_headers_dependencies + full_path_sources_dependencies - - mock_prefix = @configurator.project_config_hash[:cmock_mock_prefix] - # Creating list of mocks - mocks += full_path_headers_dependencies.find_all do |header| - File.basename(header) =~ /^#{mock_prefix}.*$/ - end.compact - - # ignore real file when both mock and real file exist - mocks.each do |mock| - list.each do |filename| - if File.basename(filename) == File.basename(mock).sub(mock_prefix, '') - ignore_list << filename - end - end - end.compact + def extract_nested_includes(filepath:, include_paths:, flags:, defines:, shallow:false) + ## + ## Preprocessor Header File Listing Handling + ## ========================================= + ## + ## Creation: + ## - This output is created with the -MM -MG -H command line options. + ## - -MM -MG generates unused make rule that significantly reduces overall output. + ## - -H creates the header file output listing we actually want. + ## - Search paths are provided towards fully preprocessing all macros / conditionals and + ## symbols. (This produces a rich list of #includes far greater than we need.) + ## + ## Format (ignoring throwaway make rule): + ## - Each included filepath is listed per line. + ## - The depth of the #include nesting is signified by precending '.'s. + ## - Files directly #include'd in the file being preprocessed are at depth 1 ('.') + ## + ## Notes: + ## - Because search paths and defines are provided, error-free execution is assumed. + ## If the preprocessor fails, issues exist that will cause full compilation to fail. + ## - Unfortuantely, because of ordering and nesting effects, a file directly #include'd may + ## not be listed at depth 1 ('.'). Instead, it may end up listed at greater depth beneath + ## another #include'd file if both files reference it. That is, there is no way + ## to give the preprocessor full context and ask for only the files directly + ## #include'd in the file being processed. + ## - The preprocessor outputs the -H #include listing to STDERR. ToolExecutor does this + ## by default in creating the shell result output. + ## - Since we're using search paths, all #included files will include paths. Depending on + ## circumstances, this could yield a list with generated mocks with full build paths. + ## + ## Approach: + ## - Match on each listing line a filepath preceeded by its depth + ## - One mode of using this preprocessor approach is as a fallback / double-check method + ## if the simpler, earler shallow preprocessing produces no #include results. When used + ## this way we match only #include'd files at depth 1 ('.'), hoping we extract an + ## appropriate, usable list of #includes. + ## + ## Example output follows + ## ----------------------------------------------------------------------------------------- + ## . build/vendor/unity/src/unity.h + ## .. build/vendor/unity/src/unity_internals.h + ## . src/Types.h + ## . src/Model.h + ## . src/TimerModel.h + ## .. src/Testing.h + ## TestModel.o: test/TestModel.c build/vendor/unity/src/unity.h \ + ## build/vendor/unity/src/unity_internals.h setjmp.h math.h stddef.h \ + ## stdint.h limits.h stdio.h src/Types.h src/Model.h src/TimerModel.h \ + ## src/Testing.h MockTaskScheduler.h MockTemperatureFilter.h + ## - # Filtering list of final includes to only include mocks and anything that is NOT in the ignore_list - list = list.select do |item| - mocks.include? item or !(ignore_list.any? { |ignore_item| !item.match(/^(.*\/)?#{Regexp.escape(ignore_item)}$/).nil? }) - end + command = + @tool_executor.build_command_line( + @configurator.tools_test_nested_includes_preprocessor, + flags, + filepath, + include_paths, + defines + ) - to_process = [] + # Let the preprocessor do as much as possible + # We'll extract nothing if a catastrophic error, but we'll see it in debug logging + # Any real problems will be flagged by actual compilation step + command[:options][:boom] = false - if @configurator.project_config_hash[:project_auto_link_deep_dependencies] - # Creating list of headers that should be recursively pre-processed - # Skipping mocks and vendor headers - headers_to_deep_link = full_path_headers_dependencies.select do |hdr| - !(mocks.include? hdr) and (hdr.match(/^(.*\/)(#{VENDORS_FILES.join('|')}) + #{Regexp.escape(hdr_ext)}$/).nil?) - end - headers_to_deep_link.map! {|hdr| File.expand_path(hdr) } - headers_to_deep_link.compact! - - headers_to_deep_link.each do |hdr| - if (ignore_list.none? {|ignore_header| hdr.match(/^(.*\/)?#{Regexp.escape(ignore_header)}$/)} and - include_paths.none? {|include_path| hdr =~ /^#{include_path}\.*/}) - if File.exist?(hdr) - to_process << hdr - src = @file_finder.find_compilation_input_file(hdr, :ignore) - to_process << src if src - end - end - end + shell_result = @tool_executor.exec( command ) + + list = shell_result[:output] + + includes = [] + + # Extract entries from #include listing + if shallow + # First level of includes in preprocessor output + includes = list.scan(/^\. (.+$)\s*$/) # . + else + # All levels of includes in preprocessor output + includes = list.scan(/^\.+ (.+$)\s*$/) # ... end - return list, to_process, mocks + includes.flatten! # Regex results can be nested arrays becuase of paren captures + return includes.uniq end - def write_shallow_includes_list(filepath, list) - @yaml_wrapper.dump(filepath, list) + def combine_mocks(*lists) + # Handle mocks + # - Ensure no build filepaths in mock listings + # - Do not return mocks if mocking is disabled + mocks = [] + + # Bail out early if mocks are not enabled + return [] if !@configurator.project_use_mocks + + # Use some greediness to ensure we get all possible mocks + lists.each { |list| mocks |= extract_mocks( list ) } + + # If generated mocks are in the build directory, the preprocessor will have found them. + # This leads to duplicated mocks -- the shallow list with no paths and the nested list with paths. + # Remove mocks with any path (the path will be the build directory); preserve just the shallow list. + mocks.reject! {|mock| File.dirname( mock ) != '.' } + + return mocks end - private + # Return a list of mock .h files with no paths + def extract_mocks(includes) + return includes.select { |include| File.basename(include).start_with?( @configurator.cmock_mock_prefix ) } + end + + # Return list of includes with any mocks removed + def remove_mocks(includes) + return includes.reject { |include| File.basename(include).start_with?( @configurator.cmock_mock_prefix ) } + end + + # Return includes common in both lists with the full paths of the nested list + def common_includes(shallow:, nested:) + return shallow if nested.empty? + return nested if shallow.empty? + + # Notes: + # - We want to preserve filepaths whenever possible. Other areas of Ceedling use or discard the + # filepath as needed. + # - We generally do not have filepaths in the shallow list--except when the #include is in the + # same directory as the file being processed + + # Approach + # 1. Create hashed lists of shallow and nested for easier matching / deletion + # 2. Iterate through nested hash list and extract to common[] any filepath also in shallow + # 3. For each filepath extracted + # a. Delete it from the nested hash list + # b. Delete the corresponding entry in the shallow hash list + # 4. Iterate remaining nested hash list and extract to common[] and filepath whose base + # filename matches a remaining entry in the shallow hash list + + common = [] - def extract_full_path_dependencies(dependencies) - # Separate the real files form the annotated ones and remove the '@@@@' - annotated_files, real_files = dependencies.partition {|file| file =~ /^@@@@/} - annotated_files.map! {|file| file.gsub('@@@@','') } - # Matching annotated_files values against real_files to ensure that - # annotated_files contain full path entries (as returned by make rule) - annotated_files.map! {|file| real_files.find {|real| !real.match(/^(.*\/)?#{Regexp.escape(file)}$/).nil?}} - annotated_files = annotated_files.compact - - # Find which of our annotated files are "real" dependencies. This is - # intended to weed out dependencies that have been removed due to build - # options defined in the project yaml and/or in the files themselves. - return annotated_files.find_all do |annotated_file| - # find the index of the "real" file that matches the annotated one. - idx = real_files.find_index do |real_file| - real_file =~ /^(.*\/)?#{Regexp.escape(annotated_file)}$/ + # Hash list + _shallow = {} + shallow.each { |item| _shallow[item] = nil } + + # Hash list + _nested = {} + nested.each { |item| _nested[item] = nil } + + # Iterate each _nested entry and extract filepaths with matching filepath in _shallow list + _nested.each_key do |filepath| + if _shallow.has_key?( filepath ) + common << filepath # Copy to common + _shallow.delete(filepath) # Remove matching filepath from _shallow list end - # If we found a real file, delete it from the array and return it, - # otherwise return nil. Since nil is falsy this has the effect of making - # find_all return only the annotated filess for which a real file was - # found/deleted - idx ? real_files.delete_at(idx) : nil - end.compact + end + + # For each mached filepath, remove it from _nested list + common.each { |item| _nested.delete(item) } + + # Find any reamining filepaths whose baseneame matches an entry in _shallow + _nested.each_key do |filepath| + common << filepath if _shallow.has_key?( File.basename(filepath) ) + end + + return common end + end diff --git a/lib/ceedling/project_config_manager.rb b/lib/ceedling/project_config_manager.rb deleted file mode 100644 index ed7a73b8c..000000000 --- a/lib/ceedling/project_config_manager.rb +++ /dev/null @@ -1,52 +0,0 @@ -require 'ceedling/constants' - - -class ProjectConfigManager - - attr_reader :options_files, :release_config_changed, :test_config_changed, :test_defines_changed - attr_accessor :config_hash - - constructor :cacheinator, :configurator, :yaml_wrapper, :file_wrapper - - - def setup - @options_files = [] - @release_config_changed = false - @test_config_changed = false - @test_defines_changed = false - end - - - def merge_options(config_hash, option_filepath) - @options_files << File.basename( option_filepath ) - config_hash.deep_merge!( @yaml_wrapper.load( option_filepath ) ) - end - - - def filter_internal_sources(sources) - filtered_sources = sources.clone - filtered_sources.delete_if { |item| item =~ /#{CMOCK_MOCK_PREFIX}.+#{Regexp.escape(EXTENSION_SOURCE)}$/ } - filtered_sources.delete_if { |item| item =~ /#{VENDORS_FILES.map{|source| '\b' + Regexp.escape(source.ext(EXTENSION_SOURCE)) + '\b'}.join('|')}$/ } - return filtered_sources - end - - def process_release_config_change - # has project configuration changed since last release build - @release_config_changed = @cacheinator.diff_cached_release_config?( @config_hash ) - end - - - def process_test_config_change - # has project configuration changed since last test build - @test_config_changed = @cacheinator.diff_cached_test_config?( @config_hash ) - end - - def process_test_defines_change(files) - # has definitions changed since last test build - @test_defines_changed = @cacheinator.diff_cached_test_defines?( files ) - if @test_defines_changed - # update timestamp for rake task prerequisites - @file_wrapper.touch( @configurator.project_test_force_rebuild_filepath, :mtime => Time.now + 10 ) - end - end -end diff --git a/lib/ceedling/project_file_loader.rb b/lib/ceedling/project_file_loader.rb deleted file mode 100644 index bf5dcd41d..000000000 --- a/lib/ceedling/project_file_loader.rb +++ /dev/null @@ -1,99 +0,0 @@ -require 'ceedling/constants' - - -class ProjectFileLoader - - attr_reader :main_file, :user_file - - constructor :yaml_wrapper, :stream_wrapper, :system_wrapper, :file_wrapper - - def setup - @main_file = nil - @mixin_files = [] - @user_file = nil - - @main_project_filepath = '' - @mixin_project_filepaths = [] - @user_project_filepath = '' - end - - - def find_project_files - # first go hunting for optional user project file by looking for environment variable and then default location on disk - user_filepath = @system_wrapper.env_get('CEEDLING_USER_PROJECT_FILE') - - if ( not user_filepath.nil? and @file_wrapper.exist?(user_filepath) ) - @user_project_filepath = user_filepath - elsif (@file_wrapper.exist?(DEFAULT_CEEDLING_USER_PROJECT_FILE)) - @user_project_filepath = DEFAULT_CEEDLING_USER_PROJECT_FILE - end - - # next check for mixin project files by looking for environment variable - mixin_filepaths = @system_wrapper.env_get('CEEDLING_MIXIN_PROJECT_FILES') - if ( not mixin_filepaths.nil? ) - mixin_filepaths.split(File::PATH_SEPARATOR).each do |filepath| - if ( @file_wrapper.exist?(filepath) ) - @mixin_project_filepaths.push(filepath) - end - end - end - - # next check for main project file by looking for environment variable and then default location on disk; - # blow up if we don't find this guy -- like, he's so totally important - main_filepath = @system_wrapper.env_get('CEEDLING_MAIN_PROJECT_FILE') - - if ( not main_filepath.nil? and @file_wrapper.exist?(main_filepath) ) - @main_project_filepath = main_filepath - elsif (@file_wrapper.exist?(DEFAULT_CEEDLING_MAIN_PROJECT_FILE)) - @main_project_filepath = DEFAULT_CEEDLING_MAIN_PROJECT_FILE - else - # no verbosity checking since this is lowest level reporting anyhow & - # verbosity checking depends on configurator which in turns needs this class (circular dependency) - @stream_wrapper.stderr_puts('Found no Ceedling project file (*.yml)') - raise - end - - @main_file = File.basename( @main_project_filepath ) - @mixin_project_filepaths.each do |filepath| - @mixin_files.push(File.basename( filepath )) - end - @user_file = File.basename( @user_project_filepath ) if ( not @user_project_filepath.empty? ) - end - - def yaml_merger(y1, y2) - o1 = y1 - y2.each_pair do |k,v| - if o1[k].nil? - o1[k] = v - else - if (o1[k].instance_of? Hash) - o1[k] = yaml_merger(o1[k], v) - elsif (o1[k].instance_of? Array) - o1[k] += v - else - o1[k] = v - end - end - end - return o1 - end - - def load_project_config - config_hash = @yaml_wrapper.load(@main_project_filepath) - - # if there are mixin project files, then use them - @mixin_project_filepaths.each do |filepath| - mixin = @yaml_wrapper.load(filepath) - config_hash = yaml_merger( config_hash, mixin ) - end - - # if there's a user project file, then use it - if ( not @user_project_filepath.empty? ) - user_hash = @yaml_wrapper.load(@user_project_filepath) - config_hash = yaml_merger( config_hash, user_hash ) - end - - return config_hash - end - -end diff --git a/lib/ceedling/rake_utils.rb b/lib/ceedling/rake_utils.rb index 3f667c852..742f6cac6 100644 --- a/lib/ceedling/rake_utils.rb +++ b/lib/ceedling/rake_utils.rb @@ -1,3 +1,9 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= class RakeUtils diff --git a/lib/ceedling/rake_wrapper.rb b/lib/ceedling/rake_wrapper.rb index 15e479611..75d095dc6 100644 --- a/lib/ceedling/rake_wrapper.rb +++ b/lib/ceedling/rake_wrapper.rb @@ -1,3 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'rubygems' require 'rake' require 'ceedling/makefile' # our replacement for rake's make-style dependency loader diff --git a/lib/ceedling/rakefile.rb b/lib/ceedling/rakefile.rb index 1bcb82491..0b820979e 100644 --- a/lib/ceedling/rakefile.rb +++ b/lib/ceedling/rakefile.rb @@ -1,85 +1,145 @@ -require 'fileutils' +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= -# get directory containing this here file, back up one directory, and expand to full path -CEEDLING_ROOT = File.expand_path(File.dirname(__FILE__) + '/../..') -CEEDLING_LIB = File.join(CEEDLING_ROOT, 'lib') -CEEDLING_VENDOR = File.join(CEEDLING_ROOT, 'vendor') -CEEDLING_RELEASE = File.join(CEEDLING_ROOT, 'release') +require 'fileutils' -$LOAD_PATH.unshift( CEEDLING_LIB ) -$LOAD_PATH.unshift( File.join(CEEDLING_VENDOR, 'unity/auto') ) -$LOAD_PATH.unshift( File.join(CEEDLING_VENDOR, 'diy/lib') ) -$LOAD_PATH.unshift( File.join(CEEDLING_VENDOR, 'cmock/lib') ) +# Add Unity and CMock's Ruby code paths to $LOAD_PATH for runner generation and mocking +$LOAD_PATH.unshift( File.join( CEEDLING_APPCFG[:ceedling_vendor_path], 'unity/auto') ) +$LOAD_PATH.unshift( File.join( CEEDLING_APPCFG[:ceedling_vendor_path], 'cmock/lib') ) require 'rake' -#Let's make sure we remember the task descriptions in case we need them +# Let's make sure we remember the task descriptions in case we need them Rake::TaskManager.record_task_metadata = true -require 'diy' -require 'constructor' - -require 'ceedling/constants' -require 'ceedling/target_loader' - - -# construct all our objects -# ensure load path contains all libraries needed first -lib_ceedling_load_path_temp = File.join(CEEDLING_LIB, 'ceedling') -$LOAD_PATH.unshift( lib_ceedling_load_path_temp ) -@ceedling = DIY::Context.from_yaml( File.read( File.join(lib_ceedling_load_path_temp, 'objects.yml') ) ) -@ceedling.build_everything -# now that all objects are built, delete 'lib/ceedling' from load path -$LOAD_PATH.delete(lib_ceedling_load_path_temp) -# one-stop shopping for all our setup and such after construction -@ceedling[:setupinator].ceedling = @ceedling - -project_config = - begin - cfg = @ceedling[:setupinator].load_project_files - TargetLoader.inspect(cfg, ENV['TARGET']) - rescue TargetLoader::NoTargets - cfg - rescue TargetLoader::RequestReload - @ceedling[:setupinator].load_project_files +require 'ceedling/system_wrapper' +require 'ceedling/reportinator' + +# Operation duration logging +def log_runtime(run, start_time_s, end_time_s, enabled) + return if !enabled + return if !defined?(PROJECT_VERBOSITY) + return if (PROJECT_VERBOSITY < Verbosity::NORMAL) + + duration = Reportinator.generate_duration( start_time_s: start_time_s, end_time_s: end_time_s ) + + return if duration.empty? + + @ceedling[:loginator].log() # Blank line + @ceedling[:loginator].log( "Ceedling #{run} completed in #{duration}", Verbosity::NORMAL) +end + +start_time = nil # Outside scope of exception handling + +# Top-level exception handling for any otherwise un-handled exceptions, particularly around startup +begin + # Redefine start_time with actual timestamp before set up begins + start_time = SystemWrapper.time_stopwatch_s() + + # Construct all objects + # 1. Add full path to $LOAD_PATH to simplify objects.yml + # 2. Perform object construction + dependency injection + # 3. Remove full path from $LOAD_PATH + $LOAD_PATH.unshift( CEEDLING_APPCFG[:ceedling_lib_path] ) + objects_filepath = File.join( CEEDLING_APPCFG[:ceedling_lib_path], 'objects.yml' ) + + # Create object hash and dependency injection context + @ceedling = {} # Empty hash to be redefined if all goes well + @ceedling = DIY::Context.from_yaml( File.read( objects_filepath ) ) + + # Inject objects already insatantiated from bin/ bootloader before building the rest + CEEDLING_HANDOFF_OBJECTS.each_pair {|name,obj| @ceedling.set_object( name.to_s, obj )} + + # Build Ceedling application's objects + @ceedling.build_everything() + + # Simplify load path after construction + $LOAD_PATH.delete( CEEDLING_APPCFG[:ceedling_lib_path] ) + + # One-stop shopping for all our setup and such after construction + @ceedling[:setupinator].ceedling = @ceedling + @ceedling[:setupinator].do_setup( CEEDLING_APPCFG ) + + setup_done = SystemWrapper.time_stopwatch_s() + log_runtime( 'set up', start_time, setup_done, CEEDLING_APPCFG.build_tasks? ) + + # Configure high-level verbosity + unless defined?(PROJECT_DEBUG) and PROJECT_DEBUG + # Configure Ruby's default reporting for Thread exceptions. + # In Ceedling's case thread scenarios will fall into these buckets: + # 1. Jobs shut down cleanly + # 2. Jobs shut down at garbage collected after a build step terminates with an error + # + # Since Ceedling is not a daemon, server app, or something to run continuously, + # we can safely disable forced exception reporting. + Thread.report_on_exception = false + + # Tell Rake to shut up by default unless we're in DEBUG + verbose(false) + Rake.application.options.silent = true + + # Remove all Rake backtrace + Rake.application.options.suppress_backtrace_pattern = /.*/ end -@ceedling[:setupinator].do_setup( project_config ) - - -# tell all our plugins we're about to do something -@ceedling[:plugin_manager].pre_build + # Reset start_time before operations begins + start_time = SystemWrapper.time_stopwatch_s() -# load rakefile component files (*.rake) -PROJECT_RAKEFILE_COMPONENT_FILES.each { |component| load(component) } + # Tell all our plugins we're about to do something + @ceedling[:plugin_manager].pre_build if CEEDLING_APPCFG.build_tasks? -# tell rake to shut up by default (overridden in verbosity / debug tasks as appropriate) -verbose(false) + # load rakefile component files (*.rake) + PROJECT_RAKEFILE_COMPONENT_FILES.each { |component| load(component) } +rescue StandardError => ex + boom_handler( @ceedling[:loginator], ex ) + exit(1) +end +def test_failures_handler() + # $stdout test reporting plugins store test failures + exit(1) if @ceedling[:plugin_manager].plugins_failed? && !CEEDLING_APPCFG.tests_graceful_fail? +end -# end block always executed following rake run +# End block always executed following rake run END { $stdout.flush unless $stdout.nil? $stderr.flush unless $stderr.nil? - # cache our input configurations to use in comparison upon next execution - @ceedling[:cacheinator].cache_test_config( @ceedling[:setupinator].config_hash ) if (@ceedling[:task_invoker].test_invoked?) - @ceedling[:cacheinator].cache_release_config( @ceedling[:setupinator].config_hash ) if (@ceedling[:task_invoker].release_invoked?) - - # delete all temp files unless we're in debug mode - if (not @ceedling[:configurator].project_debug) - @ceedling[:file_wrapper].rm_f( @ceedling[:file_wrapper].directory_listing( File.join(@ceedling[:configurator].project_temp_path, '*') )) - end - - # only perform these final steps if we got here without runtime exceptions or errors - if (@ceedling[:system_wrapper].ruby_success) - - # tell all our plugins the build is done and process results - @ceedling[:plugin_manager].post_build - @ceedling[:plugin_manager].print_plugin_failures - exit(1) if (@ceedling[:plugin_manager].plugins_failed? && !@ceedling[:setupinator].config_hash[:graceful_fail]) + # Only perform these final steps if we got here without runtime exceptions or errors + if @ceedling[:application].build_succeeded? + # Tell all our plugins the build is done and process results + begin + if CEEDLING_APPCFG.build_tasks? + @ceedling[:plugin_manager].post_build + @ceedling[:plugin_manager].print_plugin_failures + end + ops_done = SystemWrapper.time_stopwatch_s() + log_runtime( 'operations', start_time, ops_done, CEEDLING_APPCFG.build_tasks? ) + test_failures_handler() if (@ceedling[:task_invoker].test_invoked? || @ceedling[:task_invoker].invoked?(/^gcov:/)) + rescue => ex + ops_done = SystemWrapper.time_stopwatch_s() + log_runtime( 'operations', start_time, ops_done, CEEDLING_APPCFG.build_tasks? ) + boom_handler( @ceedling[:loginator], ex ) + @ceedling[:loginator].wrapup + exit(1) + end + + @ceedling[:loginator].wrapup + exit(0) else - puts "ERROR: Ceedling Failed" - @ceedling[:plugin_manager].post_error + msg = "Ceedling could not complete operations because of errors" + @ceedling[:loginator].log( msg, Verbosity::ERRORS, LogLabels::TITLE ) + begin + @ceedling[:plugin_manager].post_error if CEEDLING_APPCFG.build_tasks? + rescue => ex + boom_handler( @ceedling[:loginator], ex) + ensure + @ceedling[:loginator].wrapup + exit(1) + end end } diff --git a/lib/ceedling/release_invoker.rb b/lib/ceedling/release_invoker.rb index 19bbca727..64c3d8b1e 100644 --- a/lib/ceedling/release_invoker.rb +++ b/lib/ceedling/release_invoker.rb @@ -1,54 +1,24 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'ceedling/constants' class ReleaseInvoker - constructor :configurator, :release_invoker_helper, :build_invoker_utils, :dependinator, :task_invoker, :file_path_utils, :file_wrapper - - - def setup_and_invoke_c_objects( c_files ) - objects = @file_path_utils.form_release_build_c_objects_filelist( c_files ) - - begin - @release_invoker_helper.process_deep_dependencies( @file_path_utils.form_release_dependencies_filelist( c_files ) ) - - @dependinator.enhance_release_file_dependencies( objects ) - @task_invoker.invoke_release_objects( objects ) - rescue => e - @build_invoker_utils.process_exception( e, RELEASE_SYM, false ) - end - - return objects - end - - - def setup_and_invoke_asm_objects( asm_files ) - objects = @file_path_utils.form_release_build_asm_objects_filelist( asm_files ) + constructor :configurator, :release_invoker_helper, :dependinator, :task_invoker, :file_path_utils, :file_wrapper - begin - @dependinator.enhance_release_file_dependencies( objects ) - @task_invoker.invoke_release_objects( objects ) - rescue => e - @build_invoker_utils.process_exception( e, RELEASE_SYM, false ) - end + def setup_and_invoke_objects( files ) + objects = @file_path_utils.form_release_build_objects_filelist( files ) + @task_invoker.invoke_release_objects( objects ) return objects end - - def refresh_c_deep_dependencies - return if (not @configurator.project_use_deep_dependencies) - - @file_wrapper.rm_f( - @file_wrapper.directory_listing( - File.join( @configurator.project_release_dependencies_path, '*' + @configurator.extension_dependencies ) ) ) - - @release_invoker_helper.process_deep_dependencies( - @file_path_utils.form_release_dependencies_filelist( - @configurator.collection_all_source ) ) - end - - def artifactinate( *files ) files.flatten.each do |file| @file_wrapper.cp( file, @configurator.project_release_artifacts_path ) if @file_wrapper.exist?( file ) diff --git a/lib/ceedling/release_invoker_helper.rb b/lib/ceedling/release_invoker_helper.rb index f83a2a53a..2c0dba27c 100644 --- a/lib/ceedling/release_invoker_helper.rb +++ b/lib/ceedling/release_invoker_helper.rb @@ -1,19 +1,12 @@ - +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= class ReleaseInvokerHelper constructor :configurator, :dependinator, :task_invoker - - def process_deep_dependencies(dependencies_list) - return if (not @configurator.project_use_deep_dependencies) - - if @configurator.project_generate_deep_dependencies - @dependinator.enhance_release_file_dependencies( dependencies_list ) - @task_invoker.invoke_release_dependencies_files( dependencies_list ) - end - - @dependinator.load_release_object_deep_dependencies( dependencies_list ) - end - end diff --git a/lib/ceedling/reportinator.rb b/lib/ceedling/reportinator.rb index 0f583d06d..0c83dee44 100644 --- a/lib/ceedling/reportinator.rb +++ b/lib/ceedling/reportinator.rb @@ -1,7 +1,74 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'unicode/display_width' + ## # Pretifies reports class Reportinator + # Generate human readable string of days, hours, minutes, seconds (and + # milliseconds) from a start count of seconds and end count of seconds. + def self.generate_duration(start_time_s:, end_time_s:) + return '' if start_time_s.nil? or end_time_s.nil? + + # Calculate duration as integer milliseconds + duration_ms = ((end_time_s - start_time_s) * 1000).to_i + + # Collect human readable time string tidbits + duration = [] + + # Singular / plural whole days + if duration_ms >= DurationCounts::DAY_MS + days = duration_ms / DurationCounts::DAY_MS + duration << "#{days} day#{'s' if days > 1}" + duration_ms -= (days * DurationCounts::DAY_MS) + # End duration string if remainder is less than 1 second (e.g. no 2 days 13 milliseconds) + duration_ms = 0 if duration_ms < 1000 + end + + # Singular / plural whole hours + if duration_ms >= DurationCounts::HOUR_MS + hours = duration_ms / DurationCounts::HOUR_MS + duration << "#{hours} hour#{'s' if hours > 1}" + duration_ms -= (hours * DurationCounts::HOUR_MS) + # End duration string if remainder is less than 1 second (e.g. no 2 days 13 milliseconds) + duration_ms = 0 if duration_ms < 1000 + end + + # Singular / plural whole minutes + if duration_ms >= DurationCounts::MINUTE_MS + minutes = duration_ms / DurationCounts::MINUTE_MS + duration << "#{minutes} minute#{'s' if minutes > 1}" + duration_ms -= (minutes * DurationCounts::MINUTE_MS) + # End duration string if remainder is less than 1 second (e.g. no 2 days 13 milliseconds) + duration_ms = 0 if duration_ms < 1000 + end + + # Plural fractional seconds (rounded) + if duration_ms >= DurationCounts::SECOND_MS + seconds = (duration_ms.to_f() / 1000.0).round(2) + duration << "#{seconds} seconds" + # End duration string + duration_ms = 0 + end + + # Singular / plural whole milliseconds (only if orginal duration less than 1 second) + if duration_ms > 0 + duration << "#{duration_ms} millisecond#{'s' if duration_ms > 1}" + end + + return duration.join(' ') + end + + def generate_duration(start_time_s:, end_time_s:) + return Reportinator.generate_duration( start_time_s: start_time_s, end_time_s: end_time_s ) + end + ## # Generates a banner for a message based on the length of the message or a # given width. @@ -19,8 +86,39 @@ class Reportinator # # def generate_banner(message, width=nil) - dash_count = ((width.nil?) ? message.strip.length : width) + # --------- + # + # --------- + dash_count = ((width.nil?) ? Unicode::DisplayWidth.of( message.strip ) : width) return "#{'-' * dash_count}\n#{message}\n#{'-' * dash_count}\n" end + def generate_heading(message) + # + # --------- + return "\n#{message}\n#{'-' * Unicode::DisplayWidth.of( message.strip )}" + end + + def generate_progress(message) + # ... + return "#{message}..." + end + + def generate_module_progress(module_name:, filename:, operation:) + # ..." + + # If filename is the module name, don't add the module label + label = (File.basename(filename).ext('') == module_name.to_s) ? '' : "#{module_name}::" + return generate_progress("#{operation} #{label}#{filename}") + end + + def generate_config_walk(keys, depth=0) + # :key ↳ :key ↳ :key + + _keys = keys.clone + _keys = _keys.slice(0, depth) if depth > 0 + _keys.reject! { |key| key.nil? } + return _keys.map{|key| ":#{key}"}.join(' ↳ ') + end + end diff --git a/lib/ceedling/rules_cmock.rake b/lib/ceedling/rules_cmock.rake deleted file mode 100644 index c4ff81aa8..000000000 --- a/lib/ceedling/rules_cmock.rake +++ /dev/null @@ -1,10 +0,0 @@ - - -rule(/#{CMOCK_MOCK_PREFIX}[^\/\\]+#{'\\'+EXTENSION_SOURCE}$/ => [ - proc do |task_name| - @ceedling[:file_finder].find_header_input_for_mock_file(task_name) - end - ]) do |mock| - - @ceedling[:generator].generate_mock(TEST_SYM, mock) -end diff --git a/lib/ceedling/rules_preprocess.rake b/lib/ceedling/rules_preprocess.rake deleted file mode 100644 index c29111272..000000000 --- a/lib/ceedling/rules_preprocess.rake +++ /dev/null @@ -1,26 +0,0 @@ - - -# invocations against this rule should only happen when enhanced dependencies are enabled; -# otherwise, dependency tracking will be too shallow and preprocessed files could intermittently -# fail to be updated when they actually need to be. -rule(/#{PROJECT_TEST_PREPROCESS_FILES_PATH}\/.+/ => [ - proc do |task_name| - @ceedling[:file_finder].find_test_or_source_or_header_file(task_name) - end - ]) do |file| - if (not @ceedling[:configurator].project_use_deep_dependencies) - raise 'ERROR: Ceedling preprocessing rule invoked though neccessary auxiliary dependency support not enabled.' - end - @ceedling[:generator].generate_preprocessed_file(TEST_SYM, file.source) -end - - -# invocations against this rule can always happen as there are no deeper dependencies to consider -rule(/#{PROJECT_TEST_PREPROCESS_INCLUDES_PATH}\/.+/ => [ - proc do |task_name| - @ceedling[:file_finder].find_test_or_source_or_header_file(task_name) - end - ]) do |file| - @ceedling[:generator].generate_shallow_includes_list(TEST_SYM, file.source) -end - diff --git a/lib/ceedling/rules_release.rake b/lib/ceedling/rules_release.rake index 4a583bd6c..0cc031cb7 100644 --- a/lib/ceedling/rules_release.rake +++ b/lib/ceedling/rules_release.rake @@ -1,3 +1,9 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= RELEASE_COMPILE_TASK_ROOT = RELEASE_TASK_ROOT + 'compile:' unless defined?(RELEASE_COMPILE_TASK_ROOT) RELEASE_ASSEMBLE_TASK_ROOT = RELEASE_TASK_ROOT + 'assemble:' unless defined?(RELEASE_ASSEMBLE_TASK_ROOT) @@ -14,48 +20,51 @@ if (TOOLS_RELEASE_COMPILER[:executable] == DEFAULT_RELEASE_COMPILER_TOOL[:execut end end -if (RELEASE_BUILD_USE_ASSEMBLY) -rule(/#{PROJECT_RELEASE_BUILD_OUTPUT_ASM_PATH}\/#{'.+\\'+EXTENSION_OBJECT}$/ => [ +rule(/#{PROJECT_RELEASE_BUILD_OUTPUT_PATH}\/#{'.+' + Regexp.escape(EXTENSION_OBJECT)}$/ => [ proc do |task_name| - @ceedling[:file_finder].find_assembly_file(task_name) + @ceedling[:file_finder].find_build_input_file(filepath: task_name, complain: :error, context: RELEASE_SYM) end ]) do |object| - @ceedling[:generator].generate_object_file( - TOOLS_RELEASE_ASSEMBLER, - OPERATION_ASSEMBLE_SYM, - RELEASE_SYM, - object.source, - object.name ) -end -end - -rule(/#{PROJECT_RELEASE_BUILD_OUTPUT_C_PATH}\/#{'.+\\'+EXTENSION_OBJECT}$/ => [ - proc do |task_name| - @ceedling[:file_finder].find_compilation_input_file(task_name, :error, true) - end - ]) do |object| - @ceedling[:generator].generate_object_file( - TOOLS_RELEASE_COMPILER, - OPERATION_COMPILE_SYM, - RELEASE_SYM, - object.source, - object.name, - @ceedling[:file_path_utils].form_release_build_c_list_filepath( object.name ), - @ceedling[:file_path_utils].form_release_dependencies_filepath( object.name ) ) + if @ceedling[:file_wrapper].extname(object.source) != EXTENSION_ASSEMBLY + @ceedling[:generator].generate_object_file_c( + tool: TOOLS_RELEASE_COMPILER, + module_name: File.basename(object.source).ext(), # Source filename as module name + context: RELEASE_SYM, + source: object.source, + object: object.name, + search_paths: COLLECTION_PATHS_INCLUDE, + flags: @ceedling[:flaginator].flag_down( context:RELEASE_SYM, operation:OPERATION_COMPILE_SYM ), + defines: @ceedling[:defineinator].defines( subkey:RELEASE_SYM ), + list: @ceedling[:file_path_utils].form_release_build_list_filepath( object.name ), + dependencies: @ceedling[:file_path_utils].form_release_dependencies_filepath( object.name ) ) + else + @ceedling[:generator].generate_object_file_asm( + tool: TOOLS_RELEASE_ASSEMBLER, + module_name: File.basename(object.source).ext(), # Source filename as module name + context: RELEASE_SYM, + source: object.source, + object: object.name, + search_paths: COLLECTION_PATHS_INCLUDE, + flags: @ceedling[:flaginator].flag_down( context:RELEASE_SYM, operation:OPERATION_ASSEMBLE_SYM ), + defines: @ceedling[:defineinator].defines( subkey:RELEASE_SYM ), + list: @ceedling[:file_path_utils].form_release_build_list_filepath( object.name ), + dependencies: @ceedling[:file_path_utils].form_release_dependencies_filepath( object.name ) ) + end end - rule(/#{PROJECT_RELEASE_BUILD_TARGET}/) do |bin_file| objects, libraries = @ceedling[:release_invoker].sort_objects_and_libraries(bin_file.prerequisites) tool = TOOLS_RELEASE_LINKER.clone lib_args = @ceedling[:release_invoker].convert_libraries_to_arguments(libraries) lib_paths = @ceedling[:release_invoker].get_library_paths_to_arguments() map_file = @ceedling[:configurator].project_release_build_map + @ceedling[:generator].generate_executable_file( tool, RELEASE_SYM, objects, + [], # Flags bin_file.name, map_file, lib_args, @@ -63,34 +72,33 @@ rule(/#{PROJECT_RELEASE_BUILD_TARGET}/) do |bin_file| @ceedling[:release_invoker].artifactinate( bin_file.name, map_file, @ceedling[:configurator].release_build_artifacts ) end - namespace RELEASE_SYM do - # use rules to increase efficiency for large projects (instead of iterating through all sources and creating defined tasks) + # Use rules to increase efficiency for large projects (instead of iterating through all sources and creating defined tasks) + # Unadvertised Rake tasks to execute source file compilation namespace :compile do - rule(/^#{RELEASE_COMPILE_TASK_ROOT}\S+#{'\\'+EXTENSION_SOURCE}$/ => [ # compile task names by regex + rule(/^#{RELEASE_COMPILE_TASK_ROOT}\S+(#{Regexp.escape(EXTENSION_SOURCE)}|#{Regexp.escape(EXTENSION_CORE_SOURCE)})$/ => [ # compile task names by regex proc do |task_name| source = task_name.sub(/#{RELEASE_COMPILE_TASK_ROOT}/, '') - @ceedling[:file_finder].find_source_file(source, :error) + @ceedling[:file_finder].find_source_file(source) end ]) do |compile| - @ceedling[:rake_wrapper][:directories].invoke - @ceedling[:project_config_manager].process_release_config_change - @ceedling[:release_invoker].setup_and_invoke_c_objects( [compile.source] ) + @ceedling[:rake_wrapper][:prepare].invoke + @ceedling[:release_invoker].setup_and_invoke_objects( [compile.source] ) end end + # Unadvertised Rake tasks to execute source file assembly if (RELEASE_BUILD_USE_ASSEMBLY) namespace :assemble do - rule(/^#{RELEASE_ASSEMBLE_TASK_ROOT}\S+#{'\\'+EXTENSION_ASSEMBLY}$/ => [ # assemble task names by regex + rule(/^#{RELEASE_ASSEMBLE_TASK_ROOT}\S+#{Regexp.escape(EXTENSION_ASSEMBLY)}$/ => [ # assemble task names by regex proc do |task_name| source = task_name.sub(/#{RELEASE_ASSEMBLE_TASK_ROOT}/, '') @ceedling[:file_finder].find_assembly_file(source) end ]) do |assemble| - @ceedling[:rake_wrapper][:directories].invoke - @ceedling[:project_config_manager].process_release_config_change - @ceedling[:release_invoker].setup_and_invoke_asm_objects( [assemble.source] ) + @ceedling[:rake_wrapper][:prepare].invoke + @ceedling[:release_invoker].setup_and_invoke_objects( [assemble.source] ) end end end diff --git a/lib/ceedling/rules_release_deep_dependencies.rake b/lib/ceedling/rules_release_deep_dependencies.rake deleted file mode 100644 index 9550783cc..000000000 --- a/lib/ceedling/rules_release_deep_dependencies.rake +++ /dev/null @@ -1,15 +0,0 @@ - - -rule(/#{PROJECT_RELEASE_DEPENDENCIES_PATH}\/#{'.+\\'+EXTENSION_DEPENDENCIES}$/ => [ - proc do |task_name| - @ceedling[:file_finder].find_compilation_input_file(task_name, :error, true) - end - ]) do |dep| - @ceedling[:generator].generate_dependencies_file( - TOOLS_RELEASE_DEPENDENCIES_GENERATOR, - RELEASE_SYM, - dep.source, - @ceedling[:file_path_utils].form_release_build_c_object_filepath(dep.source), - dep.name) -end - diff --git a/lib/ceedling/rules_tests.rake b/lib/ceedling/rules_tests.rake index 1381b5be3..bb096e51a 100644 --- a/lib/ceedling/rules_tests.rake +++ b/lib/ceedling/rules_tests.rake @@ -1,73 +1,62 @@ - - -rule(/#{PROJECT_TEST_FILE_PREFIX}#{'.+'+TEST_RUNNER_FILE_SUFFIX}#{'\\'+EXTENSION_SOURCE}$/ => [ - proc do |task_name| - @ceedling[:file_finder].find_test_input_for_runner_file(task_name) - end - ]) do |runner| - @ceedling[:generator].generate_test_runner(TEST_SYM, runner.source, runner.name) -end - -rule(/#{PROJECT_TEST_BUILD_OUTPUT_C_PATH}\/#{'.+\\'+EXTENSION_OBJECT}$/ => [ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +rule(/#{PROJECT_TEST_BUILD_OUTPUT_PATH}\/#{'.+\\' + EXTENSION_OBJECT}$/ => [ proc do |task_name| - @ceedling[:file_finder].find_compilation_input_file(task_name) + _, object = (task_name.split('+')) + @ceedling[:file_finder].find_build_input_file(filepath: object, context: TEST_SYM) end - ]) do |object| - if (File.basename(object.source) =~ /#{EXTENSION_SOURCE}$/) - @ceedling[:generator].generate_object_file( - TOOLS_TEST_COMPILER, - OPERATION_COMPILE_SYM, - TEST_SYM, - object.source, - object.name, - @ceedling[:file_path_utils].form_test_build_list_filepath( object.name ), - @ceedling[:file_path_utils].form_test_dependencies_filepath( object.name )) - elsif (defined?(TEST_BUILD_USE_ASSEMBLY) && TEST_BUILD_USE_ASSEMBLY) - @ceedling[:generator].generate_object_file( - TOOLS_TEST_ASSEMBLER, - OPERATION_ASSEMBLE_SYM, - TEST_SYM, - object.source, - object.name ) - end -end + ]) do |target| + test, object = (target.name.split('+')) + tool = TOOLS_TEST_COMPILER -rule(/#{PROJECT_TEST_BUILD_OUTPUT_PATH}\/#{'.+\\'+EXTENSION_EXECUTABLE}$/) do |bin_file| - lib_args = @ceedling[:test_invoker].convert_libraries_to_arguments() - lib_paths = @ceedling[:test_invoker].get_library_paths_to_arguments() - @ceedling[:generator].generate_executable_file( - TOOLS_TEST_LINKER, - TEST_SYM, - bin_file.prerequisites, - bin_file.name, - @ceedling[:file_path_utils].form_test_build_map_filepath( bin_file.name ), - lib_args, - lib_paths ) -end - - -rule(/#{PROJECT_TEST_RESULTS_PATH}\/#{'.+\\'+EXTENSION_TESTPASS}$/ => [ - proc do |task_name| - @ceedling[:file_path_utils].form_test_executable_filepath(task_name) + if @ceedling[:file_wrapper].extname(target.source) == EXTENSION_ASSEMBLY + tool = TOOLS_TEST_ASSEMBLER end - ]) do |test_result| - @ceedling[:generator].generate_test_results(TOOLS_TEST_FIXTURE, TEST_SYM, test_result.source, test_result.name) -end + @ceedling[:test_invoker].compile_test_component( + tool: tool, + test: test.to_sym, + source: target.source, + object: object + ) + end namespace TEST_SYM do - # use rules to increase efficiency for large projects (instead of iterating through all sources and creating defined tasks) - @ceedling[:unity_utils].create_test_runner_additional_args - rule(/^#{TEST_TASK_ROOT}\S+$/ => [ # test task names by regex + TOOL_COLLECTION_TEST_RULES = { + :context => TEST_SYM, + :test_compiler => TOOLS_TEST_COMPILER, + :test_assembler => TOOLS_TEST_ASSEMBLER, + :test_linker => TOOLS_TEST_LINKER, + :test_fixture => TOOLS_TEST_FIXTURE + } + + # Use rules to increase efficiency for large projects (instead of iterating through all sources and creating defined tasks) + rule(/^#{TEST_TASK_ROOT}\S+$/ => [ # Test task names by regex proc do |task_name| - test = task_name.sub(/#{TEST_TASK_ROOT}/, '') - test = "#{PROJECT_TEST_FILE_PREFIX}#{test}" if not (test.start_with?(PROJECT_TEST_FILE_PREFIX)) - @ceedling[:file_finder].find_test_from_file_path(test) + # Yield clean test name => Strip the task string, remove Rake test task prefix, and remove any code file extension + test = task_name.strip().sub(/^#{TEST_TASK_ROOT}/, '').chomp( EXTENSION_SOURCE ) + + # Ensure the test name begins with a test name prefix + test = PROJECT_TEST_FILE_PREFIX + test if not (test.start_with?( PROJECT_TEST_FILE_PREFIX )) + + # Provide the filepath for the target test task back to the Rake task + @ceedling[:file_finder].find_test_file_from_name( test ) end ]) do |test| - @ceedling[:rake_wrapper][:test_deps].invoke - @ceedling[:test_invoker].setup_and_invoke([test.source]) + # Do essential Rake-based set up + @ceedling[:rake_wrapper][:prepare].invoke + + # Execute the test task + @ceedling[:test_invoker].setup_and_invoke( + tests:[test.source], + options:{:force_run => true, :build_only => false}.merge( TOOL_COLLECTION_TEST_RULES ) + ) end end diff --git a/lib/ceedling/rules_tests_deep_dependencies.rake b/lib/ceedling/rules_tests_deep_dependencies.rake deleted file mode 100644 index 7175ee3f2..000000000 --- a/lib/ceedling/rules_tests_deep_dependencies.rake +++ /dev/null @@ -1,15 +0,0 @@ - - -rule(/#{PROJECT_TEST_DEPENDENCIES_PATH}\/#{'.+\\'+EXTENSION_DEPENDENCIES}$/ => [ - proc do |task_name| - @ceedling[:file_finder].find_compilation_input_file(task_name) - end - ]) do |dep| - @ceedling[:generator].generate_dependencies_file( - TOOLS_TEST_DEPENDENCIES_GENERATOR, - TEST_SYM, - dep.source, - @ceedling[:file_path_utils].form_test_build_c_object_filepath(dep.source), - dep.name) -end - diff --git a/lib/ceedling/setupinator.rb b/lib/ceedling/setupinator.rb index ea78fd97e..8e954ddc1 100644 --- a/lib/ceedling/setupinator.rb +++ b/lib/ceedling/setupinator.rb @@ -1,53 +1,199 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= class Setupinator attr_reader :config_hash - attr_writer :ceedling def setup - @ceedling = {} @config_hash = {} end - def load_project_files - @ceedling[:project_file_loader].find_project_files - return @ceedling[:project_file_loader].load_project_config + + # Override to prevent exception handling from walking & stringifying the object variables. + # Object variables are gigantic and produce a flood of output. + def inspect + # TODO: When identifying information is added to constructor, insert it into `inspect()` string + return this.class.name end - def do_setup(config_hash) - @config_hash = config_hash - - # load up all the constants and accessors our rake files, objects, & external scripts will need; - # note: configurator modifies the cmock section of the hash with a couple defaults to tie - # project together - the modified hash is used to build cmock object - @ceedling[:configurator].populate_defaults( config_hash ) - @ceedling[:configurator].populate_unity_defaults( config_hash ) - @ceedling[:configurator].populate_cmock_defaults( config_hash ) - @ceedling[:configurator].find_and_merge_plugins( config_hash ) - @ceedling[:configurator].merge_imports( config_hash ) - @ceedling[:configurator].eval_environment_variables( config_hash ) - @ceedling[:configurator].tools_setup( config_hash ) - @ceedling[:configurator].eval_paths( config_hash ) - @ceedling[:configurator].standardize_paths( config_hash ) - @ceedling[:configurator].validate( config_hash ) - @ceedling[:configurator].build( config_hash, :environment ) - - @ceedling[:configurator].insert_rake_plugins( @ceedling[:configurator].rake_plugins ) - @ceedling[:configurator].tools_supplement_arguments( config_hash ) + + # Injector method for setting Ceedling application object hash + def ceedling=(value) + # Capture application objects hash as instance variable + @ceedling = value + + # Get our dependencies from object hash rather than DIY constructor + # This is a shortcut / validates aspects of our setup + @configurator = value[:configurator] + @loginator = value[:loginator] + @reportinator = value[:reportinator] + @plugin_manager = value[:plugin_manager] + @plugin_reportinator = value[:plugin_reportinator] + @test_runner_manager = value[:test_runner_manager] + end + + + # Load up all the constants and accessors our rake files, objects, & external scripts will need. + def do_setup( app_cfg ) + @config_hash = app_cfg[:project_config] + + ## + ## 1. Miscellaneous handling and essential configuration prep + ## + + # Set special purpose test case filters (from command line) + @configurator.include_test_case = app_cfg[:include_test_case] + @configurator.exclude_test_case = app_cfg[:exclude_test_case] + + # Verbosity handling + @configurator.set_verbosity( config_hash ) + + # Logging configuration + @loginator.set_logfile( app_cfg[:log_filepath] ) + @configurator.project_logging = @loginator.project_logging + + log_step( 'Validating configuration contains minimum required sections', heading:false ) + + # Complain early about anything essential that's missing + @configurator.validate_essential( config_hash ) + + # Merge any needed runtime settings into user configuration + @configurator.merge_ceedling_runtime_config( config_hash, CEEDLING_RUNTIME_CONFIG.deep_clone ) + + ## + ## 2. Handle basic configuration + ## + + log_step( 'Base configuration handling', heading:false ) + + # Evaluate environment vars before plugin configurations that might reference with inline Ruby string expansion + @configurator.eval_environment_variables( config_hash ) + + # Standardize paths and add to Ruby load paths + plugins_paths_hash = @configurator.prepare_plugins_load_paths( app_cfg[:ceedling_plugins_path], config_hash ) + + ## + ## 3. Plugin Handling + ## + + log_step( 'Plugin Handling' ) + + # Plugin handling + @configurator.discover_plugins( plugins_paths_hash, config_hash ) + @configurator.merge_config_plugins( config_hash ) + @configurator.populate_plugins_config( plugins_paths_hash, config_hash ) + + ## + ## 4. Collect and apply defaults to user configuration + ## + + log_step( 'Assembling Default Settings' ) + + # Assemble defaults + defaults_hash = DEFAULT_CEEDLING_PROJECT_CONFIG.deep_clone() + @configurator.merge_tools_defaults( config_hash, defaults_hash ) + @configurator.populate_cmock_defaults( config_hash, defaults_hash ) + @configurator.merge_plugins_defaults( plugins_paths_hash, config_hash, defaults_hash ) + + # Set any missing essential or plugin values in configuration with assembled default values + @configurator.populate_with_defaults( config_hash, defaults_hash ) + + ## + ## 5. Fill out / modify remaining configuration from user configuration + defaults + ## + + log_step( 'Completing Project Configuration' ) + + # Populate Unity configuration with values to tie vendor tool configurations together + @configurator.populate_unity_config( config_hash ) + + # Populate CMock configuration with values to tie vendor tool configurations together + @configurator.populate_cmock_config( config_hash ) + + # Configure test runner generation + @configurator.populate_test_runner_generation_config( config_hash ) + + # Automagically enable use of exceptions based on CMock settings + @configurator.populate_exceptions_config( config_hash ) + + # Evaluate environment vars again before subsequent configurations that might reference with inline Ruby string expansion + @configurator.eval_environment_variables( config_hash ) + + # Standardize values and expand inline Ruby string substitutions + @configurator.eval_paths( config_hash ) + @configurator.eval_flags( config_hash ) + @configurator.eval_defines( config_hash ) + @configurator.standardize_paths( config_hash ) + + # Fill out any missing tool config value + @configurator.populate_tools_config( config_hash ) + + # From any tool definition shortcuts: + # - Redefine executable if set + # - Add arguments from tool definition shortcuts if set + @configurator.populate_tools_shortcuts( config_hash ) + + # Configure test runner build & runtime options + @test_runner_manager.configure_build_options( config_hash ) + @test_runner_manager.configure_runtime_options( app_cfg[:include_test_case], app_cfg[:exclude_test_case] ) + + ## + ## 6. Validate configuration + ## + + log_step( 'Validating final project configuration', heading:false ) + + @configurator.validate_final( config_hash, app_cfg ) + + ## + ## 7. Flatten configuration + process it into globals and accessors + ## + + # Skip logging this step as the end user doesn't care about this internal preparation + + # Partially flatten config + build Configurator accessors and globals + @configurator.build( app_cfg[:ceedling_lib_path], app_cfg[:logging_path], config_hash, :environment ) + + ## + ## 8. Final plugins handling + ## + + # Detailed logging already happend for plugin processing + log_step( 'Loading Plugins' ) + + @configurator.insert_rake_plugins( @configurator.rake_plugins ) - # merge in any environment variables plugins specify, after the main build - @ceedling[:plugin_manager].load_plugin_scripts( @ceedling[:configurator].script_plugins, @ceedling ) do |env| - @ceedling[:configurator].eval_environment_variables( env ) - @ceedling[:configurator].build_supplement( config_hash, env ) + # Merge in any environment variables that plugins specify after the main build + @plugin_manager.load_programmatic_plugins( @configurator.programmatic_plugins, @ceedling ) do |env| + # Evaluate environment vars that plugins may have added + @configurator.eval_environment_variables( env ) + @configurator.build_supplement( config_hash, env ) end - @ceedling[:plugin_reportinator].set_system_objects( @ceedling ) - @ceedling[:file_finder].prepare_search_sources - @ceedling[:loginator].setup_log_filepath - @ceedling[:project_config_manager].config_hash = config_hash + # Inject dependencies for plugin needs + @plugin_reportinator.set_system_objects( @ceedling ) end - def reset_defaults(config_hash) - @ceedling[:configurator].reset_defaults( config_hash ) + +### Private + +private + + # Neaten up a build step with progress message and some scope encapsulation + def log_step(msg, heading:true) + if heading + msg = @reportinator.generate_heading( @loginator.decorate( msg, LogLabels::CONSTRUCT ) ) + else # Progress message + msg = "\n" + @reportinator.generate_progress( @loginator.decorate( msg, LogLabels::CONSTRUCT ) ) + end + + @loginator.log( msg, Verbosity::OBNOXIOUS ) end + + end diff --git a/lib/ceedling/stream_wrapper.rb b/lib/ceedling/stream_wrapper.rb index 7e160527f..0dfaec34c 100644 --- a/lib/ceedling/stream_wrapper.rb +++ b/lib/ceedling/stream_wrapper.rb @@ -1,28 +1,51 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +BEGIN { + require 'io/nonblock' + + # If possible, capture standard data streams non-blocking mode at startup (to be restored at shutdown). + # A sophisticated build setup (e.g. CI) may have intended this change on either side of Ceedling, + # but it will cause trouble for Ceedling itself. + + if STDOUT.respond_to?(:nonblock?) # Non-blocking mode query not implemented on all platforms + STDIN_STARTUP_NONBLOCKING_MODE = (STDIN.nonblock?).freeze if !defined?( STDIN_STARTUP_NONBLOCKING_MODE ) + STDOUT_STARTUP_NONBLOCKING_MODE = (STDOUT.nonblock?).freeze if !defined?( STDOUT_STARTUP_NONBLOCKING_MODE ) + STDERR_STARTUP_NONBLOCKING_MODE = (STDERR.nonblock?).freeze if !defined?( STDERR_STARTUP_NONBLOCKING_MODE ) + end + + # Ensure standard data streams are in blocking mode for Ceedling runs + STDIN.nonblock = false + STDOUT.nonblock = false + STDERR.nonblock = false +} class StreamWrapper - def stdout_override(&fnc) - @stdout_overide_fnc = fnc + def initialize + STDOUT.sync + STDERR.sync end def stdout_puts(string) - if @stdout_overide_fnc - @stdout_overide_fnc.call(string) - else - $stdout.puts(string) - end + $stdout.puts(string) end - def stdout_flush - $stdout.flush - end - def stderr_puts(string) $stderr.puts(string) end - def stderr_flush - $stderr.flush - end - end + +END { + require 'io/nonblock' + + # If they were captured, reset standard data streams' non-blocking mode to the setting captured at startup + STDIN.nonblock = STDIN_STARTUP_NONBLOCKING_MODE if defined?( STDIN_STARTUP_NONBLOCKING_MODE ) + STDOUT.nonblock = STDOUT_STARTUP_NONBLOCKING_MODE if defined?( STDOUT_STARTUP_NONBLOCKING_MODE ) + STDERR.nonblock = STDERR_STARTUP_NONBLOCKING_MODE if defined?( STDERR_STARTUP_NONBLOCKING_MODE ) +} diff --git a/lib/ceedling/streaminator.rb b/lib/ceedling/streaminator.rb deleted file mode 100644 index b8dcd070f..000000000 --- a/lib/ceedling/streaminator.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'ceedling/constants' - -class Streaminator - - constructor :streaminator_helper, :verbosinator, :loginator, :stream_wrapper - - # for those objects for whom the configurator has already been instantiated, - # Streaminator is a convenience object for handling verbosity and writing to the std streams - - def stdout_puts(string, verbosity=Verbosity::NORMAL) - if (@verbosinator.should_output?(verbosity)) - @stream_wrapper.stdout_puts(string) - @stream_wrapper.stdout_flush - end - - # write to log as though Verbosity::OBNOXIOUS - @loginator.log( string, @streaminator_helper.extract_name($stdout) ) - end - - def stderr_puts(string, verbosity=Verbosity::NORMAL) - if (@verbosinator.should_output?(verbosity)) - @stream_wrapper.stderr_puts(string) - @stream_wrapper.stderr_flush - end - - # write to log as though Verbosity::OBNOXIOUS - @loginator.log( string, @streaminator_helper.extract_name($stderr) ) - end - - def stream_puts(stream, string, verbosity=Verbosity::NORMAL) - if (@verbosinator.should_output?(verbosity)) - stream.puts(string) - stream.flush - end - - # write to log as though Verbosity::OBNOXIOUS - @loginator.log( string, @streaminator_helper.extract_name(stream) ) - end - -end diff --git a/lib/ceedling/streaminator_helper.rb b/lib/ceedling/streaminator_helper.rb deleted file mode 100644 index 9fb5cc0b7..000000000 --- a/lib/ceedling/streaminator_helper.rb +++ /dev/null @@ -1,15 +0,0 @@ - -class StreaminatorHelper - - def extract_name(stream) - name = case (stream.fileno) - when 0 then '#' - when 1 then '#' - when 2 then '#' - else stream.inspect - end - - return name - end - -end diff --git a/lib/ceedling/system_utils.rb b/lib/ceedling/system_utils.rb index 477aba4f1..4fe4f2a52 100644 --- a/lib/ceedling/system_utils.rb +++ b/lib/ceedling/system_utils.rb @@ -1,3 +1,9 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= class Object def deep_clone @@ -22,9 +28,9 @@ def setup # Checks the system shell to see if it a tcsh shell. def tcsh_shell? # once run a single time, return state determined at that execution - return @tcsh_shell if not @tcsh_shell.nil? + return @tcsh_shell unless @tcsh_shell.nil? - result = @system_wrapper.shell_backticks('echo $version') + result = @system_wrapper.shell_backticks(command:'echo $version') if ((result[:exit_code] == 0) and (result[:output].strip =~ /^tcsh/)) @tcsh_shell = true diff --git a/lib/ceedling/system_wrapper.rb b/lib/ceedling/system_wrapper.rb index 2b0f1edda..1ff435f41 100644 --- a/lib/ceedling/system_wrapper.rb +++ b/lib/ceedling/system_wrapper.rb @@ -1,4 +1,12 @@ -require 'rbconfig' +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'rbconfig' +require 'open3' class SystemWrapper @@ -8,6 +16,20 @@ def self.windows? return ((Config::CONFIG['host_os'] =~ /mswin|mingw/) ? true : false) end + def self.time_stopwatch_s + # Wall clock time that can be adjusted for a variety of reasons and lead to + # unexpected negative durations -- only option on Windows. + return Time.now() if SystemWrapper.windows? + + # On Posix systems, this time value is a steadily increasing count from + # a known system event (usually restart) and is more better + return Process.clock_gettime( Process::CLOCK_MONOTONIC, :float_second ) + end + + def initialize() + @argv = ARGV.clone.freeze + end + # class method so as to be mockable for tests def windows? return SystemWrapper.windows? @@ -25,8 +47,8 @@ def search_paths return ENV['PATH'].split(File::PATH_SEPARATOR) end - def cmdline_args - return ARGV + def get_cmdline + return @argv end def env_set(name, value) @@ -37,37 +59,85 @@ def env_get(name) return ENV[name] end - def time_now - return Time.now.asctime + def time_now(format=nil) + return Time.now.asctime if format.nil? + return Time.now.strftime( format ) + end + + # If set, `boom` allows a non-zero exit code in results. + # Otherwise, disabled `boom` forces a success exit code but collects errors. + def shell_capture3(command:, boom:false) + # Beginning with later versions of Ruby2, simple exit codes were replaced + # by the more capable and robust Process::Status. + # Parts of Process::Status's behavior is similar to an integer exit code in + # some operations but not all. + exit_code = 0 + + stdout, stderr = '' # Safe initialization defaults + status = nil # Safe initialization default + + stdout, stderr, status = Open3.capture3( command ) + + # If boom, then capture the actual exit code. + # Otherwise, leave it as zero as though execution succeeded. + exit_code = status.exitstatus.freeze if boom and !status.nil? + + # (Re)set the global system exit code so everything matches + $exit_code = exit_code + + return { + # Combine stdout & stderr streams for complete output + :output => (stdout + stderr).to_s.freeze, + + # Individual streams for detailed logging + :stdout => stdout.freeze, + :stderr => stderr.freeze, + + # Relay full Process::Status + :status => status.freeze, + + # Provide simple exit code accessor + :exit_code => exit_code.freeze + } end - def shell_backticks(command, boom = true) - retval = `#{command}`.freeze + def shell_backticks(command:, boom:false) + output = `#{command}`.freeze $exit_code = ($?.exitstatus).freeze if boom return { - :output => retval.freeze, + :output => output.freeze, :exit_code => ($?.exitstatus).freeze } end - def shell_system(command, boom = true) - system( command ) + def shell_system(command:, args:[], verbose:false, boom:false) + result = nil + + if verbose + # Allow console output + result = system( command, *args ) + else + # Shush the console output + result = system( command, *args, [:out, :err] => File::NULL ) + end + $exit_code = ($?.exitstatus).freeze if boom return { - :output => "".freeze, + :result => result.freeze, :exit_code => ($?.exitstatus).freeze } end def add_load_path(path) - $LOAD_PATH.unshift(path) + # Prevent trouble with string freezing by dup()ing paths here + $LOAD_PATH.unshift( path.dup() ) end def require_file(path) require(path) end - def ruby_success + def ruby_success? # We are successful if we've never had an exit code that went boom (either because it's empty or it was 0) return ($exit_code.nil? || ($exit_code == 0)) && ($!.nil? || $!.is_a?(SystemExit) && $!.success?) end diff --git a/lib/ceedling/target_loader.rb b/lib/ceedling/target_loader.rb deleted file mode 100644 index f1e95120f..000000000 --- a/lib/ceedling/target_loader.rb +++ /dev/null @@ -1,38 +0,0 @@ -module TargetLoader - class NoTargets < RuntimeError; end - class NoDirectory < RuntimeError; end - class NoDefault < RuntimeError; end - class NoSuchTarget < RuntimeError; end - - class RequestReload < RuntimeError; end - - def self.inspect(config, target_name=nil) - unless config[:targets] - raise NoTargets - end - - targets = config[:targets] - unless targets[:targets_directory] - raise NoDirectory.new("No targets directory specified.") - end - unless targets[:default_target] - raise NoDefault.new("No default target specified.") - end - - target_path = lambda {|name| File.join(targets[:targets_directory], name + ".yml")} - - target = if target_name - target_path.call(target_name) - else - target_path.call(targets[:default_target]) - end - - unless File.exist? target - raise NoSuchTarget.new("No such target: #{target}") - end - - ENV['CEEDLING_MAIN_PROJECT_FILE'] = target - - raise RequestReload - end -end diff --git a/lib/ceedling/task_invoker.rb b/lib/ceedling/task_invoker.rb index 7bfabbb12..d140f81d1 100644 --- a/lib/ceedling/task_invoker.rb +++ b/lib/ceedling/task_invoker.rb @@ -1,15 +1,23 @@ -require 'ceedling/par_map' +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= class TaskInvoker attr_accessor :first_run - constructor :dependinator, :rake_utils, :rake_wrapper, :project_config_manager + constructor :dependinator, :build_batchinator, :rake_utils, :rake_wrapper def setup @test_regexs = [/^#{TEST_ROOT_NAME}:/] @release_regexs = [/^#{RELEASE_ROOT_NAME}(:|$)/] @first_run = true + + # Alias for brevity + @batchinator = @build_batchinator end def add_test_task_regex(regex) @@ -46,75 +54,15 @@ def invoked?(regex) return @rake_utils.task_invoked?(regex) end - def reset_rake_task_for_changed_defines(file) - if !(file =~ /#{VENDORS_FILES.map{|ignore| '\b' + ignore.ext(File.extname(file)) + '\b'}.join('|')}$/) - @rake_wrapper[file].clear_actions if @first_run == false && @project_config_manager.test_defines_changed - @rake_wrapper[file].reenable if @first_run == false && @project_config_manager.test_defines_changed + def invoke_test_objects(test:, objects:) + @batchinator.exec(workload: :compile, things: objects) do |object| + # Encode context with concatenated compilation target: + + @rake_wrapper["#{test}+#{object}"].invoke end end - def invoke_test_mocks(mocks) - @dependinator.enhance_mock_dependencies( mocks ) - mocks.each { |mock| - reset_rake_task_for_changed_defines( mock ) - @rake_wrapper[mock].invoke - } - end - - def invoke_test_runner(runner) - @dependinator.enhance_runner_dependencies( runner ) - reset_rake_task_for_changed_defines( runner ) - @rake_wrapper[runner].invoke - end - - def invoke_test_shallow_include_lists(files) - @dependinator.enhance_shallow_include_lists_dependencies( files ) - par_map(PROJECT_COMPILE_THREADS, files) do |file| - reset_rake_task_for_changed_defines( file ) - @rake_wrapper[file].invoke - end - end - - def invoke_test_preprocessed_files(files) - @dependinator.enhance_preprocesed_file_dependencies( files ) - par_map(PROJECT_COMPILE_THREADS, files) do |file| - reset_rake_task_for_changed_defines( file ) - @rake_wrapper[file].invoke - end - end - - def invoke_test_dependencies_files(files) - @dependinator.enhance_dependencies_dependencies( files ) - par_map(PROJECT_COMPILE_THREADS, files) do |file| - reset_rake_task_for_changed_defines( file ) - @rake_wrapper[file].invoke - end - end - - def invoke_test_objects(objects) - par_map(PROJECT_COMPILE_THREADS, objects) do |object| - reset_rake_task_for_changed_defines( object ) - @rake_wrapper[object].invoke - end - end - - def invoke_test_executable(file) - @rake_wrapper[file].invoke - end - - def invoke_test_results(result) - @dependinator.enhance_results_dependencies( result ) - @rake_wrapper[result].invoke - end - - def invoke_release_dependencies_files(files) - par_map(PROJECT_COMPILE_THREADS, files) do |file| - @rake_wrapper[file].invoke - end - end - def invoke_release_objects(objects) - par_map(PROJECT_COMPILE_THREADS, objects) do |object| + @batchinator.exec(workload: :compile, things: objects) do |object| @rake_wrapper[object].invoke end end diff --git a/lib/ceedling/tasks_base.rake b/lib/ceedling/tasks_base.rake index a35cde75e..9838ddbca 100644 --- a/lib/ceedling/tasks_base.rake +++ b/lib/ceedling/tasks_base.rake @@ -1,44 +1,25 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'ceedling/constants' require 'ceedling/file_path_utils' -require 'ceedling/version' - -desc "Display build environment version info." -task :version do - puts " Ceedling:: #{Ceedling::Version::CEEDLING}" - puts " Unity:: #{Ceedling::Version::UNITY}" - puts " CMock:: #{Ceedling::Version::CMOCK}" - puts " CException:: #{Ceedling::Version::CEXCEPTION}" -end - -desc "Set verbose output (silent:[#{Verbosity::SILENT}] - obnoxious:[#{Verbosity::OBNOXIOUS}])." -task :verbosity, :level do |t, args| - verbosity_level = args.level.to_i - if (PROJECT_USE_MOCKS) - # don't store verbosity level in setupinator's config hash, use a copy; - # otherwise, the input configuration will change and trigger entire project rebuilds - hash = @ceedling[:setupinator].config_hash[:cmock].clone - hash[:verbosity] = verbosity_level - - @ceedling[:cmock_builder].manufacture( hash ) +# Set Rake verbosity using global constant verbosity set before Rake is loaded +if !!defined?(PROJECT_VERBOSITY) + verbose(PROJECT_VERBOSITY >= Verbosity::OBNOXIOUS) + if PROJECT_VERBOSITY >= Verbosity::OBNOXIOUS + Rake.application.options.silent = false + Rake.application.options.suppress_backtrace_pattern = nil end - - @ceedling[:configurator].project_verbosity = verbosity_level - - # control rake's verbosity with new setting - verbose( ((verbosity_level >= Verbosity::OBNOXIOUS) ? true : false) ) end -desc "Enable logging" -task :logging do - @ceedling[:configurator].project_logging = true -end - -# non advertised debug task +# Non-advertised debug task task :debug do - Rake::Task[:verbosity].invoke(Verbosity::DEBUG) Rake.application.options.trace = true - @ceedling[:configurator].project_debug = true end # non advertised sanity checking task @@ -47,65 +28,7 @@ task :sanity_checks, :level do |t, args| @ceedling[:configurator].sanity_checks = check_level end -# non advertised catch for calling upgrade in the wrong place -task :upgrade do - puts "WARNING: You're currently IN your project directory. Take a step out and try" - puts "again if you'd like to perform an upgrade." -end - -# list expanded environment variables -if (not ENVIRONMENT.empty?) -desc "List all configured environment variables." -task :environment do - env_list = [] - ENVIRONMENT.each do |env| - env.each_key do |key| - name = key.to_s.upcase - env_list.push(" - #{name}: \"#{env[key]}\"") - end - end - env_list.sort.each do |env_line| - puts env_line - end -end -end - -namespace :options do - - COLLECTION_PROJECT_OPTIONS.each do |option_path| - option = File.basename(option_path, '.yml') - - desc "Merge #{option} project options." - task option.to_sym do - hash = @ceedling[:project_config_manager].merge_options( @ceedling[:setupinator].config_hash, option_path ) - @ceedling[:setupinator].do_setup( hash ) - if @ceedling[:configurator].project_release_build - load(File.join(CEEDLING_LIB, 'ceedling', 'rules_release.rake')) - end - end - end - - # This is to give nice errors when typing options - rule /^options:.*/ do |t, args| - filename = t.to_s.split(':')[-1] + '.yml' - filelist = COLLECTION_PROJECT_OPTIONS.map{|s| File.basename(s) } - @ceedling[:file_finder].find_file_from_list(filename, filelist, :error) - end - - # This will output the fully-merged tools options to their own project.yml file - desc "Export tools options to a new project file" - task :export, :filename do |t, args| - outfile = args.filename || 'tools.yml' - toolcfg = {} - @ceedling[:configurator].project_config_hash.each_pair do |k,v| - toolcfg[k] = v if (k.to_s[0..5] == 'tools_') - end - File.open(outfile,'w') {|f| f << toolcfg.to_yaml({:indentation => 2})} - end -end - - -# do not present task if there's no plugins +# Do not present task if there's no plugins if (not PLUGINS_ENABLED.empty?) desc "Execute plugin result summaries (no build triggering)." task :summary do diff --git a/lib/ceedling/tasks_filesystem.rake b/lib/ceedling/tasks_filesystem.rake index 482c32bfe..748e888cd 100644 --- a/lib/ceedling/tasks_filesystem.rake +++ b/lib/ceedling/tasks_filesystem.rake @@ -1,3 +1,9 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= # rather than require 'rake/clean' & try to override, we replicate for finer control CLEAN = Rake::FileList["**/*~", "**/*.bak"] @@ -16,13 +22,12 @@ CLOBBER.include(File.join(PROJECT_BUILD_ARTIFACTS_ROOT, '**/*')) CLOBBER.include(File.join(PROJECT_BUILD_TESTS_ROOT, '**/*')) CLOBBER.include(File.join(PROJECT_BUILD_RELEASE_ROOT, '**/*')) CLOBBER.include(File.join(PROJECT_LOG_PATH, '**/*')) -CLOBBER.include(File.join(PROJECT_TEMP_PATH, '**/*')) # just in case they're using git, let's make sure we allow them to preserved the build directory if desired. CLOBBER.exclude(File.join(TESTS_BASE_PATH), '**/.gitkeep') # because of cmock config, mock path can optionally exist apart from standard test build paths -CLOBBER.include(File.join(CMOCK_MOCK_PATH, '*')) +CLOBBER.include(File.join(CMOCK_MOCK_PATH, '*')) if PROJECT_USE_MOCKS REMOVE_FILE_PROC = Proc.new { |fn| rm_r fn rescue nil } @@ -31,80 +36,61 @@ desc "Delete all build artifacts and temporary products." task(:clean) do # because :clean is a prerequisite for :clobber, intelligently display the progress message if (not @ceedling[:task_invoker].invoked?(/^clobber$/)) - @ceedling[:streaminator].stdout_puts("\nCleaning build artifacts...\n(For large projects, this task may take a long time to complete)\n\n") - end - begin - CLEAN.each { |fn| REMOVE_FILE_PROC.call(fn) } - rescue + @ceedling[:loginator].log("\nCleaning build artifacts...\n(For large projects, this task may take a long time to complete)\n\n") end + CLEAN.each { |fn| REMOVE_FILE_PROC.call(fn) } end # redefine clobber so we can override how it advertises itself desc "Delete all generated files (and build artifacts)." task(:clobber => [:clean]) do - @ceedling[:streaminator].stdout_puts("\nClobbering all generated files...\n(For large projects, this task may take a long time to complete)\n\n") - begin - CLOBBER.each { |fn| REMOVE_FILE_PROC.call(fn) } - @ceedling[:rake_wrapper][:directories].invoke - @ceedling[:dependinator].touch_force_rebuild_files - rescue - end + @ceedling[:loginator].log("\nClobbering all generated files...\n(For large projects, this task may take a long time to complete)\n\n") + CLOBBER.each { |fn| REMOVE_FILE_PROC.call(fn) } end # create a directory task for each of the paths, so we know how to build them PROJECT_BUILD_PATHS.each { |path| directory(path) } +# create a single prepare task which collects all release and test prerequisites +task :prepare => [:directories] + # create a single directory task which verifies all the others get built task :directories => PROJECT_BUILD_PATHS -# when the force file doesn't exist, it probably means we clobbered or are on a fresh -# install. In either case, stuff was deleted, so assume we want to rebuild it all -file @ceedling[:configurator].project_test_force_rebuild_filepath do - unless File.exist?(@ceedling[:configurator].project_test_force_rebuild_filepath) - @ceedling[:dependinator].touch_force_rebuild_files - end -end - # list paths discovered at load time namespace :paths do - standard_paths = ['test','source','include'] + standard_paths = ['test', 'source', 'include', 'support'] + paths = @ceedling[:setupinator].config_hash[:paths].keys.map{|n| n.to_s.downcase} - paths = (paths + standard_paths).uniq + paths.each do |name| - path_list = Object.const_get("COLLECTION_PATHS_#{name.upcase}") - - if (path_list.size != 0) || (standard_paths.include?(name)) - desc "List all collected #{name} paths." - task(name.to_sym) { puts "#{name} paths:"; path_list.sort.each {|path| puts " - #{path}" } } + desc "List all collected #{name} paths." if standard_paths.include?(name) + task(name.to_sym) do + path_list = Object.const_get("COLLECTION_PATHS_#{name.upcase}") + puts "#{name.capitalize} paths:#{' None' if path_list.size == 0}" + if path_list.size > 0 + path_list.sort.each {|path| puts " - #{path}" } + puts "Path count: #{path_list.size}" + end end end - end # list files & file counts discovered at load time namespace :files do - - categories = [ - ['test', COLLECTION_ALL_TESTS], - ['source', COLLECTION_ALL_SOURCE], - ['include', COLLECTION_ALL_HEADERS], - ['support', COLLECTION_ALL_SUPPORT] - ] - - using_assembly = (defined?(TEST_BUILD_USE_ASSEMBLY) && TEST_BUILD_USE_ASSEMBLY) || - (defined?(RELEASE_BUILD_USE_ASSEMBLY) && RELEASE_BUILD_USE_ASSEMBLY) - categories << ['assembly', COLLECTION_ALL_ASSEMBLY] if using_assembly + categories = ['tests', 'source', 'assembly', 'headers', 'support'] categories.each do |category| - name = category[0] - collection = category[1] - - desc "List all collected #{name} files." - task(name.to_sym) do - puts "#{name} files:" - collection.sort.each { |filepath| puts " - #{filepath}" } - puts "file count: #{collection.size}" + desc "List all collected #{category.chomp('s')} files." + task(category.chomp('s').to_sym) do + files_list = Object.const_get("COLLECTION_ALL_#{category.upcase}") + puts "#{category.chomp('s').capitalize} files:#{' None' if files_list.size == 0}" + if files_list.size > 0 + files_list.sort.each { |filepath| puts " - #{filepath}" } + puts "File count: #{files_list.size}" + puts "Note: This list sourced only from your project file, not from any build directive macros in test files." + end end end diff --git a/lib/ceedling/tasks_release.rake b/lib/ceedling/tasks_release.rake index b313b2f53..db3de9969 100644 --- a/lib/ceedling/tasks_release.rake +++ b/lib/ceedling/tasks_release.rake @@ -1,30 +1,43 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'ceedling/constants' require 'ceedling/file_path_utils' desc "Build release target." -task RELEASE_SYM => [:directories] do - header = "Release build '#{File.basename(PROJECT_RELEASE_BUILD_TARGET)}'" - @ceedling[:streaminator].stdout_puts("\n\n#{header}\n#{'-' * header.length}") +task RELEASE_SYM => [:prepare] do + header = "Release build '#{File.basename( PROJECT_RELEASE_BUILD_TARGET )}'" + + banner = @ceedling[:reportinator].generate_banner( header ) + + @ceedling[:loginator].log( banner ) begin @ceedling[:plugin_manager].pre_release core_objects = [] - extra_objects = @ceedling[:file_path_utils].form_release_build_c_objects_filelist( COLLECTION_RELEASE_ARTIFACT_EXTRA_LINK_OBJECTS ) + extra_objects = @ceedling[:file_path_utils].form_release_build_objects_filelist( COLLECTION_RELEASE_ARTIFACT_EXTRA_LINK_OBJECTS ) - @ceedling[:project_config_manager].process_release_config_change - core_objects.concat( @ceedling[:release_invoker].setup_and_invoke_c_objects( COLLECTION_ALL_SOURCE ) ) - - # if assembler use isn't enabled, COLLECTION_ALL_ASSEMBLY is empty array & nothing happens - core_objects.concat( @ceedling[:release_invoker].setup_and_invoke_asm_objects( COLLECTION_ALL_ASSEMBLY ) ) + core_objects.concat( @ceedling[:release_invoker].setup_and_invoke_objects( COLLECTION_RELEASE_BUILD_INPUT ) ) - # if we're using libraries, we need to add those to our collection as well + # If we're using libraries, we need to add those to our collection as well library_objects = (defined? LIBRARIES_RELEASE && !LIBRARIES_RELEASE.empty?) ? LIBRARIES_RELEASE.flatten.compact : [] file( PROJECT_RELEASE_BUILD_TARGET => (core_objects + extra_objects + library_objects) ) - Rake::Task[PROJECT_RELEASE_BUILD_TARGET].invoke + Rake::Task[PROJECT_RELEASE_BUILD_TARGET].invoke() + + rescue StandardError => ex + @ceedling[:application].register_build_failure + + @ceedling[:loginator].log( ex.message, Verbosity::ERRORS, LogLabels::EXCEPTION ) + + # Debug backtrace (only if debug verbosity) + @ceedling[:loginator].log_debug_backtrace( ex ) ensure @ceedling[:plugin_manager].post_release end end - diff --git a/lib/ceedling/tasks_release_deep_dependencies.rake b/lib/ceedling/tasks_release_deep_dependencies.rake deleted file mode 100644 index db2be5f34..000000000 --- a/lib/ceedling/tasks_release_deep_dependencies.rake +++ /dev/null @@ -1,9 +0,0 @@ -require 'ceedling/constants' - -namespace REFRESH_SYM do - - task RELEASE_SYM do - @ceedling[:release_invoker].refresh_c_deep_dependencies - end - -end diff --git a/lib/ceedling/tasks_tests.rake b/lib/ceedling/tasks_tests.rake index 6c51ebcc9..ba5ec96bb 100644 --- a/lib/ceedling/tasks_tests.rake +++ b/lib/ceedling/tasks_tests.rake @@ -1,60 +1,69 @@ -require 'ceedling/constants' +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= -task :test_deps => [:directories] +require 'ceedling/constants' -task :test => [:test_deps] do +task :test => [:prepare] do Rake.application['test:all'].invoke end namespace TEST_SYM do + TOOL_COLLECTION_TEST_TASKS = { + :test_compiler => TOOLS_TEST_COMPILER, + :test_assembler => TOOLS_TEST_ASSEMBLER, + :test_linker => TOOLS_TEST_LINKER, + :test_fixture => TOOLS_TEST_FIXTURE + } + desc "Run all unit tests (also just 'test' works)." - task :all => [:test_deps] do - @ceedling[:test_invoker].setup_and_invoke(COLLECTION_ALL_TESTS) + task :all => [:prepare] do + @ceedling[:test_invoker].setup_and_invoke( + tests:COLLECTION_ALL_TESTS, + options:{:force_run => true, :build_only => false}.merge(TOOL_COLLECTION_TEST_TASKS)) end - desc "Run single test ([*] real test or source file name, no path)." + desc "Run single test ([*] test or source file name, no path)." task :* do - message = "\nOops! '#{TEST_ROOT_NAME}:*' isn't a real task. " + + message = "Oops! '#{TEST_ROOT_NAME}:*' isn't a real task. " + "Use a real test or source file name (no path) in place of the wildcard.\n" + - "Example: rake #{TEST_ROOT_NAME}:foo.c\n\n" - - @ceedling[:streaminator].stdout_puts( message ) - end + "Example: `ceedling #{TEST_ROOT_NAME}:foo.c`" - desc "Run tests for changed files." - task :delta => [:test_deps] do - @ceedling[:test_invoker].setup_and_invoke(COLLECTION_ALL_TESTS, TEST_SYM, {:force_run => false}) + @ceedling[:loginator].log( message, Verbosity::ERRORS ) end desc "Just build tests without running." - task :build_only => [:test_deps] do - @ceedling[:test_invoker].setup_and_invoke(COLLECTION_ALL_TESTS, TEST_SYM, {:build_only => true}) + task :build_only => [:prepare] do + @ceedling[:test_invoker].setup_and_invoke(tests:COLLECTION_ALL_TESTS, options:{:build_only => true}.merge(TOOL_COLLECTION_TEST_TASKS)) end desc "Run tests by matching regular expression pattern." - task :pattern, [:regex] => [:test_deps] do |t, args| + task :pattern, [:regex] => [:prepare] do |t, args| matches = [] COLLECTION_ALL_TESTS.each { |test| matches << test if (test =~ /#{args.regex}/) } if (matches.size > 0) - @ceedling[:test_invoker].setup_and_invoke(matches, TEST_SYM, {:force_run => false}) + @ceedling[:test_invoker].setup_and_invoke(tests:matches, options:{:force_run => false}.merge(TOOL_COLLECTION_TEST_TASKS)) else - @ceedling[:streaminator].stdout_puts("\nFound no tests matching pattern /#{args.regex}/.") + @ceedling[:loginator].log( "Found no tests matching pattern /#{args.regex}/", Verbosity::ERRORS ) end end desc "Run tests whose test path contains [dir] or [dir] substring." - task :path, [:dir] => [:test_deps] do |t, args| + task :path, [:dir] => [:prepare] do |t, args| matches = [] COLLECTION_ALL_TESTS.each { |test| matches << test if File.dirname(test).include?(args.dir.gsub(/\\/, '/')) } if (matches.size > 0) - @ceedling[:test_invoker].setup_and_invoke(matches, TEST_SYM, {:force_run => false}) + @ceedling[:test_invoker].setup_and_invoke(tests:matches, options:{:force_run => false}.merge(TOOL_COLLECTION_TEST_TASKS)) else - @ceedling[:streaminator].stdout_puts("\nFound no tests including the given path or path component.") + @ceedling[:loginator].log( "Found no tests including the given path or path component", Verbosity::ERRORS ) end end diff --git a/lib/ceedling/tasks_tests_deep_dependencies.rake b/lib/ceedling/tasks_tests_deep_dependencies.rake deleted file mode 100644 index f89940716..000000000 --- a/lib/ceedling/tasks_tests_deep_dependencies.rake +++ /dev/null @@ -1,9 +0,0 @@ -require 'ceedling/constants' - -namespace REFRESH_SYM do - - task TEST_SYM do - @ceedling[:test_invoker].refresh_deep_dependencies - end - -end diff --git a/lib/ceedling/tasks_vendor.rake b/lib/ceedling/tasks_vendor.rake deleted file mode 100644 index 63c2ca55b..000000000 --- a/lib/ceedling/tasks_vendor.rake +++ /dev/null @@ -1,35 +0,0 @@ -require 'ceedling/constants' -require 'ceedling/file_path_utils' - -# create file dependencies to ensure C-based components of vendor tools are recompiled when they are updated with new versions -# forming these explicitly rather than depend on auxiliary dependencies so all scenarios are explicitly covered - -file( @ceedling[:file_path_utils].form_test_build_c_object_filepath( UNITY_C_FILE ) => [ - File.join( UNITY_VENDOR_PATH, UNITY_LIB_PATH, UNITY_C_FILE ), - File.join( UNITY_VENDOR_PATH, UNITY_LIB_PATH, UNITY_H_FILE ), - File.join( UNITY_VENDOR_PATH, UNITY_LIB_PATH, UNITY_INTERNALS_H_FILE ) ] - ) - - -if (PROJECT_USE_MOCKS) -file( @ceedling[:file_path_utils].form_test_build_c_object_filepath( CMOCK_C_FILE ) => [ - File.join( CMOCK_VENDOR_PATH, CMOCK_LIB_PATH, CMOCK_C_FILE ), - File.join( CMOCK_VENDOR_PATH, CMOCK_LIB_PATH, CMOCK_H_FILE ) ] - ) -end - - -if (PROJECT_USE_EXCEPTIONS) -file( @ceedling[:file_path_utils].form_test_build_c_object_filepath( CEXCEPTION_C_FILE ) => [ - File.join( CEXCEPTION_VENDOR_PATH, CEXCEPTION_LIB_PATH, CEXCEPTION_C_FILE ), - File.join( CEXCEPTION_VENDOR_PATH, CEXCEPTION_LIB_PATH, CEXCEPTION_H_FILE ) ] - ) -end - - -if (PROJECT_USE_EXCEPTIONS and PROJECT_RELEASE_BUILD) -file( @ceedling[:file_path_utils].form_release_build_c_object_filepath( CEXCEPTION_C_FILE ) => [ - File.join( CEXCEPTION_VENDOR_PATH, CEXCEPTION_LIB_PATH, CEXCEPTION_C_FILE ), - File.join( CEXCEPTION_VENDOR_PATH, CEXCEPTION_LIB_PATH, CEXCEPTION_H_FILE ) ] - ) -end diff --git a/lib/ceedling/test_context_extractor.rb b/lib/ceedling/test_context_extractor.rb new file mode 100644 index 000000000..b21c96ff7 --- /dev/null +++ b/lib/ceedling/test_context_extractor.rb @@ -0,0 +1,440 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'ceedling/exceptions' +require 'ceedling/file_path_utils' +require 'ceedling/generator_test_runner' # From lib/ not vendor/unity/auto + +class TestContextExtractor + + constructor :configurator, :file_wrapper, :loginator + + def setup + # Per test-file lookup hashes + @all_header_includes = {} # Full list of all headers from test #include statements + @header_includes = {} # List of all headers minus mocks & framework files + @source_includes = {} # List of C files #include'd in a test file + @source_extras = {} # C source files outside of header convention added to test build by TEST_SOURCE_FILE() + @test_runner_details = {} # Test case lists & Unity runner generator instances + @mocks = {} # List of mocks by name without header file extension + @include_paths = {} # Additional search paths added to a test build via TEST_INCLUDE_PATH() + + # Arrays + @all_include_paths = [] # List of all search paths added through individual test files using TEST_INCLUDE_PATH() + + @lock = Mutex.new + end + + # `input` must have the interface of IO -- StringIO for testing or File in typical use + def collect_simple_context( filepath, input, *args ) + all_options = [ + :build_directive_include_paths, + :build_directive_source_files, + :includes, + :test_runner_details + ] + + # Code error check--bad context symbol argument + args.each do |context| + next if context == :all + msg = "Unrecognized test context for collection :#{context}" + raise CeedlingException.new( msg ) if !all_options.include?( context ) + end + + # Handle the :all shortcut to redefine list to include all contexts + args = all_options if args.include?( :all ) + + include_paths = [] + source_extras = [] + includes = [] + + code_lines( input ) do |line| + if args.include?( :build_directive_include_paths ) + # Scan for build directives: TEST_INCLUDE_PATH() + include_paths += extract_build_directive_include_paths( line ) + end + + if args.include?( :build_directive_source_files ) + # Scan for build directives: TEST_SOURCE_FILE() + source_extras += extract_build_directive_source_files( line ) + end + + if args.include?( :includes ) + # Scan for contents of #include directives + includes += _extract_includes( line ) + end + end + + collect_build_directive_include_paths( filepath, include_paths ) if args.include?( :build_directive_include_paths ) + collect_build_directive_source_files( filepath, source_extras ) if args.include?( :build_directive_source_files ) + collect_includes( filepath, includes ) if args.include?( :includes ) + + # Different code processing pattern for test runner + if args.include?( :test_runner_details ) + # Go back to beginning of IO object for a full string extraction + input.rewind() + + # Ultimately, we rely on Unity's runner generator that processes file contents as a single string + _collect_test_runner_details( filepath, input.read() ) + end + end + + def collect_test_runner_details(test_filepath, input_filepath=nil) + # Ultimately, we rely on Unity's runner generator that processes file contents as a single string + _collect_test_runner_details( + test_filepath, + @file_wrapper.read( test_filepath ), + input_filepath.nil? ? nil : @file_wrapper.read( input_filepath ) + ) + end + + # Scan for all includes. + # Unlike other extract() calls, extract_includes() is public to be called externally. + # `input` must have the interface of IO -- StringIO for testing or File in typical use + def extract_includes(input) + includes = [] + + code_lines( input ) {|line| includes += _extract_includes( line ) } + + return includes.uniq + end + + # All header includes .h of test file + def lookup_full_header_includes_list(filepath) + val = nil + @lock.synchronize do + val = @all_header_includes[form_file_key( filepath )] || [] + end + return val + end + + # Header includes .h (minus mocks & framework headers) in test file + def lookup_header_includes_list(filepath) + val = nil + @lock.synchronize do + val = @header_includes[form_file_key( filepath )] || [] + end + return val + end + + # Include paths of test file specified with TEST_INCLUDE_PATH() + def lookup_include_paths_list(filepath) + val = nil + @lock.synchronize do + val = @include_paths[form_file_key( filepath )] || [] + end + return val + end + + # Source header_includes within test file + def lookup_source_includes_list(filepath) + val = nil + @lock.synchronize do + val = @source_includes[form_file_key( filepath )] || [] + end + return val + end + + # Source extras via TEST_SOURCE_FILE() within test file + def lookup_build_directive_sources_list(filepath) + val = nil + @lock.synchronize do + val = @source_extras[form_file_key( filepath )] || [] + end + return val + end + + def lookup_test_cases(filepath) + val = [] + @lock.synchronize do + details = @test_runner_details[form_file_key( filepath )] + if !details.nil? + val = details[:test_cases] + end + end + return val + end + + def lookup_test_runner_generator(filepath) + val = nil + @lock.synchronize do + details = @test_runner_details[form_file_key( filepath )] + if !details.nil? + val = details[:generator] + end + end + return val + end + + # Mocks within test file with no file extension + def lookup_raw_mock_list(filepath) + val = nil + @lock.synchronize do + val = @mocks[form_file_key( filepath )] || [] + end + return val + end + + def lookup_all_include_paths + val = nil + @lock.synchronize do + val = @all_include_paths.uniq + end + return val + end + + def inspect_include_paths + @lock.synchronize do + @include_paths.each { |test, paths| yield test, paths } + end + end + + # Unlike other ingest() calls, ingest_includes() can be called externally. + def ingest_includes(filepath, includes) + mock_prefix = @configurator.cmock_mock_prefix + file_key = form_file_key( filepath ) + + mocks = [] + all_headers = [] + headers = [] + sources = [] + + includes.each do |include| + # <*.h> + if include =~ /#{Regexp.escape(@configurator.extension_header)}$/ + # Check if include is a mock with regex match that extracts only mock name (no .h) + scan_results = include.scan(/(#{mock_prefix}.+)#{Regexp.escape(@configurator.extension_header)}/) + + if (scan_results.size > 0) + # Collect mock name + mocks << scan_results[0][0] + else + # If not a mock or framework file, collect tailored header filename + headers << include unless VENDORS_FILES.include?( include.ext('') ) + end + + # Add to .h includes list + all_headers << include + # <*.c> + elsif include =~ /#{Regexp.escape(@configurator.extension_source)}$/ + # Add to .c includes list + sources << include + end + end + + @lock.synchronize do + @mocks[file_key] = mocks + @all_header_includes[file_key] = all_headers + @header_includes[file_key] = headers + @source_includes[file_key] = sources + end + end + + # Exposed for testing + def code_lines(input) + comment_block = false + # Far more memory efficient and faster (for large files) than slurping entire file into memory + input.each_line do |line| + _line, comment_block = clean_code_line( line, comment_block ) + yield( _line ) + end + end + + private ################################# + + def collect_build_directive_source_files(filepath, files) + ingest_build_directive_source_files( filepath, files.uniq ) + + debug_log_list( + "Extra source files found via #{UNITY_TEST_SOURCE_FILE}()", + filepath, + files + ) + end + + def collect_build_directive_include_paths(filepath, paths) + ingest_build_directive_include_paths( filepath, paths.uniq ) + + debug_log_list( + "Search paths for #includes found via #{UNITY_TEST_INCLUDE_PATH}()", + filepath, + paths + ) + end + + def collect_includes(filepath, includes) + ingest_includes( filepath, includes.uniq ) + debug_log_list( "#includes found", filepath, includes ) + end + + def _collect_test_runner_details(filepath, test_content, input_content=nil) + unity_test_runner_generator = GeneratorTestRunner.new( + config: @configurator.get_runner_config, + test_file_contents: test_content, + preprocessed_file_contents: input_content + ) + + ingest_test_runner_details( + filepath: filepath, + test_runner_generator: unity_test_runner_generator + ) + + test_cases = unity_test_runner_generator.test_cases + test_cases = test_cases.map {|test_case| "#{test_case[:line_number]}:#{test_case[:test]}()" } + + debug_log_list( "Test cases found ", filepath, test_cases ) + end + + def extract_build_directive_source_files(line) + source_extras = [] + + # Look for TEST_SOURCE_FILE("<*>.<*>") statement + results = line.scan(/#{UNITY_TEST_SOURCE_FILE}\(\s*\"\s*(.+?\.\w+)*?\s*\"\s*\)/) + results.each do |result| + source_extras << FilePathUtils.standardize( result[0] ) + end + + return source_extras + end + + def extract_build_directive_include_paths(line) + include_paths = [] + + # Look for TEST_INCLUDE_PATH("<*>") statements + results = line.scan(/#{UNITY_TEST_INCLUDE_PATH}\(\s*\"\s*(.+?)\s*\"\s*\)/) + results.each do |result| + include_paths << FilePathUtils.standardize( result[0] ) + end + + return include_paths + end + + def _extract_includes(line) + includes = [] + + # Look for #include statements + results = line.match(/#\s*include\s+\"\s*((\w|\.)+)\s*\"/) + includes << results[1] if !results.nil? + + return includes + end + + ## + ## Data structure management ingest methods + ## + + def ingest_build_directive_source_files(filepath, source_extras) + return if source_extras.empty? + + key = form_file_key( filepath ) + + @lock.synchronize do + @source_extras[key] = source_extras + end + end + + def ingest_build_directive_include_paths(filepath, include_paths) + return if include_paths.empty? + + key = form_file_key( filepath ) + + @lock.synchronize do + @include_paths[key] = include_paths + end + + @lock.synchronize do + @all_include_paths += include_paths + end + end + + def ingest_test_runner_details(filepath:, test_runner_generator:) + key = form_file_key( filepath ) + + @lock.synchronize do + @test_runner_details[key] = { + :test_cases => test_runner_generator.test_cases, + :generator => test_runner_generator + } + end + end + + ## + ## Utility methods + ## + + def form_file_key( filepath ) + return filepath.to_s.to_sym + end + + def clean_code_line(line, comment_block) + _line = sanitize_encoding( line ) + + # Remove line comments + _line.gsub!(/\/\/.*$/, '') + + # Handle end of previously begun comment block + if comment_block + if _line.include?( '*/' ) + # Turn off comment block handling state + comment_block = false + + # Remove everything up to end of comment block + _line.gsub!(/^.*\*\//, '') + else + # Ignore contents of the line if its entirely within a comment block + return '', comment_block + end + + end + + # Block comments inside a C string are valid C, but we remove to simplify other parsing. + # No code we care about will be inside a C string. + # Note that we're not attempting the complex case of multiline string enclosed comment blocks + _line.gsub!(/"\s*\/\*.*"/, '') + + # Remove single-line block comments + _line.gsub!(/\/\*.*\*\//, '') + + # Handle beginning of any remaining multiline comment block + if _line.include?( '/*' ) + comment_block = true + + # Remove beginning of block comment + _line.gsub!(/\/\*.*/, '') + end + + return _line, comment_block + end + + # Note: This method modifies encoding in place (encode!) in an attempt to reduce long string copies + def sanitize_encoding(content) + encoding_options = { + :invalid => :replace, # Replace invalid byte sequences + :undef => :replace, # Replace anything not defined in ASCII + :replace => '', # Use a blank for those replacements + :universal_newline => true # Always break lines with \n + } + + return content.encode("ASCII", **encoding_options).encode('UTF-8') + end + + def debug_log_list(message, filepath, list) + msg = "#{message} in #{filepath}:" + if list.empty? + msg += " " + else + msg += "\n" + list.each do |item| + msg += " - #{item}\n" + end + end + + @loginator.log( msg, Verbosity::DEBUG ) + @loginator.log( '', Verbosity::DEBUG ) + end + +end diff --git a/lib/ceedling/test_includes_extractor.rb b/lib/ceedling/test_includes_extractor.rb deleted file mode 100644 index e94dd7d3e..000000000 --- a/lib/ceedling/test_includes_extractor.rb +++ /dev/null @@ -1,111 +0,0 @@ - -class TestIncludesExtractor - - constructor :configurator, :yaml_wrapper, :file_wrapper - - def setup - @includes = {} - @mocks = {} - end - - - # for includes_list file, slurp up array from yaml file and sort & store includes - def parse_includes_list(includes_list) - gather_and_store_includes( includes_list, @yaml_wrapper.load(includes_list) ) - end - - # open, scan for, and sort & store includes of test file - def parse_test_file(test) - gather_and_store_includes( test, extract_from_file(test) ) - end - - # open, scan for, and sort & store includes of test file - def parse_test_file_source_include(test) - return extract_source_include_from_file(test) - end - - # mocks with no file extension - def lookup_raw_mock_list(test) - file_key = form_file_key(test) - return [] if @mocks[file_key].nil? - return @mocks[file_key] - end - - # includes with file extension - def lookup_includes_list(file) - file_key = form_file_key(file) - return [] if (@includes[file_key]).nil? - return @includes[file_key] - end - - private ################################# - - def form_file_key(filepath) - return File.basename(filepath).to_sym - end - - def extract_from_file(file) - includes = [] - header_extension = @configurator.extension_header - - contents = @file_wrapper.read(file) - - # remove line comments - contents = contents.gsub(/\/\/.*$/, '') - # remove block comments - contents = contents.gsub(/\/\*.*?\*\//m, '') - - contents.split("\n").each do |line| - # look for include statement - scan_results = line.scan(/#\s*include\s+\"\s*(.+#{'\\'+header_extension})\s*\"/) - - includes << scan_results[0][0] if (scan_results.size > 0) - - # look for TEST_FILE statement - scan_results = line.scan(/TEST_FILE\(\s*\"\s*(.+\.\w+)\s*\"\s*\)/) - - includes << scan_results[0][0] if (scan_results.size > 0) - end - - return includes.uniq - end - - def extract_source_include_from_file(file) - source_includes = [] - source_extension = @configurator.extension_source - - contents = @file_wrapper.read(file) - - # remove line comments - contents = contents.gsub(/\/\/.*$/, '') - # remove block comments - contents = contents.gsub(/\/\*.*?\*\//m, '') - - contents.split("\n").each do |line| - # look for include statement - scan_results = line.scan(/#include\s+\"\s*(.+#{'\\'+source_extension})\s*\"/) - - source_includes << scan_results[0][0] if (scan_results.size > 0) - end - - return source_includes.uniq - end - - def gather_and_store_includes(file, includes) - mock_prefix = @configurator.cmock_mock_prefix - header_extension = @configurator.extension_header - file_key = form_file_key(file) - @mocks[file_key] = [] - - # add includes to lookup hash - @includes[file_key] = includes - - includes.each do |include_file| - # check if include is a mock - scan_results = include_file.scan(/(.*#{mock_prefix}.+)#{'\\'+header_extension}/) - # add mock to lookup hash - @mocks[file_key] << scan_results[0][0] if (scan_results.size > 0) - end - end - -end diff --git a/lib/ceedling/test_invoker.rb b/lib/ceedling/test_invoker.rb index 911634c0c..d388cfade 100644 --- a/lib/ceedling/test_invoker.rb +++ b/lib/ceedling/test_invoker.rb @@ -1,165 +1,505 @@ -require 'ceedling/constants' +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= +require 'ceedling/constants' +require 'fileutils' class TestInvoker attr_reader :sources, :tests, :mocks - constructor :configurator, + constructor :application, + :configurator, :test_invoker_helper, :plugin_manager, - :streaminator, + :reportinator, + :loginator, + :build_batchinator, :preprocessinator, :task_invoker, - :dependinator, - :project_config_manager, - :build_invoker_utils, + :generator, + :test_context_extractor, :file_path_utils, - :file_wrapper + :file_wrapper, + :file_finder, + :verbosinator def setup - @sources = [] - @tests = [] - @mocks = [] - end + # Master data structure for all test activities + @testables = {} + # For thread-safe operations on @testables + @lock = Mutex.new - # Convert libraries configuration form YAML configuration - # into a string that can be given to the compiler. - def convert_libraries_to_arguments() - args = ((@configurator.project_config_hash[:libraries_test] || []) + ((defined? LIBRARIES_SYSTEM) ? LIBRARIES_SYSTEM : [])).flatten - if (defined? LIBRARIES_FLAG) - args.map! {|v| LIBRARIES_FLAG.gsub(/\$\{1\}/, v) } - end - return args + # Aliases for brevity in code that follows + @helper = @test_invoker_helper + @batchinator = @build_batchinator + @context_extractor = @test_context_extractor end - def get_library_paths_to_arguments() - paths = (defined? PATHS_LIBRARIES) ? (PATHS_LIBRARIES || []).clone : [] - if (defined? LIBRARIES_PATH_FLAG) - paths.map! {|v| LIBRARIES_PATH_FLAG.gsub(/\$\{1\}/, v) } - end - return paths - end + def setup_and_invoke(tests:, context:TEST_SYM, options:{}) + # Wrap everything in an exception handler + begin + # Begin fleshing out the testables data structure + @batchinator.build_step("Preparing Build Paths", heading: false) do + results_path = File.join( @configurator.project_build_root, context.to_s, 'results' ) + + @batchinator.exec(workload: :compile, things: tests) do |filepath| + filepath = filepath.to_s + key = testable_symbolize(filepath) + name = key.to_s + build_path = File.join( @configurator.project_build_root, context.to_s, 'out', name ) + mocks_path = File.join( @configurator.cmock_mock_path, name ) + + preprocess_includes_path = File.join( @configurator.project_test_preprocess_includes_path, name ) + preprocess_files_path = File.join( @configurator.project_test_preprocess_files_path, name ) + + @lock.synchronize do + @testables[key] = { + :filepath => filepath, + :name => name, + :paths => {} + } + + paths = @testables[key][:paths] + paths[:build] = build_path + paths[:results] = results_path + paths[:mocks] = mocks_path if @configurator.project_use_mocks + if @configurator.project_use_test_preprocessor != :none + paths[:preprocess_incudes] = preprocess_includes_path + paths[:preprocess_files] = preprocess_files_path + paths[:preprocess_files_full_expansion] = File.join( preprocess_files_path, PREPROCESS_FULL_EXPANSION_DIR ) + paths[:preprocess_files_directives_only] = File.join( preprocess_files_path, PREPROCESS_DIRECTIVES_ONLY_DIR ) + end + end + + @testables[key][:paths].each {|_, path| @file_wrapper.mkdir( path ) } + end - def setup_and_invoke(tests, context=TEST_SYM, options={:force_run => true, :build_only => false}) + # Remove any left over test results from previous runs + @helper.clean_test_results( results_path, @testables.map{ |_, t| t[:name] } ) + end - @tests = tests + # Collect in-test build directives, #include statements, and test cases from test files. + # (Actions depend on preprocessing configuration) + @batchinator.build_step("Collecting Test Context") do + @batchinator.exec(workload: :compile, things: @testables) do |_, details| + filepath = details[:filepath] + + if @configurator.project_use_test_preprocessor_tests + msg = @reportinator.generate_progress( "Parsing #{File.basename(filepath)} for include path build directive macros" ) + @loginator.log( msg ) + + # Just build directive macro using simple text scanning. + # Other context collected in later steps with help of preprocessing. + @file_wrapper.open( filepath, 'r' ) do |input| + @context_extractor.collect_simple_context( filepath, input, :build_directive_include_paths ) + end + else + msg = @reportinator.generate_progress( "Parsing #{File.basename(filepath)} for build directive macros, #includes, and test case names" ) + @loginator.log( msg ) + + # Collect everything using simple text scanning (no preprocessing involved). + @file_wrapper.open( filepath, 'r' ) do |input| + @context_extractor.collect_simple_context( filepath, input, :all ) + end + end - @project_config_manager.process_test_config_change + end - @tests.each do |test| - # announce beginning of test run - header = "Test '#{test}'" - @streaminator.stdout_puts("\n\n#{header}\n#{'-' * header.length}") + # Validate paths via TEST_INCLUDE_PATH() & augment header file collection from the same + @helper.process_project_include_paths() + end - begin - @plugin_manager.pre_test( test ) - test_name ="#{File.basename(test)}".chomp('.c') - def_test_key="defines_#{test_name.downcase}" + # Fill out testables data structure with build context + @batchinator.build_step("Ingesting Test Configurations") do + framework_defines = @helper.framework_defines() + runner_defines = @helper.runner_defines() + + @batchinator.exec(workload: :compile, things: @testables) do |_, details| + filepath = details[:filepath] + + search_paths = @helper.search_paths( filepath, details[:name] ) + + compile_flags = @helper.flags( context:context, operation:OPERATION_COMPILE_SYM, filepath:filepath ) + preprocess_flags = @helper.preprocess_flags( context:context, compile_flags:compile_flags, filepath:filepath ) + assembler_flags = @helper.flags( context:context, operation:OPERATION_ASSEMBLE_SYM, filepath:filepath ) + link_flags = @helper.flags( context:context, operation:OPERATION_LINK_SYM, filepath:filepath ) + + compile_defines = @helper.compile_defines( context:context, filepath:filepath ) + preprocess_defines = @helper.preprocess_defines( test_defines: compile_defines, filepath:filepath ) + + msg = @reportinator.generate_module_progress( + operation: 'Collecting search paths, flags, and defines', + module_name: details[:name], + filename: File.basename( details[:filepath] ) + ) + @loginator.log( msg ) + + @lock.synchronize do + details[:search_paths] = search_paths + details[:preprocess_flags] = preprocess_flags + details[:compile_flags] = compile_flags + details[:assembler_flags] = assembler_flags + details[:link_flags] = link_flags + details[:compile_defines] = compile_defines + framework_defines + runner_defines + details[:preprocess_defines] = preprocess_defines + framework_defines + end + end + end - if @configurator.project_config_hash.has_key?(def_test_key.to_sym) || @configurator.defines_use_test_definition - defs_bkp = Array.new(COLLECTION_DEFINES_TEST_AND_VENDOR) - tst_defs_cfg = Array.new(defs_bkp) - if @configurator.project_config_hash.has_key?(def_test_key.to_sym) - tst_defs_cfg.replace(@configurator.project_config_hash[def_test_key.to_sym]) - tst_defs_cfg .concat(COLLECTION_DEFINES_VENDOR) if COLLECTION_DEFINES_VENDOR + # Collect include statements & mocks from test files + @batchinator.build_step("Collecting Test Context") do + @batchinator.exec(workload: :compile, things: @testables) do |_, details| + arg_hash = { + filepath: details[:filepath], + test: details[:name], + flags: details[:preprocess_flags], + include_paths: details[:search_paths], + defines: details[:preprocess_defines] + } + + msg = @reportinator.generate_module_progress( + operation: 'Preprocessing #include statements for', + module_name: arg_hash[:test], + filename: File.basename( arg_hash[:filepath] ) + ) + @loginator.log( msg ) + + @helper.extract_include_directives( arg_hash ) + end + end if @configurator.project_use_test_preprocessor_tests + + # Determine Runners & Mocks For All Tests + @batchinator.build_step("Determining Files to be Generated", heading: false) do + @batchinator.exec(workload: :compile, things: @testables) do |test, details| + runner_filepath = @file_path_utils.form_runner_filepath_from_test( details[:filepath] ) + + mocks = {} + mocks_list = @configurator.project_use_mocks ? @context_extractor.lookup_raw_mock_list( details[:filepath] ) : [] + mocks_list.each do |name| + source = @helper.find_header_input_for_mock( name, details[:search_paths] ) + preprocessed_input = @file_path_utils.form_preprocessed_file_filepath( source, details[:name] ) + mocks[name.to_sym] = { + :name => name, + :source => source, + :input => (@configurator.project_use_test_preprocessor_mocks ? preprocessed_input : source) + } end - if @configurator.defines_use_test_definition - tst_defs_cfg << File.basename(test, ".*").strip.upcase.sub(/@.*$/, "") + + @lock.synchronize do + details[:runner] = { + :output_filepath => runner_filepath, + :input_filepath => details[:filepath] # Default of the test file + } + details[:mocks] = mocks + details[:mock_list] = mocks_list + + # Trigger pre_test plugin hook after having assembled all testing context + @plugin_manager.pre_test( details[:filepath] ) end - COLLECTION_DEFINES_TEST_AND_VENDOR.replace(tst_defs_cfg) end + end - # redefine the project out path and preprocessor defines - if @configurator.project_config_hash.has_key?(def_test_key.to_sym) - @streaminator.stdout_puts("Updating test definitions for #{test_name}", Verbosity::NORMAL) - orig_path = @configurator.project_test_build_output_path - @configurator.project_config_hash[:project_test_build_output_path] = File.join(@configurator.project_test_build_output_path, test_name) - @file_wrapper.mkdir(@configurator.project_test_build_output_path) + # Create inverted/flattened mock lookup list to take advantage of threading + # (Iterating each testable and mock list instead would limits the number of simultaneous mocking threads) + mocks = [] + if @configurator.project_use_mocks + @testables.each do |_, details| + details[:mocks].each do |name, elems| + mocks << {:name => name, :details => elems, :testable => details} + end end + end - # collect up test fixture pieces & parts - runner = @file_path_utils.form_runner_filepath_from_test( test ) - mock_list = @preprocessinator.preprocess_test_and_invoke_test_mocks( test ) - sources = @test_invoker_helper.extract_sources( test ) - extras = @configurator.collection_test_fixture_extra_link_objects - core = [test] + mock_list + sources - objects = @file_path_utils.form_test_build_objects_filelist( [runner] + core + extras ).uniq - results_pass = @file_path_utils.form_pass_results_filepath( test ) - results_fail = @file_path_utils.form_fail_results_filepath( test ) + # Preprocess Header Files + @batchinator.build_step("Preprocessing for Mocks") { + @batchinator.exec(workload: :compile, things: mocks) do |mock| + details = mock[:details] + testable = mock[:testable] + + arg_hash = { + filepath: details[:source], + test: testable[:name], + flags: testable[:preprocess_flags], + include_paths: testable[:search_paths], + defines: testable[:preprocess_defines] + } + + @preprocessinator.preprocess_mockable_header_file( **arg_hash ) + end + } if @configurator.project_use_mocks and @configurator.project_use_test_preprocessor_mocks + + # Generate mocks for all tests + @batchinator.build_step("Mocking") { + @batchinator.exec(workload: :compile, things: mocks) do |mock| + details = mock[:details] + testable = mock[:testable] + + arg_hash = { + context: context, + mock: mock[:name], + test: testable[:name], + input_filepath: details[:input], + output_path: testable[:paths][:mocks] + } + + @generator.generate_mock(**arg_hash) + end + } if @configurator.project_use_mocks - # identify all the objects shall not be linked and then remove them from objects list. - no_link_objects = @file_path_utils.form_test_build_objects_filelist(@preprocessinator.preprocess_shallow_source_includes( test )) - objects = objects.uniq - no_link_objects + # Preprocess test files + @batchinator.build_step("Preprocessing Test Files") { + @batchinator.exec(workload: :compile, things: @testables) do |_, details| - @project_config_manager.process_test_defines_change(@project_config_manager.filter_internal_sources(sources)) + arg_hash = { + filepath: details[:filepath], + test: details[:name], + flags: details[:preprocess_flags], + include_paths: details[:search_paths], + defines: details[:preprocess_defines] + } - # clean results files so we have a missing file with which to kick off rake's dependency rules - @test_invoker_helper.clean_results( {:pass => results_pass, :fail => results_fail}, options ) + filepath = @preprocessinator.preprocess_test_file(**arg_hash) - # load up auxiliary dependencies so deep changes cause rebuilding appropriately - @test_invoker_helper.process_deep_dependencies( core ) do |dependencies_list| - @dependinator.load_test_object_deep_dependencies( dependencies_list ) - end + # Replace default input with preprocessed file + @lock.synchronize { details[:runner][:input_filepath] = filepath } - # tell rake to create test runner if needed - @task_invoker.invoke_test_runner( runner ) + # Collect sources added to test build with TEST_SOURCE_FILE() directive macro + # TEST_SOURCE_FILE() can be within #ifdef's--this retrieves them + @file_wrapper.open( filepath, 'r' ) do |input| + @context_extractor.collect_simple_context( details[:filepath], input, :build_directive_source_files ) + end - # enhance object file dependencies to capture externalities influencing regeneration - @dependinator.enhance_test_build_object_dependencies( objects ) + # Validate test build directive source file entries via TEST_SOURCE_FILE() + @testables.each do |_, details| + @helper.validate_build_directive_source_files( test:details[:name], filepath:details[:filepath] ) + end + end + } if @configurator.project_use_test_preprocessor_tests - # associate object files with executable - @dependinator.enhance_test_executable_dependencies( test, objects ) + # Collect test case names + @batchinator.build_step("Collecting Test Context") { + @batchinator.exec(workload: :compile, things: @testables) do |_, details| - # build test objects - @task_invoker.invoke_test_objects( objects ) + msg = @reportinator.generate_module_progress( + operation: 'Parsing test case names', + module_name: details[:name], + filename: File.basename( details[:filepath] ) + ) + @loginator.log( msg ) - # if the option build_only has been specified, build only the executable - # but don't run the test - if (options[:build_only]) - executable = @file_path_utils.form_test_executable_filepath( test ) - @task_invoker.invoke_test_executable( executable ) - else - # 3, 2, 1... launch - @task_invoker.invoke_test_results( results_pass ) + @context_extractor.collect_test_runner_details( details[:filepath], details[:runner][:input_filepath] ) + end + } if @configurator.project_use_test_preprocessor_tests + + # Generate runners for all tests + @batchinator.build_step("Test Runners") do + @batchinator.exec(workload: :compile, things: @testables) do |_, details| + arg_hash = { + context: context, + mock_list: details[:mock_list], + includes_list: @test_context_extractor.lookup_header_includes_list( details[:filepath] ), + test_filepath: details[:filepath], + input_filepath: details[:runner][:input_filepath], + runner_filepath: details[:runner][:output_filepath] + } + + @generator.generate_test_runner(**arg_hash) end - rescue => e - @build_invoker_utils.process_exception( e, context ) - ensure - @plugin_manager.post_test( test ) - # restore the project test defines - if @configurator.project_config_hash.has_key?(def_test_key.to_sym) || @configurator.defines_use_test_definition - COLLECTION_DEFINES_TEST_AND_VENDOR.replace(defs_bkp) - if @configurator.project_config_hash.has_key?(def_test_key.to_sym) - @configurator.project_config_hash[:project_test_build_output_path] = orig_path - @streaminator.stdout_puts("Restored defines and build path to standard", Verbosity::NORMAL) + end + + # Determine objects required for each test + @batchinator.build_step("Determining Artifacts to Be Built", heading: false) do + @batchinator.exec(workload: :compile, things: @testables) do |test, details| + # Source files referenced by conventions or specified by build directives in a test file + test_sources = @helper.extract_sources( details[:filepath] ) + test_core = test_sources + @helper.form_mock_filenames( details[:mock_list] ) + + # When we have a mock and an include for the same file, the mock wins + @helper.remove_mock_original_headers( test_core, details[:mock_list] ) + + # CMock + Unity + CException + test_frameworks = @helper.collect_test_framework_sources( !details[:mock_list].empty? ) + + # Extra suport source files (e.g. microcontroller startup code needed by simulator) + test_support = @configurator.collection_all_support + + compilations = [] + compilations << details[:filepath] + compilations += test_core + compilations << details[:runner][:output_filepath] + compilations += test_frameworks + compilations += test_support + compilations.uniq! + + test_objects = @file_path_utils.form_test_build_objects_filelist( details[:paths][:build], compilations ) + + test_executable = @file_path_utils.form_test_executable_filepath( details[:paths][:build], details[:filepath] ) + test_pass = @file_path_utils.form_pass_results_filepath( details[:paths][:results], details[:filepath] ) + test_fail = @file_path_utils.form_fail_results_filepath( details[:paths][:results], details[:filepath] ) + + # Identify all the objects shall not be linked and then remove them from objects list. + test_no_link_objects = + @file_path_utils.form_test_build_objects_filelist( + details[:paths][:build], + @helper.fetch_shallow_source_includes( details[:filepath] )) + + test_objects = test_objects.uniq - test_no_link_objects + + @lock.synchronize do + details[:sources] = test_sources + details[:frameworks] = test_frameworks + details[:core] = test_core + details[:objects] = test_objects + details[:executable] = test_executable + details[:no_link_objects] = test_no_link_objects + details[:results_pass] = test_pass + details[:results_fail] = test_fail + details[:tool] = TOOLS_TEST_COMPILER end end end - # store away what's been processed - @mocks.concat( mock_list ) - @sources.concat( sources ) + # Build All Test objects + @batchinator.build_step("Building Objects") do + @testables.each do |_, details| + details[:objects].each do |obj| + src = @file_finder.find_build_input_file(filepath: obj, context: context) + compile_test_component(tool: details[:tool], context: context, test: details[:name], source: src, object: obj, msg: details[:msg]) + end + end + end - @task_invoker.first_run = false + # Create test binary + @batchinator.build_step("Building Test Executables") do + lib_args = @helper.convert_libraries_to_arguments() + lib_paths = @helper.get_library_paths_to_arguments() + @batchinator.exec(workload: :compile, things: @testables) do |_, details| + arg_hash = { + context: context, + build_path: details[:paths][:build], + executable: details[:executable], + objects: details[:objects], + flags: details[:link_flags], + lib_args: lib_args, + lib_paths: lib_paths, + options: options + } + + @helper.generate_executable_now(**arg_hash) + end + end + + # Execute Final Tests + @batchinator.build_step("Executing") { + @batchinator.exec(workload: :test, things: @testables) do |_, details| + begin + arg_hash = { + context: context, + test_name: details[:name], + test_filepath: details[:filepath], + executable: details[:executable], + result: details[:results_pass], + options: options + } + + @helper.run_fixture_now(**arg_hash) + + # Handle exceptions so we can ensure post_test() is called. + # A lone `ensure` includes an implicit rescuing of StandardError + # with the exception continuing up the call trace. + ensure + @plugin_manager.post_test( details[:filepath] ) + end + end + } unless options[:build_only] + + # Handle application-level exceptions. + # StandardError is the parent class of all application-level exceptions. + # Runtime errors (parent is Exception) continue on up to be handled by Ruby itself. + rescue StandardError => ex + @application.register_build_failure + + @loginator.log( ex.message, Verbosity::ERRORS, LogLabels::EXCEPTION ) + + # Debug backtrace (only if debug verbosity) + @loginator.log_debug_backtrace( ex ) end + end - # post-process collected mock list - @mocks.uniq! + def each_test_with_sources + @testables.each do |test, details| + yield(test.to_s, lookup_sources(test:test)) + end + end - # post-process collected sources list - @sources.uniq! + def lookup_sources(test:) + _test = test.is_a?(Symbol) ? test : test.to_sym + return (@testables[_test])[:sources] end + def compile_test_component(tool:, context:TEST_SYM, test:, source:, object:, msg:nil) + testable = @testables[test.to_sym] + filepath = testable[:filepath] + defines = testable[:compile_defines] + + # Tailor search path: + # 1. Remove duplicates. + # 2. If it's compilations of vendor / support files, reduce paths to only framework & support paths + # (e.g. we don't need all search paths to compile unity.c). + search_paths = @helper.tailor_search_paths(search_paths:testable[:search_paths], filepath:source) + + # C files (user-configured extension or core framework file extensions) + if @file_wrapper.extname(source) != @configurator.extension_assembly + flags = testable[:compile_flags] + + arg_hash = { + tool: tool, + module_name: test, + context: context, + source: source, + object: object, + search_paths: search_paths, + flags: flags, + defines: defines, + list: @file_path_utils.form_test_build_list_filepath( object ), + dependencies: @file_path_utils.form_test_dependencies_filepath( object ), + msg: msg + } + + @generator.generate_object_file_c(**arg_hash) + + # Assembly files + elsif @configurator.test_build_use_assembly + flags = testable[:assembler_flags] + + arg_hash = { + tool: tool, + module_name: test, + context: context, + source: source, + object: object, + search_paths: search_paths, + flags: flags, + defines: defines, # Generally ignored by assemblers + list: @file_path_utils.form_test_build_list_filepath( object ), + dependencies: @file_path_utils.form_test_dependencies_filepath( object ), + msg: msg + } + + @generator.generate_object_file_asm(**arg_hash) + end + end - def refresh_deep_dependencies - @file_wrapper.rm_f( - @file_wrapper.directory_listing( - File.join( @configurator.project_test_dependencies_path, '*' + @configurator.extension_dependencies ) ) ) + private - @test_invoker_helper.process_deep_dependencies( - @configurator.collection_all_tests + @configurator.collection_all_source ) + def testable_symbolize(filepath) + return (File.basename( filepath ).ext('')).to_sym end end diff --git a/lib/ceedling/test_invoker_helper.rb b/lib/ceedling/test_invoker_helper.rb index 403d93e3e..d3940f1b6 100644 --- a/lib/ceedling/test_invoker_helper.rb +++ b/lib/ceedling/test_invoker_helper.rb @@ -1,32 +1,347 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'ceedling/exceptions' class TestInvokerHelper - constructor :configurator, :task_invoker, :test_includes_extractor, :file_finder, :file_path_utils, :file_wrapper + constructor :configurator, + :loginator, + :build_batchinator, + :task_invoker, + :test_context_extractor, + :include_pathinator, + :preprocessinator, + :defineinator, + :flaginator, + :file_finder, + :file_path_utils, + :file_wrapper, + :generator, + :test_runner_manager + + def setup() + # Alias for brevity + @batchinator = @build_batchinator + end - def clean_results(results, options) - @file_wrapper.rm_f( results[:fail] ) - @file_wrapper.rm_f( results[:pass] ) if (options[:force_run]) + def process_project_include_paths() + @include_pathinator.validate_test_build_directive_paths() + headers = @include_pathinator.validate_header_files_collection() + @include_pathinator.augment_environment_header_files( headers ) end - def process_deep_dependencies(files) - return if (not @configurator.project_use_deep_dependencies) + def extract_include_directives(arg_hash) + # Run test file through preprocessor to parse out include statements and then collect header files, mocks, etc. + includes = @preprocessinator.preprocess_includes( **arg_hash ) - dependencies_list = @file_path_utils.form_test_dependencies_filelist( files ).uniq + # Store the include statements we found + @test_context_extractor.ingest_includes( arg_hash[:filepath], includes ) + end + + def validate_build_directive_source_files(test:, filepath:) + sources = @test_context_extractor.lookup_build_directive_sources_list(filepath) - if @configurator.project_generate_deep_dependencies - @task_invoker.invoke_test_dependencies_files( dependencies_list ) + ext_message = @configurator.extension_source + if @configurator.test_build_use_assembly + ext_message += " or #{@configurator.extension_assembly}" end - yield( dependencies_list ) if block_given? + sources.each do |source| + valid_extension = true + + # Only C files in test build + if not @configurator.test_build_use_assembly + valid_extension = false if @file_wrapper.extname(source) != @configurator.extension_source + # C and assembly files in test build + else + ext = @file_wrapper.extname(source) + valid_extension = false if (ext != @configurator.extension_assembly) and (ext != @configurator.extension_source) + end + + if not valid_extension + error = "File '#{source}' specified with #{UNITY_TEST_SOURCE_FILE}() in #{test} is not a #{ext_message} source file" + raise CeedlingException.new(error) + end + + if @file_finder.find_build_input_file(filepath: source, complain: :ignore, context: TEST_SYM).nil? + error = "File '#{source}' specified with #{UNITY_TEST_SOURCE_FILE}() in #{test} cannot be found in the source file collection" + raise CeedlingException.new(error) + end + end end - - def extract_sources(test) - sources = [] - includes = @test_includes_extractor.lookup_includes_list(test) - - includes.each { |include| sources << @file_finder.find_compilation_input_file(include, :ignore) } + + def search_paths(filepath, subdir) + paths = [] + + # Start with mock path to ensure any CMock-reworked header files are encountered first + paths << File.join( @configurator.cmock_mock_path, subdir ) if @configurator.project_use_mocks + paths += @include_pathinator.lookup_test_directive_include_paths( filepath ) + paths += @include_pathinator.collect_test_include_paths() + paths += @configurator.collection_paths_support + paths += @configurator.collection_paths_include + paths += @configurator.collection_paths_libraries + paths += @configurator.collection_paths_vendor + paths += @configurator.collection_paths_test_toolchain_include - return sources.compact + return paths.uniq + end + + def framework_defines() + defines = [] + + # Unity defines + defines += @defineinator.defines( topkey:UNITY_SYM, subkey: :defines ) + + # CMock defines + defines += @defineinator.defines( topkey:CMOCK_SYM, subkey: :defines ) + + # CException defines + defines += @defineinator.defines( topkey:CEXCEPTION_SYM, subkey: :defines ) + + return defines.uniq + end + + def tailor_search_paths(filepath:, search_paths:) + _search_paths = [] + + # Unity search paths + if filepath == File.join(PROJECT_BUILD_VENDOR_UNITY_PATH, UNITY_C_FILE) + _search_paths += @configurator.collection_paths_support + _search_paths << PROJECT_BUILD_VENDOR_UNITY_PATH + + # CMock search paths + elsif @configurator.project_use_mocks and + (filepath == File.join(PROJECT_BUILD_VENDOR_CMOCK_PATH, CMOCK_C_FILE)) + _search_paths += @configurator.collection_paths_support + _search_paths << PROJECT_BUILD_VENDOR_UNITY_PATH + _search_paths << PROJECT_BUILD_VENDOR_CMOCK_PATH + _search_paths << PROJECT_BUILD_VENDOR_CEXCEPTION_PATH if @configurator.project_use_exceptions + + # CException search paths + elsif @configurator.project_use_exceptions and + (filepath == File.join(PROJECT_BUILD_VENDOR_CEXCEPTION_PATH, CEXCEPTION_C_FILE)) + _search_paths += @configurator.collection_paths_support + _search_paths << PROJECT_BUILD_VENDOR_CEXCEPTION_PATH + + # Support files search paths + elsif (@configurator.collection_all_support.include?(filepath)) + _search_paths = search_paths + _search_paths += @configurator.collection_paths_support + _search_paths << PROJECT_BUILD_VENDOR_UNITY_PATH + _search_paths << PROJECT_BUILD_VENDOR_CMOCK_PATH if @configurator.project_use_mocks + _search_paths << PROJECT_BUILD_VENDOR_CEXCEPTION_PATH if @configurator.project_use_exceptions + end + + # Not a vendor file, return original search paths + if _search_paths.length == 0 + return search_paths + end + + return _search_paths.uniq + end + + def runner_defines() + return @test_runner_manager.collect_defines() + end + + def compile_defines(context:, filepath:) + # If this context exists ([:defines][context]), use it. Otherwise, default to test context. + context = TEST_SYM unless @defineinator.defines_defined?( context:context ) + + defines = @defineinator.generate_test_definition( filepath:filepath ) + defines += @defineinator.defines( subkey:context, filepath:filepath ) + + return defines.uniq + end + + def preprocess_defines(test_defines:, filepath:) + # Preprocessing defines for the test file + preprocessing_defines = @defineinator.defines( subkey:PREPROCESS_SYM, filepath:filepath, default:nil ) + + # If no defines were set, default to using test_defines + return test_defines if preprocessing_defines.nil? + + # Otherwise, return the defines we looked up + # This includes an explicitly set empty list to override / clear test_defines + return preprocessing_defines + end + + def flags(context:, operation:, filepath:, default:[]) + # If this context + operation exists ([:flags][context][operation]), use it. Otherwise, default to test context. + context = TEST_SYM unless @flaginator.flags_defined?( context:context, operation:operation ) + + return @flaginator.flag_down( context:context, operation:operation, filepath:filepath, default:default ) + end + + def preprocess_flags(context:, compile_flags:, filepath:) + preprocessing_flags = flags( context:context, operation:OPERATION_PREPROCESS_SYM, filepath:filepath, default:nil ) + + # If no flags were set, default to using compile_flags + return compile_flags if preprocessing_flags.nil? + + # Otherwise, return the flags we looked up + # This includes an explicitly set empty list to override / clear compile_flags + return preprocessing_flags + end + + def collect_test_framework_sources(mocks) + sources = [] + + sources << File.join(PROJECT_BUILD_VENDOR_UNITY_PATH, UNITY_C_FILE) + sources << File.join(PROJECT_BUILD_VENDOR_CMOCK_PATH, CMOCK_C_FILE) if @configurator.project_use_mocks and mocks + sources << File.join(PROJECT_BUILD_VENDOR_CEXCEPTION_PATH, CEXCEPTION_C_FILE) if @configurator.project_use_exceptions + + # If we're (a) using mocks (b) a Unity helper is defined and (c) that unity helper includes a source file component, + # then link in the unity_helper object file too. + if @configurator.project_use_mocks + @configurator.cmock_unity_helper_path.each do |helper| + if @file_wrapper.exist?( helper.ext( EXTENSION_SOURCE ) ) + sources << helper + end + end + end + + return sources + end + + def extract_sources(test_filepath) + sources = [] + + # Get any additional source files specified by TEST_SOURCE_FILE() in test file + _sources = @test_context_extractor.lookup_build_directive_sources_list(test_filepath) + _sources.each do |source| + sources << @file_finder.find_build_input_file(filepath: source, complain: :ignore, context: TEST_SYM) + end + + _support_headers = COLLECTION_ALL_SUPPORT.map { |filepath| File.basename(filepath).ext(EXTENSION_HEADER) } + + # Get all #include .h files from test file so we can find any source files by convention + includes = @test_context_extractor.lookup_full_header_includes_list(test_filepath) + includes.each do |include| + _basename = File.basename(include) + next if _basename == UNITY_H_FILE # Ignore Unity in this list + next if _basename.start_with?(CMOCK_MOCK_PREFIX) # Ignore mocks in this list + next if _support_headers.include?(_basename) # Ignore any sources in our support files list + + sources << @file_finder.find_build_input_file(filepath: include, complain: :ignore, context: TEST_SYM) + end + + # Remove any nil or duplicate entries in list + return sources.compact.uniq + end + + def fetch_shallow_source_includes(test_filepath) + return @test_context_extractor.lookup_source_includes_list(test_filepath) + end + + def fetch_include_search_paths_for_test_file(test_filepath) + return @test_context_extractor.lookup_include_paths_list(test_filepath) + end + + # TODO: Use search_paths to find/match header file from which to generate mock + # Today, this is just a pass-through wrapper + def find_header_input_for_mock(mock, search_paths) + return @file_finder.find_header_input_for_mock( mock ) + end + + # Transform list of mock names into filenames with source extension + def form_mock_filenames(mocklist) + return mocklist.map {|mock| mock + @configurator.extension_source} + end + + def remove_mock_original_headers( filelist, mocklist ) + filelist.delete_if do |filepath| + # Create a simple mock name from the filepath => mock prefix + filepath base name with no extension + mock_name = @configurator.cmock_mock_prefix + File.basename( filepath, '.*' ) + # Tell `delete_if()` logic to remove inspected filepath if simple mocklist includes the name we just generated + mocklist.include?( mock_name ) + end + end + + def clean_test_results(path, tests) + tests.each do |test| + @file_wrapper.rm_f( Dir.glob( File.join( path, test + '.*' ) ) ) + end + end + + # Convert libraries configuration form YAML configuration + # into a string that can be given to the compiler. + def convert_libraries_to_arguments() + args = ((@configurator.project_config_hash[:libraries_test] || []) + ((defined? LIBRARIES_SYSTEM) ? LIBRARIES_SYSTEM : [])).flatten + if (defined? LIBRARIES_FLAG) + args.map! {|v| LIBRARIES_FLAG.gsub(/\$\{1\}/, v) } + end + return args + end + + def get_library_paths_to_arguments() + paths = (defined? PATHS_LIBRARIES) ? (PATHS_LIBRARIES || []).clone : [] + if (defined? LIBRARIES_PATH_FLAG) + paths.map! {|v| LIBRARIES_PATH_FLAG.gsub(/\$\{1\}/, v) } + end + return paths + end + + def generate_executable_now(context:, build_path:, executable:, objects:, flags:, lib_args:, lib_paths:, options:) + begin + @generator.generate_executable_file( + options[:test_linker], + context, + objects.map{|v| "\"#{v}\""}, + flags, + executable, + @file_path_utils.form_test_build_map_filepath( build_path, executable ), + lib_args, + lib_paths ) + rescue ShellException => ex + if ex.shell_result[:output] =~ /symbol/i + notice = "If the linker reports missing symbols, the following may be to blame:\n" + + " 1. This test lacks #include statements corresponding to needed source files (see note below).\n" + + " 2. Project file paths omit source files corresponding to #include statements in this test.\n" + + " 3. Complex macros, #ifdefs, etc. have obscured correct #include statements in this test.\n" + + " 4. Your project is attempting to mix C++ and C file extensions (not supported).\n" + if (@configurator.project_use_mocks) + notice += " 5. This test does not #include needed mocks (that triggers their generation).\n" + end + + notice += "\n" + notice += "NOTE: A test file directs the build of a test executable with #include statemetns:\n" + + " * By convention, Ceedling assumes header filenames correspond to source filenames.\n" + + " * Which code files to compile and link are determined by #include statements.\n" + if (@configurator.project_use_mocks) + notice += " * An #include statement convention directs the generation of mocks from header files.\n" + end + + notice += "\n" + notice += "OPTIONS:\n" + + " 1. Doublecheck this test's #include statements.\n" + + " 2. Simplify complex macros or fully specify symbols for this test in :project ↳ :defines.\n" + + " 3. If no header file corresponds to the needed source file, use the #{UNITY_TEST_SOURCE_FILE}()\n" + + " build diective macro in this test to inject a source file into the build.\n\n" + + "See the docs on conventions, paths, preprocessing, compilation symbols, and build directive macros.\n\n" + + # Print helpful notice + @loginator.log( notice, Verbosity::COMPLAIN, LogLabels::NOTICE ) + end + + # Re-raise the exception + raise ex + end + end + + def run_fixture_now(context:, test_name:, test_filepath:, executable:, result:, options:) + @generator.generate_test_results( + tool: options[:test_fixture], + context: context, + test_name: test_name, + test_filepath: test_filepath, + executable: executable, + result: result) end end diff --git a/lib/ceedling/test_runner_manager.rb b/lib/ceedling/test_runner_manager.rb new file mode 100644 index 000000000..47c2ee356 --- /dev/null +++ b/lib/ceedling/test_runner_manager.rb @@ -0,0 +1,47 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'ceedling/constants' + +class TestRunnerManager + + def initialize() + @test_case_incl = nil + @test_case_excl = nil + @test_runner_defines = [] + end + + def configure_build_options(config) + cmdline_args = config[:test_runner][:cmdline_args] + + # Should never happen because of external config handling, but... + return if cmdline_args.nil? + + @test_runner_defines << RUNNER_BUILD_CMDLINE_ARGS_DEFINE if cmdline_args + end + + def configure_runtime_options(include_test_case, exclude_test_case) + if !include_test_case.empty? + @test_case_incl = "-f #{include_test_case}" + end + + if !exclude_test_case.empty? + @test_case_excl = "-x #{exclude_test_case}" + end + end + + # Return test case arguments (empty if not set) + def collect_cmdline_args() + return [ @test_case_incl, @test_case_excl ].compact() + end + + # Return ['UNITY_USE_COMMAND_LINE_ARGS'] #define required by Unity to enable cmd line arguments + def collect_defines() + return @test_runner_defines + end + +end diff --git a/lib/ceedling/tool_executor.rb b/lib/ceedling/tool_executor.rb index 78e5f869a..54df2022b 100644 --- a/lib/ceedling/tool_executor.rb +++ b/lib/ceedling/tool_executor.rb @@ -1,119 +1,121 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'ceedling/constants' +require 'ceedling/exceptions' require 'benchmark' -class ShellExecutionException < RuntimeError - attr_reader :shell_result - def initialize(shell_result) - @shell_result = shell_result - end -end - class ToolExecutor - constructor :configurator, :tool_executor_helper, :streaminator, :system_wrapper - - def setup - @tool_name = '' - @executable = '' - end + constructor :configurator, :tool_executor_helper, :loginator, :verbosinator, :system_wrapper # build up a command line from yaml provided config - # @param extra_params is an array of parameters to append to executable + # @param extra_params is an array of parameters to append to executable (prepend to rest of command line) def build_command_line(tool_config, extra_params, *args) - @tool_name = tool_config[:name] - @executable = tool_config[:executable] - command = {} - # basic premise is to iterate top to bottom through arguments using '$' as - # a string replacement indicator to expand globals or inline yaml arrays - # into command line arguments via substitution strings - # executable must be quoted if it includes spaces (common on windows) - executable = @tool_executor_helper.osify_path_separators( expandify_element(@executable, *args) ) - executable = "\"#{executable}\"" if executable.include?(' ') + command[:name] = tool_config[:name] + command[:executable] = tool_config[:executable] + + command[:options] = {} # Blank to hold options set before `exec()` processes + + # Basic premise is to iterate top to bottom through arguments using '$' as + # a string replacement indicator to expand globals or inline yaml arrays + # into command line arguments via substitution strings. + executable = @tool_executor_helper.osify_path_separators( + expandify_element(tool_config[:name], tool_config[:executable], *args) + ) + command[:line] = [ executable, extra_params.join(' ').strip, - build_arguments(tool_config[:arguments], *args), + build_arguments(tool_config[:name], tool_config[:arguments], *args), ].reject{|s| s.nil? || s.empty?}.join(' ').strip - command[:options] = { - :stderr_redirect => @tool_executor_helper.stderr_redirection(tool_config, @configurator.project_logging), - :background_exec => tool_config[:background_exec] - } + # Log command as is + @loginator.log( "Command: #{command}", Verbosity::DEBUG ) + + # Update executable after any expansion + command[:executable] = executable return command end # shell out, execute command, and return response - def exec(command, options={}, args=[]) + def exec(command, args=[]) + options = command[:options] + options[:boom] = true if (options[:boom].nil?) options[:stderr_redirect] = StdErrRedirect::NONE if (options[:stderr_redirect].nil?) - options[:background_exec] = BackgroundExec::NONE if (options[:background_exec].nil?) - # build command line + + # Build command line command_line = [ - @tool_executor_helper.background_exec_cmdline_prepend( options ), - command.strip, + command[:line].strip, args, @tool_executor_helper.stderr_redirect_cmdline_append( options ), - @tool_executor_helper.background_exec_cmdline_append( options ), ].flatten.compact.join(' ') - @streaminator.stderr_puts("Verbose: #{__method__}(): #{command_line}", Verbosity::DEBUG) - shell_result = {} - # depending on background exec option, we shell out differently - time = Benchmark.realtime do - if (options[:background_exec] != BackgroundExec::NONE) - shell_result = @system_wrapper.shell_system( command_line, options[:boom] ) - else - shell_result = @system_wrapper.shell_backticks( command_line, options[:boom] ) + # Wrap system level tool execution in exception handling + begin + time = Benchmark.realtime do + shell_result = @system_wrapper.shell_capture3( command:command_line, boom:options[:boom] ) + end + shell_result[:time] = time + + # Ultimately, re-raise the exception as ShellException populated with the exception message + rescue => error + raise ShellException.new( name:pretty_tool_name( command ), message: error.message ) + + # Be sure to log what we can + ensure + # Scrub the string for illegal output + unless shell_result[:output].nil? + shell_result[:output] = shell_result[:output].scrub if "".respond_to?(:scrub) + shell_result[:output].gsub!(/\033\[\d\dm/,'') end - end - shell_result[:time] = time - #scrub the string for illegal output - unless shell_result[:output].nil? - shell_result[:output] = shell_result[:output].scrub if "".respond_to?(:scrub) - shell_result[:output].gsub!(/\033\[\d\dm/,'') + @tool_executor_helper.log_results( command_line, shell_result ) end - @tool_executor_helper.print_happy_results( command_line, shell_result, options[:boom] ) - @tool_executor_helper.print_error_results( command_line, shell_result, options[:boom] ) - - # go boom if exit code isn't 0 (but in some cases we don't want a non-0 exit code to raise) - raise ShellExecutionException.new(shell_result) if ((shell_result[:exit_code] != 0) and options[:boom]) + # Go boom if exit code is not 0 and that code means a fatal error + # (Sometimes we don't want a non-0 exit code to cause an exception as the exit code may not mean a build-ending failure) + if ((shell_result[:exit_code] != 0) and options[:boom]) + raise ShellException.new( shell_result:shell_result, name:pretty_tool_name( command ) ) + end return shell_result end - private ############################# - def build_arguments(config, *args) + def build_arguments(tool_name, config, *args) build_string = '' return nil if (config.nil?) - # iterate through each argument + # Iterate through each argument - # the yaml blob array needs to be flattened so that yaml substitution - # is handled correctly, since it creates a nested array when an anchor is - # dereferenced + # The yaml blob array needs to be flattened so that yaml alias substitution is handled + # correctly as it creates a nested array when an anchor is dereferenced config.flatten.each do |element| argument = '' case(element) - # if we find a simple string then look for string replacement operators + # If we find a simple string then look for string replacement operators # and expand with the parameters in this method's argument list - when String then argument = expandify_element(element, *args) - # if we find a hash, then we grab the key as a substitution string and expand the + when String then argument = expandify_element(tool_name, element, *args) + # If we find a hash, then we grab the key as a substitution string and expand the # hash's value(s) within that substitution string - when Hash then argument = dehashify_argument_elements(element) + when Hash then argument = dehashify_argument_elements(tool_name, element) end build_string.concat("#{argument} ") if (argument.length > 0) @@ -126,7 +128,7 @@ def build_arguments(config, *args) # handle simple text string argument & argument array string replacement operators - def expandify_element(element, *args) + def expandify_element(tool_name, element, *args) match = // to_process = nil args_index = 0 @@ -136,8 +138,8 @@ def expandify_element(element, *args) args_index = ($2.to_i - 1) if (args.nil? or args[args_index].nil?) - @streaminator.stderr_puts("ERROR: Tool '#{@tool_name}' expected valid argument data to accompany replacement operator #{$1}.", Verbosity::ERRORS) - raise + error = "Tool '#{tool_name}' expected valid argument data to accompany replacement operator #{$1}." + raise CeedlingException.new( error ) end match = /#{Regexp.escape($1)}/ @@ -148,11 +150,6 @@ def expandify_element(element, *args) element.sub!(/\\\$/, '$') element.strip! - # handle inline ruby execution - if (element =~ RUBY_EVAL_REPLACEMENT_PATTERN) - element.replace(eval($1)) - end - build_string = '' # handle array or anything else passed into method to be expanded in place of replacement operators @@ -171,7 +168,7 @@ def expandify_element(element, *args) # handle argument hash: keys are substitution strings, values are data to be expanded within substitution strings - def dehashify_argument_elements(hash) + def dehashify_argument_elements(tool_name, hash) build_string = '' elements = [] @@ -181,37 +178,34 @@ def dehashify_argument_elements(hash) expand = hash[hash.keys[0]] if (expand.nil?) - @streaminator.stderr_puts("ERROR: Tool '#{@tool_name}' could not expand nil elements for substitution string '#{substitution}'.", Verbosity::ERRORS) - raise + error = "Tool '#{tool_name}' could not expand nil elements for substitution string '#{substitution}'." + raise CeedlingException.new( error ) end # array-ify expansion input if only a single string expansion = ((expand.class == String) ? [expand] : expand) expansion.each do |item| - # code eval substitution - if (item =~ RUBY_EVAL_REPLACEMENT_PATTERN) - elements << eval($1) - # string eval substitution - elsif (item =~ RUBY_STRING_REPLACEMENT_PATTERN) + # String eval substitution + if (item =~ RUBY_STRING_REPLACEMENT_PATTERN) elements << @system_wrapper.module_eval(item) - # global constants + # Global constants elsif (@system_wrapper.constants_include?(item)) const = Object.const_get(item) if (const.nil?) - @streaminator.stderr_puts("ERROR: Tool '#{@tool_name}' found constant '#{item}' to be nil.", Verbosity::ERRORS) - raise + error = "Tool '#{tool_name}' found constant '#{item}' to be nil." + raise CeedlingException.new( error ) else elements << const end elsif (item.class == Array) elements << item elsif (item.class == String) - @streaminator.stderr_puts("ERROR: Tool '#{@tool_name}' cannot expand nonexistent value '#{item}' for substitution string '#{substitution}'.", Verbosity::ERRORS) - raise + error = "Tool '#{tool_name}' cannot expand nonexistent value '#{item}' for substitution string '#{substitution}'." + raise CeedlingException.new( error ) else - @streaminator.stderr_puts("ERROR: Tool '#{@tool_name}' cannot expand value having type '#{item.class}' for substitution string '#{substitution}'.", Verbosity::ERRORS) - raise + error = "Tool '#{tool_name}' cannot expand value having type '#{item.class}' for substitution string '#{substitution}'." + raise CeedlingException.new( error ) end end @@ -226,4 +220,14 @@ def dehashify_argument_elements(hash) return build_string.strip end + def pretty_tool_name(command) + # Titleize command's name -- each word capitalized plus underscores replaced with spaces + name = "#{command[:name].split(/ |\_/).map(&:capitalize).join(" ")}" + + executable = command[:executable].empty? ? '' : command[:executable] + + # 'Name' (executable) + return "'#{name}' " + "(#{executable})" + end + end diff --git a/lib/ceedling/tool_executor_helper.rb b/lib/ceedling/tool_executor_helper.rb index de4cafe46..0d17d412e 100644 --- a/lib/ceedling/tool_executor_helper.rb +++ b/lib/ceedling/tool_executor_helper.rb @@ -1,52 +1,17 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'ceedling/constants' # for Verbosity enumeration & $stderr redirect enumeration ## # Helper functions for the tool executor class ToolExecutorHelper - constructor :streaminator, :system_utils, :system_wrapper - - ## - # Returns the stderr redirection based on the config and logging. - # ==== Attributes - # - # * _tool_config_: A hash containing config information. - # * _logging_: A boolean representing if logging is enabled or not. - # - def stderr_redirection(tool_config, logging) - # if there's no logging enabled, return :stderr_redirect unmodified - return tool_config[:stderr_redirect] if (not logging) - - # if there is logging enabled but the redirect is a custom value (not enum), return the custom string - return tool_config[:stderr_redirect] if (tool_config[:stderr_redirect].class == String) - - # if logging is enabled but there's no custom string, return the AUTO enumeration so $stderr goes into the log - return StdErrRedirect::AUTO - end - - - ## - # Returns the background execution prepend based on the config. - # ==== Attributes - # - # * _tool_config_: A hash containing config information. - # - def background_exec_cmdline_prepend(tool_config) - return nil if (tool_config.nil? || tool_config[:background_exec].nil?) - - config_exec = tool_config[:background_exec] - - if ((config_exec == BackgroundExec::AUTO) and (@system_wrapper.windows?)) - return 'start' - end - - if (config_exec == BackgroundExec::WIN) - return 'start' - end - - return nil - end - + constructor :loginator, :system_utils, :system_wrapper, :verbosinator ## # Modifies an executables path based on platform. @@ -82,7 +47,6 @@ def stderr_redirect_cmdline_append(tool_config) end case redirect - # we may need more complicated processing after some learning with various environments when StdErrRedirect::NONE then nil when StdErrRedirect::WIN then '2>&1' when StdErrRedirect::UNIX then '2>&1' @@ -92,73 +56,52 @@ def stderr_redirect_cmdline_append(tool_config) end ## - # Returns the background execution append based on the config. - # ==== Attributes - # - # * _tool_config_: A hash containing config information. - # - def background_exec_cmdline_append(tool_config) - return nil if (tool_config.nil? || tool_config[:background_exec].nil?) - - config_exec = tool_config[:background_exec] - - # if :auto & windows, then we already prepended 'start' and should append nothing - return nil if ((config_exec == BackgroundExec::AUTO) and (@system_wrapper.windows?)) - - # if :auto & not windows, then we append standard '&' - return '&' if ((config_exec == BackgroundExec::AUTO) and (not @system_wrapper.windows?)) - - # if explicitly Unix, then append '&' - return '&' if (config_exec == BackgroundExec::UNIX) - - # * _command_str_: A hash containing config information. - # all other cases, including :none, :win, & anything unrecognized, append nothing - return nil - end - - ## - # Outputs success results if command succeeded and we have verbosity cranked up. + # Logs tool execution results # ==== Attributes # # * _command_str_: The command ran. - # * _shell_results_: The outputs of the command including exit code and - # output. - # * _boom_: A boolean representing if a non zero result is erroneous. + # * _shell_results_: The outputs of the command including exit code and output. # - def print_happy_results(command_str, shell_result, boom=true) - if ((shell_result[:exit_code] == 0) or ((shell_result[:exit_code] != 0) and not boom)) - output = "> Shell executed command:\n" - output += "'#{command_str}'\n" - output += "> Produced output:\n" if (not shell_result[:output].empty?) - output += "#{shell_result[:output].strip}\n" if (not shell_result[:output].empty?) - output += "> And exited with status: [#{shell_result[:exit_code]}].\n" if (shell_result[:exit_code] != 0) - output += "\n" - - @streaminator.stdout_puts(output, Verbosity::OBNOXIOUS) + def log_results(command_str, shell_result) + # No logging unless we're at least at Obnoxious + return if !@verbosinator.should_output?( Verbosity::OBNOXIOUS ) + + output = "> Shell executed command:\n" + output += "`#{command_str}`\n" + + if !shell_result.empty? + # Detailed debug logging + if @verbosinator.should_output?( Verbosity::DEBUG ) + output += "> With $stdout: " + output += shell_result[:stdout].empty? ? "\n" : "\n#{shell_result[:stdout].strip()}\n" + + output += "> With $stderr: " + output += shell_result[:stderr].empty? ? "\n" : "\n#{shell_result[:stderr].strip()}\n" + + output += "> And terminated with status: #{shell_result[:status]}\n" + + @loginator.log( '', Verbosity::DEBUG ) + @loginator.log( output, Verbosity::DEBUG ) + @loginator.log( '', Verbosity::DEBUG ) + + return # Bail out + end + + # Slightly less verbose obnoxious logging + if !shell_result[:output].empty? + output += "> Produced output: " + output += shell_result[:output].strip().empty? ? "\n" : "\n#{shell_result[:output].strip()}\n" + end + + if !shell_result[:exit_code].nil? + output += "> And terminated with exit code: [#{shell_result[:exit_code]}]\n" + else + output += "> And exited prematurely\n" + end end - end - ## - # Outputs failures results if command failed and we have verbosity set to minimum error level. - # ==== Attributes - # - # * _command_str_: The command ran. - # * _shell_results_: The outputs of the command including exit code and - # output. - # * _boom_: A boolean representing if a non zero result is erroneous. - # - def print_error_results(command_str, shell_result, boom=true) - if ((shell_result[:exit_code] != 0) and boom) - output = "ERROR: Shell command failed.\n" - output += "> Shell executed command:\n" - output += "'#{command_str}'\n" - output += "> Produced output:\n" if (not shell_result[:output].empty?) - output += "#{shell_result[:output].strip}\n" if (not shell_result[:output].empty?) - output += "> And exited with status: [#{shell_result[:exit_code]}].\n" if (shell_result[:exit_code] != nil) - output += "> And then likely crashed.\n" if (shell_result[:exit_code] == nil) - output += "\n" - - @streaminator.stderr_puts(output, Verbosity::ERRORS) - end + @loginator.log( '', Verbosity::OBNOXIOUS ) + @loginator.log( output, Verbosity::OBNOXIOUS ) + @loginator.log( '', Verbosity::OBNOXIOUS ) end end diff --git a/lib/ceedling/tool_validator.rb b/lib/ceedling/tool_validator.rb new file mode 100644 index 000000000..30c567fda --- /dev/null +++ b/lib/ceedling/tool_validator.rb @@ -0,0 +1,149 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'rake' # For ext() +require 'ceedling/constants' +require 'ceedling/tool_executor' # For argument replacement pattern +require 'ceedling/file_path_utils' # For glob handling class methods + + +class ToolValidator + + constructor :file_wrapper, :loginator, :system_wrapper, :reportinator + + def validate(tool:, name:nil, extension:EXTENSION_EXECUTABLE, respect_optional:false, boom:false) + # Redefine name with name inside tool hash if it's not provided + # If the name is provided it's likely the formatted key path into the configuration file + name = tool[:name] if name.nil? or name.empty? + + valid = true + + valid &= validate_executable( tool:tool, name:name, extension:extension, respect_optional:respect_optional, boom:boom ) + valid &= validate_stderr_redirect( tool:tool, name:name, boom:boom ) + + return valid + end + + ### Private ### + + private + + def validate_executable(tool:, name:, extension:, respect_optional:, boom:) + exists = false + error = '' + + # Get unfrozen copy so we can modify for our processing + executable = tool[:executable].dup() + + # Handle a missing :executable + if (executable.nil? or executable.empty?) + error = "Tool #{name} is missing :executable in its configuration." + if !boom + @loginator.log( error, Verbosity::ERRORS ) + return false + end + + raise CeedlingException.new(error) + end + + # If tool is optional and we're respecting that, don't bother to check if executable is legit + return true if tool[:optional] and respect_optional + + # Skip everything if we've got an argument replacement pattern or Ruby string replacement in :executable + # (Allow executable to be validated by shell at run time) + return true if (executable =~ TOOL_EXECUTOR_ARGUMENT_REPLACEMENT_PATTERN) + return true if (executable =~ RUBY_STRING_REPLACEMENT_PATTERN) + + # Extract the executable (including optional filepath) apart from any additional arguments + # Be mindful of legal quote enclosures (e.g. `"Code Cruncher" foo bar`) + executable.strip! + if (matched = executable.match(/^"(.+)"/)) + # If the regex matched, extract contents of match group within parens + executable = matched[1] + else + # Otherwise grab first token before arguments + executable = executable.split(' ')[0] + end + + # If no path included, verify file exists in system search paths + if (not executable.include?('/')) + + # Iterate over search paths + @system_wrapper.search_paths.each do |path| + # File exists as named + if (@file_wrapper.exist?( File.join(path, executable)) ) + exists = true + break + # File exists with executable file extension + elsif (@file_wrapper.exist?( (File.join(path, executable)).ext( extension ) )) + exists = true + break + # We're on Windows and file exists with .exe file extension + elsif (@system_wrapper.windows? and @file_wrapper.exist?( (File.join(path, executable)).ext( EXTENSION_WIN_EXE ) )) + exists = true + break + end + end + + # Construct end of error message + error = "does not exist in system search paths" if not exists + + # If there is a path included, check that explicit filepath exists + else + if @file_wrapper.exist?( executable ) + exists = true + else + # Construct end of error message + error = "does not exist on disk" if not exists + end + end + + if !exists + error = "#{name} ↳ :executable => `#{executable}` " + error + end + + # Raise exception if executable can't be found and boom is set + if !exists and boom + raise CeedlingException.new( error ) + end + + # Otherwise, log error + if !exists + @loginator.log( error, Verbosity::ERRORS ) + end + + return exists + end + + def validate_stderr_redirect(tool:, name:, boom:) + error = '' + redirect = tool[:stderr_redirect] + + # If no redirect set at all, it's cool + return if redirect.nil? + + # Otherwise, process the redirect that's been set + if redirect.class == Symbol + if not StdErrRedirect.constants.map{|constant| constant.to_s}.include?( redirect.to_s.upcase ) + options = StdErrRedirect.constants.map{|constant| ':' + constant.to_s.downcase}.join(', ') + error = "#{name} ↳ :stderr_redirect => :#{redirect} is not a recognized option {#{options}}" + + # Raise exception if requested + raise CeedlingException.new( error ) if boom + + # Otherwise log error + @loginator.log( error, Verbosity::ERRORS ) + return false + end + elsif redirect.class != String + raise CeedlingException.new( "#{name} ↳ :stderr_redirect is neither a recognized value nor custom string" ) + end + + return true + end + +end diff --git a/lib/ceedling/unity_utils.rb b/lib/ceedling/unity_utils.rb deleted file mode 100644 index 2141987c5..000000000 --- a/lib/ceedling/unity_utils.rb +++ /dev/null @@ -1,101 +0,0 @@ -# The Unity utils class, -# Store functions to enable test execution of single test case under test file -# and additional warning definitions -class UnityUtils - attr_reader :test_runner_disabled_replay, :arg_option_map - attr_accessor :test_runner_args, :not_supported - - constructor :configurator - - def setup - @test_runner_disabled_replay = "NOTICE: \n" \ - "The option[s]: %.s \ncannot be applied." \ - 'To enable it, please add `:cmdline_args` under' \ - ' :test_runner option in your project.yml.' - @test_runner_args = '' - @not_supported = '' - - # Refering to Unity implementation of the parser implemented in the unit.c : - # - # case 'l': /* list tests */ - # case 'n': /* include tests with name including this string */ - # case 'f': /* an alias for -n */ - # case 'q': /* quiet */ - # case 'v': /* verbose */ - # case 'x': /* exclude tests with name including this string */ - @arg_option_map = - { - 'test_case' => 'n', - 'list_test_cases' => 'l', - 'run_tests_verbose' => 'v', - 'exclude_test_case' => 'x' - } - end - - # Create test runner args which can be passed to executable test file as - # filter to execute one test case from test file - # - # @param [String, #argument] argument passed after test file name - # e.g.: ceedling test:: - # @param [String, #option] one of the supported by unity arguments. - # At current moment only "test_case_name" to - # run single test - def additional_test_run_args(argument, option) - # Confirm wherever cmdline_args is set to true - # and parsing arguments under generated test runner in Unity is enabled - # and passed argument is not nil - - return nil if argument.nil? - - raise TypeError, 'option expects an arg_option_map key' unless \ - option.is_a?(String) - raise 'Unknown Unity argument option' unless \ - @arg_option_map.key?(option) - - @test_runner_args += " -#{@arg_option_map[option]} #{argument} " - end - - # Return test case arguments - # - # @return [String] formatted arguments for test file - def collect_test_runner_additional_args - @test_runner_args - end - - # Parse passed by user arguments - def create_test_runner_additional_args - if ENV['CEEDLING_INCLUDE_TEST_CASE_NAME'] - if @configurator.project_config_hash[:test_runner_cmdline_args] - additional_test_run_args(ENV['CEEDLING_INCLUDE_TEST_CASE_NAME'], - 'test_case') - else - @not_supported = "\n\t--test_case" - end - end - - if ENV['CEEDLING_EXCLUDE_TEST_CASE_NAME'] - if @configurator.project_config_hash[:test_runner_cmdline_args] - additional_test_run_args(ENV['CEEDLING_EXCLUDE_TEST_CASE_NAME'], - 'exclude_test_case') - else - @not_supported = "\n\t--exclude_test_case" - end - end - print_warning_about_not_enabled_cmdline_args - end - - # Return UNITY_USE_COMMAND_LINE_ARGS define required by Unity to - # compile unity with enabled cmd line arguments - # - # @return [Array] - empty if cmdline_args is not set - def self.update_defines_if_args_enables(in_hash) - in_hash[:test_runner_cmdline_args] ? ['UNITY_USE_COMMAND_LINE_ARGS'] : [] - end - - # Print on output console warning about lack of support for single test run - # if cmdline_args is not set to true in project.yml file, that - def print_warning_about_not_enabled_cmdline_args - puts(format(@test_runner_disabled_replay, opt: @not_supported)) unless \ - @configurator.project_config_hash[:test_runner_cmdline_args] - end -end diff --git a/lib/ceedling/verbosinator.rb b/lib/ceedling/verbosinator.rb index e8ed38d78..5a3b30a81 100644 --- a/lib/ceedling/verbosinator.rb +++ b/lib/ceedling/verbosinator.rb @@ -1,10 +1,22 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= -class Verbosinator +require 'ceedling/constants' - constructor :configurator +class Verbosinator def should_output?(level) - return (level <= @configurator.project_verbosity) + # Rely on global constant created at early stages of command line processing + + if !defined?(PROJECT_VERBOSITY) + return (level <= Verbosity::NORMAL) + end + + return (level <= PROJECT_VERBOSITY) end end diff --git a/lib/ceedling/version.rb b/lib/ceedling/version.rb deleted file mode 100644 index 5c3d7f4b7..000000000 --- a/lib/ceedling/version.rb +++ /dev/null @@ -1,54 +0,0 @@ - -# @private -module Ceedling - module Version - { "UNITY" => File.join("unity","src","unity.h"), - "CMOCK" => File.join("cmock","src","cmock.h"), - "CEXCEPTION" => File.join("c_exception","lib","CException.h") - }.each_pair do |name, path| - # Check for local or global version of vendor directory in order to look up versions - path1 = File.expand_path( File.join("..","..","vendor",path) ) - path2 = File.expand_path( File.join(File.dirname(__FILE__),"..","..","vendor",path) ) - filename = if (File.exist?(path1)) - path1 - elsif (File.exist?(path2)) - path2 - elsif File.exist?(CEEDLING_VENDOR) - path3 = File.expand_path( File.join(CEEDLING_VENDOR,path) ) - if (File.exist?(path3)) - path3 - else - basepath = File.join( CEEDLING_VENDOR, path.split(/\\\//)[0], 'release') - begin - [ @ceedling[:file_wrapper].read( File.join(base_path, 'release', 'version.info') ).strip, - @ceedling[:file_wrapper].read( File.join(base_path, 'release', 'build.info') ).strip ].join('.') - rescue - "#{name}" - end - end - else - module_eval("#{name} = 'unknown'") - continue - end - - # Actually look up the versions - a = [0,0,0] - begin - File.readlines(filename).each do |line| - ["VERSION_MAJOR", "VERSION_MINOR", "VERSION_BUILD"].each_with_index do |field, i| - m = line.match(/#{name}_#{field}\s+(\d+)/) - a[i] = m[1] unless (m.nil?) - end - end - rescue - abort("Can't collect data for vendor component: \"#{filename}\" . \nPlease check your setup.") - end - - # splat it to return the final value - eval("#{name} = '#{a.join(".")}'") - end - - GEM = "0.31.1" - CEEDLING = GEM - end -end diff --git a/lib/ceedling/yaml_wrapper.rb b/lib/ceedling/yaml_wrapper.rb index d5b9355dd..2ecd80090 100644 --- a/lib/ceedling/yaml_wrapper.rb +++ b/lib/ceedling/yaml_wrapper.rb @@ -1,3 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'yaml' require 'erb' diff --git a/lib/version.rb b/lib/version.rb new file mode 100644 index 000000000..56f88bd9f --- /dev/null +++ b/lib/version.rb @@ -0,0 +1,24 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +## +## version.rb is run as: +## 1. An executable script for a Ceedling tag used in the release build process +## `ruby Ceedling/lib/version.rb` +## 2. As a code module of constants consumed by Ruby's gem building process +## + +module Ceedling + module Version + # Convenience constants for gem building, etc. + GEM = '1.0.0' + TAG = GEM + + # If run as a script print Ceedling's version to $stdout + puts( TAG ) if (__FILE__ == $0) + end +end diff --git a/license.txt b/license.txt index ba3766166..568fb43fe 100644 --- a/license.txt +++ b/license.txt @@ -1,31 +1,22 @@ - Copyright (c) 2007-2019 Mike Karlesky, Mark VanderVoord, Greg Williams +Copyright (c) 2010-2024 Michael Karlesky, Mark VanderVoord, Greg Williams - Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation - files (the "Software"), to deal in the Software without - restriction, including without limitation the rights to use, - copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following - conditions: +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. - The end-user documentation included with the redistribution, if - any, must include the following acknowledgment: "This product - includes software developed for the Unity Project, by Mike Karlesky, - Mark VanderVoord, and Greg Williams and other contributors", in - the same place and form as other third-party acknowledgments. - Alternately, this acknowledgment may appear in the software - itself, in the same form and location as other such third-party - acknowledgments. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/plugins/beep/README.md b/plugins/beep/README.md index e59d881b3..bc06b2c68 100644 --- a/plugins/beep/README.md +++ b/plugins/beep/README.md @@ -1,22 +1,133 @@ -ceedling-beep -============= +# Ceedling Plugin: Beep -This is a simple plugin that just beeps at the end of a build and/or test sequence. Are you getting too distracted surfing -the internet, chatting with coworkers, or swordfighting while it's building or testing? The friendly beep will let you know +Hear a useful beep at the end of a build. + +# Plugin Overview + +Are you getting too distracted surfing the internet, chatting with coworkers, or swordfighting while a long build runs? A friendly beep will let you know it's time to pay attention again. -This plugin has very few configuration options. At this time it can beep on completion of a task and/or on an error condition. -For each of these, you can configure the method that it should beep. +# Setup +To use this plugin, it must be enabled: + +```yaml +:plugins: + :enabled: + - beep ``` -:tools: - :beep_on_done: :bell - :beep_on_error: :bell + +# Configuration + +Beep includes a default configuration. By just enabling the plugin, the simplest cross-platform sound mechanism (`:bell` below) is automatically enabled for both +build completion and build error events. + +If you would like to customize your beeps, the following explains your options. + +## Events + +When this plugin is enabled, a beep is sounded when: + +* A test build or release build finish successfully. +* An error condition breaks a build. + +To change the default sound for each event, define `:on_done` and `:on_error` beneath a top-level `:beep` entry in your configuration file. See example below. + +## Sound options + +The following options are fixed. At present, this plugin does not expose customization settings. + +* `:bell` + + `:bell` is the simplest and most widely available option for beeping. This option simply `echo`s the unprintable [ASCII bell character][ascii-bel-character] to a command terminal. This option is generally available on all platforms, including Windows. + + [ascii-bel-character]: https://en.wikipedia.org/wiki/Bell_character + +* `:tput` + + [`tput`][tput] is a command line utility widely availble among Unix derivatives, including Linux and macOS. The `tput` utility uses the terminfo database to make the values of terminal-dependent capabilities (including the [ASCII bell character][ascii-bel-character]) and terminal information available to the shell. + + If the `echo`-based method used by the `:bell` option is not successful, `:tput` is a good backup option (except on Windows). + + [tput]: https://linux.die.net/man/1/tput + +* `:beep` + + [`beep`][beep] is an old but widely available Linux package for tone generation using the PC speaker. + + `beep` requires isntallation and the possibility of a complementary kernel module. + + The original audio device in a PC before sound cards was a simple and limited speaker directly wired to a motherboard. Rarely, modern systems still have this device. More commonly, its functions are routed to a default mode of modern audio hardware. `beep` may not work on modern Linux systems. If it is a viable option, this utility is typically dependent on a PC speaker kernel module and related configuration. + + [beep]: https://linux.die.net/man/1/beep + +* `:speaker_test` + + [`speaker-test`][speaker-test] is a Linux package commonly available for tone generation using a system's audio features. + + `speaker-test` requires installation as well as audio subsystem configuration. + + _Note:_ `speaker-test` typically mandates a 4 second minimum run, even if the configured sound plays for less than this minimum. Options to limit `speaker-test`'s minimum time are likely possible but would require combining advanced Ceedling features. + + [speaker-test]: https://linux.die.net/man/1/speaker-test + +* `:say` + + macOS includes a built-in text-to-speech command line application, [`say`][say]. When Ceedling is running on macOS and this beep option is selected, Ceedling events will be verbally announced. + + [say]: https://ss64.com/mac/say.html + +## Adding arguments to a beep tool + +Each of the sound options above map to a command line tool that Ceedling executes. + +The `:beep`, `:speaker_test`, and `:say` tools can accept additional command line arguments to modify their behavior and sound ouput. + +The `:speaker_test` tool is preconfigured with its `-t`, `-f`, and `-l` arguments to generate a 1 second 1000 Hz sine wave. Any additional arguments added through configuration will follow these (and could conflict). + +To add additional arguments, a feature of Ceedling's project file handling allows you to merge a partial tool definition with tools already fully defined. + +```yaml +:tools_beep_: # Fill in as from the list above + :arguments: + - ... # Add any aguments as a list of strings +``` + +## Example beep configurations in YAML + +Enabling the plugin and event handlers with beep tool selections: + +```yaml +:plugins: + :enabled: + - beep + +# The following is the default configuration. +# It is shown for completeness, but it need not be duplicated in your project file +# if the default settings work for you. +:beep: + :on_done: :bell + :on_error: :bell +``` + +Adding an argument to a beep tool: + +```yaml +:plugins: + :enabled: + - beep + +:beep: + :on_done: :say # Choose the macOS `say` tool for build done events + # `:bell` remains the default for :on_error: + +:tools_beep_say: + :arguments: + - -v daniel # Change `say` command line to use Daniel voice + ``` -Each of these have the following options: +# Notes - - :bell - this option uses the ASCII bell character out stdout - - :speaker_test - this uses the linux speaker-test command if installed +* Some terminal emulators intercept and/or silence beeps. Remote terminal sessions can add further complication. Be sure to check relevant configuration options to accomplish what you want. -Very likely, we'll be adding to this list if people find this to be useful. diff --git a/plugins/beep/config/defaults.yml b/plugins/beep/config/defaults.yml new file mode 100644 index 000000000..080195bcc --- /dev/null +++ b/plugins/beep/config/defaults.yml @@ -0,0 +1,12 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +--- +:beep: + :on_done: :bell + :on_error: :bell +... diff --git a/plugins/beep/config/defaults_beep.rb b/plugins/beep/config/defaults_beep.rb new file mode 100644 index 000000000..938de8947 --- /dev/null +++ b/plugins/beep/config/defaults_beep.rb @@ -0,0 +1,67 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +# Most generic beep option across all platforms -- echo the ASCII bell character +DEFAULT_BEEP_BELL_TOOL = { + :executable => 'echo'.freeze, # Using `echo` shell command / command line application + :optional => true.freeze, + :name => 'default_beep_bell'.freeze, + :arguments => [ + *('-n'.freeze unless SystemWrapper.windows?), # No trailing newline for Unix-style echo (argument omitted on Windows) + "\x07".freeze # Unprintable ASCII bell character, escaped in Ruby string + ].freeze + } + +# Terminal put the bell character on Unix-derived platforms +DEFAULT_BEEP_TPUT_TOOL = { + :executable => 'tput'.freeze, # `tput` command line application + :optional => true.freeze, + :name => 'default_beep_tput'.freeze, + :arguments => [ + "bel".freeze # `tput` argument for bell character (named 'bel' in ASCII standard) + ].freeze + } + +# Old but widely available `beep` tone generator package for Unix-derived platforms (not macOS) +DEFAULT_BEEP_BEEP_TOOL = { + :executable => 'beep'.freeze, # `beep` command line application + :optional => true.freeze, + :name => 'default_beep_beep'.freeze, + :arguments => [].freeze # Default beep (no arguments) + } + +# Widely available tone generator package for Unix-derived platforms (not macOS) +DEFAULT_BEEP_SPEAKER_TEST_TOOL = { + :executable => 'speaker-test'.freeze, # `speaker-test` command line application + :optional => true.freeze, + :name => 'default_beep_speaker_test'.freeze, + :arguments => [ # 1000 hz sine wave frequency + '-t sine'.freeze, + '-f 1000'.freeze, + '-l 1'.freeze + ].freeze + } + +# macOS text-to-speech tool +DEFAULT_BEEP_SAY_TOOL = { + :executable => 'say'.freeze, # macOS `say` command line application + :optional => true.freeze, + :name => 'default_beep_say'.freeze, + :arguments => [ + "\"${1}\"" # Replacement argument for text + ].freeze + } + +def get_default_config + return :tools => { + :beep_bell => DEFAULT_BEEP_BELL_TOOL, + :beep_tput => DEFAULT_BEEP_TPUT_TOOL, + :beep_beep => DEFAULT_BEEP_BEEP_TOOL, + :beep_speaker_test => DEFAULT_BEEP_SPEAKER_TEST_TOOL, + :beep_say => DEFAULT_BEEP_SAY_TOOL + } +end \ No newline at end of file diff --git a/plugins/beep/lib/beep.rb b/plugins/beep/lib/beep.rb index 6a6d01ab2..c658f69f9 100755 --- a/plugins/beep/lib/beep.rb +++ b/plugins/beep/lib/beep.rb @@ -1,40 +1,85 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'ceedling/plugin' -require 'ceedling/constants' +require 'ceedling/exceptions' + +BEEP_ROOT_NAME = 'beep'.freeze +BEEP_SYM = BEEP_ROOT_NAME.to_sym class Beep < Plugin + + # `Plugin` setup() + def setup + # Get non-flattenified project configuration + project_config = @ceedling[:setupinator].config_hash + + # Get beep configuration hash + beep_config = project_config[BEEP_SYM] - attr_reader :config + # Get tools hash + tools = project_config[:tools] - def setup - @config = { - :on_done => ((defined? TOOLS_BEEP_ON_DONE) ? TOOLS_BEEP_ON_DONE : :bell ), - :on_error => ((defined? TOOLS_BEEP_ON_ERROR) ? TOOLS_BEEP_ON_ERROR : :bell ), + # Lookup and capture the selected beep tools + @tools = { + :beep_on_done => tools["beep_#{beep_config[:on_done]}".to_sym], + :beep_on_error => tools["beep_#{beep_config[:on_error]}".to_sym] } + + # Ensure configuration option is an actual tool + if @tools[:beep_on_done].nil? + error = "Option :#{beep_config[:on_done]} for :beep ↳ :on_done plugin configuration does not map to a tool." + raise CeedlingException.new( error ) + end + + # Ensure configuration option is an actual tool + if @tools[:beep_on_error].nil? + error = "Option :#{beep_config[:on_done]} for :beep ↳ :on_error plugin configuration does not map to a tool." + raise CeedlingException.new( error ) + end + + # Validate the selected beep tools + # Do not validate the `:bell` tool as it relies on `echo` that could be a shell feature rather than executable + @ceedling[:tool_validator].validate( + tool: @tools[:beep_on_done], + boom: true + ) if tools[:on_done] != :bell + + @ceedling[:tool_validator].validate( + tool: @tools[:beep_on_error], + boom: true + ) if tools[:on_error] != :bell end + # `Plugin` build step hook def post_build - beep @config[:on_done] - end + command = @ceedling[:tool_executor].build_command_line( + @tools[:beep_on_done], + [], + # Only used by tools with `${1}` replacement argument + 'ceedling build done' + ) - def post_error - beep @config[:on_error] + + # Verbosity is enabled to allow shell output (primarily for sake of the bell character) + @ceedling[:system_wrapper].shell_system( command: command[:line], verbose: true ) end + + # `Plugin` build step hook + def post_error + command = @ceedling[:tool_executor].build_command_line( + @tools[:beep_on_error], + [], + # Only used by tools with `${1}` replacement argument + 'ceedling build error' + ) - private - - def beep(method = :none) - case method - when :bell - if (SystemWrapper.windows?) - puts "echo '\007'" - else - puts "echo -ne '\007'" - end - when :speaker_test - `speaker-test -t sine -f 1000 -l 1` - else - #do nothing with illegal or :none - end + # Verbosity is enabled to allow shell output (primarily for sake of the bell character) + @ceedling[:system_wrapper].shell_system( command: command[:line], verbose: true ) end + end - diff --git a/plugins/bullseye/README.md b/plugins/bullseye/README.md index ab0b53b45..7702c4a1c 100644 --- a/plugins/bullseye/README.md +++ b/plugins/bullseye/README.md @@ -1,5 +1,12 @@ -ceedling-bullseye -================= +Bullseye Code Coverage Plugin +============================= + +# June 1, 2024 Bullseye Plugin Disabled + +Until the Bullseye Plugin can be updated for compatibility with Ceedling >= 1.0.0, +it has been disabled. + +(The key hurdle is access to a license for the proprietary Bullseye coverage tooling.) # Plugin Overview @@ -55,7 +62,6 @@ by Ceedling. The following is a typical configuration example: - -w140 :bullseye_report_covfn: :executable: covfn - :stderr_redirect: :auto :arguments: - '--file $': ENVIRONMENT_COVFILE - --width 120 @@ -63,7 +69,6 @@ by Ceedling. The following is a typical configuration example: - '"${1}"' :bullseye_browser: :executable: CoverageBrowser - :background_exec: :auto :optional: TRUE :arguments: - '"$"': ENVIRONMENT_COVFILE diff --git a/plugins/bullseye/bullseye.rake b/plugins/bullseye/bullseye.rake index 11073e786..21bc606ce 100755 --- a/plugins/bullseye/bullseye.rake +++ b/plugins/bullseye/bullseye.rake @@ -1,3 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + directory(BULLSEYE_BUILD_OUTPUT_PATH) directory(BULLSEYE_RESULTS_PATH) directory(BULLSEYE_ARTIFACTS_PATH) @@ -12,7 +19,7 @@ PLUGINS_BULLSEYE_LIB_PATH = 'C:\\tools\\BullseyeCoverage\\lib' if not defined?(P rule(/#{BULLSEYE_BUILD_OUTPUT_PATH}\/#{'.+\\'+EXTENSION_OBJECT}$/ => [ proc do |task_name| - @ceedling[:file_finder].find_compilation_input_file(task_name) + @ceedling[:file_finder].find_build_input_file(filepath: task_name, context: BULLSEYE_SYM) end ]) do |object| @@ -55,7 +62,7 @@ end rule(/#{BULLSEYE_DEPENDENCIES_PATH}\/#{'.+\\'+EXTENSION_DEPENDENCIES}$/ => [ proc do |task_name| - @ceedling[:file_finder].find_compilation_input_file(task_name) + @ceedling[:file_finder].find_build_input_file(filepath: task_name, context: BULLSEYE_SYM) end ]) do |dep| @ceedling[:generator].generate_dependencies_file( @@ -70,13 +77,22 @@ end task :directories => [BULLSEYE_BUILD_OUTPUT_PATH, BULLSEYE_RESULTS_PATH, BULLSEYE_DEPENDENCIES_PATH, BULLSEYE_ARTIFACTS_PATH] namespace BULLSEYE_SYM do + + TOOL_COLLECTION_BULLSEYE_TASKS = { + :context => BULLSEYE_SYM, + :test_compiler => TOOLS_BULLSEYE_COMPILER, + :test_assembler => TOOLS_TEST_ASSEMBLER, + :test_linker => TOOLS_BULLSEYE_LINKER, + :test_fixture => TOOLS_BULLSEYE_FIXTURE + } + task source_coverage: COLLECTION_ALL_SOURCE.pathmap("#{BULLSEYE_BUILD_OUTPUT_PATH}/%n#{@ceedling[:configurator].extension_object}") desc 'Run code coverage for all tests' - task all: [:test_deps] do + task all: [:prepare] do @ceedling[:configurator].replace_flattened_config(@ceedling[BULLSEYE_SYM].config) @ceedling[BULLSEYE_SYM].enableBullseye(true) - @ceedling[:test_invoker].setup_and_invoke(COLLECTION_ALL_TESTS, BULLSEYE_SYM) + @ceedling[:test_invoker].setup_and_invoke(COLLECTION_ALL_TESTS, TOOL_COLLECTION_BULLSEYE_TASKS) @ceedling[:configurator].restore_config end @@ -86,11 +102,11 @@ namespace BULLSEYE_SYM do "Use a real test or source file name (no path) in place of the wildcard.\n" + "Example: rake #{BULLSEYE_ROOT_NAME}:foo.c\n\n" - @ceedling[:streaminator].stdout_puts( message ) + @ceedling[:loginator].log( message ) end desc 'Run tests by matching regular expression pattern.' - task :pattern, [:regex] => [:test_deps] do |_t, args| + task :pattern, [:regex] => [:prepare] do |_t, args| matches = [] COLLECTION_ALL_TESTS.each do |test| @@ -100,15 +116,15 @@ namespace BULLSEYE_SYM do if !matches.empty? @ceedling[:configurator].replace_flattened_config(@ceedling[BULLSEYE_SYM].config) @ceedling[BULLSEYE_SYM].enableBullseye(true) - @ceedling[:test_invoker].setup_and_invoke(matches, BULLSEYE_SYM, force_run: false) + @ceedling[:test_invoker].setup_and_invoke(matches, { force_run: false }.merge(TOOL_COLLECTION_BULLSEYE_TASKS)) @ceedling[:configurator].restore_config else - @ceedling[:streaminator].stdout_puts("\nFound no tests matching pattern /#{args.regex}/.") + @ceedling[:loginator].log("\nFound no tests matching pattern /#{args.regex}/.") end end desc 'Run tests whose test path contains [dir] or [dir] substring.' - task :path, [:dir] => [:test_deps] do |_t, args| + task :path, [:dir] => [:prepare] do |_t, args| matches = [] COLLECTION_ALL_TESTS.each do |test| @@ -118,56 +134,47 @@ namespace BULLSEYE_SYM do if !matches.empty? @ceedling[:configurator].replace_flattened_config(@ceedling[BULLSEYE_SYM].config) @ceedling[BULLSEYE_SYM].enableBullseye(true) - @ceedling[:test_invoker].setup_and_invoke(matches, BULLSEYE_SYM, force_run: false) + @ceedling[:test_invoker].setup_and_invoke(matches, { force_run: false }.merge(TOOL_COLLECTION_BULLSEYE_TASKS)) @ceedling[:configurator].restore_config else - @ceedling[:streaminator].stdout_puts("\nFound no tests including the given path or path component.") + @ceedling[:loginator].log("\nFound no tests including the given path or path component.") end end desc 'Run code coverage for changed files' - task delta: [:test_deps] do + task delta: [:prepare] do @ceedling[:configurator].replace_flattened_config(@ceedling[BULLSEYE_SYM].config) @ceedling[BULLSEYE_SYM].enableBullseye(true) - @ceedling[:test_invoker].setup_and_invoke(COLLECTION_ALL_TESTS, BULLSEYE_SYM, {:force_run => false}) + @ceedling[:test_invoker].setup_and_invoke(COLLECTION_ALL_TESTS, {:force_run => false}.merge(TOOL_COLLECTION_BULLSEYE_TASKS)) @ceedling[:configurator].restore_config end - # use a rule to increase efficiency for large projects - # bullseye test tasks by regex - rule(/^#{BULLSEYE_TASK_ROOT}\S+$/ => [ + # Use a rule to increase efficiency for large projects + rule(/^#{BULLSEYE_TASK_ROOT}\S+$/ => [ # Bullseye test tasks by regex proc do |task_name| - test = task_name.sub(/#{BULLSEYE_TASK_ROOT}/, '') - test = "#{PROJECT_TEST_FILE_PREFIX}#{test}" unless test.start_with?(PROJECT_TEST_FILE_PREFIX) - @ceedling[:file_finder].find_test_from_file_path(test) + # Yield clean test name => Strip the task string, remove Rake test task prefix, and remove any code file extension + test = task_name.strip().sub(/^#{BULLSEYE_TASK_ROOT}/, '').chomp( EXTENSION_SOURCE ) + + # Ensure the test name begins with a test name prefix + test = PROJECT_TEST_FILE_PREFIX + test if not (test.start_with?( PROJECT_TEST_FILE_PREFIX )) + + # Provide the filepath for the target test task back to the Rake task + @ceedling[:file_finder].find_test_file_from_name( test ) end ]) do |test| - @ceedling[:rake_wrapper][:test_deps].invoke - @ceedling[:configurator].replace_flattened_config(@ceedling[BULLSEYE_SYM].config) + @ceedling[:rake_wrapper][:prepare].invoke @ceedling[BULLSEYE_SYM].enableBullseye(true) - @ceedling[:test_invoker].setup_and_invoke([test.source], BULLSEYE_SYM) - @ceedling[:configurator].restore_config + @ceedling[:test_invoker].setup_and_invoke( [test.source], TOOL_COLLECTION_BULLSEYE_TASKS ) end end -if PROJECT_USE_DEEP_DEPENDENCIES -namespace REFRESH_SYM do - task BULLSEYE_SYM do - @ceedling[:configurator].replace_flattened_config(@ceedling[BULLSEYE_SYM].config) - @ceedling[BULLSEYE_SYM].enableBullseye(true) - @ceedling[:test_invoker].refresh_deep_dependencies - @ceedling[:configurator].restore_config - end -end -end - namespace UTILS_SYM do desc "Open Bullseye code coverage browser" task BULLSEYE_SYM do - command = @ceedling[:tool_executor].build_command_line(TOOLS_BULLSEYE_BROWSER, []) - @ceedling[:tool_executor].exec(command[:line], command[:options]) + command = @ceedling[:tool_executor].build_command_line( TOOLS_BULLSEYE_BROWSER, [] ) + @ceedling[:tool_executor].exec( command ) end end diff --git a/plugins/bullseye/config/defaults.yml b/plugins/bullseye/config/defaults.yml index ed261d8e5..533cae60d 100755 --- a/plugins/bullseye/config/defaults.yml +++ b/plugins/bullseye/config/defaults.yml @@ -1,3 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + --- :bullseye: @@ -7,51 +14,53 @@ :paths: :bullseye_toolchain_include: [] -:tools: - :bullseye_instrumentation: - :executable: covc - :arguments: - - '--file $': ENVIRONMENT_COVFILE - - -q - - ${1} - :bullseye_compiler: - :executable: gcc - :arguments: - - -g - - -I"$": COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR - - -I"$": COLLECTION_PATHS_BULLSEYE_TOOLCHAIN_INCLUDE - - -D$: COLLECTION_DEFINES_TEST_AND_VENDOR - - -DBULLSEYE_COMPILER - - -c "${1}" - - -o "${2}" - :bullseye_linker: - :executable: gcc - :arguments: - - ${1} - - -o ${2} - - -L$: PLUGINS_BULLSEYE_LIB_PATH - - -lcov - :bullseye_fixture: - :executable: ${1} - :bullseye_report_covsrc: - :executable: covsrc - :arguments: - - '--file $': ENVIRONMENT_COVFILE - - -q - - -w140 - :bullseye_report_covfn: - :executable: covfn - :stderr_redirect: :auto - :arguments: - - '--file $': ENVIRONMENT_COVFILE - - --width 120 - - --no-source - - '"${1}"' - :bullseye_browser: - :executable: CoverageBrowser - :background_exec: :auto - :optional: TRUE - :arguments: - - '"$"': ENVIRONMENT_COVFILE +# TODO: Restore :tools once Bullseye plugin has been updated for Ceedling >= 1.0.0 +# :tools: +# :bullseye_instrumentation: +# :executable: covc +# :optional: true +# :arguments: +# - '--file $': ENVIRONMENT_COVFILE +# - -q +# - ${1} +# :bullseye_compiler: +# :executable: gcc +# :arguments: +# - -g +# - -I"$": COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR +# - -I"$": COLLECTION_PATHS_BULLSEYE_TOOLCHAIN_INCLUDE +# - -D$: COLLECTION_DEFINES_TEST_AND_VENDOR +# - -DBULLSEYE_COMPILER +# - -c "${1}" +# - -o "${2}" +# :bullseye_linker: +# :executable: gcc +# :arguments: +# - ${1} +# - -o ${2} +# - -L$: PLUGINS_BULLSEYE_LIB_PATH +# - -lcov +# :bullseye_fixture: +# :executable: ${1} +# :bullseye_report_covsrc: +# :executable: covsrc +# :optional: true +# :arguments: +# - '--file $': ENVIRONMENT_COVFILE +# - -q +# - -w140 +# :bullseye_report_covfn: +# :executable: covfn +# :optional: true +# :arguments: +# - '--file $': ENVIRONMENT_COVFILE +# - --width 120 +# - --no-source +# - '"${1}"' +# :bullseye_browser: +# :executable: CoverageBrowser +# :optional: TRUE +# :arguments: +# - '"$"': ENVIRONMENT_COVFILE ... diff --git a/plugins/bullseye/lib/bullseye.rb b/plugins/bullseye/lib/bullseye.rb index ffa444ac7..764ab53ba 100755 --- a/plugins/bullseye/lib/bullseye.rb +++ b/plugins/bullseye/lib/bullseye.rb @@ -1,5 +1,13 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'ceedling/plugin' require 'ceedling/constants' +require 'ceedling/exceptions' BULLSEYE_ROOT_NAME = 'bullseye' BULLSEYE_TASK_ROOT = BULLSEYE_ROOT_NAME + ':' @@ -17,15 +25,17 @@ class Bullseye < Plugin def setup + # TODO: Remove `raise` when Bullseye plugin has been updated for Ceedling >= 1.0.0 + raise CeedlingException.new( "The Bullseye plugin is disabled until it can be updated for this version of Ceedling" ) @result_list = [] @environment = [ {:covfile => File.join( BULLSEYE_ARTIFACTS_PATH, 'test.cov' )} ] - @plugin_root = File.expand_path(File.join(File.dirname(__FILE__), '..')) - @coverage_template_all = @ceedling[:file_wrapper].read(File.join(@plugin_root, 'assets/template.erb')) + @coverage_template_all = @ceedling[:file_wrapper].read( File.join( @plugin_root_path, 'assets/template.erb' ) ) end def config { :project_test_build_output_path => BULLSEYE_BUILD_OUTPUT_PATH, + :project_test_build_output_c_path => BULLSEYE_BUILD_OUTPUT_PATH, :project_test_results_path => BULLSEYE_RESULTS_PATH, :project_test_dependencies_path => BULLSEYE_DEPENDENCIES_PATH, :defines_test => DEFINES_TEST + ['CODE_COVERAGE'], @@ -37,7 +47,7 @@ def generate_coverage_object_file(source, object) arg_hash = {:tool => TOOLS_BULLSEYE_INSTRUMENTATION, :context => BULLSEYE_SYM, :source => source, :object => object} @ceedling[:plugin_manager].pre_compile_execute(arg_hash) - @ceedling[:streaminator].stdout_puts("Compiling #{File.basename(source)} with coverage...") + @ceedling[:loginator].log("Compiling #{File.basename(source)} with coverage...") compile_command = @ceedling[:tool_executor].build_command_line( TOOLS_BULLSEYE_COMPILER, @@ -81,7 +91,7 @@ def post_build return if (verify_coverage_file() == false) if (@ceedling[:task_invoker].invoked?(/^#{BULLSEYE_TASK_ROOT}(all|delta)/)) command = @ceedling[:tool_executor].build_command_line(TOOLS_BULLSEYE_REPORT_COVSRC, []) - shell_result = @ceedling[:tool_executor].exec(command[:line], command[:options]) + shell_result = @ceedling[:tool_executor].exec( command ) report_coverage_results_all(shell_result[:output]) else report_per_function_coverage_results(@ceedling[:test_invoker].sources) @@ -103,7 +113,7 @@ def summary # coverage results command = @ceedling[:tool_executor].build_command_line(TOOLS_BULLSEYE_REPORT_COVSRC) - shell_result = @ceedling[:tool_executor].exec(command[:line], command[:options]) + shell_result = @ceedling[:tool_executor].exec( command ) report_coverage_results_all(shell_result[:output]) end @@ -111,15 +121,15 @@ def enableBullseye(enable) if BULLSEYE_AUTO_LICENSE if (enable) args = ['push', 'on'] - @ceedling[:streaminator].stdout_puts("Enabling Bullseye") + @ceedling[:loginator].log("Enabling Bullseye") else args = ['pop'] - @ceedling[:streaminator].stdout_puts("Reverting Bullseye to previous state") + @ceedling[:loginator].log("Reverting Bullseye to previous state") end args.each do |arg| command = @ceedling[:tool_executor].build_command_line(TOOLS_BULLSEYE_BUILD_ENABLE_DISABLE, [], arg) - shell_result = @ceedling[:tool_executor].exec(command[:line], command[:options]) + shell_result = @ceedling[:tool_executor].exec( command ) end end @@ -144,12 +154,12 @@ def report_coverage_results_all(coverage) results[:coverage][:branches] = $1.to_i end - @ceedling[:plugin_reportinator].run_report($stdout, @coverage_template_all, results) + @ceedling[:plugin_reportinator].run_report( @coverage_template_all, results ) end def report_per_function_coverage_results(sources) banner = @ceedling[:plugin_reportinator].generate_banner( "#{BULLSEYE_ROOT_NAME.upcase}: CODE COVERAGE SUMMARY" ) - @ceedling[:streaminator].stdout_puts "\n" + banner + @ceedling[:loginator].log "\n" + banner coverage_sources = sources.clone coverage_sources.delete_if {|item| item =~ /#{CMOCK_MOCK_PREFIX}.+#{EXTENSION_SOURCE}$/} @@ -157,15 +167,15 @@ def report_per_function_coverage_results(sources) coverage_sources.each do |source| command = @ceedling[:tool_executor].build_command_line(TOOLS_BULLSEYE_REPORT_COVFN, [], source) - shell_results = @ceedling[:tool_executor].exec(command[:line], command[:options]) + shell_results = @ceedling[:tool_executor].exec( command ) coverage_results = shell_results[:output].deep_clone coverage_results.sub!(/.*\n.*\n/,'') # Remove the Bullseye tool banner if (coverage_results =~ /warning cov814: report is empty/) - coverage_results = "WARNING: #{source} contains no coverage data!\n\n" - @ceedling[:streaminator].stdout_puts(coverage_results, Verbosity::COMPLAIN) + coverage_results = "#{source} contains no coverage data" + @ceedling[:loginator].log(coverage_results, Verbosity::COMPLAIN) else coverage_results += "\n" - @ceedling[:streaminator].stdout_puts(coverage_results) + @ceedling[:loginator].log(coverage_results) end end end @@ -175,7 +185,7 @@ def verify_coverage_file if (!exist) banner = @ceedling[:plugin_reportinator].generate_banner( "#{BULLSEYE_ROOT_NAME.upcase}: CODE COVERAGE SUMMARY" ) - @ceedling[:streaminator].stdout_puts "\n" + banner + "\nNo coverage file.\n\n" + @ceedling[:loginator].log "\n" + banner + "\nNo coverage file.\n\n" end return exist @@ -188,7 +198,6 @@ def verify_coverage_file END { # cache our input configurations to use in comparison upon next execution if (@ceedling[:task_invoker].invoked?(/^#{BULLSEYE_TASK_ROOT}/)) - @ceedling[:cacheinator].cache_test_config( @ceedling[:setupinator].config_hash ) @ceedling[BULLSEYE_SYM].enableBullseye(false) end } diff --git a/plugins/colour_report/README.md b/plugins/colour_report/README.md deleted file mode 100644 index 4e0fcd45e..000000000 --- a/plugins/colour_report/README.md +++ /dev/null @@ -1,20 +0,0 @@ -ceedling-colour-report -====================== - -## Overview - -The colour_report replaces the normal ceedling "pretty" output with -a colorized variant, in order to make the results easier to read from -a standard command line. This is very useful on developer machines, but -can occasionally cause problems with parsing on CI servers. - -## Setup - -Enable the plugin in your project.yml by adding `colour_report` -to the list of enabled plugins. - -``` YAML -:plugins: - :enabled: - - colour_report -``` diff --git a/plugins/colour_report/lib/colour_report.rb b/plugins/colour_report/lib/colour_report.rb deleted file mode 100644 index 1211eab4d..000000000 --- a/plugins/colour_report/lib/colour_report.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'ceedling/plugin' -require 'ceedling/streaminator' -require 'ceedling/constants' - -class ColourReport < Plugin - - def setup - @ceedling[:stream_wrapper].stdout_override(&ColourReport.method(:colour_stdout)) - end - - def self.colour_stdout(string) - require 'colour_reporter.rb' - report string - end - -end diff --git a/plugins/command_hooks/README.md b/plugins/command_hooks/README.md index 8ac64afce..175ee8f8f 100644 --- a/plugins/command_hooks/README.md +++ b/plugins/command_hooks/README.md @@ -1,53 +1,241 @@ -ceedling-command-hooks -====================== +# Ceedling Plugin: Command Hooks -Plugin for easily calling command line tools at various points in the build process +Easily run command line tools and scripts at various points in a Ceedling build. -Define any of these sections in :tools: to provide additional hooks to be called on demand: +# Plugin Overview -``` - :pre_mock_generate - :post_mock_generate - :pre_runner_generate - :post_runner_generate - :pre_compile_execute - :post_compile_execute - :pre_link_execute - :post_link_execute - :pre_test_fixture_execute - :pre_test - :post_test - :pre_release - :post_release - :pre_build - :post_build -``` +This plugin allows you to skip creating a full Ceedling plugin for many common use cases. It links Ceedling's programmatic `Plugin` code hooks to easily managed tool definitions. -Each of these tools can support an :executable string and an :arguments list, like so: +# Setup +To use this plugin, it must be enabled: + +```yaml +:plugins: + :enabled: + - command_hooks ``` -:tools: - :post_link_execute: - :executable: objcopy.exe + +# Configuration + +## Overview + +To connect utilties or scripts to build step hooks, Ceedling tools must be defined. + +A Ceedling tool is just a YAML blob that gathers together a handful of settings and values that tell Ceedling how to build and execute a command line. Your tool can be a command line utility, a script, etc. + +Example Ceedling tools follow. When enabled, this plugin ensures any tools you define are executed by the corresponding build step hook they are organized beneath. The configurtion of enabled hooks and tools happens in a top-level `:command_hooks:` block within your project configuration. One or more tools can be attached to a build step hook. + +## Tool lists + +A command hook can execute one or more tools. + +If only a single tool is needed, its hash keys and value can be organized as a YAML sub-hash beneath the hook key. Alternatively, a single tool can exist as the only entry in a YAML list. + +If multiple tools are needed, they must be organized as entries in a YAML list. + +See the commented examples below. + +## Tool definitions + +Each Ceedling tool requires an `:executable` string and an optional `:arguments` list. See _[CeedlingPacket][ceedling-packet]_ documentation for project configuration [`:tools`][tools-doc] entries to understand how to craft your argument list and other tool options. + +At present, this plugin passes at most one runtime parameter for use in a hook's tool argument list. If available, this parameter can be referenced with a Ceedling tool argument expansion identifier `${1}`. That is, wherever you place `${1}` in your tool argument list, `${1}` will expand in the command line Ceedling constructs with the parameter this plugin provides for that build step hook. The list of build steps hooks below document any single parameters they provide at execution. + +[tools-doc]: https://github.com/ThrowTheSwitch/Ceedling/blob/test/ceedling_0_32_rc/docs/CeedlingPacket.md#tools-configuring-command-line-tools-used-for-build-steps + +## Hook logging + +In addition to the standard Ceedling tool definition elements, a hook configuration entry may optionally include a `:logging` setting. + +`:logging` may be set to `TRUE` or `FALSE`. An omitted setting is equivalent to `FALSE`. + +When logging is enabled and logging conditions are appropriate, any output from the hook tool will be logged to the console with a brief header identifying the hook. + +* Explicit command hook output logging only occurs at verbosity levels Normal and Obnoxious. +* Debug logging naturally displays hook output as part of normal tool execution logging. It is not duplicated by hook logging. +* At Normal verbosity, blank hook output is not logged at all; Obnoxious verbosity will display blank output as ``. + +## Command Hooks example configuration YAML + +```yaml +:command_hooks: + # Hook called every time a mock is generated + # Who knows what my_script.py does -- sky is the limit + :pre_mock_generate: + # This tool is organized as a sub-hash beneath the command hook key + :executable: python :arguments: - - ${1} #This is replaced with the executable name - - output.srec - - --strip-all + - my_script.py + - --some-arg + - ${1} # Replaced with the filepath of the header file that will be mocked + :logging: TRUE # Log any tool output to console + + # Hook called for each linking operation + # Here, we are performing two tasks for the same build step hook, converting a + # binary executable to S-record format and, then, archiving with other artifacts. + :post_link_execute: + # These tools are organized in a YAML list beneath the command hook key + - :executable: objcopy.exe + :arguments: + - ${1} # Replaced with the filepath to the linker's binary artifact output + - output.srec + - --strip-all + - :executable: + :arguments: tar.exe + - -acf + - awesome_build.zip + - ${1} # Replaced with the filepath to the linker's binary artifact output + - memory_report.txt ``` -You may also specify an array of executables to be called in a particular place, like so: +# Available Build Step Hooks -``` -:tools: - :post_test: - - :executable: echo - :arguments: "${1} was glorious!" - - :executable: echo - :arguments: - - it kinda made me cry a little. - - you? -``` +Define any of the following entries within a top-level `:command_hooks:` section of your Ceedling project file to automagically connect utilities or scripts to build process steps. + +Some hooks are called for every file-related operation for which the hook is named. Other hooks are triggered by the single build step for which the hook is named. + +As an example, consider a Ceedling project with ten test files and seventeen mocks. The command line `ceedling test:all` would trigger: + +* 1 occurrence of the `:pre_build` hook. +* 10 occurrences of the `:pre_test` and `:post_test` hooks. +* 17 occurrences of the `:pre_mock_generate` and `:post_mock_generate` hooks. +* 10 occurrences of the `:pre_test_runner_generate` and `:post_test_runner_generate` hooks. +* 27(+) occurrences of the `:pre_compile` and `:post_compile` hooks. These hooks would be called 27 times for test file and mock file compilation. A test suite build will also include compilation of the source files under tests, Unity's source, CMock's source, and generated test runner C files -- easily more than another two dozen compilation hook calls. +* 10 occurrences of the `:pre_link` and `:post_link` hooks for test executable creation. +* 10 occurences of the `:pre_test_fixture_execute` and `:post_test_fixture_execute` hooks for running test executables and gathering the results of the tests cases they contain. +* 1 occurence of the `:post_build` hook unless a build error occurred (`:post_error` would be called isntead). + +## `:pre_build` + +Called once just before Ceedling executes any tasks. + +No parameters are provided for a tool's argument list when the hook is called. + +## `:post_build` + +Called once just before Ceedling terminates. + +No parameters are provided for a tool's argument list when the hook is called. + +## `:post_error` + +Called once just after any build failure and just before Ceedling terminates. + +No parameters are provided for a tool's argument list when the hook is called. + +## `:pre_test` + +Called just before each test begins its build pipeline and just after all context for that build has been gathered. + +The parameter available to a tool (`${1}`) when the hook is called is the test's filepath. + +## `:post_test` + +Called just after each test completes its build and execution. + +The parameter available to a tool (`${1}`) when the hook is called is the test's filepath. + +## `:pre_release` + +Called once just before a release build begins. + +No parameters are provided for a tool's argument list when the hook is called. + +## `:post_release` + +Called once just after a release build finishes. + +No parameters are provided for a tool's argument list when the hook is called. + +## `:pre_mock_preprocess` + +If mocks are enabled and preprocessing is in use, this is called just before each header file to be mocked is preprocessed. + +The parameter available to a tool (`${1}`) when the hook is called is the filepath of the header file to be mocked. + +See _[CeedlingPacket][ceedling-packet]_ for details on how Ceedling preprocessing operates. + +[ceedling-packet]: ../docs/CeedlingPacket.md + +## `:post_mock_preprocess` + +If mocks are enabled and preprocessing is in use, this is called just after each header file to be mocked is preprocessed. + +The parameter available to a tool (`${1}`) when the hook is called is the filepath of the header file to be mocked. + +## `:pre_mock_generate` + +If mocks are enabled, this is called just before each header file to be mocked is processed by mock generation. + +The parameter available to a tool (`${1}`) when the hook is called is the filepath of the header file to be mocked. + +## `:post_mock_generate` + +If mocks are enabled, this is called just after each mock generation. + +The parameter available to a tool (`${1}`) when the hook is called is the filepath of the header file to be mocked. + +## `:pre_test_preprocess` + +If preprocessing is in use, this is called just before each test file is preprocessed before runner generation. + +The parameter available to a tool (`${1}`) when the hook is called is the test's filepath. + +See _[CeedlingPacket][ceedling-packet]_ for details on how Ceedling preprocessing operates. + +## `:post_test_preprocess` + +If preprocessing is in use, this is called just after each test file is preprocessed. + +The parameter available to a tool (`${1}`) when the hook is called is the test's filepath. + +See _[CeedlingPacket][ceedling-packet]_ for details on how Ceedling preprocessing operates. + +## `:pre_runner_generate` + +Called just before each test file is processed by test runner generation. + +The parameter available to a tool (`${1}`) when the hook is called is the test's filepath. + +## `:post_runner_generate` + +Called just after each test runner is generated. + +The parameter available to a tool (`${1}`) when the hook is called is the test's filepath. + +## `:pre_compile_execute` + +Called just before each C or assembly file is compiled. + +The parameter available to a tool (`${1}`) when the hook is called is the filepath of the file to be compiled. + +## `:post_compile_execute` + +Called just after each file compilation. + +The parameter available to a tool (`${1}`) when the hook is called is the filepath of the input file that was compiled. + +## `:pre_link_execute` + +Called just before any binary artifact—test or release—is linked. + +The parameter available to a tool (`${1}`) when the hook is called is the binary output artifact's filepath. + +## `:post_link_execute` + +Called just after a binary artifact is linked. + +The parameter available to a tool (`${1}`) when the hook is called is the binary output artifact's filepath. + +## `:pre_test_fixture_execute` + +Called just before each test is executed in its corresponding test fixture. + +The parameter available to a tool (`${1}`) when the hook is called is the filepath of the binary artifact to be executed by the fixture. + +## `:post_test_fixture_execute` + +Called just after each test's fixture is executed and test results are collected. -Please note that it varies which arguments are being parsed down to the -hooks. For now see `command_hooks.rb` to figure out which suits you best. -Happy Tweaking! +The parameter available to a tool (`${1}`) when the hook is called is the filepath of the binary artifact that was executed by the fixture. diff --git a/plugins/command_hooks/lib/command_hooks.rb b/plugins/command_hooks/lib/command_hooks.rb index 4bf8b5312..85915d62e 100755 --- a/plugins/command_hooks/lib/command_hooks.rb +++ b/plugins/command_hooks/lib/command_hooks.rb @@ -1,70 +1,165 @@ -require 'ceedling/plugin' +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'ceedling/constants' -class CommandHooks < Plugin +require 'ceedling/exceptions' +require 'ceedling/plugin' - attr_reader :config +COMMAND_HOOKS_ROOT_NAME = 'command_hooks'.freeze +COMMAND_HOOKS_SYM = COMMAND_HOOKS_ROOT_NAME.to_sym + +COMMAND_HOOKS_LIST = [ + :pre_mock_preprocess, + :post_mock_preprocess, + :pre_test_preprocess, + :post_test_preprocess, + :pre_mock_generate, + :post_mock_generate, + :pre_runner_generate, + :post_runner_generate, + :pre_compile_execute, + :post_compile_execute, + :pre_link_execute, + :post_link_execute, + :pre_test_fixture_execute, + :post_test_fixture_execute, + :pre_test, + :post_test, + :pre_release, + :post_release, + :pre_build, + :post_build, + :post_error, +].freeze + +class CommandHooks < Plugin def setup - @config = { - :pre_mock_generate => ((defined? TOOLS_PRE_MOCK_GENERATE) ? TOOLS_PRE_MOCK_GENERATE : nil ), - :post_mock_generate => ((defined? TOOLS_POST_MOCK_GENERATE) ? TOOLS_POST_MOCK_GENERATE : nil ), - :pre_runner_generate => ((defined? TOOLS_PRE_RUNNER_GENERATE) ? TOOLS_PRE_RUNNER_GENERATE : nil ), - :post_runner_generate => ((defined? TOOLS_POST_RUNNER_GENERATE) ? TOOLS_POST_RUNNER_GENERATE : nil ), - :pre_compile_execute => ((defined? TOOLS_PRE_COMPILE_EXECUTE) ? TOOLS_PRE_COMPILE_EXECUTE : nil ), - :post_compile_execute => ((defined? TOOLS_POST_COMPILE_EXECUTE) ? TOOLS_POST_COMPILE_EXECUTE : nil ), - :pre_link_execute => ((defined? TOOLS_PRE_LINK_EXECUTE) ? TOOLS_PRE_LINK_EXECUTE : nil ), - :post_link_execute => ((defined? TOOLS_POST_LINK_EXECUTE) ? TOOLS_POST_LINK_EXECUTE : nil ), - :pre_test_fixture_execute => ((defined? TOOLS_PRE_TEST_FIXTURE_EXECUTE) ? TOOLS_PRE_TEST_FIXTURE_EXECUTE : nil ), - :post_test_fixture_execute => ((defined? TOOLS_POST_TEST_FIXTURE_EXECUTE) ? TOOLS_POST_TEST_FIXTURE_EXECUTE : nil ), - :pre_test => ((defined? TOOLS_PRE_TEST) ? TOOLS_PRE_TEST : nil ), - :post_test => ((defined? TOOLS_POST_TEST) ? TOOLS_POST_TEST : nil ), - :pre_release => ((defined? TOOLS_PRE_RELEASE) ? TOOLS_PRE_RELEASE : nil ), - :post_release => ((defined? TOOLS_POST_RELEASE) ? TOOLS_POST_RELEASE : nil ), - :pre_build => ((defined? TOOLS_PRE_BUILD) ? TOOLS_PRE_BUILD : nil ), - :post_build => ((defined? TOOLS_POST_BUILD) ? TOOLS_POST_BUILD : nil ), - :post_error => ((defined? TOOLS_POST_ERROR) ? TOOLS_POST_ERROR : nil ), - } - @plugin_root = File.expand_path(File.join(File.dirname(__FILE__), '..')) + # Get a copy of the project configuration + project_config = @ceedling[:setupinator].config_hash + + # Convenience object references + @loginator = @ceedling[:loginator] + @reportinator = @ceedling[:reportinator] + @walkinator = @ceedling[:config_walkinator] + @tool_validator = @ceedling[:tool_validator] + @tool_executor = @ceedling[:tool_executor] + @verbosinator = @ceedling[:verbosinator] + @configurator_validator = @ceedling[:configurator_validator] + + # Look up if the accompanying `:command_hooks` configuration block exists + config_exists = @configurator_validator.exists?( + project_config, + COMMAND_HOOKS_SYM + ) + + # Go boom if the required configuration block does not exist + unless config_exists + name = @reportinator.generate_config_walk([COMMAND_HOOKS_SYM]) + error = "Command Hooks plugin is enabled but is missing a required configuration block `#{name}`" + raise CeedlingException.new(error) + end + + @config = project_config[COMMAND_HOOKS_SYM] + + # Validate the command hook keys (look out for typos) + validate_config( @config ) + + # Validate the tools beneath the keys + @config.each do |hook, tool| + if tool.is_a?(Array) + tool.each_index {|index| validate_hook( project_config, hook, index )} + else + validate_hook( project_config, hook ) + end + end end - def pre_mock_generate(arg_hash); run_hook(:pre_mock_generate, arg_hash[:header_file] ); end - def post_mock_generate(arg_hash); run_hook(:post_mock_generate, arg_hash[:header_file] ); end - def pre_runner_generate(arg_hash); run_hook(:pre_runner_generate, arg_hash[:source ] ); end - def post_runner_generate(arg_hash); run_hook(:post_runner_generate, arg_hash[:runner_file] ); end - def pre_compile_execute(arg_hash); run_hook(:pre_compile_execute, arg_hash[:source_file] ); end - def post_compile_execute(arg_hash); run_hook(:post_compile_execute, arg_hash[:object_file] ); end - def pre_link_execute(arg_hash); run_hook(:pre_link_execute, arg_hash[:executable] ); end - def post_link_execute(arg_hash); run_hook(:post_link_execute, arg_hash[:executable] ); end - def pre_test_fixture_execute(arg_hash); run_hook(:pre_test_fixture_execute, arg_hash[:executable] ); end - def post_test_fixture_execute(arg_hash); run_hook(:post_test_fixture_execute, arg_hash[:executable] ); end - def pre_test(test); run_hook(:pre_test, test ); end - def post_test(test); run_hook(:post_test, test ); end - def pre_release; run_hook(:pre_release ); end - def post_release; run_hook(:post_release ); end - def pre_build; run_hook(:pre_build ); end - def post_build; run_hook(:post_build ); end - def post_error; run_hook(:post_error ); end + def pre_mock_preprocess(arg_hash); run_hook( :pre_mock_preprocess, arg_hash[:header_file] ); end + def post_mock_preprocess(arg_hash); run_hook( :post_mock_preprocess, arg_hash[:header_file] ); end + def pre_test_preprocess(arg_hash); run_hook( :pre_test_preprocess, arg_hash[:test_file] ); end + def post_test_preprocess(arg_hash); run_hook( :post_test_preprocess, arg_hash[:test_file] ); end + def pre_mock_generate(arg_hash); run_hook( :pre_mock_generate, arg_hash[:header_file] ); end + def post_mock_generate(arg_hash); run_hook( :post_mock_generate, arg_hash[:header_file] ); end + def pre_runner_generate(arg_hash); run_hook( :pre_runner_generate, arg_hash[:source] ); end + def post_runner_generate(arg_hash); run_hook( :post_runner_generate, arg_hash[:runner_file] ); end + def pre_compile_execute(arg_hash); run_hook( :pre_compile_execute, arg_hash[:source_file] ); end + def post_compile_execute(arg_hash); run_hook( :post_compile_execute, arg_hash[:object_file] ); end + def pre_link_execute(arg_hash); run_hook( :pre_link_execute, arg_hash[:executable] ); end + def post_link_execute(arg_hash); run_hook( :post_link_execute, arg_hash[:executable] ); end + def pre_test_fixture_execute(arg_hash); run_hook( :pre_test_fixture_execute, arg_hash[:executable] ); end + def post_test_fixture_execute(arg_hash); run_hook( :post_test_fixture_execute, arg_hash[:executable] ); end + def pre_test(test); run_hook( :pre_test, test ); end + def post_test(test); run_hook( :post_test, test ); end + def pre_release; run_hook( :pre_release ); end + def post_release; run_hook( :post_release ); end + def pre_build; run_hook( :pre_build ); end + def post_build; run_hook( :post_build ); end + def post_error; run_hook( :post_error ); end - private + ### Private + private + ## - # Run a hook if its available. + # Validate plugin configuration. # # :args: - # - hook: Name of the hook to run - # - name: Name of file (default: "") - # - # :return: - # shell_result. + # - config: :command_hooks section from project config hash # - def run_hook_step(hook, name="") - if (hook[:executable]) - # Handle argument replacemant ({$1}), and get commandline - cmd = @ceedling[:tool_executor].build_command_line( hook, [], name ) - shell_result = @ceedling[:tool_executor].exec(cmd[:line], cmd[:options]) + def validate_config(config) + unless config.is_a?(Hash) + name = @reportinator.generate_config_walk([COMMAND_HOOKS_SYM]) + error = "Expected configuration #{name} to be a Hash but found #{config.class}" + raise CeedlingException.new(error) + end + + unrecognized_hooks = config.keys - COMMAND_HOOKS_LIST + + unrecognized_hooks.each do |not_a_hook| + name = @reportinator.generate_config_walk( [COMMAND_HOOKS_SYM, not_a_hook] ) + error = "Unrecognized Command Hook: #{name}" + @loginator.log( error, Verbosity::ERRORS ) + end + + unless unrecognized_hooks.empty? + error = "Unrecognized hooks found in Command Hooks plugin configuration" + raise CeedlingException.new(error) end end + + ## + # Validate given hook + # + # :args: + # - config: Project configuration hash + # - keys: Key and index of hook inside :command_hooks configuration + # + def validate_hook(config, *keys) + walk = [COMMAND_HOOKS_SYM, *keys] + name = @reportinator.generate_config_walk( walk ) + entry, _ = @walkinator.fetch_value( *walk, hash:config ) + if entry.nil? + raise CeedlingException.new( "Missing Command Hook plugin configuration for #{name}" ) + end + + unless entry.is_a?(Hash) + error = "Expected configuration #{name} for Command Hooks plugin to be a Hash but found #{entry.class}" + raise CeedlingException.new( error ) + end + + # Validate the Ceedling tool components of the hook entry config + @tool_validator.validate( tool: entry, name: name, boom: true ) + + # Default logging configuration + config[:logging] = false if config[:logging].nil? + end + ## # Run a hook if its available. # @@ -76,17 +171,61 @@ def run_hook_step(hook, name="") # def run_hook(which_hook, name="") if (@config[which_hook]) - @ceedling[:streaminator].stdout_puts("Running Hook #{which_hook}...", Verbosity::NORMAL) - if (@config[which_hook].is_a? Array) + msg = "Running Command Hook :#{which_hook}" + msg = @reportinator.generate_progress( msg ) + @loginator.log( msg ) + + # Single tool config + if (@config[which_hook].is_a? Hash) + run_hook_step( which_hook, @config[which_hook], name ) + + # Multiple tool configs + elsif (@config[which_hook].is_a? Array) @config[which_hook].each do |hook| - run_hook_step(hook, name) + run_hook_step( which_hook, hook, name ) end - elsif (@config[which_hook].is_a? Hash) - run_hook_step( @config[which_hook], name ) + + # Tool config is bad else - @ceedling[:streaminator].stdout_puts("Hook #{which_hook} was poorly formed", Verbosity::COMPLAINT) + msg = "The tool config for Command Hook #{which_hook} was poorly formed and not run" + @loginator.log( msg, Verbosity::COMPLAIN ) end end end -end + ## + # Run a hook if its available. + # + # :args: + # - hook: Name of the hook to run + # - name: Name of file (default: "") + # + # :return: + # shell_result. + # + def run_hook_step(which_hook, hook, name="") + if (hook[:executable]) + # Handle argument replacemant ({$1}), and get commandline + cmd = @ceedling[:tool_executor].build_command_line( hook, [], name ) + shell_result = @ceedling[:tool_executor].exec( cmd ) + + # If hook logging is enabled + if hook[:logging] + # Skip debug logging -- allow normal tool debug logging to do its thing + return if @verbosinator.should_output?( Verbosity::DEBUG ) + + output = shell_result[:output].strip + + # Set empty output to empty string if we're in OBNOXIOUS logging mode + output = '' if output.empty? and @verbosinator.should_output?( Verbosity::OBNOXIOUS ) + + # Don't add to logging output if there's nothing to output + return if output.empty? + + # NORMAL and OBNOXIOUS logging + @loginator.log( "Command Hook :#{which_hook} output >> #{output}" ) + end + end + end + +end diff --git a/plugins/compile_commands_json/README.md b/plugins/compile_commands_json/README.md deleted file mode 100644 index ea80b7397..000000000 --- a/plugins/compile_commands_json/README.md +++ /dev/null @@ -1,29 +0,0 @@ -compile_commands_json -===================== - -## Overview - -Syntax highlighting and code completion are hard. Historically each editor or IDE has implemented their own and then competed amongst themselves to offer the best experience for developers. Often developers would still to an IDE that felt cumbersome and slow just because it had the best syntax highlighting on the market. If doing it for one language is hard (and it is) imagine doing it for dozens of them. Imagine a full stack developer who has to work with CSS, HTML, JavaScript and some Ruby - they need excellent support in all those languages which just made things even harder. - -In June of 2016, Microsoft with Red Hat and Codenvy got together to create a standard called the Language Server Protocol (LSP). The idea was simple, by standardising on one protocol, all the IDEs and editors out there would only have to support LSP, and not have custom plugins for each language. In turn, the backend code that actually does the highlighting can be written once and used by any IDE that supports LSP. Many editors already support it such as Sublime Text, vim and emacs. This means that if you're using a crufty old IDE or worse, you're using a shiny new editor without code completion, then this could be just the upgrade you're looking for! - -For C and C++ projects, many people use the `clangd` backend. So that it can do things like "go to definition", `clangd` needs to know how to build the project so that it can figure out all the pieces to the puzzle. There are manual tools such as `bear` which can be run with `gcc` or `clang` to extract this information it has a big limitation in that if run with `ceedling release` you won't get any auto completion for Unity and you'll also get error messages reported by your IDE because of what it perceives as missing headers. If you do the same with `ceedling test` now you get Unity but you might miss things that are only seen in the release build. - -This plugin resolves that issue. As it is run by Ceedling, it has access to all the build information it needs to create the perfect `compile_commands.json`. Once enabled, this plugin will generate that file and place it in `./build/artifacts/compile_commands.json`. `clangd` will search your project for this file, but it is easier to symlink it into the root directory (for example `ln -s ./build/artifacts/compile_commands.json`. - -For more information on LSP and to find out if your editor supports it, check out https://langserver.org/ - -## Setup - -Enable the plugin in your project.yml by adding `compile_commands_json` to the list -of enabled plugins. - -``` YAML -:plugins: - :enabled: - - compile_commands_json -``` - -## Configuration - -There is no additional configuration necessary to run this plugin. diff --git a/plugins/compile_commands_json/lib/compile_commands_json.rb b/plugins/compile_commands_json/lib/compile_commands_json.rb deleted file mode 100644 index b04999f3f..000000000 --- a/plugins/compile_commands_json/lib/compile_commands_json.rb +++ /dev/null @@ -1,35 +0,0 @@ -require 'ceedling/plugin' -require 'ceedling/constants' -require 'json' - -class CompileCommandsJson < Plugin - def setup - @fullpath = File.join(PROJECT_BUILD_ARTIFACTS_ROOT, "compile_commands.json") - @database = if (File.exist?(@fullpath) && File.size(@fullpath) > 0) - JSON.parse( File.read(@fullpath) ) - else - [] - end - end - - def post_compile_execute(arg_hash) - - # Create the new Entry - value = { - "directory" => Dir.pwd, - "command" => arg_hash[:shell_command], - "file" => arg_hash[:source] - } - - # Determine if we're updating an existing file description or adding a new one - index = @database.index {|h| h["file"] == arg_hash[:source]} - if index - @database[index] = value - else - @database << value - end - - # Update the Actual compile_commands.json file - File.open(@fullpath,'w') {|f| f << JSON.pretty_generate(@database)} - end -end diff --git a/plugins/compile_commands_json_db/README.md b/plugins/compile_commands_json_db/README.md new file mode 100644 index 000000000..871b24bff --- /dev/null +++ b/plugins/compile_commands_json_db/README.md @@ -0,0 +1,40 @@ +# Ceedling Plugin: JSON Compilation Database + +Language Server Protocol (LSP) support for Clang tooling. + +# Background + +Syntax highlighting and code completion are hard. Historically each editor or IDE has implemented their own and then competed amongst themselves to offer the best experience for developers. Good syntax highlighting can be so valuable as to outweigh the consideration of alternate editors. If implementing sytnax highlighting and related features in a tool is hard for one language — and it is — imagine doing it for dozens of them. Further, on the flip side, imagine the complexities involved for a developer working with multiple languages at once. + +In June of 2016, Microsoft with Red Hat and Codenvy got together to create the [Language Server Protocol (LSP)][lsp-microsoft] ([community site][lsp-community]). The idea was simple. By standardizing, any conforming IDE or editor would only need to support LSP instead of custom plugins for each language. In turn, the backend code that performs syntax highlighting and similar features can be written once and used by any IDE that supports LSP. Today, [Many editors support LSP][lsp-tools]. + +[lsp-microsoft]: https://microsoft.github.io/language-server-protocol/ +[lsp-community]: https://langserver.org/ +[lsp-tools]: https://microsoft.github.io/language-server-protocol/implementors/tools/ + +# Plugin Overview + +For C and C++ projects, perhaps the most popular LSP server is the [`clangd`][clangd] backend. In order to provide features like _go to definition_, `clangd` needs to understand how to build a project so that it can discover all the pieces to the puzzle. Because of the various flavors of builds Ceedling supports and especially because of the complexities of test suite builds, components of a build can easily go missing from the view of `clangd`. + +This plugin gives `clangd` — or any tool that understands a [JSON compilation database][json-compilation-database] — full visibility into a Ceedling build. + +Once enabled, this plugin generates the database as `/artifacts/compile_commands.json` for each new build. Tools that understand JSON Compilation Database files can then process it to make their features fully available to you. + +[clangd]: https://clangd.llvm.org +[json-compilation-database]: https://clang.llvm.org/docs/JSONCompilationDatabase.html + +# Setup + +Enable the plugin in your Ceedling project file by adding `compile_commands_json_db` to the list of enabled plugins. + +``` YAML +:plugins: + :enabled: + - compile_commands_json_db +``` + +# Configuration + +There is no additional configuration necessary to run this plugin. + +`clangd` will search your build directory for the JSON compilation database, but in some instances on Unix-asbed platforms it can be easier and necessary to symlink the file into the root directory of your project (e.g. `ln -s ./build/artifacts/compile_commands.json .`). diff --git a/plugins/compile_commands_json_db/lib/compile_commands_json_db.rb b/plugins/compile_commands_json_db/lib/compile_commands_json_db.rb new file mode 100644 index 000000000..40b32e592 --- /dev/null +++ b/plugins/compile_commands_json_db/lib/compile_commands_json_db.rb @@ -0,0 +1,51 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'ceedling/plugin' +require 'ceedling/constants' +require 'json' + +class CompileCommandsJsonDb < Plugin + + # `Plugin` setup() + def setup + @fullpath = File.join(PROJECT_BUILD_ARTIFACTS_ROOT, "compile_commands.json") + + @database = [] + + if (File.exist?(@fullpath) && File.size(@fullpath) > 0) + @database = JSON.parse( File.read(@fullpath) ) + end + + @mutex = Mutex.new() + end + + # `Plugin` build step hook + def post_compile_execute(arg_hash) + + # Create new Entry from compilation + value = { + "directory" => Dir.pwd, # TODO: Replace with Ceedling project root when it exists + "file" => arg_hash[:source], + "command" => arg_hash[:shell_command], + "output" => arg_hash[:object] + } + + @mutex.synchronize do + # Determine if we're updating an existing file description or adding a new one + index = @database.index {|h| h["file"] == arg_hash[:source]} + if index + @database[index] = value + else + @database << value + end + + # Rewrite the compile_commands.json file + File.open(@fullpath,'w') {|f| f << JSON.pretty_generate(@database)} + end + end +end diff --git a/plugins/dependencies/README.md b/plugins/dependencies/README.md index 256467dfa..c5c930015 100644 --- a/plugins/dependencies/README.md +++ b/plugins/dependencies/README.md @@ -24,19 +24,26 @@ containing header files that might want to be included by your release project. So how does all this magic work? -First, you need to add the `:dependencies` plugin to your list. Then, we'll add a new -section called :dependencies. There, you can list as many dependencies as you desire. Each -has a series of fields which help Ceedling to understand your needs. Many of them are -optional. If you don't need that feature, just don't include it! In the end, it'll look -something like this: +First, you need to add the Dependencies plugin to your list of enabled plugins. Then, we'll +add a new comfiguration section called `:dependencies`. There, you can list as many +dependencies as you desire. Each has a series of fields that help Ceedling to understand +your needs. Many of them are optional. If you don't need that feature, just don't include +it! In the end, it'll look something like this: + +```yaml +:plugins: + :enabled: + - dependencies -``` :dependencies: - :libraries: + :deps: - :name: WolfSSL - :source_path: third_party/wolfssl/source - :build_path: third_party/wolfssl/build - :artifact_path: third_party/wolfssl/install + :paths: + :fetch: third_party/wolfssl/source + :source: third_party/wolfssl/source + :build: third_party/wolfssl/build + :artifact_lib: third_party/wolfssl/install + :artifact_inc: third_party/wolfssl/install :fetch: :method: :zip :source: \\shared_drive\third_party_libs\wolfssl\wolfssl-4.2.0.zip @@ -72,25 +79,32 @@ it easier for us to see the name of each dependency with starting dash. The name field is only used to print progress while we're running Ceedling. You may call the name of the field whatever you wish. -Working Folders ---------------- - -The `:source_path` field allows us to specify where the source code for each of our -dependencies is stored. If fetching the dependency from elsewhere, it will be fetched -to this location. All commands to build this dependency will be executed from -this location (override this by specifying a `:build_path`). Finally, the output -artifacts will be referenced to this location (override this by specifying a `:artifact_path`) - -If unspecified, the `:source_path` will be `dependencies\dep_name` where `dep_name` -is the name specified in `:name` above (with special characters removed). It's best, -though, if you specify exactly where you want your dependencies to live. +Working Paths +------------- + +All paths are collected under `:dependencies` ↳ `:paths`. The `:source` field allows us +to specify where the source code for each of our dependencies is stored. By default, it's +the same as the `:fetch` path, which is where source will be fetched TO when fetching the +dependency from elsewhere. All commands to build this dependency will be executed from +the `:source` location. Temporary data will be placed in the `:build` location. Unless you're +using one of Ceedling's built-in builders, you'll need to learn where the tool you're using to +build places it's built artifacts , and list that here. Finally, the output +artifacts will be referenced to this location. You override this by specifying a `:artifact` +path. In summary: + + - `:paths` + - `:fetch` -- where things are fetched to (defaults to `build/deps/depname/`) + - `:source` -- where we trigger builds (defaults to `:fetch`) + - `:build` -- where we have the produced build files (defaults to `<:fetch>/build`) + - `:deploy` -- where any produced library files should be copied (defaults to same as release executable) + - `:artifact` -- where output libraries can be found (defaults to `:build`) If the dependency is directly included in your project (you've specified `:none` as the -`:method` for fetching), then `:source_path` should be where your Ceedling can find the +`:method` for fetching), then `:source` should be where your Ceedling can find the source for your dependency in you repo. -All artifacts are relative to the `:artifact_path` (which defaults to be the same as -`:source_path`) +All artifacts are relative to the appropriate `:artifact` path. So if there are multiple +include dirs, choose the highest level and make the rest relative from there. Fetching Dependencies --------------------- @@ -102,30 +116,43 @@ couple of fields: - `:method` -- This is the method that this dependency is fetched. - `:none` -- This tells Ceedling that the code is already included in the project. - `:zip` -- This tells Ceedling that we want to unpack a zip file to our source path. + - `:gzip` -- This tells Ceedling that we want to unpack a gzip file to our source path. - `:git` -- This tells Ceedling that we want to clone a git repo to our source path. - `:svn` -- This tells Ceedling that we want to checkout a subversion repo to our source path. - `:custom` -- This tells Ceedling that we want to use a custom command or commands to fetch the code. -- `:source` -- This is the path or url to fetch code when using the zip or git method. -- `:tag`/`:branch` -- This is the specific tag or branch that you wish to retrieve (git only. optional). -- `:hash` -- This is the specific SHA1 hash you want to fetch (git only. optional, requires a deep clone). -- `:revision` -- This is the specific revision you want to fetch (svn only. optional). -- `:executable` -- This is a list of commands to execute when using the `:custom` method +- `:source` -- This is the path or url to fetch code when using the `:zip`, `:gzip` or `:git` method. +- `:tag`/`:branch` -- This is the specific tag or branch that you wish to retrieve (`:git` only, optional). +- `:hash` -- This is the specific SHA1 hash you want to fetch (`:git` only, optional and triggers a deep clone). +- `:revision` -- This is the specific revision you want to fetch (`:svn` only, optional). +- `:executable` -- This is a YAML list of commands to execute when using the `:custom` method +Some notes: + +The `:source` location for fetching a `:zip` or `:gzip` file is relative to the `:paths` ↳ `:source` +folder. Environment Variables --------------------- Many build systems support customization through environment variables. By specifying -an array of environment variables, Ceedling will customize the shell environment before -calling the build process. +an array of environment variables, the Dependencies plugin will customize the shell environment +before calling the build process. + +Note that Ceedling’s project configuration includes a top-level `:environment` sections itself. +The top-level `:environment` section is for all of Ceedling. The `:environment` section nested +within a specific dependency’s configuration is only for the shell environment used to process +that dependency. The format and abilities of the two `:environment` configuration sections are +also different. Environment variables may be specified in three ways. Let's look at one of each: -``` - :environment: - - ARCHITECTURE=ARM9 - - CFLAGS+=-DADD_AWESOMENESS - - CFLAGS-=-DWASTE +```yaml +:dependencies: + : + :environment: + - ARCHITECTURE=ARM9 + - CFLAGS+=-DADD_AWESOMENESS + - CFLAGS-=-DWASTE ``` In the first example, you see the most straightforward method. The environment variable @@ -199,7 +226,7 @@ In this case, Ceedling is able to automatically add these to its internal source these files to be used while building your release code. Tasks ------ +===== Once configured correctly, the `:dependencies` plugin should integrate seamlessly into your workflow and you shouldn't have to think about it. In the real world, that doesn't always happen. @@ -240,15 +267,55 @@ dependencies. Maybe you want to take that query further and actually get a list of ALL the header files Ceedling has found, including those belonging to your dependencies. -Testing -======= +Custom Tools +============ + +You can optionally specify a compiler, assembler, and linker, just as you would a release build: + +```yaml +:tools: + :deps_compiler: + :executable: gcc + :arguments: + - -g + - -I"$": COLLECTION_PATHS_SUBPROJECTS + - -D$: COLLECTION_DEFINES_SUBPROJECTS + - -c "${1}" + - -o "${2}" + :deps_linker: + :executable: ar + :arguments: + - rcs + - ${2} + - ${1} +``` -Hopefully all your dependencies are fully tested... but we can't always depend on that. -In the event that they are tested with Ceedling, you'll probably want to consider using -the `:subprojects` plugin instead of this one. The purpose of this plugin is to pull in -third party code for release... and to provide a mockable interface for Ceedling to use -during its tests of other modules. +Then, once created, you can reference these tools in your build steps by using the `:build_lib` symbol instead +of a series of strings to explain all the steps. Ceedling will understand that it should build all the specified +source and/or assembly files into the specified library: -If that's what you're after... you've found the right plugin! +```yaml +:dependencies: + :deps: + - :name: CaptainCrunch + :paths: + :fetch: ../cc/ + :source: ../cc/ + :build: ../cc/build + :artifact: ../cc/build + :fetch: + :method: :none + :environment: [] + :build: + - :build_lib + :artifacts: + :static_libraries: + - release/cc.a + :dynamic_libraries: [] + :includes: + - ./cc.h + :defines: + - THESE_GET_USED_DURING_COMPILATION +``` Happy Testing! diff --git a/plugins/dependencies/Rakefile b/plugins/dependencies/Rakefile new file mode 100644 index 000000000..0674949b8 --- /dev/null +++ b/plugins/dependencies/Rakefile @@ -0,0 +1,188 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'rake' + +require 'rbconfig' + +def windows?() + return (RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/) +end + +# Add `ruby` to the command line on Windows to execute the Ruby-based shell script bin/ceedling +CEEDLING_CLI_EXEC = "#{'ruby ' if windows?}../../../../bin/ceedling" + +def prep_test +end + +def assert_file_exist(path) + if File.exist?(path) + puts "File #{path} exists." + else + raise "File #{path} doesn't exist after create" + end +end + +def assert_file_contains(path, expected) + if File.exist?(path) + actual = File.read(path) + if actual.match?(expected) + puts "File #{path} exists and contains specified contents." + else + puts "Expected content: #{expected}" # Debug logging + puts "Actual content: #{actual}" # Debug logging + raise "File #{path} exists but doesn't contain specified contents." + end + else + raise "File #{path} doesn't exist after create" + end +end + +def assert_file_not_exist(path) + unless File.exist?(path) + puts "File #{path} doesn't exist after destroy" + else + raise "File #{path} still exists after destroy." + end +end + +def assert_cmd_return(cmd, expected) + retval = `#{CEEDLING_CLI_EXEC} #{cmd}` + if (retval.include? expected) + puts "Testing included `#{expected}`" + else + puts retval # Debug logging + raise "Testing did not include `#{expected}`" + end +end + +def assert_cmd_not_return(cmd, expected) + retval = `#{CEEDLING_CLI_EXEC} #{cmd}` + if (!retval.include? expected) + puts "Testing didn't included `#{expected}`" + else + raise "Testing included `#{expected}`, which was unexpected." + end +end + +desc "Run integration test on example" +task :integration_test do + chdir("./example/boss") do + + # Start with a blank example project + prep_test + + # verify we can clean the dependencies + puts "\nCleaning the Dependencies:" + assert_cmd_not_return("dependencies:clean",'error') + assert_file_not_exist("./third_party/bees/source/makefile") + assert_file_not_exist("./third_party/bees/source/src/worker.c") + assert_file_not_exist("./third_party/bees/source/src/worker.h") + assert_file_not_exist("./third_party/bees/source/build/libworker.a") + assert_file_not_exist("./third_party/bees/source/build/libworker.h") + assert_file_not_exist("../supervisor/build/release/libsupervisor.a") + assert_file_not_exist("../supervisor/build/artifacts/release/libsupervisor.a") + assert_file_exist("../supervisor/src/supervisor.c") + assert_file_exist("../supervisor/src/supervisor.h") + + # verify we can fetch the dependencies + puts "\nFetching the Dependencies:" + assert_cmd_not_return("dependencies:fetch",'error') + assert_file_exist("./third_party/bees/source/makefile") + assert_file_exist("./third_party/bees/source/src/worker.c") + assert_file_exist("./third_party/bees/source/src/worker.h") + assert_file_not_exist("./third_party/bees/source/build/libworker.a") + assert_file_not_exist("./third_party/bees/source/build/libworker.h") + assert_file_not_exist("../supervisor/build/release/libsupervisor.a") + assert_file_not_exist("../supervisor/build/artifacts/release/libsupervisor.a") + assert_file_exist("../supervisor/src/supervisor.c") + assert_file_exist("../supervisor/src/supervisor.h") + + # verify we can make the dependencies + puts "\nMaking the Dependencies:" + assert_cmd_not_return("dependencies:make",'error') + assert_file_exist("./third_party/bees/source/makefile") + assert_file_exist("./third_party/bees/source/src/worker.c") + assert_file_exist("./third_party/bees/source/src/worker.h") + assert_file_exist("./third_party/bees/source/build/libworker.a") + assert_file_exist("./third_party/bees/source/build/libworker.h") + assert_file_exist("../supervisor/build/release/libsupervisor.a") + assert_file_exist("../supervisor/build/artifacts/release/libsupervisor.a") + assert_file_exist("../supervisor/src/supervisor.c") + assert_file_exist("../supervisor/src/supervisor.h") + + # verify we can clean the dependencies again + puts "\nCleaning the Dependencies (round 2):" + assert_cmd_not_return("dependencies:clean",'error') + assert_file_not_exist("./third_party/bees/source/makefile") + assert_file_not_exist("./third_party/bees/source/src/worker.c") + assert_file_not_exist("./third_party/bees/source/src/worker.h") + assert_file_not_exist("./third_party/bees/source/build/libworker.a") + assert_file_not_exist("./third_party/bees/source/build/libworker.h") + assert_file_not_exist("../supervisor/build/release/libsupervisor.a") + assert_file_not_exist("../supervisor/build/artifacts/release/libsupervisor.a") + assert_file_exist("../supervisor/src/supervisor.c") + assert_file_exist("../supervisor/src/supervisor.h") + + # verify dependencies are built automatically for a release build + puts "\nRelease with Dependencies:" + assert_cmd_not_return("release",'error') + assert_file_exist("./third_party/bees/source/makefile") + assert_file_exist("./third_party/bees/source/src/worker.c") + assert_file_exist("./third_party/bees/source/src/worker.h") + assert_file_exist("./third_party/bees/source/build/libworker.a") + assert_file_exist("./third_party/bees/source/build/libworker.h") + assert_file_exist("../supervisor/build/release/libsupervisor.a") + assert_file_exist("../supervisor/build/artifacts/release/libsupervisor.a") + assert_file_exist("../supervisor/src/supervisor.c") + assert_file_exist("../supervisor/src/supervisor.h") + + # verify we can clean the dependencies again + puts "\nCleaning the Dependencies (round 3):" + assert_cmd_not_return("dependencies:clean",'error') + assert_file_not_exist("./third_party/bees/source/makefile") + assert_file_not_exist("./third_party/bees/source/src/worker.c") + assert_file_not_exist("./third_party/bees/source/src/worker.h") + assert_file_not_exist("./third_party/bees/source/build/libworker.a") + assert_file_not_exist("./third_party/bees/source/build/libworker.h") + assert_file_not_exist("../supervisor/build/release/libsupervisor.a") + assert_file_not_exist("../supervisor/build/artifacts/release/libsupervisor.a") + assert_file_exist("../supervisor/src/supervisor.c") + assert_file_exist("../supervisor/src/supervisor.h") + + # verify dependencies are built automatically for a test build + puts "\nTesting with Dependencies:" + assert_cmd_not_return("test:all",'error') + assert_file_exist("./third_party/bees/source/makefile") + assert_file_exist("./third_party/bees/source/src/worker.c") + assert_file_exist("./third_party/bees/source/src/worker.h") + assert_file_exist("./third_party/bees/source/build/libworker.a") + assert_file_exist("./third_party/bees/source/build/libworker.h") + assert_file_exist("../supervisor/build/release/libsupervisor.a") + assert_file_exist("../supervisor/build/artifacts/release/libsupervisor.a") + assert_file_exist("../supervisor/src/supervisor.c") + assert_file_exist("../supervisor/src/supervisor.h") + + # verify we can clean the dependencies again + puts "\nCleaning the Dependencies (round 4):" + assert_cmd_not_return("dependencies:clean",'error') + assert_file_not_exist("./third_party/bees/source/makefile") + assert_file_not_exist("./third_party/bees/source/src/worker.c") + assert_file_not_exist("./third_party/bees/source/src/worker.h") + assert_file_not_exist("./third_party/bees/source/build/libworker.a") + assert_file_not_exist("./third_party/bees/source/build/libworker.h") + assert_file_not_exist("../supervisor/build/release/libsupervisor.a") + assert_file_not_exist("../supervisor/build/artifacts/release/libsupervisor.a") + assert_file_exist("../supervisor/src/supervisor.c") + assert_file_exist("../supervisor/src/supervisor.h") + + puts "\nPASSES MODULE SELF-TESTS" + + end +end + +task :default => [:integration_test] \ No newline at end of file diff --git a/plugins/dependencies/config/defaults.yml b/plugins/dependencies/config/defaults.yml index 0415f8ea1..e9a024903 100644 --- a/plugins/dependencies/config/defaults.yml +++ b/plugins/dependencies/config/defaults.yml @@ -1,5 +1,78 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + --- :dependencies: - :libraries: [] + :deps: [] + +:tools: + :deps_compiler: + :executable: gcc + :name: 'Dependencies compiler' + :arguments: + - -g + - -I"$": COLLECTION_PATHS_DEPS + - -D$: COLLECTION_DEFINES_DEPS + - -c "${1}" + - -o "${2}" + + :deps_linker: + :executable: ar + :name: 'Dependencies archiver' + :arguments: + - rcs + - ${2} + - ${1} + + :deps_zip: + :executable: unzip + :name: 'Dependencies zip unarchiver' + :optional: true + :arguments: + - -o + - ${1} # Filepath + + :deps_targzip: + :executable: tar + :name: 'Dependencies tar gzip unarchiver' + :optional: true + :arguments: + - -xvzf + - ${1} # Filepath + - -C + - ./ + + :deps_git_clone: + :executable: git + :name: 'Dependencies git clone' + :optional: true + :arguments: + - clone + - ${1} # Optional branch with `-b` flag + - ${2} # Optional depth with `--depth` flag + - ${3} # Repository source + - . + + :deps_git_checkout: + :executable: git + :name: 'Dependencies git checkout' + :optional: true + :arguments: + - checkout + - ${1} # Git hash + + :deps_subversion: + :executable: svn + :name: 'Dependencies subversion' + :optional: true + :arguments: + - checkout + - ${1} # Optional branch with `--revision` flag + - ${2} # Repository source + - . ... diff --git a/plugins/dependencies/dependencies.rake b/plugins/dependencies/dependencies.rake index 87ab4b955..1e70549f3 100644 --- a/plugins/dependencies/dependencies.rake +++ b/plugins/dependencies/dependencies.rake @@ -1,5 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= -DEPENDENCIES_LIBRARIES.each do |deplib| +DEPENDENCIES_DEPS.each do |deplib| # Look up the name of this dependency library deplib_name = @ceedling[DEPENDENCIES_SYM].get_name(deplib) @@ -13,6 +19,7 @@ DEPENDENCIES_LIBRARIES.each do |deplib| all_deps = @ceedling[DEPENDENCIES_SYM].get_static_libraries_for_dependency(deplib) + @ceedling[DEPENDENCIES_SYM].get_dynamic_libraries_for_dependency(deplib) + @ceedling[DEPENDENCIES_SYM].get_include_directories_for_dependency(deplib) + + @ceedling[DEPENDENCIES_SYM].get_include_files_for_dependency(deplib) + @ceedling[DEPENDENCIES_SYM].get_source_files_for_dependency(deplib) # Add a rule for building the actual libraries from dependency list @@ -24,8 +31,9 @@ DEPENDENCIES_LIBRARIES.each do |deplib| # We double-check that it doesn't already exist, because this process sometimes # produces multiple files, but they may have already been flagged as invoked - unless (File.exist?(path)) - + if (File.exist?(path)) + @ceedling[:loginator].log("Nothing to do for dependency #{path}", Verbosity::OBNOXIOUS) + else # Set Environment Variables, Fetch, and Build @ceedling[DEPENDENCIES_SYM].set_env_if_required(path) @ceedling[DEPENDENCIES_SYM].fetch_if_required(path) @@ -36,13 +44,15 @@ DEPENDENCIES_LIBRARIES.each do |deplib| # Add a rule for building the source and includes from dependency list (@ceedling[DEPENDENCIES_SYM].get_include_directories_for_dependency(deplib) + + @ceedling[DEPENDENCIES_SYM].get_include_files_for_dependency(deplib) + @ceedling[DEPENDENCIES_SYM].get_source_files_for_dependency(deplib) ).each do |libpath| task libpath do |filetask| - path = filetask.name - - unless (File.file?(path) || File.directory?(path)) + path = File.expand_path(filetask.name) + if (File.file?(path) || File.directory?(path)) + @ceedling[:loginator].log("Nothing to do for dependency #{path}", Verbosity::OBNOXIOUS) + else # Set Environment Variables, Fetch, and Build @ceedling[DEPENDENCIES_SYM].set_env_if_required(path) @ceedling[DEPENDENCIES_SYM].fetch_if_required(path) @@ -74,12 +84,16 @@ DEPENDENCIES_LIBRARIES.each do |deplib| namespace :fetch do # Add task to directly clobber this dependency - task(deplib_name) do + task(deplib_name => @ceedling[DEPENDENCIES_SYM].get_source_path(deplib)) do @ceedling[DEPENDENCIES_SYM].fetch_if_required(deplib_name) end end end + + # grab our own reference to the main configuration hash + project_config = @ceedling[:configurator].project_config_hash + # Add source files to our list of things to build during release source_files = @ceedling[DEPENDENCIES_SYM].get_source_files_for_dependency(deplib) task PROJECT_RELEASE_BUILD_TARGET => source_files @@ -92,10 +106,6 @@ DEPENDENCIES_LIBRARIES.each do |deplib| dynamic_libs = @ceedling[DEPENDENCIES_SYM].get_dynamic_libraries_for_dependency(deplib) task RELEASE_SYM => dynamic_libs - # Add the include dirs / files to our list of dependencies for release - headers = @ceedling[DEPENDENCIES_SYM].get_include_directories_for_dependency(deplib) - task RELEASE_SYM => headers - # Paths to Libraries need to be Added to the Lib Path List all_libs = static_libs + dynamic_libs PATHS_LIBRARIES ||= [] @@ -108,6 +118,8 @@ DEPENDENCIES_LIBRARIES.each do |deplib| all_libs.each {|lib| LIBRARIES_SYSTEM << File.basename(lib,'.*').sub(/^lib/,'') } LIBRARIES_SYSTEM.uniq! LIBRARIES_SYSTEM.reject!{|s| s.empty?} + + task :prepare => all_deps end # Add any artifact:include or :source folders to our release & test includes paths so linking and mocking work. @@ -116,16 +128,16 @@ end # Add tasks for building or cleaning ALL depencies namespace DEPENDENCIES_SYM do desc "Deploy missing dependencies." - task :deploy => DEPENDENCIES_LIBRARIES.map{|deplib| "#{DEPENDENCIES_SYM}:deploy:#{@ceedling[DEPENDENCIES_SYM].get_name(deplib)}"} + task :deploy => DEPENDENCIES_DEPS.map{|deplib| "#{DEPENDENCIES_SYM}:deploy:#{@ceedling[DEPENDENCIES_SYM].get_name(deplib)}"} desc "Build any missing dependencies." - task :make => DEPENDENCIES_LIBRARIES.map{|deplib| "#{DEPENDENCIES_SYM}:make:#{@ceedling[DEPENDENCIES_SYM].get_name(deplib)}"} + task :make => DEPENDENCIES_DEPS.map{|deplib| "#{DEPENDENCIES_SYM}:make:#{@ceedling[DEPENDENCIES_SYM].get_name(deplib)}"} desc "Clean all dependencies." - task :clean => DEPENDENCIES_LIBRARIES.map{|deplib| "#{DEPENDENCIES_SYM}:clean:#{@ceedling[DEPENDENCIES_SYM].get_name(deplib)}"} + task :clean => DEPENDENCIES_DEPS.map{|deplib| "#{DEPENDENCIES_SYM}:clean:#{@ceedling[DEPENDENCIES_SYM].get_name(deplib)}"} desc "Fetch all dependencies." - task :fetch => DEPENDENCIES_LIBRARIES.map{|deplib| "#{DEPENDENCIES_SYM}:fetch:#{@ceedling[DEPENDENCIES_SYM].get_name(deplib)}"} + task :fetch => DEPENDENCIES_DEPS.map{|deplib| "#{DEPENDENCIES_SYM}:fetch:#{@ceedling[DEPENDENCIES_SYM].get_name(deplib)}"} end namespace :files do @@ -133,7 +145,7 @@ namespace :files do task :dependencies do puts "dependency files:" deps = [] - DEPENDENCIES_LIBRARIES.each do |deplib| + DEPENDENCIES_DEPS.each do |deplib| deps << @ceedling[DEPENDENCIES_SYM].get_static_libraries_for_dependency(deplib) deps << @ceedling[DEPENDENCIES_SYM].get_dynamic_libraries_for_dependency(deplib) end @@ -143,5 +155,3 @@ namespace :files do end end -# Make sure that we build dependencies before attempting to tackle any of the unit tests -Rake::Task[:test_deps].enhance ['dependencies:make'] diff --git a/plugins/dependencies/example/boss/project.yml b/plugins/dependencies/example/boss/project.yml new file mode 100644 index 000000000..fdab1bbe0 --- /dev/null +++ b/plugins/dependencies/example/boss/project.yml @@ -0,0 +1,234 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +--- +:project: + # how to use ceedling. If you're not sure, leave this as `gem` and `?` + :which_ceedling: ../../../.. + :ceedling_version: '?' + + # optional features. If you don't need them, keep them turned off for performance + :use_mocks: TRUE + :use_test_preprocessor: :all + :use_backtrace: :none + + # tweak the way ceedling handles automatic tasks + :build_root: build + :test_file_prefix: test_ + :default_tasks: + - test:all + + # performance options. If your tools start giving mysterious errors, consider + # dropping this to 1 to force single-tasking + :test_threads: 8 + :compile_threads: 8 + + # you can specify different yaml config files which modify the existing one + :options_paths: [] + + # enable release build (more details in release_build section below) + :release_build: TRUE + +# further details to configure the way Ceedling handles test code +:test_build: + :use_assembly: FALSE + +# further details to configure the way Ceedling handles release code +:release_build: + :output: DepTest + :use_assembly: FALSE + :artifacts: [] + +# add the following dependencies to our build +:dependencies: + :deps: + - :name: SupervisorSupremo + :paths: + :fetch: ../supervisor/ + :source: ../supervisor/ + :build: ../supervisor/build + :artifact: ../supervisor/build + :fetch: + :method: :none + :environment: [] + :build: + - "ceedling clobber test:all release" + :artifacts: + :static_libraries: + - release/libsupervisor.a + :dynamic_libraries: [] + :includes: + - ../src/supervisor.h + - :name: WorkerBees + :paths: + :fetch: third_party/bees/source + :source: third_party/bees/source + :build: third_party/bees/source/build + :artifact: third_party/bees/source/build + :fetch: + :method: :zip + :source: ../../../../workerbees.zip #relative to source_path above + :environment: [] + :build: + - make + :artifacts: + :static_libraries: + - libworker.a + :dynamic_libraries: [] + :includes: + - libworker.h + - :name: VersionReporter + :paths: + :fetch: third_party/version/reporter + :source: third_party/version/reporter + :build: third_party/version/build + :artifact: third_party/version/build + :fetch: + :method: :tar_gzip + :source: ../../../../version.tar.gzip #relative to source_path above + :environment: [] + :build: + - :build_lib + :artifacts: + :static_libraries: + - libver.a + :dynamic_libraries: [] + :includes: + - ../reporter/version.h + +# Plugins are optional Ceedling features which can be enabled. Ceedling supports +# a variety of plugins which may effect the way things are compiled, reported, +# or may provide new command options. Refer to the readme in each plugin for +# details on how to use it. +:plugins: + :load_paths: [] + :enabled: + #- beep # beeps when finished, so you don't waste time waiting for ceedling + #- module_generator # handy for quickly creating source, header, and test templates + #- gcov # test coverage using gcov. Requires gcc, gcov, and a coverage analyzer like gcovr + #- bullseye # test coverage using bullseye. Requires bullseye for your platform + #- command_hooks # write custom actions to be called at different points during the build process + #- compile_commands_json # generate a compile_commands.json file + - dependencies # automatically fetch 3rd party libraries, etc. + #- subprojects # managing builds and test for static libraries + #- fake_function_framework # use FFF instead of CMock + + # Report options (You'll want to choose one stdout option, but may choose multiple stored options if desired) + - report_tests_pretty_stdout + +# override the default extensions for your system and toolchain +:extension: + #:header: .h + #:source: .c + #:assembly: .s + #:dependencies: .d + #:object: .o + :executable: .out + #:testpass: .pass + #:testfail: .fail + :subprojects: .a + +# This is where Ceedling should look for your source and test files. +# see documentation for the many options for specifying this. +:paths: + :test: + - ./test + :source: + - ./src + :include: + - ./src + :libraries: [] + +# You can even specify specific files to add or remove from your test +# and release collections. Usually it's better to use paths and let +# Ceedling do the work for you! +:files: + :test: [] + :source: [] + +# Compilation symbols to be injected into builds +# See documentation for advanced options: +# - Test name matchers for different symbols per test executable build +# - Referencing symbols in multiple lists using advanced YAML +# - Specifiying symbols used during test preprocessing +:defines: + :test: + - TEST # Simple list option to add symbol 'TEST' to compilation of all files in all test executables + - STATIC= + :release: + - STATIC=static + + # Enable to inject name of a test as a unique compilation symbol into its respective executable build. + :use_test_definition: FALSE + +# Configure additional command line flags provided to tools used in each build step +# :flags: +# :release: +# :compile: # Add '-Wall' and '--02' to compilation of all files in release target +# - -Wall +# - --O2 +# :test: +# :compile: +# '(_|-)special': # Add '-pedantic' to compilation of all files in all test executables with '_special' or '-special' in their names +# - -pedantic +# '*': # Add '-foo' to compilation of all files in all test executables +# - -foo + +# Configuration Options specific to CMock. See CMock docs for details +: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 + +# Configuration options specific to Unity. +:unity: + :defines: + - UNITY_EXCLUDE_FLOAT + +# You can optionally have ceedling create environment variables for you before +# performing the rest of its tasks. +:environment: [] + +# LIBRARIES +# These libraries are automatically injected into the build process. Those specified as +# common will be used in all types of builds. Otherwise, libraries can be injected in just +# tests or releases. These options are MERGED with the options in supplemental yaml files. +:libraries: + :placement: :end + :flag: "-l${1}" + :path_flag: "-L ${1}" + :system: [] # for example, you might list 'm' to grab the math library + :test: [] + :release: [] + +# TOOLS +# This is custom configuration for any tools, but in this case, we are highlighting the +# configuration options for the dependency tools +:tools: + :deps_compiler: + :executable: gcc + :arguments: + - -g + - -I"$": COLLECTION_PATHS_DEPS + - -D$: COLLECTION_DEFINES_DEPS + - -c "${1}" + - -o "${2}" + :deps_linker: + :executable: ar + :arguments: + - -rcs + - ${2} + - ${1} diff --git a/plugins/dependencies/example/boss/src/boss.c b/plugins/dependencies/example/boss/src/boss.c new file mode 100644 index 000000000..49a994d21 --- /dev/null +++ b/plugins/dependencies/example/boss/src/boss.c @@ -0,0 +1,80 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#include "boss.h" +#include "supervisor.h" +#include "libworker.h" + +#define MAXIMUM_WORKERS 20 + +STATIC int hours_worked[MAXIMUM_WORKERS]; +STATIC int total_workers = 0; +STATIC int total_hours = 0; + +void boss_start() +{ + int i = 0; + + total_workers = 0; + total_hours = 0; + + for (i = 0; i < MAXIMUM_WORKERS; i++) + { + hours_worked[i] = 0; + } +} + +void boss_hire_workers(int num_workers) +{ + if (num_workers > 0) { + total_workers += num_workers; + } +} + +void boss_fire_workers(int num_workers) +{ + if (num_workers > total_workers) + { + num_workers = total_workers; + } + + if (num_workers > 0) + { + total_workers -= num_workers; + } +} + +int boss_micro_manage(int* chunks_of_work, int num_chunks) +{ + int i; + int id; + + if ((num_chunks < 0) || (chunks_of_work == 0)) + { + return -1; + } + + /* Start of the work iteration */ + for (i = 0; i < total_workers; i++) + { + worker_start_over(i); + } + + /* Distribute the work "fairly" */ + for (i = 0; i < num_chunks; i++) + { + id = supervisor_delegate(hours_worked, total_workers); + if (id >= 0) + { + worker_work(id, chunks_of_work[i]); + hours_worked[id] = worker_progress(id); + } + } + + /* How much work was finished? */ + return supervisor_progress(hours_worked, total_workers); +} \ No newline at end of file diff --git a/plugins/dependencies/example/boss/src/boss.h b/plugins/dependencies/example/boss/src/boss.h new file mode 100644 index 000000000..dcc14d44e --- /dev/null +++ b/plugins/dependencies/example/boss/src/boss.h @@ -0,0 +1,16 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#ifndef BOSS_H +#define BOSS_H + +void boss_start(); +void boss_hire_workers(int num_workers); +void boss_fire_workers(int num_workers); +int boss_micro_manage(int* chunks_of_work, int num_chunks); + +#endif diff --git a/plugins/dependencies/example/boss/src/main.c b/plugins/dependencies/example/boss/src/main.c new file mode 100644 index 000000000..dec9fd3ca --- /dev/null +++ b/plugins/dependencies/example/boss/src/main.c @@ -0,0 +1,45 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#include +#include + +#include "boss.h" +#include "version.h" + +#define WORK 20 + +int main(int argc, char *argv[]) +{ + int i; + int work[WORK]; + int retval; + + /* output the version */ + puts(get_version()); + + /* This could be more interesting... but honestly, we're just proving this all builds */ + boss_start(); + + /* Hire some workers */ + for (i=0; i < 3; i++) + { + boss_hire_workers( 1 + rand() % 5 ); + } + + /* Fire a few */ + boss_fire_workers( rand() % 3 ); + + /* Do some work */ + for (i= 0; i < WORK; i++) + { + work[i] = rand() % 10; + } + retval = boss_micro_manage(work, WORK); + + return retval; +} \ No newline at end of file diff --git a/plugins/dependencies/example/boss/test/test_boss.c b/plugins/dependencies/example/boss/test/test_boss.c new file mode 100644 index 000000000..a64760370 --- /dev/null +++ b/plugins/dependencies/example/boss/test/test_boss.c @@ -0,0 +1,118 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#ifdef TEST + +#include "unity.h" + +#include "mock_supervisor.h" +#include "mock_libworker.h" +#include "boss.h" + + +extern int hours_worked[]; +extern int total_workers; +extern int total_hours; + +void setUp(void) +{ + boss_start(); +} + +void tearDown(void) +{ +} + +void test_boss_start_ResetsAllTheStuff(void) +{ + int i; + + total_workers = 3; + total_hours = 33; + + for (i=0; i < 3; i++) + { + hours_worked[i] = i+1; + } + + boss_start(); + + TEST_ASSERT_EQUAL_INT(0, total_workers); + TEST_ASSERT_EQUAL_INT(0, total_hours); + TEST_ASSERT_EQUAL_INT(0, hours_worked[0]); + TEST_ASSERT_EQUAL_INT(0, hours_worked[1]); + TEST_ASSERT_EQUAL_INT(0, hours_worked[2]); +} + +void test_boss_can_HireAndFireWorkers(void) +{ + TEST_ASSERT_EQUAL(0, total_workers); + + boss_hire_workers(3); + TEST_ASSERT_EQUAL(3, total_workers); + + boss_hire_workers(1); + boss_hire_workers(7); + TEST_ASSERT_EQUAL(11, total_workers); + + boss_hire_workers(0); + TEST_ASSERT_EQUAL(11, total_workers); + + boss_hire_workers(-1); + TEST_ASSERT_EQUAL(11, total_workers); + + boss_fire_workers(3); + TEST_ASSERT_EQUAL(8, total_workers); + + boss_fire_workers(2); + boss_fire_workers(4); + TEST_ASSERT_EQUAL(2, total_workers); + + boss_fire_workers(0); + TEST_ASSERT_EQUAL(2, total_workers); + + boss_fire_workers(-1); + TEST_ASSERT_EQUAL(2, total_workers); + + boss_hire_workers(18); + TEST_ASSERT_EQUAL(20, total_workers); + + boss_fire_workers(20); + TEST_ASSERT_EQUAL(0, total_workers); + + boss_fire_workers(5); + TEST_ASSERT_EQUAL(0, total_workers); +} + +void test_boss_can_MicroManageLikeABoss(void) +{ + /* An ever-increasing amount of work. this boss is kinda mean. */ + int i; + const int work_to_do[8] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + + worker_start_over_Ignore(); + worker_work_Ignore(); + worker_progress_IgnoreAndReturn(1); + supervisor_progress_IgnoreAndReturn(36); + + for (i=0; i < 8; i++) + { + supervisor_delegate_IgnoreAndReturn(i % 4); + } + + /* assign all the hours */ + boss_hire_workers(4); + TEST_ASSERT_EQUAL_INT(36, boss_micro_manage(work_to_do, 8)); + + /* make sure everyone has work to do */ + for (i=0; i < 4; i++) + { + TEST_ASSERT_NOT_EQUAL_INT(0, hours_worked[i]); + } +} + +#endif diff --git a/plugins/dependencies/example/supervisor/project.yml b/plugins/dependencies/example/supervisor/project.yml new file mode 100644 index 000000000..57b3b1d8f --- /dev/null +++ b/plugins/dependencies/example/supervisor/project.yml @@ -0,0 +1,168 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +--- +:project: + # how to use ceedling. If you're not sure, leave this as `gem` and `?` + :which_ceedling: ../../../.. + :ceedling_version: '?' + + # optional features. If you don't need them, keep them turned off for performance + :use_mocks: TRUE + :use_test_preprocessor: :all + :use_backtrace: :none + + # tweak the way ceedling handles automatic tasks + :build_root: build + :test_file_prefix: test_ + :default_tasks: + - test:all + + # performance options. If your tools start giving mysterious errors, consider + # dropping this to 1 to force single-tasking + :test_threads: 8 + :compile_threads: 8 + + # you can specify different yaml config files which modify the existing one + :options_paths: [] + + # enable release build (more details in release_build section below) + :release_build: TRUE + +# further details to configure the way Ceedling handles test code +:test_build: + :use_assembly: FALSE + +# further details to configure the way Ceedling handles release code +:release_build: + :output: libsupervisor.a + :use_assembly: FALSE + :artifacts: [] + +# Plugins are optional Ceedling features which can be enabled. Ceedling supports +# a variety of plugins which may effect the way things are compiled, reported, +# or may provide new command options. Refer to the readme in each plugin for +# details on how to use it. +:plugins: + :load_paths: [] + :enabled: + #- beep # beeps when finished, so you don't waste time waiting for ceedling + #- module_generator # handy for quickly creating source, header, and test templates + #- gcov # test coverage using gcov. Requires gcc, gcov, and a coverage analyzer like gcovr + #- bullseye # test coverage using bullseye. Requires bullseye for your platform + #- command_hooks # write custom actions to be called at different points during the build process + #- compile_commands_json # generate a compile_commands.json file + #- dependencies # automatically fetch 3rd party libraries, etc. + #- subprojects # managing builds and test for static libraries + #- fake_function_framework # use FFF instead of CMock + + # Report options (You'll want to choose one stdout option, but may choose multiple stored options if desired) + - report_tests_pretty_stdout + +# override the default extensions for your system and toolchain +:extension: + #:header: .h + #:source: .c + #:assembly: .s + #:dependencies: .d + #:object: .o + :executable: .a + #:testpass: .pass + #:testfail: .fail + #:subprojects: .a + +# This is where Ceedling should look for your source and test files. +# see documentation for the many options for specifying this. +:paths: + :test: + - ./test + :source: + - ./src + :include: + - ./src + :libraries: [] + +# You can even specify specific files to add or remove from your test +# and release collections. Usually it's better to use paths and let +# Ceedling do the work for you! +:files: + :test: [] + :source: [] + +# Compilation symbols to be injected into builds +# See documentation for advanced options: +# - Test name matchers for different symbols per test executable build +# - Referencing symbols in multiple lists using advanced YAML +# - Specifiying symbols used during test preprocessing +:defines: + :test: + - TEST # Simple list option to add symbol 'TEST' to compilation of all files in all test executables + :release: [] + + # Enable to inject name of a test as a unique compilation symbol into its respective executable build. + :use_test_definition: FALSE + +# Configure additional command line flags provided to tools used in each build step +# :flags: +# :release: +# :compile: # Add '-Wall' and '--02' to compilation of all files in release target +# - -Wall +# - --O2 +# :test: +# :compile: +# '(_|-)special': # Add '-pedantic' to compilation of all files in all test executables with '_special' or '-special' in their names +# - -pedantic +# '*': # Add '-foo' to compilation of all files in all test executables +# - -foo + +# Configuration Options specific to CMock. See CMock docs for details +: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 + +# Configuration options specific to Unity. +:unity: + :defines: + - UNITY_EXCLUDE_FLOAT + +# You can optionally have ceedling create environment variables for you before +# performing the rest of its tasks. +:environment: [] + +# LIBRARIES +# These libraries are automatically injected into the build process. Those specified as +# common will be used in all types of builds. Otherwise, libraries can be injected in just +# tests or releases. These options are MERGED with the options in supplemental yaml files. +:libraries: + :placement: :end + :flag: "-l${1}" + :path_flag: "-L ${1}" + :system: [] # for example, you might list 'm' to grab the math library + :test: [] + :release: [] + +# Override the default linker tool to build a static library release instead of +# as executable. woo! +:tools: + :release_linker: + :name: library linker + :executable: as + :arguments: + - "\"${1}\"" + - "${5}" + - "-o \"${2}\"" + - "${4}" diff --git a/plugins/dependencies/example/supervisor/src/supervisor.c b/plugins/dependencies/example/supervisor/src/supervisor.c new file mode 100644 index 000000000..81e678bc6 --- /dev/null +++ b/plugins/dependencies/example/supervisor/src/supervisor.c @@ -0,0 +1,45 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#include "supervisor.h" + +int supervisor_delegate(int* worker_loads, int num_workers) +{ + int i; + int most_bored_id = 0; + int most_bored_hours = 999999; + + if ((num_workers < 0) || (worker_loads == 0)) + return -1; + + for (i=0; i < num_workers; i++) + { + if (worker_loads[i] < most_bored_hours) + { + most_bored_hours = worker_loads[i]; + most_bored_id = i; + } + } + + return most_bored_id; +} + +int supervisor_progress(int* worker_loads, int num_workers) +{ + int i; + int total_hours = 0; + + if (worker_loads == 0) + return 0; + + for (i=0; i < num_workers; i++) + { + total_hours += worker_loads[i]; + } + + return total_hours; +} diff --git a/plugins/dependencies/example/supervisor/src/supervisor.h b/plugins/dependencies/example/supervisor/src/supervisor.h new file mode 100644 index 000000000..789ac76fc --- /dev/null +++ b/plugins/dependencies/example/supervisor/src/supervisor.h @@ -0,0 +1,14 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#ifndef SUPERVISOR_H +#define SUPERVISOR_H + +int supervisor_delegate(int* worker_loads, int num_workers); +int supervisor_progress(int* worker_loads, int num_workers); + +#endif diff --git a/plugins/dependencies/example/supervisor/test/test_supervisor.c b/plugins/dependencies/example/supervisor/test/test_supervisor.c new file mode 100644 index 000000000..1590a537b --- /dev/null +++ b/plugins/dependencies/example/supervisor/test/test_supervisor.c @@ -0,0 +1,58 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#ifdef TEST + +#include "unity.h" + +#include "supervisor.h" + +void setUp(void) +{ +} + +void tearDown(void) +{ +} + +void test_supervisor_can_DelegateProperlyToLeastBusyWorker(void) +{ + int loads1[] = { 1, 2, 3, 4 }; + int loads2[] = { 2, 1, 3, 4 }; + int loads3[] = { 2, 1, 0, 8 }; + int loads4[] = { 9, 9, 7, 0 }; + int loads5[] = { 0, 0, 1, 4 }; + + TEST_ASSERT_EQUAL(0, supervisor_delegate(loads1, 2)); + TEST_ASSERT_EQUAL(0, supervisor_delegate(loads1, 4)); + TEST_ASSERT_EQUAL(1, supervisor_delegate(loads2, 4)); + TEST_ASSERT_EQUAL(2, supervisor_delegate(loads3, 4)); + TEST_ASSERT_EQUAL(2, supervisor_delegate(loads3, 3)); + TEST_ASSERT_EQUAL(3, supervisor_delegate(loads4, 4)); + TEST_ASSERT_EQUAL(0, supervisor_delegate(loads5, 4)); + TEST_ASSERT_EQUAL(0, supervisor_delegate(loads5, 2)); +} + +void test_supervisor_can_TrackProgressProperlyAcrossAllWorkers(void) +{ + int loads1[] = { 1, 2, 3, 4 }; + int loads2[] = { 2, 1, 3, 4 }; + int loads3[] = { 2, 1, 0, 8 }; + int loads4[] = { 9, 9, 7, 0 }; + int loads5[] = { 0, 0, 1, 4 }; + + TEST_ASSERT_EQUAL(3, supervisor_progress(loads1, 2)); + TEST_ASSERT_EQUAL(10, supervisor_progress(loads1, 4)); + TEST_ASSERT_EQUAL(10, supervisor_progress(loads2, 4)); + TEST_ASSERT_EQUAL(11, supervisor_progress(loads3, 4)); + TEST_ASSERT_EQUAL(3, supervisor_progress(loads3, 3)); + TEST_ASSERT_EQUAL(25, supervisor_progress(loads4, 4)); + TEST_ASSERT_EQUAL(5, supervisor_progress(loads5, 4)); + TEST_ASSERT_EQUAL(0, supervisor_progress(loads5, 2)); +} + +#endif diff --git a/plugins/dependencies/example/version.tar.gzip b/plugins/dependencies/example/version.tar.gzip new file mode 100644 index 000000000..d2f5868bd Binary files /dev/null and b/plugins/dependencies/example/version.tar.gzip differ diff --git a/plugins/dependencies/example/workerbees.zip b/plugins/dependencies/example/workerbees.zip new file mode 100644 index 000000000..fdc47ba97 Binary files /dev/null and b/plugins/dependencies/example/workerbees.zip differ diff --git a/plugins/dependencies/lib/dependencies.rb b/plugins/dependencies/lib/dependencies.rb index d6c00872f..ae9538bfa 100644 --- a/plugins/dependencies/lib/dependencies.rb +++ b/plugins/dependencies/lib/dependencies.rb @@ -1,19 +1,26 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'ceedling/plugin' require 'ceedling/constants' +require 'ceedling/exceptions' +require 'pathname' -DEPENDENCIES_ROOT_NAME = 'dependencies' -DEPENDENCIES_TASK_ROOT = DEPENDENCIES_ROOT_NAME + ':' -DEPENDENCIES_SYM = DEPENDENCIES_ROOT_NAME.to_sym +DEPENDENCIES_ROOT_NAME = 'dependencies' +DEPENDENCIES_TASK_ROOT = DEPENDENCIES_ROOT_NAME + ':' +DEPENDENCIES_SYM = DEPENDENCIES_ROOT_NAME.to_sym class Dependencies < Plugin - def setup - @plugin_root = File.expand_path(File.join(File.dirname(__FILE__), '..')) - + def setup() # Set up a fast way to look up dependencies by name or static lib path @dependencies = {} @dynamic_libraries = [] - DEPENDENCIES_LIBRARIES.each do |deplib| + DEPENDENCIES_DEPS.each do |deplib| @dependencies[ deplib[:name] ] = deplib.clone all_deps = get_static_libraries_for_dependency(deplib) + @@ -26,43 +33,77 @@ def setup @dynamic_libraries += get_dynamic_libraries_for_dependency(deplib) end + + # Validate fetch tools per the configuration + @dependencies.each {|_, config| validate_fetch_tools( config )} end - def config + def config() updates = { :collection_paths_include => COLLECTION_PATHS_INCLUDE, :collection_all_headers => COLLECTION_ALL_HEADERS, } - @ceedling[DEPENDENCIES_SYM].get_include_directories_for_dependency(deplib).each do |incpath| - updates[:collection_paths_include] << incpath - Dir[ File.join(incpath, "*#{EXTENSION_HEADER}") ].each do |f| - updates[:collection_all_headers] << f + DEPENDENCIES_DEPS.each do |deplib| + @ceedling[DEPENDENCIES_SYM].get_include_directories_for_dependency(deplib).each do |incpath| + updates[:collection_paths_include] << incpath + end + + @ceedling[DEPENDENCIES_SYM].get_include_files_for_dependency(deplib).each do |inc| + updates[:collection_all_headers] << inc end end - return updates + updates end def get_name(deplib) - raise "Each dependency must have a name!" if deplib[:name].nil? + raise CeedlingException.new( "Each dependency must have a name!" ) if deplib[:name].nil? return deplib[:name].gsub(/\W*/,'') end + def get_fetch_path(deplib) + if deplib.include? :paths + return deplib[:paths][:fetch] || deplib[:paths][:source] || File.join('dependencies', get_name(deplib)) + else + return File.join('dependencies', get_name(deplib)) + end + end + def get_source_path(deplib) - return deplib[:source_path] || File.join('dependencies', get_name(deplib)) + if deplib.include? :paths + return deplib[:paths][:source] || deplib[:paths][:fetch] || File.join('dependencies', get_name(deplib)) + else + return File.join('dependencies', get_name(deplib)) + end end def get_build_path(deplib) - return deplib[:build_path] || deplib[:source_path] || File.join('dependencies', get_name(deplib)) + if deplib.include? :paths + return deplib[:paths][:build] || deplib[:paths][:source] || deplib[:paths][:fetch] || File.join('dependencies', get_name(deplib)) + else + return File.join('dependencies', get_name(deplib)) + end end def get_artifact_path(deplib) - return deplib[:artifact_path] || deplib[:source_path] || File.join('dependencies', get_name(deplib)) + if deplib.include? :paths + return deplib[:paths][:artifact] || deplib[:paths][:build] || File.join('dependencies', get_name(deplib)) + else + return File.join('dependencies', get_name(deplib)) + end end - def get_working_paths(deplib) - paths = [deplib[:source_path], deplib[:build_path], deplib[:artifact_paths]].compact.uniq + def get_working_paths(deplib, artifact_only=false) + paths = if deplib.include?(:paths) + if artifact_only + [deplib[:paths][:artifact]].compact.uniq + else + deplib[:paths].values.compact.uniq + end + else + [] + end paths = [ File.join('dependencies', get_name(deplib)) ] if (paths.empty?) return paths end @@ -80,13 +121,25 @@ def get_source_files_for_dependency(deplib) end def get_include_directories_for_dependency(deplib) - paths = (deplib[:artifacts][:includes] || []).map {|path| File.join(get_artifact_path(deplib), path)} - @ceedling[:file_system_utils].collect_paths(paths) + paths = (deplib[:artifacts][:includes] || []).map do |path| + if (path =~ /.*\.h$/) + path.split(/[\/\\]/)[0..-2] + elsif (path =~ /(?:^\+:)|(?:^-:)|(?:\*\*)/) + @ceedling[:file_path_collection_utils].collect_paths([path]) + else + path + end + end + return paths.map{|path| File.join(get_artifact_path(deplib), path) }.uniq + end + + def get_include_files_for_dependency(deplib) + (deplib[:artifacts][:includes] || []).map {|path| File.join(get_artifact_path(deplib), path)} end def set_env_if_required(lib_path) blob = @dependencies[lib_path] - raise "Could not find dependency '#{lib_path}'" if blob.nil? + raise CeedlingException.new( "Could not find dependency '#{lib_path}'" ) if blob.nil? return if (blob[:environment].nil?) return if (blob[:environment].empty?) @@ -105,44 +158,126 @@ def set_env_if_required(lib_path) end end + def generate_command_line(cmdline, name=nil) + # Break apart command line at white spaces + cmdline_items = cmdline.split(/\s+/) + + # Even though it may seem redundant to build a command line we already have, + # we do so for possible argument expansion/substitution and, more importantly, logging output. + + # Construct a tool configuration + tool_config = { + # Use tool name if provided, otherwise, grab something from the command line + :name => name.nil? ? cmdline_items[0] : name, + + # Extract executable as first item on the command line + :executable => cmdline_items[0], + + # Extract remaining arguments if there are any + :arguments => (cmdline_items.length > 1) ? cmdline_items[1..-1] : [] + } + + # Construct a command from our tool configuration + command = @ceedling[:tool_executor].build_command_line( tool_config, [] ) + + return command + end + def fetch_if_required(lib_path) blob = @dependencies[lib_path] - raise "Could not find dependency '#{lib_path}'" if blob.nil? - return if (blob[:fetch].nil?) - return if (blob[:fetch][:method].nil?) - return if (directory(blob[:source_path]) && !Dir.empty?(blob[:source_path])) - - steps = case blob[:fetch][:method] - when :none - return - when :zip - [ "gzip -d #{blob[:fetch][:source]}" ] - when :git - branch = blob[:fetch][:tag] || blob[:fetch][:branch] || '' - branch = ("-b " + branch) unless branch.empty? - unless blob[:fetch][:hash].nil? - # Do a deep clone to ensure the commit we want is available - retval = [ "git clone #{branch} #{blob[:fetch][:source]} ." ] - # Checkout the specified commit - retval << "git checkout #{blob[:fetch][:hash]}" - else - # Do a thin clone - retval = [ "git clone #{branch} --depth 1 #{blob[:fetch][:source]} ." ] - end - when :svn - revision = blob[:fetch][:revision] || '' - revision = ("--revision " + branch) unless branch.empty? - retval = [ "svn checkout #{revision} #{blob[:fetch][:source]} ." ] - retval - when :custom - blob[:fetch][:executable] - else - raise "Unknown fetch method '#{blob[:fetch][:method]}' for dependency '#{blob[:name]}'" - end + + raise CeedlingException.new( "Could not find dependency '#{lib_path}'" ) if blob.nil? + + if (blob[:fetch].nil?) || (blob[:fetch][:method].nil?) + @ceedling[:loginator].log("No fetch method for dependency '#{blob[:name]}'", Verbosity::COMPLAIN) + return + end + + unless (directory(get_source_path(blob))) + @ceedling[:loginator].log("Path #{get_source_path(blob)} is required", Verbosity::COMPLAIN) + return + end + + FileUtils.mkdir_p(get_fetch_path(blob)) unless File.exist?(get_fetch_path(blob)) + + steps = [] + + # Tools already validated within `setup()` + case blob[:fetch][:method] + when :none + # Do nothing + + when :zip + steps << + @ceedling[:tool_executor].build_command_line( + TOOLS_DEPS_ZIP, + [], + blob[:fetch][:source] + ) + + when :tar_gzip + steps << + @ceedling[:tool_executor].build_command_line( + TOOLS_DEPS_TARGZIP, + [], + blob[:fetch][:source] + ) + + when :git + branch = blob[:fetch][:tag] || blob[:fetch][:branch] || '' + branch = '-b ' + branch unless branch.empty? + + unless blob[:fetch][:hash].nil? + # Do a deep clone to ensure the commit we want is available + steps << + @ceedling[:tool_executor].build_command_line( + TOOLS_DEPS_GIT_CLONE, + [], + branch, + '', # No depth + blob[:fetch][:source] + ) + + # Checkout the specified commit + steps << + @ceedling[:tool_executor].build_command_line( + TOOLS_DEPS_GIT_CHECKOUT, + [], + blob[:fetch][:hash] + ) + else + # Do a thin clone + steps << + @ceedling[:tool_executor].build_command_line( + TOOLS_DEPS_GIT_CLONE, + [], + branch, + '--depth 1', + blob[:fetch][:source] + ) + end + + when :svn + revision = blob[:fetch][:revision] || '' + revision = '--revision ' + revision unless revision.empty? + + steps << + @ceedling[:tool_executor].build_command_line( + TOOLS_DEPS_SUBVERSION, + [], + revision, + blob[:fetch][:source] + ) + + when :custom + blob[:fetch][:executable].each.with_index(1) do |cmdline, index| + steps << generate_command_line( cmdline, "Dependencies custom command \##{index}" ) + end + end # Perform the actual fetching - @ceedling[:streaminator].stdout_puts("Fetching dependency #{blob[:name]}...", Verbosity::NORMAL) - Dir.chdir(get_source_path(blob)) do + @ceedling[:loginator].log("Fetching dependency #{blob[:name]}...", Verbosity::NORMAL) + Dir.chdir(get_fetch_path(blob)) do steps.each do |step| @ceedling[:tool_executor].exec( step ) end @@ -151,72 +286,87 @@ def fetch_if_required(lib_path) def build_if_required(lib_path) blob = @dependencies[lib_path] - raise "Could not find dependency '#{lib_path}'" if blob.nil? + raise CeedlingException.new( "Could not find dependency '#{lib_path}'" ) if blob.nil? # We don't clean anything unless we know how to fetch a new copy if (blob[:build].nil? || blob[:build].empty?) - @ceedling[:streaminator].stdout_puts("Nothing to build for dependency #{blob[:name]}", Verbosity::NORMAL) + @ceedling[:loginator].log("Nothing to build for dependency #{blob[:name]}", Verbosity::NORMAL) return end + FileUtils.mkdir_p(get_source_path(blob)) unless File.exist?(get_source_path(blob)) + FileUtils.mkdir_p(get_artifact_path(blob)) unless File.exist?(get_artifact_path(blob)) + # Perform the build - @ceedling[:streaminator].stdout_puts("Building dependency #{blob[:name]}...", Verbosity::NORMAL) - Dir.chdir(get_build_path(blob)) do + @ceedling[:loginator].log("Building dependency #{blob[:name]}...", Verbosity::NORMAL) + Dir.chdir(get_source_path(blob)) do blob[:build].each do |step| - @ceedling[:tool_executor].exec( step ) + if (step.class == Symbol) + exec_dependency_builtin_command(step, blob) + else + @ceedling[:tool_executor].exec( generate_command_line(step) ) + end end end end def clean_if_required(lib_path) blob = @dependencies[lib_path] - raise "Could not find dependency '#{lib_path}'" if blob.nil? + raise CeedlingException.new( "Could not find dependency '#{lib_path}'" ) if blob.nil? # We don't clean anything unless we know how to fetch a new copy - if (blob[:fetch].nil? || blob[:fetch][:method].nil? || (blob[:fetch][:method] == :none)) - @ceedling[:streaminator].stdout_puts("Nothing to clean for dependency #{blob[:name]}", Verbosity::NORMAL) + if (blob[:fetch].nil? || blob[:fetch][:method].nil?) + @ceedling[:loginator].log("Nothing to clean for dependency #{blob[:name]}", Verbosity::NORMAL) return end + # We only need to clean the artifacts if the source isn't being fetched + artifacts_only = (blob[:fetch][:method] == :none) + # Perform the actual Cleaning - @ceedling[:streaminator].stdout_puts("Cleaning dependency #{blob[:name]}...", Verbosity::NORMAL) - get_working_paths(blob).each do |path| + @ceedling[:loginator].log("Cleaning dependency #{blob[:name]}...", Verbosity::NORMAL) + get_working_paths(blob, artifacts_only).each do |path| FileUtils.rm_rf(path) if File.directory?(path) end end def deploy_if_required(lib_path) blob = @dependencies[lib_path] - raise "Could not find dependency '#{lib_path}'" if blob.nil? + raise CeedlingException.new( "Could not find dependency '#{lib_path}'" ) if blob.nil? # We don't need to deploy anything if there isn't anything to deploy - if (blob[:artifacts].nil? || blob[:artifacts][:dynamic_libraries].nil? || blob[:artifacts][:dynamic_libraries].empty?) - @ceedling[:streaminator].stdout_puts("Nothing to deploy for dependency #{blob[:name]}", Verbosity::NORMAL) + if (blob[:artifacts].nil? || blob[:artifacts][:dynamic_libraries].nil? || blob[:artifacts][:dynamic_libraries].empty?) + @ceedling[:loginator].log("Nothing to deploy for dependency #{blob[:name]}", Verbosity::NORMAL) return end # Perform the actual Deploying - @ceedling[:streaminator].stdout_puts("Deploying dependency #{blob[:name]}...", Verbosity::NORMAL) + @ceedling[:loginator].log("Deploying dependency #{blob[:name]}...", Verbosity::NORMAL) FileUtils.cp( lib_path, File.dirname(PROJECT_RELEASE_BUILD_TARGET) ) end def add_headers_and_sources() # Search for header file paths and files to add to our collections - DEPENDENCIES_LIBRARIES.each do |deplib| + cfg = @ceedling[:configurator].project_config_hash + + DEPENDENCIES_DEPS.each do |deplib| get_include_directories_for_dependency(deplib).each do |header| - cfg = @ceedling[:configurator].project_config_hash cfg[:collection_paths_include] << header cfg[:collection_paths_source_and_include] << header cfg[:collection_paths_test_support_source_include] << header cfg[:collection_paths_test_support_source_include_vendor] << header cfg[:collection_paths_release_toolchain_include] << header - Dir[ File.join(header, "*#{EXTENSION_HEADER}") ].each do |f| - cfg[:collection_all_headers] << f - end + end + + get_include_files_for_dependency(deplib).each do |header| + cfg[:collection_all_headers] << header + + cfg[:files] ||= {} + cfg[:files][:include] ||= [] + cfg[:files][:include] << header end get_source_files_for_dependency(deplib).each do |source| - cfg = @ceedling[:configurator].project_config_hash cfg[:collection_paths_source_and_include] << source cfg[:collection_paths_test_support_source_include] << source cfg[:collection_paths_test_support_source_include_vendor] << source @@ -226,12 +376,156 @@ def add_headers_and_sources() end end end + end - # Make all these updated files findable by Ceedling - @ceedling[:file_finder].prepare_search_sources() + def exec_dependency_builtin_command(step, blob) + case step + when :build_lib # We are going to use our defined deps tools to build this library + build_lib(blob) + else + raise CeedlingException.new( "No such build action as #{step.inspect} for dependency #{blob[:name]}" ) + end end + + def build_lib(blob) + src = [] + asm = [] + hdr = [] + obj = [] + + name = blob[:name] || "" + source_path = Pathname.new get_source_path(blob) + build_path = Pathname.new get_build_path(blob) + relative_build_path = begin + build_path.relative_path_from(source_path) + rescue StandardError + build_path + end + + # Verify there is an artifact that we're building that makes sense + libs = [] + raise CeedlingException.new( "No library artifacts specified for dependency #{name}" ) unless blob.include?(:artifacts) + libs += blob[:artifacts][:static_libraries] if blob[:artifacts].include?(:static_libraries) + libs += blob[:artifacts][:static_libraries] if blob[:artifacts].include?(:static_libraries) + libs = libs.flatten.uniq + raise CeedlingException.new( "No library artifacts specified for dependency #{name}" ) if libs.empty? + lib = libs[0] + + # Find all the source, header, and assembly files + src = Dir["./**/*#{EXTENSION_SOURCE}"] + hdr = Dir["./**/*#{EXTENSION_HEADER}"].map{|f| File.dirname(f) }.uniq + if (EXTENSION_ASSEMBLY && !EXTENSION_ASSEMBLY.empty?) + asm = Dir["./**/*#{EXTENSION_ASSEMBLY}"] + end + + # Do we have what we need to do this? + raise CeedlingException.new( "Nothing to build" ) if (asm.empty? and src.empty?) + raise CeedlingException.new( "No assembler specified for building dependency #{name}" ) unless (defined?(TOOLS_DEPS_ASSEMBLER) || asm.empty?) + raise CeedlingException.new( "No compiler specified for building dependency #{name}" ) unless (defined?(TOOLS_DEPS_COMPILER) || src.empty?) + raise CeedlingException.new( "No linker specified for building dependency #{name}" ) unless defined?(TOOLS_DEPS_LINKER) + + # Build all the source files + src.each do |src_file| + object_file = relative_build_path + File.basename(src_file).ext(EXTENSION_OBJECT) + @ceedling[DEPENDENCIES_SYM].replace_constant(:COLLECTION_PATHS_DEPS, find_my_paths(src_file, blob)) + @ceedling[DEPENDENCIES_SYM].replace_constant(:COLLECTION_DEFINES_DEPS, find_my_defines(src_file, blob)) + @ceedling[:generator].generate_object_file_c( + tool: TOOLS_DEPS_COMPILER, + module_name: File.basename(src_file).ext(), + context: DEPENDENCIES_SYM, + source: src_file, + object: object_file, + search_paths: hdr, + flags: (blob[:flags] || []), + defines: (blob[:defines] || []), + list: @ceedling[:file_path_utils].form_release_build_list_filepath( File.basename(src_file,EXTENSION_OBJECT) ) + ) + obj << object_file + end + + # Build all the assembly files + asm.each do |src_file| + object_file = relative_build_path + File.basename(src_file).ext(EXTENSION_OBJECT) + @ceedling[DEPENDENCIES_SYM].replace_constant(:COLLECTION_PATHS_DEPS, find_my_paths(src_file, blob)) + @ceedling[DEPENDENCIES_SYM].replace_constant(:COLLECTION_DEFINES_DEPS, find_my_defines(src_file, blob)) + @ceedling[:generator].generate_object_file_asm( + tool: TOOLS_DEPS_ASSEMBLER, + module_name: File.basename(src_file).ext(), + context: DEPENDENCIES_SYM, + source: src_file, + object: object_file + ) + obj << object_file + end + + # Link the library + @ceedling[:generator].generate_executable_file( + TOOLS_DEPS_LINKER, + DEPENDENCIES_SYM, + obj, + [], + relative_build_path+lib, + @ceedling[:file_path_utils].form_test_build_map_filepath(get_artifact_path(blob),lib), + (blob[:libraries] || []), + (blob[:libpaths] || []) + ) + + # Move the library to the specifed artifact folder + unless get_build_path(blob) == get_artifact_path(blob) + src = File.expand_path(lib) + dst = File.expand_path(get_artifact_path(blob), Array.new(get_build_path(blob).split(/[\\\/]+/).length,"../").join()) + "/" + lib + FileUtils.cp_r(src, dst) + end + end + + def find_my_paths( c_file, blob, file_type = :c ) + return ((blob[:source] || []) + (blob[:include] || [])).compact.uniq + end + + def find_my_defines( c_file, blob, file_type = :c ) + return (blob[:defines] || []).compact.uniq + end + + def replace_constant(constant, new_value) + Object.send(:remove_const, constant.to_sym) if (Object.const_defined? constant) + Object.const_set(constant, new_value) + end + + ### Private ### + + private + + def validate_fetch_tools(blob) + return if blob[:fetch].nil? || blob[:fetch][:method].nil? + + case blob[:fetch][:method] + when :none + # Do nothing + + when :zip + @ceedling[:tool_validator].validate( tool:TOOLS_DEPS_ZIP, boom:true ) + + when :tar_gzip + @ceedling[:tool_validator].validate( tool:TOOLS_DEPS_TARGZIP, boom:true ) + + when :git + @ceedling[:tool_validator].validate( tool:TOOLS_DEPS_GIT_CLONE, boom:true ) + @ceedling[:tool_validator].validate( tool:TOOLS_DEPS_GIT_CHECKOUT, boom: true ) + + when :svn + @ceedling[:tool_validator].validate( tool:TOOLS_DEPS_SUBVERSION, boom: true ) + + when :custom + # Do nothing + + else + raise CeedlingException.new( "Unknown fetch method '#{blob[:fetch][:method]}' for dependency '#{blob[:name]}'" ) + end + end + end + # end blocks always executed following rake run END { } diff --git a/plugins/fake_function_framework b/plugins/fake_function_framework deleted file mode 160000 index d3914ef0e..000000000 --- a/plugins/fake_function_framework +++ /dev/null @@ -1 +0,0 @@ -Subproject commit d3914ef0e21afb1354f1320db3d365f82e8d9ae3 diff --git a/plugins/fff/README.md b/plugins/fff/README.md new file mode 100644 index 000000000..692956293 --- /dev/null +++ b/plugins/fff/README.md @@ -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: + - report_tests_pretty_stdout + - 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. diff --git a/plugins/fff/Rakefile b/plugins/fff/Rakefile new file mode 100644 index 000000000..520753678 --- /dev/null +++ b/plugins/fff/Rakefile @@ -0,0 +1,26 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +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] \ No newline at end of file diff --git a/plugins/fff/config/fff.yml b/plugins/fff/config/fff.yml new file mode 100644 index 000000000..dd761a31c --- /dev/null +++ b/plugins/fff/config/fff.yml @@ -0,0 +1,13 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +--- +:paths: + :support: + - $PLUGIN_PATH/src + - $PLUGIN_PATH/vendor/fff +... diff --git a/plugins/fff/examples/fff_example/build/.gitignore b/plugins/fff/examples/fff_example/build/.gitignore new file mode 100644 index 000000000..d6b7ef32c --- /dev/null +++ b/plugins/fff/examples/fff_example/build/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/plugins/fff/examples/fff_example/project.yml b/plugins/fff/examples/fff_example/project.yml new file mode 100644 index 000000000..df023436a --- /dev/null +++ b/plugins/fff/examples/fff_example/project.yml @@ -0,0 +1,145 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +--- +:project: + # how to use ceedling. If you're not sure, leave this as `gem` and `?` + :which_ceedling: ../../../.. + :ceedling_version: '?' + + # optional features. If you don't need them, keep them turned off for performance + :use_mocks: TRUE + :use_test_preprocessor: :all + :use_backtrace: :none + + # tweak the way ceedling handles automatic tasks + :build_root: build + :test_file_prefix: test_ + :default_tasks: + - test:all + + # performance options. If your tools start giving mysterious errors, consider + # dropping this to 1 to force single-tasking + :test_threads: 8 + :compile_threads: 8 + + # enable release build (more details in release_build section below) + :release_build: FALSE + +# further details to configure the way Ceedling handles test code +:test_build: + :use_assembly: FALSE + +# further details to configure the way Ceedling handles release code +:release_build: + :output: MyApp.out + :use_assembly: FALSE + :artifacts: [] + +# Plugins are optional Ceedling features which can be enabled. Ceedling supports +# a variety of plugins which may effect the way things are compiled, reported, +# or may provide new command options. Refer to the readme in each plugin for +# details on how to use it. +:plugins: + :load_paths: [] + :enabled: + - module_generator + - fff + - report_tests_pretty_stdout + +# override the default extensions for your system and toolchain +:extension: + #:header: .h + #:source: .c + #:assembly: .s + #:dependencies: .d + #:object: .o + :executable: .out + #:testpass: .pass + #:testfail: .fail + #:subprojects: .a + +# This is where Ceedling should look for your source and test files. +# see documentation for the many options for specifying this. +:paths: + :test: + - +:test/** + :source: + - src/** + :include: + - src/** + :libraries: [] + +# You can even specify specific files to add or remove from your test +# and release collections. Usually it's better to use paths and let +# Ceedling do the work for you! +:files: + :test: [] + :source: [] + +# Compilation symbols to be injected into builds +# See documentation for advanced options: +# - Test name matchers for different symbols per test executable build +# - Referencing symbols in multiple lists using advanced YAML +# - Specifiying symbols used during test preprocessing +:defines: + :test: + - TEST # Simple list option to add symbol 'TEST' to compilation of all files in all test executables + :release: [] + + # Enable to inject name of a test as a unique compilation symbol into its respective executable build. + :use_test_definition: FALSE + +# Configure additional command line flags provided to tools used in each build step +# :flags: +# :release: +# :compile: # Add '-Wall' and '--02' to compilation of all files in release target +# - -Wall +# - --O2 +# :test: +# :compile: +# '(_|-)special': # Add '-pedantic' to compilation of all files in all test executables with '_special' or '-special' in their names +# - -pedantic +# '*': # Add '-foo' to compilation of all files in all test executables +# - -foo + +# Configuration Options specific to CMock. See CMock docs for details +: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 + +# Configuration options specific to Unity. +:unity: + :defines: + - UNITY_EXCLUDE_FLOAT + +# You can optionally have ceedling create environment variables for you before +# performing the rest of its tasks. +:environment: [] + +# LIBRARIES +# These libraries are automatically injected into the build process. Those specified as +# common will be used in all types of builds. Otherwise, libraries can be injected in just +# tests or releases. These options are MERGED with the options in supplemental yaml files. +:libraries: + :placement: :end + :flag: "-l${1}" + :path_flag: "-L ${1}" + :system: [] # for example, you might list 'm' to grab the math library + :test: [] + :release: [] + diff --git a/plugins/fff/examples/fff_example/src/bar.c b/plugins/fff/examples/fff_example/src/bar.c new file mode 100644 index 000000000..52cad1b42 --- /dev/null +++ b/plugins/fff/examples/fff_example/src/bar.c @@ -0,0 +1,8 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#include "bar.h" diff --git a/plugins/fff/examples/fff_example/src/bar.h b/plugins/fff/examples/fff_example/src/bar.h new file mode 100644 index 000000000..8a4d8dbfc --- /dev/null +++ b/plugins/fff/examples/fff_example/src/bar.h @@ -0,0 +1,21 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#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 diff --git a/plugins/fff/examples/fff_example/src/custom_types.h b/plugins/fff/examples/fff_example/src/custom_types.h new file mode 100644 index 000000000..36184c87e --- /dev/null +++ b/plugins/fff/examples/fff_example/src/custom_types.h @@ -0,0 +1,13 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#ifndef custom_types_H +#define custom_types_H + +typedef int custom_t; + +#endif diff --git a/plugins/fff/examples/fff_example/src/display.c b/plugins/fff/examples/fff_example/src/display.c new file mode 100644 index 000000000..ef06fa7ec --- /dev/null +++ b/plugins/fff/examples/fff_example/src/display.c @@ -0,0 +1,14 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#include +#include "display.h" + +void display_turnOffStatusLed(void) +{ + printf("Display: Status LED off"); +} \ No newline at end of file diff --git a/plugins/fff/examples/fff_example/src/display.h b/plugins/fff/examples/fff_example/src/display.h new file mode 100644 index 000000000..df93da3dd --- /dev/null +++ b/plugins/fff/examples/fff_example/src/display.h @@ -0,0 +1,23 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#include + +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); diff --git a/plugins/fff/examples/fff_example/src/event_processor.c b/plugins/fff/examples/fff_example/src/event_processor.c new file mode 100644 index 000000000..1fe2a875b --- /dev/null +++ b/plugins/fff/examples/fff_example/src/event_processor.c @@ -0,0 +1,100 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +/* + This module implements some business logic to test. + + Signal events by calling the functions on the module. +*/ + +#include +#include +#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; +} diff --git a/plugins/fff/examples/fff_example/src/event_processor.h b/plugins/fff/examples/fff_example/src/event_processor.h new file mode 100644 index 000000000..6097c3448 --- /dev/null +++ b/plugins/fff/examples/fff_example/src/event_processor.h @@ -0,0 +1,18 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#include + +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); diff --git a/plugins/fff/examples/fff_example/src/foo.c b/plugins/fff/examples/fff_example/src/foo.c new file mode 100644 index 000000000..95bacd131 --- /dev/null +++ b/plugins/fff/examples/fff_example/src/foo.c @@ -0,0 +1,23 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#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); +} diff --git a/plugins/fff/examples/fff_example/src/foo.h b/plugins/fff/examples/fff_example/src/foo.h new file mode 100644 index 000000000..db1319283 --- /dev/null +++ b/plugins/fff/examples/fff_example/src/foo.h @@ -0,0 +1,15 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#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 diff --git a/plugins/fff/examples/fff_example/src/subfolder/zzz.c b/plugins/fff/examples/fff_example/src/subfolder/zzz.c new file mode 100644 index 000000000..de3531290 --- /dev/null +++ b/plugins/fff/examples/fff_example/src/subfolder/zzz.c @@ -0,0 +1,8 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#include "zzz.h" diff --git a/plugins/fff/examples/fff_example/src/subfolder/zzz.h b/plugins/fff/examples/fff_example/src/subfolder/zzz.h new file mode 100644 index 000000000..1810f86c6 --- /dev/null +++ b/plugins/fff/examples/fff_example/src/subfolder/zzz.h @@ -0,0 +1,13 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#ifndef zzz_H +#define zzz_H + +int zzz_sleep(int time, char * name); + +#endif // zzz_H diff --git a/plugins/fff/examples/fff_example/test/test_event_processor.c b/plugins/fff/examples/fff_example/test/test_event_processor.c new file mode 100644 index 000000000..f1c0af239 --- /dev/null +++ b/plugins/fff/examples/fff_example/test/test_event_processor.c @@ -0,0 +1,160 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#include "unity.h" +#include "event_processor.h" +#include "mock_display.h" +#include + +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); +} + +/* + Mocking a function with a value returned by reference. +*/ +void return_mock_value(char * entry, int length) +{ + const char mockedEntry[] = "sleep"; + if (length > strlen(mockedEntry)) + { + strncpy(entry, mockedEntry, length); + } +} + +void +test_givenTheUserHasTypedSleep_whenItIsTimeToCheckTheKeyboard_theDisplayIsPoweredDown(void) +{ + // Given + 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(*registeredCallback)(void) = 0; +void mock_display_updateData(int data, void(*callback)(void)) +{ + //Save the callback function. + registeredCallback = callback; +} + +void +test_givenNewDataIsAvailable_whenTheDisplayHasUpdated_thenTheEventIsComplete(void) +{ + // A mock function for capturing the callback handler function pointer. + display_updateData_fake.custom_fake = mock_display_updateData; + + // Given + event_newDataAvailable(10); + + // When + if (registeredCallback != 0) + { + registeredCallback(); + } + + // Then + TEST_ASSERT_EQUAL(true, eventProcessor_isLastEventComplete()); +} diff --git a/plugins/fff/examples/fff_example/test/test_foo.c b/plugins/fff/examples/fff_example/test/test_foo.c new file mode 100644 index 000000000..ed7ad22d0 --- /dev/null +++ b/plugins/fff/examples/fff_example/test/test_foo.c @@ -0,0 +1,54 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#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); +} diff --git a/plugins/fff/lib/fff.rb b/plugins/fff/lib/fff.rb new file mode 100644 index 000000000..96679ced2 --- /dev/null +++ b/plugins/fff/lib/fff.rb @@ -0,0 +1,90 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'ceedling/plugin' +require 'fff_mock_generator' + +class Fff < Plugin + + # Set up Ceedling to use this plugin + def setup + @ceedling[:loginator].log( "Using Fake Function Framework (fff)...", Verbosity::OBNOXIOUS ) + 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 Fff + +class FffCMockWrapper + + 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 =~ /" + 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 diff --git a/plugins/fff/spec/fff_mock_header_generator_spec.rb b/plugins/fff/spec/fff_mock_header_generator_spec.rb new file mode 100644 index 000000000..20213c3bb --- /dev/null +++ b/plugins/fff/spec/fff_mock_header_generator_spec.rb @@ -0,0 +1,311 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +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 diff --git a/plugins/fff/spec/fff_mock_source_generator_spec.rb b/plugins/fff/spec/fff_mock_source_generator_spec.rb new file mode 100644 index 000000000..938c6a7d5 --- /dev/null +++ b/plugins/fff/spec/fff_mock_source_generator_spec.rb @@ -0,0 +1,156 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +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 \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 } + ) + 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 \ No newline at end of file diff --git a/plugins/fff/spec/header_generator.rb b/plugins/fff/spec/header_generator.rb new file mode 100644 index 000000000..c210e3530 --- /dev/null +++ b/plugins/fff/spec/header_generator.rb @@ -0,0 +1,58 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +# 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 \ No newline at end of file diff --git a/plugins/fff/spec/spec_helper.rb b/plugins/fff/spec/spec_helper.rb new file mode 100644 index 000000000..becc739d3 --- /dev/null +++ b/plugins/fff/spec/spec_helper.rb @@ -0,0 +1,103 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +# 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 diff --git a/plugins/fff/src/fff_unity_helper.h b/plugins/fff/src/fff_unity_helper.h new file mode 100644 index 000000000..d5152f4ef --- /dev/null +++ b/plugins/fff/src/fff_unity_helper.h @@ -0,0 +1,40 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#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 \ No newline at end of file diff --git a/plugins/fff/vendor/fff/LICENSE b/plugins/fff/vendor/fff/LICENSE new file mode 100644 index 000000000..7b3129bdc --- /dev/null +++ b/plugins/fff/vendor/fff/LICENSE @@ -0,0 +1,25 @@ +/* +LICENSE + +The MIT License (MIT) + +Copyright (c) 2010 Michael Long + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ \ No newline at end of file diff --git a/plugins/fff/vendor/fff/Makefile b/plugins/fff/vendor/fff/Makefile new file mode 100644 index 000000000..a774e0b52 --- /dev/null +++ b/plugins/fff/vendor/fff/Makefile @@ -0,0 +1,10 @@ +all: + mkdir -p build + cd gtest; $(MAKE) all + cd test; $(MAKE) all + cd examples; $(MAKE) all + +clean: + cd gtest; $(MAKE) clean + cd test; $(MAKE) clean + cd examples; $(MAKE) clean diff --git a/plugins/fff/vendor/fff/README.md b/plugins/fff/vendor/fff/README.md new file mode 100644 index 000000000..d91bec47e --- /dev/null +++ b/plugins/fff/vendor/fff/README.md @@ -0,0 +1,454 @@ +# Fake Function Framework (fff) +----------------------------- +> How long can we _maintain_? I wonder. How long before one of us starts raving +> and jabbering at this boy? What will he think then? This same lonely desert +> was the last known home of the Manson family. Will he make that grim +> connection... + +## A Fake Function Framework for C +fff is a micro-framework for creating fake C functions for tests. Because life +is too short to spend time hand-writing fake functions for testing. + + +## Hello fake world! + +Say you are testing an embedded user interface and you have a function that +you want to create a fake for: + + // UI.c + ... + void DISPLAY_init(); + ... + +Here's how you would define a fake function for this in your test suite: + + // test.c(pp) + #include "fff.h" + DEFINE_FFF_GLOBALS; + FAKE_VOID_FUNC(DISPLAY_init); + +And the unit test might look something like this: + + TEST_F(GreeterTests, init_initialises_display) + { + UI_init(); + ASSERT_EQ(DISPLAY_init_fake.call_count, 1); + } + +So what has happened here? The first thing to note is that the framework is +header only, all you need to do to use it is download fff.h and include +it in your test suite. + +The magic is in the FAKE_VOID_FUNC. This +expands a macro that defines a function returning void +which has zero arguments. It also defines a struct +"function_name"_fake which contains all the information about the fake. +For instance, DISPLAY_init_fake.call_countis incremented every time the faked +function is called. + +Under the hood it generates a struct that looks like this: + + typedef struct DISPLAY_init_Fake { + unsigned int call_count; + unsigned int arg_history_len; + unsigned int arg_histories_dropped; + void(*custom_fake)(); + } DISPLAY_init_Fake; + DISPLAY_init_Fake DISPLAY_init_fake; + + + + + +## Capturing arguments + +Ok, enough with the toy examples. What about faking functions with arguments? + + // UI.c + ... + void DISPLAY_output(char * message); + ... + +Here's how you would define a fake function for this in your test suite: + + FAKE_VOID_FUNC(DISPLAY_output, char *); + +And the unit test might look something like this: + + TEST_F(UITests, write_line_outputs_lines_to_display) + { + char msg[] = "helloworld"; + UI_write_line(msg); + ASSERT_EQ(DISPLAY_output_fake.call_count, 1); + ASSERT_EQ(strncmp(DISPLAY_output_fake.arg0_val, msg, 26), 0); + } + + +There is no more magic here, the FAKE_VOID_FUNC works as in the +previous example. The number of arguments that the function takes is calculated, + and the macro arguments following the function name defines the argument +type (a char pointer in this example). + +A variable is created for every argument in the form +"function_name"fake.argN_val + + + +## Return values + +When you want to define a fake function that returns a value, you should use the +FAKE_VALUE_FUNC macro. For instance: + + // UI.c + ... + unsigned int DISPLAY_get_line_capacity(); + unsigned int DISPLAY_get_line_insert_index(); + ... + +Here's how you would define fake functions for these in your test suite: + + FAKE_VALUE_FUNC(unsigned int, DISPLAY_get_line_capacity); + FAKE_VALUE_FUNC(unsigned int, DISPLAY_get_line_insert_index); + +And the unit test might look something like this: + + TEST_F(UITests, when_empty_lines_write_line_doesnt_clear_screen) + { + // given + DISPLAY_get_line_insert_index_fake.return_val = 1; + char msg[] = "helloworld"; + // when + UI_write_line(msg); + // then + ASSERT_EQ(DISPLAY_clear_fake.call_count, 0); + } + +Of course you can mix and match these macros to define a value function with +arguments, for instance to fake: + + double pow(double base, double exponent); + +you would use a syntax like this: + + FAKE_VALUE_FUNC(double, pow, double, double); + + + +## Resetting a fake + +Good tests are isolated tests, so it is important to reset the fakes for each +unit test. All the fakes have a reset function to reset their arguments and +call counts. It is good prectice is to call the reset function for all the +fakes in the setup function of your test suite. + + void setup() + { + // Register resets + RESET_FAKE(DISPLAY_init); + RESET_FAKE(DISPLAY_clear); + RESET_FAKE(DISPLAY_output_message); + RESET_FAKE(DISPLAY_get_line_capacity); + RESET_FAKE(DISPLAY_get_line_insert_index); + } + +You might want to define a macro to do this: + +``` +/* List of fakes used by this unit tester */ +#define FFF_FAKES_LIST(FAKE) \ + FAKE(DISPLAY_init) \ + FAKE(DISPLAY_clear) \ + FAKE(DISPLAY_output_message) \ + FAKE(DISPLAY_get_line_capacity) \ + FAKE(DISPLAY_get_line_insert_index) + +void setup() +{ + /* Register resets */ + FFF_FAKES_LIST(RESET_FAKE); + + /* reset common FFF internal structures */ + FFF_RESET_HISTORY(); +} +``` + +## Call history +Say you want to test that a function calls functionA, then functionB, then +functionA again, how would you do that? Well fff maintains a call +history so that it is easy to assert these expectations. + +Here's how it works: + + FAKE_VOID_FUNC(voidfunc2, char, char); + FAKE_VALUE_FUNC(long, longfunc0); + + TEST_F(FFFTestSuite, calls_in_correct_order) + { + longfunc0(); + voidfunc2(); + longfunc0(); + + ASSERT_EQ(fff.call_history[0], (void *)longfunc0); + ASSERT_EQ(fff.call_history[1], (void *)voidfunc2); + ASSERT_EQ(fff.call_history[2], (void *)longfunc0); + } + +They are reset by calling FFF_RESET_HISTORY(); + + +## Default Argument History + +The framework will by default store the arguments for the last ten calls made +to a fake function. + + TEST_F(FFFTestSuite, when_fake_func_called_then_arguments_captured_in_history) + { + voidfunc2('g', 'h'); + voidfunc2('i', 'j'); + ASSERT_EQ('g', voidfunc2_fake.arg0_history[0]); + ASSERT_EQ('h', voidfunc2_fake.arg1_history[0]); + ASSERT_EQ('i', voidfunc2_fake.arg0_history[1]); + ASSERT_EQ('j', voidfunc2_fake.arg1_history[1]); + } + +There are two ways to find out if calls have been dropped. The first is to +check the dropped histories counter: + + TEST_F(FFFTestSuite, when_fake_func_called_max_times_plus_one_then_one_argument_history_dropped) + { + int i; + for(i = 0; i < 10; i++) + { + voidfunc2('1'+i, '2'+i); + } + voidfunc2('1', '2'); + ASSERT_EQ(1u, voidfunc2_fake.arg_histories_dropped); + } + +The other is to check if the call count is greater than the history size: + + ASSERT(voidfunc2_fake.arg_history_len < voidfunc2_fake.call_count); + +The argument histories for a fake function are reset when the RESET_FAKE +function is called + +## User Defined Argument History + +If you wish to control how many calls to capture for argument history you can +override the default by defining it before include the fff.h like this: + + // Want to keep the argument history for 13 calls + #define FFF_ARG_HISTORY_LEN 13 + // Want to keep the call sequence history for 17 function calls + #define FFF_CALL_HISTORY_LEN 17 + + #include "../fff.h" + + +## Function Return Value Sequences + +Often in testing we would like to test the behaviour of sequence of function call +events. One way to do this with fff is to specify a sequence of return values +with for the fake function. It is probably easier to describe with an example: + + // faking "long longfunc();" + FAKE_VALUE_FUNC(long, longfunc0); + + TEST_F(FFFTestSuite, return_value_sequences_exhausted) + { + long myReturnVals[3] = { 3, 7, 9 }; + SET_RETURN_SEQ(longfunc0, myReturnVals, 3); + ASSERT_EQ(myReturnVals[0], longfunc0()); + ASSERT_EQ(myReturnVals[1], longfunc0()); + ASSERT_EQ(myReturnVals[2], longfunc0()); + ASSERT_EQ(myReturnVals[2], longfunc0()); + ASSERT_EQ(myReturnVals[2], longfunc0()); + } + +By specifying a return value sequence using the SET_RETURN_SEQ macro, +the fake will return the values given in the parameter array in sequence. When +the end of the sequence is reached the fake will continue to return the last +value in the sequence indefinitely. + +## Custom Return Value Delegate + +You can specify your own function to provide the return value for the fake. This +is done by setting the custom_fake member of the fake. Here's an example: + + #define MEANING_OF_LIFE 42 + long my_custom_value_fake(void) + { + return MEANING_OF_LIFE; + } + TEST_F(FFFTestSuite, when_value_custom_fake_called_THEN_it_returns_custom_return_value) + { + longfunc0_fake.custom_fake = my_custom_value_fake; + long retval = longfunc0(); + ASSERT_EQ(MEANING_OF_LIFE, retval); + } + +## How do I fake a function that returns a value by reference? +The basic mechanism that FFF provides you in this case is the custom_fake field described in the *Custom Return Value Delegate* example above. + +You need to create a custom function (e.g. getTime_custom_fake) to produce the output optionally by use of a helper variable (e.g. getTime_custom_now) to retrieve that output from. Then some creativity to tie it all together. The most important part (IMHO) is to keep your test case readable and maintainable. + +In case your project uses a C99 compliant C compiler you can even combine all this in a single unit test function so you can easily oversee all details of the test. See the example below. + + /* The time structure */ + typedef struct { + int hour, min; + } Time; + + /* Our fake function */ + FAKE_VOID_FUNC(getTime, Time*); + + /* A test using the getTime fake function */ + TEST_F(FFFTestSuite, when_value_custom_fake_called_THEN_it_returns_custom_output) + { + Time t; + Time getTime_custom_now; + void getTime_custom_fake(Time *now) { + *now = getTime_custom_now; + } + getTime_fake.custom_fake = getTime_custom_fake; + + /* given a specific time */ + getTime_custom_now.hour = 13; + getTime_custom_now.min = 05; + + /* when getTime is called */ + getTime(&t); + + /* then the specific time must be produced */ + ASSERT_EQ(t.hour, 13); + ASSERT_EQ(t.min, 05); + } + +## How do I fake a function with a function pointer parameter? +Using FFF to stub functions that have function pointer parameter can cause problems when trying to stub them. Presented here is an example how to deal with this situation. + +If you need to stub a function that has a function pointer parameter, e.g. something like: + +``` +/* timer.h */ +typedef int timer_handle; +extern int timer_start(timer_handle handle, long delay, void (*cb_function) (int arg), int arg); +``` + +Then creating a fake like below will horribly fail when trying to compile because the FFF macro will internally expand into an illegal variable ```int (*)(int) arg2_val```. + +``` +/* The fake, attempt one */ +FAKE_VALUE_FUNC(int, + timer_start, + timer_handle, + long, + void (*) (int argument), + int); +``` + +The solution to this problem is to create a bridging type that needs only to be visible in the unit tester. The fake will use that intermediate type. This way the compiler will not complain because the types match. + +``` +/* Additional type needed to be able to use callback in FFF */ +typedef void (*timer_cb) (int argument); + +/* The fake, attempt two */ +FAKE_VALUE_FUNC(int, + timer_start, + timer_handle, + long, + timer_cb, + int); +``` + +Here are some ideas how to create a test case with callbacks. + +``` +/* Unit test */ +TEST_F(FFFTestSuite, test_fake_with_function_pointer) +{ + int cb_timeout_called = 0; + int result = 0; + + void cb_timeout(int argument) + { + cb_timeout_called++; + } + + int timer_start_custom_fake(timer_handle handle, + long delay, + void (*cb_function) (int arg), + int arg) + { + if (cb_function) cb_function(arg); + return timer_start_fake.return_val; + } + + /* given the custom fake for timer_start */ + timer_start_fake.return_val = 33; + timer_start_fake.custom_fake = timer_start_custom_fake; + + /* when timer_start is called + * (actually you would call your own function-under-test + * that would then call the fake function) + */ + result = timer_start(10, 100, cb_timeout, 55); + + /* then the timer_start fake must have been called correctly */ + ASSERT_EQ(result, 33); + ASSERT_EQ(timer_start_fake.call_count, 1); + ASSERT_EQ(timer_start_fake.arg0_val, 10); + ASSERT_EQ(timer_start_fake.arg1_val, 100); + ASSERT_EQ(timer_start_fake.arg2_val, cb_timeout); /* callback provided by unit tester */ + ASSERT_EQ(timer_start_fake.arg3_val, 55); + + /* and ofcourse our custom fake correctly calls the registered callback */ + ASSERT_EQ(cb_timeout_called, 1); +} +``` + +## Find out more... + +Look under the examlples directory for full length examples in both C and C++. +There is also a test suite for the framework under the test directory. + +------------------------- + +## Benefits +So whats the point? + + * To make it easy to create fake functions for testing C code. + * It is simple - just include a header file and you are good to go. + * To work in both C and C++ test environments + + +## Under the hood: + * The fff.h header file is generated by a ruby script + * There are tests under src/test + * There is an example for testing an embedded UI and a hardware driver under src/examples + + +## Cheat Sheet + + + + + + + + + + + + + + + + + + + + + +
MacroDescriptionExample
FAKE_VOID_FUNC(fn [,arg_types*]);Define a fake function named fn returning void with n argumentsFAKE_VOID_FUNC(DISPLAY_output_message, const char*);
FAKE_VALUE_FUNC(return_type, fn [,arg_types*]);Define a fake function returning a value with type return_type taking n argumentsFAKE_VALUE_FUNC(int, DISPLAY_get_line_insert_index);
RESET_FAKE(fn);Reset the state of fake function called fnRESET_FAKE(DISPLAY_init);
\ No newline at end of file diff --git a/plugins/fff/vendor/fff/buildandtest b/plugins/fff/vendor/fff/buildandtest new file mode 100755 index 000000000..b7fecb6b8 --- /dev/null +++ b/plugins/fff/vendor/fff/buildandtest @@ -0,0 +1,15 @@ +#!/bin/bash +set -e + +cat LICENSE > fff.h +ruby fakegen.rb >> fff.h +make clean +make all +build/fff_test_c +build/fff_test_cpp --gtest_output=xml:build/test_results.xml +build/ui_test_ansic +build/ui_test_cpp --gtest_output=xml:build/example_results.xml +build/fff_test_glob_c +build/fff_test_glob_cpp --gtest_output=xml:build/test_global_results.xml +build/driver_testing --gtest_output=xml:build/driver_testing.xml +build/driver_testing_fff --gtest_output=xml:build/driver_testing_fff.xml diff --git a/plugins/fff/vendor/fff/examples/Makefile b/plugins/fff/vendor/fff/examples/Makefile new file mode 100644 index 000000000..f352de316 --- /dev/null +++ b/plugins/fff/vendor/fff/examples/Makefile @@ -0,0 +1,7 @@ +all: + cd embedded_ui; $(MAKE) all + cd driver_testing; $(MAKE) all + +clean: + cd embedded_ui; $(MAKE) clean + cd driver_testing; $(MAKE) clean diff --git a/plugins/fff/vendor/fff/examples/driver_testing/Makefile b/plugins/fff/vendor/fff/examples/driver_testing/Makefile new file mode 100644 index 000000000..e32faafd0 --- /dev/null +++ b/plugins/fff/vendor/fff/examples/driver_testing/Makefile @@ -0,0 +1,64 @@ +$(VERBOSE).SILENT: + +BUILD_DIR = ../../build +TEMPLATE_PROGNAME = $(BUILD_DIR)/template +CPP_PROGNAME_NOFFF = $(BUILD_DIR)/driver_testing +CPP_PROGNAME_FFF = $(BUILD_DIR)/driver_testing_fff +CC = gcc +CC += -c +CPP = g++ +CPP += -c +LD = g++ + +GTEST_OBJS = $(BUILD_DIR)/gtest-all.o $(BUILD_DIR)/gtest-main.o +C_OBJFILES = $(BUILD_DIR)/driver.o +TEMPLATE_OBJFILES = $(BUILD_DIR)/test_suite_template.o +FFF_OBJFILES = $(BUILD_DIR)/driver.test.fff.o $(GTEST_OBJS) +NOFFF_OBJFILES = $(BUILD_DIR)/driver.test.o $(GTEST_OBJS) +CPP_LIBS = -lpthread + + +all: $(CPP_PROGNAME_NOFFF) $(CPP_PROGNAME_FFF) $(TEMPLATE_PROGNAME) + +.PHONY: clean + +clean: + @echo "Cleaning object files" + @echo " rm -f $(BUILD_DIR)/*.o" + rm -f $(BUILD_DIR)/*.o + @echo "Cleaning backups" + @echo " rm -f *~" + rm -f *~ + @echo "Removing programs" + @echo " rm -f $(CPP_PROGNAME_NOFFF) $(CPP_PROGNAME_FFF) $(TEMPLATE_PROGNAME)" + rm -f $(CPP_PROGNAME_NOFFF) $(CPP_PROGNAME_FFF) $(TEMPLATE_PROGNAME) + + +$(BUILD_DIR)/%.o: %.c + @echo "Compiling "$@ + @echo " CC "$< + $(CC) -o $@ $< -DTESTING + +$(BUILD_DIR)/%.o: %.cpp + @echo "Compiling "$@ + @echo " CPP "$< + $(CPP) -DGTEST_USE_OWN_TR1_TUPLE=1 -I../.. -o $@ $< -DTESTING + +$(TEMPLATE_PROGNAME): $(TEMPLATE_OBJFILES) + @echo "Linking "$@ + @echo " LD -o "ctemplate" "$(TEMPLATE_OBJFILES) + $(LD) -o $(TEMPLATE_PROGNAME) $(TEMPLATE_OBJFILES) + +$(CPP_PROGNAME_FFF): $(FFF_OBJFILES) $(C_OBJFILES) + @echo "Linking "$@ + @echo " LD -o "$(CPP_PROGNAME_FFF)" "$(FFF_OBJFILES) + $(LD) -o $(CPP_PROGNAME_FFF) $(FFF_OBJFILES) $(C_OBJFILES) $(CPP_LIBS) + +$(CPP_PROGNAME_NOFFF): $(NOFFF_OBJFILES) $(C_OBJFILES) + @echo "Linking "$@ + @echo " LD -o "$(CPP_PROGNAME_NOFFF)" "$(NOFFF_OBJFILES) + $(LD) -o $(CPP_PROGNAME_NOFFF) $(NOFFF_OBJFILES) $(C_OBJFILES) $(CPP_LIBS) + +nothing: + @echo "Nothing to do; quitting :(" + @echo "HINT: Try make all" diff --git a/plugins/fff/vendor/fff/examples/driver_testing/driver.c b/plugins/fff/vendor/fff/examples/driver_testing/driver.c new file mode 100644 index 000000000..7da76924c --- /dev/null +++ b/plugins/fff/vendor/fff/examples/driver_testing/driver.c @@ -0,0 +1,32 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + + + + +#include "hardware_abstraction.h" +#include "registers.h" + +void driver_write(uint8_t val) +{ + IO_MEM_WR8(DRIVER_OUTPUT_REGISTER, val); +} + +uint8_t driver_read() +{ + return IO_MEM_RD8(DRIVER_INPUT_REGISTER); +} + +void driver_init_device() +{ + uint8_t hw_version = IO_MEM_RD8(HARDWARE_VERSION_REGISTER); + if(HARDWARE_REV_B == hw_version) + { + IO_MEM_WR8(DRIVER_PERIPHERAL_ENABLE_REG, 1); + } + IO_MEM_WR8(DRIVER_PERIPHERAL_INITIALIZE_REG, 1); +} diff --git a/plugins/fff/vendor/fff/examples/driver_testing/driver.h b/plugins/fff/vendor/fff/examples/driver_testing/driver.h new file mode 100644 index 000000000..fd1a8743c --- /dev/null +++ b/plugins/fff/vendor/fff/examples/driver_testing/driver.h @@ -0,0 +1,21 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + + + + + +#ifndef DRIVER +#define DRIVER + +#include + +void driver_write(uint8_t val); +uint8_t driver_read(); +void driver_init_device(); + +#endif /*include guard*/ diff --git a/plugins/fff/vendor/fff/examples/driver_testing/driver.test.cpp b/plugins/fff/vendor/fff/examples/driver_testing/driver.test.cpp new file mode 100644 index 000000000..26c03727f --- /dev/null +++ b/plugins/fff/vendor/fff/examples/driver_testing/driver.test.cpp @@ -0,0 +1,58 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + + +extern "C" +{ +#include "driver.h" +#include "registers.h" +} +#include "../../fff.h" +#include + + +extern "C" +{ + static uint8_t readVal; + static int readCalled; + static uint32_t readRegister; + uint8_t IO_MEM_RD8(uint32_t reg) + { + readRegister = reg; + readCalled++; + return readVal; + } + + static uint32_t writeRegister; + static uint8_t writeVal; + static int writeCalled; + void IO_MEM_WR8(uint32_t reg, uint8_t val) + { + writeRegister = reg; + writeVal = val; + writeCalled++; + } +} + +TEST(Driver, When_writing_Then_writes_data_to_DRIVER_OUTPUT_REGISTER) +{ + driver_write(0x34); + ASSERT_EQ(1u, writeCalled); + ASSERT_EQ(0x34u, writeVal); + ASSERT_EQ(DRIVER_OUTPUT_REGISTER, writeRegister); +} + + +TEST(Driver, When_reading_data_Then_reads_from_DRIVER_INPUT_REGISTER) +{ + readVal = 0x55; + uint8_t returnedValue = driver_read(); + ASSERT_EQ(1u, readCalled); + ASSERT_EQ(0x55u, returnedValue); + ASSERT_EQ(readRegister, DRIVER_INPUT_REGISTER); +} + diff --git a/plugins/fff/vendor/fff/examples/driver_testing/driver.test.fff.cpp b/plugins/fff/vendor/fff/examples/driver_testing/driver.test.fff.cpp new file mode 100644 index 000000000..4f690cdcb --- /dev/null +++ b/plugins/fff/vendor/fff/examples/driver_testing/driver.test.fff.cpp @@ -0,0 +1,70 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + + +extern "C"{ + #include "driver.h" + #include "registers.h" +} +#include "../../fff.h" +#include + +DEFINE_FFF_GLOBALS; + +FAKE_VOID_FUNC(IO_MEM_WR8, uint32_t, uint8_t); +FAKE_VALUE_FUNC(uint8_t, IO_MEM_RD8, uint32_t); + +class DriverTestFFF : public testing::Test +{ +public: + void SetUp() + { + RESET_FAKE(IO_MEM_WR8); + RESET_FAKE(IO_MEM_RD8); + FFF_RESET_HISTORY(); + } + +}; + +TEST_F(DriverTestFFF, When_writing_Then_writes_data_to_DRIVER_OUTPUT_REGISTER) +{ + driver_write(0x34); + ASSERT_EQ(1u, IO_MEM_WR8_fake.call_count); + ASSERT_EQ(0x34u, IO_MEM_WR8_fake.arg1_val); + ASSERT_EQ(DRIVER_OUTPUT_REGISTER, IO_MEM_WR8_fake.arg0_val); +} + + +TEST_F(DriverTestFFF, When_reading_data_Then_reads_from_DRIVER_INPUT_REGISTER) +{ + IO_MEM_RD8_fake.return_val = 0x55; + uint8_t returnedValue = driver_read(); + ASSERT_EQ(1u, IO_MEM_RD8_fake.call_count); + ASSERT_EQ(0x55u, returnedValue); + ASSERT_EQ(IO_MEM_RD8_fake.arg0_val, DRIVER_INPUT_REGISTER); +} + +TEST_F(DriverTestFFF, Given_revisionB_device_When_initialize_Then_enable_peripheral_before_initializing_it) +{ + // Given + IO_MEM_RD8_fake.return_val = HARDWARE_REV_B; + // When + driver_init_device(); + + //Then + // Gets the hardware revision + ASSERT_EQ((void*) IO_MEM_RD8, fff.call_history[0]); + ASSERT_EQ(HARDWARE_VERSION_REGISTER, IO_MEM_RD8_fake.arg0_history[0]); + // Enables Peripheral + ASSERT_EQ((void*) IO_MEM_WR8, fff.call_history[1]); + ASSERT_EQ(DRIVER_PERIPHERAL_ENABLE_REG, IO_MEM_WR8_fake.arg0_history[0]); + ASSERT_EQ(1, IO_MEM_WR8_fake.arg1_history[0]); + // Initializes Peripheral + ASSERT_EQ((void*) IO_MEM_WR8, fff.call_history[2]); + ASSERT_EQ(DRIVER_PERIPHERAL_INITIALIZE_REG,IO_MEM_WR8_fake.arg0_history[1]); + ASSERT_EQ(1, IO_MEM_WR8_fake.arg1_history[1]); +} diff --git a/plugins/fff/vendor/fff/examples/driver_testing/hardware_abstraction.h b/plugins/fff/vendor/fff/examples/driver_testing/hardware_abstraction.h new file mode 100644 index 000000000..a3507d3e4 --- /dev/null +++ b/plugins/fff/vendor/fff/examples/driver_testing/hardware_abstraction.h @@ -0,0 +1,25 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + + + + +#ifndef HARDWARE_ABSTRACTION +#define HARDWARE_ABSTRACTION + +#include + +#ifndef TESTING +#define IO_MEM_RD8(ADDR) (*((volatile uint8_t *)(ADDR))) +#define IO_MEM_WR8(ADDR, VAL_8) (*((volatile uint8_t *)(ADDR)) = (VAL_8)) +#else +/* In testing use fake functions to record calls to IO memory */ +uint8_t IO_MEM_RD8(uint32_t reg); +void IO_MEM_WR8(uint32_t reg, uint8_t val); +#endif + +#endif /* Include guard */ diff --git a/plugins/fff/vendor/fff/examples/driver_testing/registers.h b/plugins/fff/vendor/fff/examples/driver_testing/registers.h new file mode 100644 index 000000000..30f9f6aa7 --- /dev/null +++ b/plugins/fff/vendor/fff/examples/driver_testing/registers.h @@ -0,0 +1,23 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + + + + +#ifndef REGISTERS_H_ +#define REGISTERS_H_ + +#define DRIVER_OUTPUT_REGISTER 0xFFAAu +#define DRIVER_INPUT_REGISTER 0XFFABu +#define DRIVER_PERIPHERAL_ENABLE_REG 0xFFACu +#define DRIVER_PERIPHERAL_INITIALIZE_REG 0xFFACu + +#define HARDWARE_VERSION_REGISTER 0xFF00u +#define HARDWARE_REV_A 0x00u +#define HARDWARE_REV_B 0x01u + +#endif /* REGISTERS_H_ */ diff --git a/plugins/fff/vendor/fff/examples/embedded_ui/DISPLAY.h b/plugins/fff/vendor/fff/examples/embedded_ui/DISPLAY.h new file mode 100644 index 000000000..92eb0764d --- /dev/null +++ b/plugins/fff/vendor/fff/examples/embedded_ui/DISPLAY.h @@ -0,0 +1,27 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + + + + +/* + * DISPLAY.h + * + * Created on: Dec 17, 2010 + * Author: mlong + */ + +#ifndef DISPLAY_H_ +#define DISPLAY_H_ + +void DISPLAY_init(); +void DISPLAY_clear(); +unsigned int DISPLAY_get_line_capacity(); +unsigned int DISPLAY_get_line_insert_index(); +void DISPLAY_output(char * message); + +#endif /* DISPLAY_H_ */ diff --git a/plugins/fff/vendor/fff/examples/embedded_ui/Kata.txt b/plugins/fff/vendor/fff/examples/embedded_ui/Kata.txt new file mode 100644 index 000000000..b466c3649 --- /dev/null +++ b/plugins/fff/vendor/fff/examples/embedded_ui/Kata.txt @@ -0,0 +1,25 @@ + +Problem Definition +------------------ +The task is to write a user interface module for an embedded device. + +Interrupts: + * The user interface is responsible for initializing the display. + * The user interface will register an interrupt handler for GPIO input 2 (a + push button). + * It will be possible to register a callback function for button presses. + * When there is no callback function set the irq handler will increment a + missed irq counter. + * When the interrupt occurs the handler will schedule or execute the button + press callback if there is one registered. +Output: + * Tasks can write messages to the user interface to be output on the display. + * The display is line oriented; when the last line of the display is written + the user interface is responsible for clearing the display. + * The display is 26 characters wide. Any string longer than that must be + truncated before being sent to the display. The string must be null + terminated and thus maximum 27 bytes long. + + * BONUS: Have the display be scrolling, i.e. when the display is full, the + previous lines must be shifted up one and the new line written in the bottom + line. diff --git a/plugins/fff/vendor/fff/examples/embedded_ui/Makefile b/plugins/fff/vendor/fff/examples/embedded_ui/Makefile new file mode 100644 index 000000000..654beecc2 --- /dev/null +++ b/plugins/fff/vendor/fff/examples/embedded_ui/Makefile @@ -0,0 +1,67 @@ +$(VERBOSE).SILENT: + +BUILD_DIR = ../../build +TEMPLATE_PROGNAME = $(BUILD_DIR)/template +C_PROGNAME = $(BUILD_DIR)/ui_test_ansic +CPP_PROGNAME = $(BUILD_DIR)/ui_test_cpp +CC = gcc +CC += -c +CPP = g++ +CPP += -c +LD = g++ + +GTEST_OBJS = $(BUILD_DIR)/gtest-all.o $(BUILD_DIR)/gtest-main.o +C_OBJFILES = $(BUILD_DIR)/UI_test_ansic.o $(BUILD_DIR)/UI.o +TEMPLATE_OBJFILES = $(BUILD_DIR)/test_suite_template.o +CPP_OBJFILES = $(BUILD_DIR)/UI_test_cpp.o $(BUILD_DIR)/UI.o $(GTEST_OBJS) +CPP_LIBS = -lpthread + + +all: $(C_PROGNAME) $(CPP_PROGNAME) $(TEMPLATE_PROGNAME) + +.PHONY: clean + +clean: + @echo "Cleaning object files" + @echo " rm -f $(BUILD_DIR)/*.o" + rm -f $(BUILD_DIR)/*.o + @echo "Cleaning backups" + @echo " rm -f *~" + rm -f *~ + @echo "Removing programs" + @echo " rm -f "$(C_PROGNAME) + rm -f $(C_PROGNAME) + @echo " rm -f "$(CPP_PROGNAME) $(TEMPLATE_PROGNAME) + rm -f $(CPP_PROGNAME) $(TEMPLATE_PROGNAME) + + +$(BUILD_DIR)/%.o: %.c + @echo "Compiling "$@ + @echo " CC "$< + $(CC) -o $@ $< + +$(BUILD_DIR)/%.o: %.cpp + @echo "Compiling "$@ + @echo " CPP "$< + $(CPP) -DGTEST_USE_OWN_TR1_TUPLE=1 -I../.. -o $@ $< + +$(TEMPLATE_PROGNAME): $(TEMPLATE_OBJFILES) + @echo "Linking "$@ + @echo " LD -o "ctemplate" "$(TEMPLATE_OBJFILES) + $(LD) -o $(TEMPLATE_PROGNAME) $(TEMPLATE_OBJFILES) + +$(C_PROGNAME): $(C_OBJFILES) + @echo "Linking "$@ + @echo " LD -o "$(C_PROGNAME)" "$(C_OBJFILES) + $(LD) -o $(C_PROGNAME) $(C_OBJFILES) + +$(CPP_PROGNAME): $(CPP_OBJFILES) $(C_OBJFILES) + @echo "Linking "$@ + @echo " LD -o "$(CPP_PROGNAME)" "$(CPP_OBJFILES) + $(LD) -o $(CPP_PROGNAME) $(CPP_OBJFILES) $(CPP_LIBS) + + + +nothing: + @echo "Nothing to do; quitting :(" + @echo "HINT: Try make all" diff --git a/plugins/fff/vendor/fff/examples/embedded_ui/SYSTEM.h b/plugins/fff/vendor/fff/examples/embedded_ui/SYSTEM.h new file mode 100644 index 000000000..4e8be8e4f --- /dev/null +++ b/plugins/fff/vendor/fff/examples/embedded_ui/SYSTEM.h @@ -0,0 +1,31 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + + + + +/* + * DISPLAY.h + * + * Created on: Dec 17, 2010 + * Author: mlong + */ + +#ifndef SYSTEM_H_ +#define SYSTEM_H_ + +typedef void (*irq_func_t)(void); + +#define IRQ_GPIO_0 0x70 +#define IRQ_GPIO_1 0x71 +#define IRQ_GPIO_2 0x72 +#define IRQ_GPIO_3 0x73 + + +void SYSTEM_register_irq(irq_func_t, unsigned int irq); + +#endif /* SYSTEM_H_ */ diff --git a/plugins/fff/vendor/fff/examples/embedded_ui/UI.c b/plugins/fff/vendor/fff/examples/embedded_ui/UI.c new file mode 100644 index 000000000..e7f040dde --- /dev/null +++ b/plugins/fff/vendor/fff/examples/embedded_ui/UI.c @@ -0,0 +1,56 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + + +#include "UI.h" +#include "DISPLAY.h" +#include "SYSTEM.h" +#include + +static unsigned int missed_irq_counter; +button_cbk_t button_cbk; + + +void UI_init() +{ + DISPLAY_init(); + SYSTEM_register_irq(UI_button_irq_handler, IRQ_GPIO_2); + button_cbk = 0; + missed_irq_counter = 0; +} + +unsigned int UI_get_missed_irqs() +{ + return missed_irq_counter; +} + +void UI_button_irq_handler() +{ + if(button_cbk) + { + button_cbk(); + } + else + { + missed_irq_counter++; + } +} + +void UI_register_button_cbk(button_cbk_t cbk) +{ + button_cbk = cbk; +} + +void UI_write_line(char *line) +{ + static char out[27]; + strncpy(out, line, 26); + out[26] = '\0'; + if(DISPLAY_get_line_capacity() == DISPLAY_get_line_insert_index()) + DISPLAY_clear(); + DISPLAY_output(out); +} diff --git a/plugins/fff/vendor/fff/examples/embedded_ui/UI.h b/plugins/fff/vendor/fff/examples/embedded_ui/UI.h new file mode 100644 index 000000000..e4b054068 --- /dev/null +++ b/plugins/fff/vendor/fff/examples/embedded_ui/UI.h @@ -0,0 +1,22 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + + + + +#ifndef UI_H_ +#define UI_H_ + +typedef void (*button_cbk_t)(void); + +void UI_init(); +unsigned int UI_get_missed_irqs(); +void UI_button_irq_handler(); +void UI_register_button_cbk(button_cbk_t cbk); +void UI_write_line(char *line); + +#endif /* UI_H_ */ diff --git a/plugins/fff/vendor/fff/examples/embedded_ui/UI_test_ansic.c b/plugins/fff/vendor/fff/examples/embedded_ui/UI_test_ansic.c new file mode 100644 index 000000000..851ff8c1d --- /dev/null +++ b/plugins/fff/vendor/fff/examples/embedded_ui/UI_test_ansic.c @@ -0,0 +1,191 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + + +#include "UI.h" +#include "../../fff.h" +#include "SYSTEM.h" +#include "DISPLAY.h" + +#include +#include +#include + +/* Test Framework :-) */ +void setup(); +#define TEST_F(SUITE, NAME) void NAME() +#define RUN_TEST(SUITE, TESTNAME) printf(" Running %s.%s: \n", #SUITE, #TESTNAME); setup(); TESTNAME(); printf(" SUCCESS\n"); + +DEFINE_FFF_GLOBALS; + +/* SYSTEM.h */ +FAKE_VOID_FUNC2(SYSTEM_register_irq, irq_func_t, unsigned int); +/* DISPLAY.h */ +FAKE_VOID_FUNC(DISPLAY_init); +FAKE_VOID_FUNC(DISPLAY_clear); +FAKE_VOID_FUNC(DISPLAY_output, char *); +FAKE_VALUE_FUNC(unsigned int, DISPLAY_get_line_capacity); +FAKE_VALUE_FUNC(unsigned int, DISPLAY_get_line_insert_index); + +FAKE_VOID_FUNC0(button_press_cbk); + + + +/* Initialializers called for every test */ +void setup() +{ + RESET_FAKE(SYSTEM_register_irq); + + RESET_FAKE(DISPLAY_init) + RESET_FAKE(DISPLAY_clear) + RESET_FAKE(DISPLAY_output) + RESET_FAKE(DISPLAY_get_line_capacity) + RESET_FAKE(DISPLAY_get_line_insert_index); + + RESET_FAKE(button_press_cbk); + + FFF_RESET_HISTORY(); + + DISPLAY_get_line_capacity_fake.return_val = 2; +} + +/* Tests go here */ +TEST_F(UITests, init_will_initialise_display) +{ + UI_init(); + assert(DISPLAY_init_fake.call_count == 1); +} + +TEST_F(UITests, init_will_register_interrupt_gpio2) +{ + UI_init(); + assert(SYSTEM_register_irq_fake.call_count == 1); + assert(SYSTEM_register_irq_fake.arg0_val == UI_button_irq_handler); + assert(SYSTEM_register_irq_fake.arg1_val == IRQ_GPIO_2); +} + +TEST_F(UITests, when_no_irq_then_missed_irq_counter_zero) +{ + assert(UI_get_missed_irqs() == 0); +} + +TEST_F(UITests, when_one_irq_and_no_handler_then_missed_irq_counter_one) +{ + UI_button_irq_handler(); + assert(UI_get_missed_irqs() == 1); +} + +TEST_F(UITests, when_one_irq_and_valid_callback_then_missed_irq_counter_zero) +{ + UI_init(); + UI_register_button_cbk(button_press_cbk); + UI_button_irq_handler(); + assert(UI_get_missed_irqs() == 0); +} + +TEST_F(UITests, when_one_irq_and_valid_callback_then_callback_called) +{ + UI_register_button_cbk(button_press_cbk); + UI_button_irq_handler(); + assert(button_press_cbk_fake.call_count == 1); +} + +TEST_F(UITests, write_line_outputs_lines_to_display) +{ + char msg[] = "helloworld"; + UI_write_line(msg); + assert(DISPLAY_output_fake.call_count == 1); + assert(strncmp(DISPLAY_output_fake.arg0_val, msg, 26) == 0); +} + +TEST_F(UITests, when_no_empty_lines_write_line_clears_screen_and_outputs_lines_to_display) +{ + DISPLAY_get_line_insert_index_fake.return_val = 2; + char msg[] = "helloworld"; + + UI_write_line(msg); + + assert(DISPLAY_clear_fake.call_count == 1); + assert(DISPLAY_output_fake.call_count == 1); + // Check the order of the calls: Don't care about the first two: + // DISPLAY_get_line_capacity and DISPLAY_get_line_insert_index + assert(fff.call_history_idx == 4); + assert(fff.call_history[2] == (void *) DISPLAY_clear); + assert(fff.call_history[3] == (void *) DISPLAY_output); +} + +TEST_F(UITests, when_empty_lines_write_line_doesnt_clear_screen) +{ + // given + DISPLAY_get_line_insert_index_fake.return_val = 1; + char msg[] = "helloworld"; + // when + UI_write_line(msg); + // then + assert(DISPLAY_clear_fake.call_count == 0); +} + +TEST_F(UITests, when_string_longer_than_26_then_truncated_string_output) +{ + // given + char input[] = "abcdefghijklmnopqrstuvwxyz0123456789"; + char expected[] = "abcdefghijklmnopqrstuvwxyz"; + // when + UI_write_line(input); + // then + assert(strncmp(expected, DISPLAY_output_fake.arg0_val, 37) == 0); +} + +TEST_F(UITests, when_outputting_to_full_display_then_previous_inserted) +{ + // given + DISPLAY_get_line_insert_index_fake.return_val = 1; + char oldest[] = "oldest"; + char newest[] = "newest"; + // when + UI_write_line(oldest); + UI_write_line(newest); + // then + + assert(DISPLAY_output_fake.call_count == 2); + + // fills last line + assert(strncmp(oldest, DISPLAY_output_fake.arg0_history[0], 37) == 0); + //clears + assert(DISPLAY_clear_fake.call_count == 1); + // inserts old line at first + assert(strncmp(oldest, DISPLAY_output_fake.arg0_history[1], 37) == 0); + // then inserts new line + assert(strncmp(newest, DISPLAY_output_fake.arg0_history[2], 37) == 0); +} + +int main() +{ + setbuf(stdout, NULL); + fprintf(stdout, "-------------\n"); + fprintf(stdout, "Running Tests\n"); + fprintf(stdout, "-------------\n\n"); + fflush(0); + + /* Run tests */ + RUN_TEST(UITests, init_will_initialise_display); + RUN_TEST(UITests, init_will_register_interrupt_gpio2); + RUN_TEST(UITests, when_no_irq_then_missed_irq_counter_zero); + RUN_TEST(UITests, when_one_irq_and_no_handler_then_missed_irq_counter_one); + RUN_TEST(UITests, when_one_irq_and_valid_callback_then_missed_irq_counter_zero); + RUN_TEST(UITests, when_one_irq_and_valid_callback_then_callback_called); + RUN_TEST(UITests, write_line_outputs_lines_to_display); + RUN_TEST(UITests, when_no_empty_lines_write_line_clears_screen_and_outputs_lines_to_display); + RUN_TEST(UITests, when_empty_lines_write_line_doesnt_clear_screen); + RUN_TEST(UITests, when_string_longer_than_26_then_truncated_string_output); + + printf("\n-------------\n"); + printf("Complete\n"); + printf("-------------\n\n"); + + return 0; +} diff --git a/plugins/fff/vendor/fff/examples/embedded_ui/UI_test_cpp.cpp b/plugins/fff/vendor/fff/examples/embedded_ui/UI_test_cpp.cpp new file mode 100644 index 000000000..bedc58789 --- /dev/null +++ b/plugins/fff/vendor/fff/examples/embedded_ui/UI_test_cpp.cpp @@ -0,0 +1,144 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + + +extern "C"{ +#include "UI.h" +#include "SYSTEM.h" +#include "DISPLAY.h" +} +#include + + +#include "../../fff.h" +DEFINE_FFF_GLOBALS; + +/* SYSTEM.h */ +FAKE_VOID_FUNC(SYSTEM_register_irq, irq_func_t, unsigned int); +/* DISPLAY.h */ +FAKE_VOID_FUNC(DISPLAY_init); +FAKE_VOID_FUNC(DISPLAY_clear); +FAKE_VOID_FUNC(DISPLAY_output, char *); +FAKE_VALUE_FUNC(unsigned int, DISPLAY_get_line_capacity); +FAKE_VALUE_FUNC(unsigned int, DISPLAY_get_line_insert_index); + +FAKE_VOID_FUNC(button_press_cbk); + + +class UITests : public testing::Test +{ +public: + + void SetUp() + { + // Register resets + RESET_FAKE(SYSTEM_register_irq); + + RESET_FAKE(DISPLAY_init) + RESET_FAKE(DISPLAY_clear) + RESET_FAKE(DISPLAY_output) + RESET_FAKE(DISPLAY_get_line_capacity) + RESET_FAKE(DISPLAY_get_line_insert_index); + + RESET_FAKE(button_press_cbk); + + FFF_RESET_HISTORY(); + // non default init + DISPLAY_get_line_capacity_fake.return_val = 2; + } +}; + + + +/* Tests go here */ +TEST_F(UITests, init_will_initialise_display) +{ + UI_init(); + ASSERT_EQ(DISPLAY_init_fake.call_count, 1); +} + +TEST_F(UITests, init_will_register_interrupt_gpio2) +{ + UI_init(); + ASSERT_EQ(SYSTEM_register_irq_fake.call_count, 1); + ASSERT_EQ((void *)SYSTEM_register_irq_fake.arg0_val, (void *)UI_button_irq_handler); + ASSERT_EQ(SYSTEM_register_irq_fake.arg1_val, IRQ_GPIO_2); +} + +TEST_F(UITests, when_no_irq_then_missed_irq_counter_zero) +{ + ASSERT_EQ(UI_get_missed_irqs(), 0); +} + +TEST_F(UITests, when_one_irq_and_no_handler_then_missed_irq_counter_one) +{ + UI_button_irq_handler(); + ASSERT_EQ(UI_get_missed_irqs(), 1); +} + +TEST_F(UITests, when_one_irq_and_valid_callback_then_missed_irq_counter_zero) +{ + UI_init(); + UI_register_button_cbk(button_press_cbk); + UI_button_irq_handler(); + ASSERT_EQ(UI_get_missed_irqs(), 0); +} + +TEST_F(UITests, when_one_irq_and_valid_callback_then_callback_called) +{ + UI_register_button_cbk(button_press_cbk); + UI_button_irq_handler(); + ASSERT_EQ(button_press_cbk_fake.call_count, 1); +} + +TEST_F(UITests, write_line_outputs_lines_to_display) +{ + char msg[] = "helloworld"; + UI_write_line(msg); + ASSERT_EQ(DISPLAY_output_fake.call_count, 1); + ASSERT_EQ(strncmp(DISPLAY_output_fake.arg0_val, msg, 26), 0); +} + +TEST_F(UITests, when_no_empty_lines_write_line_clears_screen_and_outputs_lines_to_display) +{ + DISPLAY_get_line_insert_index_fake.return_val = 2; + char msg[] = "helloworld"; + + UI_write_line(msg); + + ASSERT_EQ(DISPLAY_clear_fake.call_count, 1); + ASSERT_EQ(DISPLAY_output_fake.call_count, 1); + // Check the order of the calls: Don't care about the first two: + // DISPLAY_get_line_capacity and DISPLAY_get_line_insert_index + ASSERT_EQ(fff.call_history_idx, 4); + ASSERT_EQ(fff.call_history[2], (void *) DISPLAY_clear); + ASSERT_EQ(fff.call_history[3], (void *) DISPLAY_output); +} + +TEST_F(UITests, when_empty_lines_write_line_doesnt_clear_screen) +{ + // given + DISPLAY_get_line_insert_index_fake.return_val = 1; + char msg[] = "helloworld"; + // when + UI_write_line(msg); + // then + ASSERT_EQ(DISPLAY_clear_fake.call_count, 0); +} + +TEST_F(UITests, when_string_longer_than_26_then_truncated_string_output) +{ + // given + char input[] = "abcdefghijklmnopqrstuvwxyz0123456789"; + char expected[] = "abcdefghijklmnopqrstuvwxyz"; + // when + UI_write_line(input); + // then + ASSERT_EQ(strncmp(expected, DISPLAY_output_fake.arg0_val, 37), 0); +} + + diff --git a/plugins/fff/vendor/fff/examples/embedded_ui/test_suite_template.c b/plugins/fff/vendor/fff/examples/embedded_ui/test_suite_template.c new file mode 100644 index 000000000..97d1cdb77 --- /dev/null +++ b/plugins/fff/vendor/fff/examples/embedded_ui/test_suite_template.c @@ -0,0 +1,42 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + + +#include "../../test/c_test_framework.h" + +/* Initialializers called for every test */ +void setup() +{ + +} + +/* Tests go here */ +TEST_F(GreeterTests, hello_world) +{ + assert(1 == 0); +} + + + +int main() +{ + setbuf(stderr, NULL); + fprintf(stdout, "-------------\n"); + fprintf(stdout, "Running Tests\n"); + fprintf(stdout, "-------------\n\n"); + fflush(0); + + /* Run tests */ + RUN_TEST(GreeterTests, hello_world); + + + printf("\n-------------\n"); + printf("Complete\n"); + printf("-------------\n\n"); + + return 0; +} diff --git a/plugins/fff/vendor/fff/fakegen.rb b/plugins/fff/vendor/fff/fakegen.rb new file mode 100644 index 000000000..a930201fc --- /dev/null +++ b/plugins/fff/vendor/fff/fakegen.rb @@ -0,0 +1,428 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + + +# fakegen.rb +# A simple code generator to create some C macros for defining test fake functions + + +$cpp_output = true +$MAX_ARGS = 20 +$DEFAULT_ARG_HISTORY = 50 +$MAX_CALL_HISTORY = 50 + +def output_constants + putd "#define FFF_MAX_ARGS (#{$MAX_ARGS}u)" + putd "#ifndef FFF_ARG_HISTORY_LEN" + putd " #define FFF_ARG_HISTORY_LEN (#{$DEFAULT_ARG_HISTORY}u)" + putd "#endif" + putd "#ifndef FFF_CALL_HISTORY_LEN" + putd " #define FFF_CALL_HISTORY_LEN (#{$MAX_CALL_HISTORY}u)" + putd "#endif" +end + + + + + +# ------ Helper macros to use internally ------ # +def output_internal_helper_macros + putd "/* -- INTERNAL HELPER MACROS -- */" + + define_return_sequence_helper + define_reset_fake_macro + define_declare_arg_helper + define_declare_all_func_common_helper + define_save_arg_helper + define_room_for_more_history + define_save_arg_history_helper + define_history_dropped_helper + define_value_function_variables_helper + define_increment_call_count_helper + define_return_fake_result_helper + define_extern_c_helper + define_reset_fake_helper + + putd "/* -- END INTERNAL HELPER MACROS -- */" + putd "" +end + +def define_return_sequence_helper + putd "#define SET_RETURN_SEQ(FUNCNAME, ARRAY_POINTER, ARRAY_LEN) \\" + putd " FUNCNAME##_fake.return_val_seq = ARRAY_POINTER; \\" + putd " FUNCNAME##_fake.return_val_seq_len = ARRAY_LEN;" +end + +def define_reset_fake_macro + putd "" + putd "/* Defining a function to reset a fake function */" + putd "#define RESET_FAKE(FUNCNAME) { \\" + putd " FUNCNAME##_reset(); \\" + putd "} \\" + putd "" +end + +def define_declare_arg_helper + putd "" + putd "#define DECLARE_ARG(type, n, FUNCNAME) \\" + putd " type arg##n##_val; \\" + putd " type arg##n##_history[FFF_ARG_HISTORY_LEN];" +end + +def define_declare_all_func_common_helper + putd "" + putd "#define DECLARE_ALL_FUNC_COMMON \\" + putd " unsigned int call_count; \\" + putd " unsigned int arg_history_len;\\" + putd " unsigned int arg_histories_dropped; \\" +end + +def define_save_arg_helper + putd "" + putd "#define SAVE_ARG(FUNCNAME, n) \\" + putd " memcpy((void*)&FUNCNAME##_fake.arg##n##_val, (void*)&arg##n, sizeof(arg##n));" +end + +def define_room_for_more_history + putd "" + putd "#define ROOM_FOR_MORE_HISTORY(FUNCNAME) \\" + putd " FUNCNAME##_fake.call_count < FFF_ARG_HISTORY_LEN" +end + +def define_save_arg_history_helper + putd "" + putd "#define SAVE_ARG_HISTORY(FUNCNAME, ARGN) \\" + putd " memcpy((void*)&FUNCNAME##_fake.arg##ARGN##_history[FUNCNAME##_fake.call_count], (void*)&arg##ARGN, sizeof(arg##ARGN));" +end + +def define_history_dropped_helper + putd "" + putd "#define HISTORY_DROPPED(FUNCNAME) \\" + putd " FUNCNAME##_fake.arg_histories_dropped++" +end + +def define_value_function_variables_helper + putd "" + putd "#define DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \\" + putd " RETURN_TYPE return_val; \\" + putd " int return_val_seq_len; \\" + putd " int return_val_seq_idx; \\" + putd " RETURN_TYPE * return_val_seq; \\" +end + +def define_increment_call_count_helper + putd "" + putd "#define INCREMENT_CALL_COUNT(FUNCNAME) \\" + putd " FUNCNAME##_fake.call_count++" +end + +def define_return_fake_result_helper + putd "" + putd "#define RETURN_FAKE_RESULT(FUNCNAME) \\" + putd " if (FUNCNAME##_fake.return_val_seq_len){ /* then its a sequence */ \\" + putd " if(FUNCNAME##_fake.return_val_seq_idx < FUNCNAME##_fake.return_val_seq_len) { \\" + putd " return FUNCNAME##_fake.return_val_seq[FUNCNAME##_fake.return_val_seq_idx++]; \\" + putd " } \\" + putd " return FUNCNAME##_fake.return_val_seq[FUNCNAME##_fake.return_val_seq_len-1]; /* return last element */ \\" + putd " } \\" + putd " return FUNCNAME##_fake.return_val; \\" +end + +def define_extern_c_helper + putd "" + putd "#ifdef __cplusplus" + putd " #define FFF_EXTERN_C extern \"C\"{" + putd " #define FFF_END_EXTERN_C } " + putd "#else /* ansi c */" + putd " #define FFF_EXTERN_C " + putd " #define FFF_END_EXTERN_C " + putd "#endif /* cpp/ansi c */" +end + +def define_reset_fake_helper + putd "" + putd "#define DEFINE_RESET_FUNCTION(FUNCNAME) \\" + putd " void FUNCNAME##_reset(){ \\" + putd " memset(&FUNCNAME##_fake, 0, sizeof(FUNCNAME##_fake)); \\" + putd " FUNCNAME##_fake.arg_history_len = FFF_ARG_HISTORY_LEN;\\" + putd " }" +end +# ------ End Helper macros ------ # + +#fakegen helpers to print at levels of indentation +$current_depth = 0 +def putd(str) + $current_depth.times {|not_used| print " "} + puts str +end + +def pushd + $current_depth = $current_depth + 4 +end + +def popd + $current_depth = $current_depth - 4 +end + +def output_macro(arg_count, has_varargs, is_value_function) + + vararg_name = has_varargs ? "_VARARG" : "" + fake_macro_name = is_value_function ? "FAKE_VALUE_FUNC#{arg_count}#{vararg_name}" : "FAKE_VOID_FUNC#{arg_count}#{vararg_name}" + declare_macro_name = "DECLARE_#{fake_macro_name}" + define_macro_name = "DEFINE_#{fake_macro_name}" + saved_arg_count = arg_count - (has_varargs ? 1 : 0) + return_type = is_value_function ? "RETURN_TYPE" : "" + + putd "" + output_macro_header(declare_macro_name, saved_arg_count, has_varargs, return_type) + pushd + extern_c { # define argument capture variables + output_variables(saved_arg_count, has_varargs, is_value_function) + } + popd + + putd "" + output_macro_header(define_macro_name, saved_arg_count, has_varargs, return_type) + pushd + extern_c { + putd "FUNCNAME##_Fake FUNCNAME##_fake;\\" + putd function_signature(saved_arg_count, has_varargs, is_value_function) + "{ \\" + pushd + output_function_body(saved_arg_count, has_varargs, is_value_function) + popd + putd "} \\" + putd "DEFINE_RESET_FUNCTION(FUNCNAME) \\" + } + popd + + putd "" + + output_macro_header(fake_macro_name, saved_arg_count, has_varargs, return_type) + pushd + putd macro_signature_for(declare_macro_name, saved_arg_count, has_varargs, return_type) + putd macro_signature_for(define_macro_name, saved_arg_count, has_varargs, return_type) + putd "" + popd +end + +def output_macro_header(macro_name, arg_count, has_varargs, return_type) + output_macro_name(macro_name, arg_count, has_varargs, return_type) +end + +# #define #macro_name(RETURN_TYPE, FUNCNAME, ARG0,...) +def output_macro_name(macro_name, arg_count, has_varargs, return_type) + putd "#define " + macro_signature_for(macro_name, arg_count, has_varargs, return_type) +end + +# #macro_name(RETURN_TYPE, FUNCNAME, ARG0,...) +def macro_signature_for(macro_name, arg_count, has_varargs, return_type) + parameter_list = "#{macro_name}(" + if return_type != "" + parameter_list += return_type + parameter_list += ", " + end + parameter_list += "FUNCNAME" + + arg_count.times { |i| parameter_list += ", ARG#{i}_TYPE" } + + parameter_list += ", ..." if has_varargs + + parameter_list += ") \\" + + parameter_list +end + +def output_argument_capture_variables(argN) + putd " DECLARE_ARG(ARG#{argN}_TYPE, #{argN}, FUNCNAME) \\" +end + +def output_variables(arg_count, has_varargs, is_value_function) + in_struct{ + arg_count.times { |argN| + putd "DECLARE_ARG(ARG#{argN}_TYPE, #{argN}, FUNCNAME) \\" + } + putd "DECLARE_ALL_FUNC_COMMON \\" + putd "DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \\" unless not is_value_function + output_custom_function_signature(arg_count, has_varargs, is_value_function) + } + putd "extern FUNCNAME##_Fake FUNCNAME##_fake;\\" + putd "void FUNCNAME##_reset(); \\" +end + +#example: ARG0_TYPE arg0, ARG1_TYPE arg1 +def arg_val_list(args_count) + arguments = [] + args_count.times { |i| arguments << "ARG#{i}_TYPE arg#{i}" } + arguments.join(", ") +end + +#example: arg0, arg1 +def arg_list(args_count) + arguments = [] + args_count.times { |i| arguments << "arg#{i}" } + arguments.join(", ") +end + +# RETURN_TYPE (*custom_fake)(ARG0_TYPE arg0);\ +# void (*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2);\ +def output_custom_function_signature(arg_count, has_varargs, is_value_function) + return_type = is_value_function ? "RETURN_TYPE" : "void" + signature = "(*custom_fake)(#{arg_val_list(arg_count)}); \\" + putd return_type + signature +end + +# example: RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1) +def function_signature(arg_count, has_varargs, is_value_function) + return_type = is_value_function ? "RETURN_TYPE" : "void" + varargs = has_varargs ? ", ..." : "" + "#{return_type} FUNCNAME(#{arg_val_list(arg_count)}#{varargs})" +end + +def output_function_body(arg_count, has_varargs, is_value_function) + arg_count.times { |i| putd "SAVE_ARG(FUNCNAME, #{i}); \\" } + putd "if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\\" + arg_count.times { |i| putd " SAVE_ARG_HISTORY(FUNCNAME, #{i}); \\" } + putd "}\\" + putd "else{\\" + putd " HISTORY_DROPPED(FUNCNAME);\\" + putd "}\\" + putd "INCREMENT_CALL_COUNT(FUNCNAME); \\" + putd "REGISTER_CALL(FUNCNAME); \\" + + return_type = is_value_function ? "return" : "" + putd "if (FUNCNAME##_fake.custom_fake) #{return_type} FUNCNAME##_fake.custom_fake(#{arg_list(arg_count)}); \\" + + putd "RETURN_FAKE_RESULT(FUNCNAME) \\" if is_value_function +end + +def output_reset_function(arg_count, is_value_function) + putd "void FUNCNAME##_reset(){ \\" + putd " memset(&FUNCNAME##_fake, 0, sizeof(FUNCNAME##_fake)); \\" + putd " FUNCNAME##_fake.arg_history_len = FFF_ARG_HISTORY_LEN;\\" + putd "} \\" +end + +def define_fff_globals + putd "typedef struct { " + putd " void * call_history[FFF_CALL_HISTORY_LEN];" + putd " unsigned int call_history_idx;" + putd "} fff_globals_t;" + putd "" + putd "FFF_EXTERN_C \\" + putd "extern fff_globals_t fff;" + putd "FFF_END_EXTERN_C \\" + putd "" + putd "#define DEFINE_FFF_GLOBALS \\" + putd " FFF_EXTERN_C \\" + putd " fff_globals_t fff; \\" + putd " FFF_END_EXTERN_C" + putd "" + putd "#define FFF_RESET_HISTORY() fff.call_history_idx = 0;" + putd "" + putd "#define REGISTER_CALL(function) \\" + putd " if(fff.call_history_idx < FFF_CALL_HISTORY_LEN) \\" + putd " fff.call_history[fff.call_history_idx++] = (void *)function;" +end + +def extern_c + putd "FFF_EXTERN_C \\" + pushd + yield + popd + putd "FFF_END_EXTERN_C \\" +end + +def in_struct + putd "typedef struct FUNCNAME##_Fake { \\" + pushd + yield + popd + putd "} FUNCNAME##_Fake;\\" +end + +def include_guard + putd "#ifndef FAKE_FUNCTIONS" + putd "#define FAKE_FUNCTIONS" + putd "" + + yield + + putd "" + putd "#endif /* FAKE_FUNCTIONS */" +end + +def output_macro_counting_shortcuts + putd <<-MACRO_COUNTING + +#define PP_NARG_MINUS2(...) \ + PP_NARG_MINUS2_(__VA_ARGS__, PP_RSEQ_N_MINUS2()) + +#define PP_NARG_MINUS2_(...) \ + PP_ARG_MINUS2_N(__VA_ARGS__) + +#define PP_ARG_MINUS2_N(returnVal, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, N, ...) N + +#define PP_RSEQ_N_MINUS2() \ + 19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0 + + +#define FAKE_VALUE_FUNC(...) \ + FUNC_VALUE_(PP_NARG_MINUS2(__VA_ARGS__), __VA_ARGS__) + +#define FUNC_VALUE_(N,...) \ + FUNC_VALUE_N(N,__VA_ARGS__) + +#define FUNC_VALUE_N(N,...) \ + FAKE_VALUE_FUNC ## N(__VA_ARGS__) + + + +#define PP_NARG_MINUS1(...) \ + PP_NARG_MINUS1_(__VA_ARGS__, PP_RSEQ_N_MINUS1()) + +#define PP_NARG_MINUS1_(...) \ + PP_ARG_MINUS1_N(__VA_ARGS__) + +#define PP_ARG_MINUS1_N(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, N, ...) N + +#define PP_RSEQ_N_MINUS1() \ + 20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0 + +#define FAKE_VOID_FUNC(...) \ + FUNC_VOID_(PP_NARG_MINUS1(__VA_ARGS__), __VA_ARGS__) + +#define FUNC_VOID_(N,...) \ + FUNC_VOID_N(N,__VA_ARGS__) + +#define FUNC_VOID_N(N,...) \ + FAKE_VOID_FUNC ## N(__VA_ARGS__) + + MACRO_COUNTING +end + +def output_c_and_cpp + + include_guard { + output_constants + output_internal_helper_macros + yield + output_macro_counting_shortcuts + } +end + +# lets generate!! +output_c_and_cpp{ + define_fff_globals + # Create fake generators for 0..MAX_ARGS + num_fake_generators = $MAX_ARGS + 1 + num_fake_generators.times {|arg_count| output_macro(arg_count, false, false)} + num_fake_generators.times {|arg_count| output_macro(arg_count, false, true)} + # generate the varargs variants + (2..$MAX_ARGS).each {|arg_count| output_macro(arg_count, true, false)} + (2..$MAX_ARGS).each {|arg_count| output_macro(arg_count, true, true)} +} diff --git a/plugins/fff/vendor/fff/fff.h b/plugins/fff/vendor/fff/fff.h new file mode 100644 index 000000000..f481b3dde --- /dev/null +++ b/plugins/fff/vendor/fff/fff.h @@ -0,0 +1,5122 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + + + + +/* +LICENSE + +The MIT License (MIT) + +Copyright (c) 2010 Michael Long + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/#ifndef FAKE_FUNCTIONS +#define FAKE_FUNCTIONS + +#define FFF_MAX_ARGS (20u) +#ifndef FFF_ARG_HISTORY_LEN + #define FFF_ARG_HISTORY_LEN (50u) +#endif +#ifndef FFF_CALL_HISTORY_LEN + #define FFF_CALL_HISTORY_LEN (50u) +#endif +/* -- INTERNAL HELPER MACROS -- */ +#define SET_RETURN_SEQ(FUNCNAME, ARRAY_POINTER, ARRAY_LEN) \ + FUNCNAME##_fake.return_val_seq = ARRAY_POINTER; \ + FUNCNAME##_fake.return_val_seq_len = ARRAY_LEN; + +/* Defining a function to reset a fake function */ +#define RESET_FAKE(FUNCNAME) { \ + FUNCNAME##_reset(); \ +} \ + + +#define DECLARE_ARG(type, n, FUNCNAME) \ + type arg##n##_val; \ + type arg##n##_history[FFF_ARG_HISTORY_LEN]; + +#define DECLARE_ALL_FUNC_COMMON \ + unsigned int call_count; \ + unsigned int arg_history_len;\ + unsigned int arg_histories_dropped; \ + +#define SAVE_ARG(FUNCNAME, n) \ + memcpy((void*)&FUNCNAME##_fake.arg##n##_val, (void*)&arg##n, sizeof(arg##n)); + +#define ROOM_FOR_MORE_HISTORY(FUNCNAME) \ + FUNCNAME##_fake.call_count < FFF_ARG_HISTORY_LEN + +#define SAVE_ARG_HISTORY(FUNCNAME, ARGN) \ + memcpy((void*)&FUNCNAME##_fake.arg##ARGN##_history[FUNCNAME##_fake.call_count], (void*)&arg##ARGN, sizeof(arg##ARGN)); + +#define HISTORY_DROPPED(FUNCNAME) \ + FUNCNAME##_fake.arg_histories_dropped++ + +#define DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE return_val; \ + int return_val_seq_len; \ + int return_val_seq_idx; \ + RETURN_TYPE * return_val_seq; \ + +#define INCREMENT_CALL_COUNT(FUNCNAME) \ + FUNCNAME##_fake.call_count++ + +#define RETURN_FAKE_RESULT(FUNCNAME) \ + if (FUNCNAME##_fake.return_val_seq_len){ /* then its a sequence */ \ + if(FUNCNAME##_fake.return_val_seq_idx < FUNCNAME##_fake.return_val_seq_len) { \ + return FUNCNAME##_fake.return_val_seq[FUNCNAME##_fake.return_val_seq_idx++]; \ + } \ + return FUNCNAME##_fake.return_val_seq[FUNCNAME##_fake.return_val_seq_len-1]; /* return last element */ \ + } \ + return FUNCNAME##_fake.return_val; \ + +#ifdef __cplusplus + #define FFF_EXTERN_C extern "C"{ + #define FFF_END_EXTERN_C } +#else /* ansi c */ + #define FFF_EXTERN_C + #define FFF_END_EXTERN_C +#endif /* cpp/ansi c */ + +#define DEFINE_RESET_FUNCTION(FUNCNAME) \ + void FUNCNAME##_reset(){ \ + memset(&FUNCNAME##_fake, 0, sizeof(FUNCNAME##_fake)); \ + FUNCNAME##_fake.arg_history_len = FFF_ARG_HISTORY_LEN;\ + } +/* -- END INTERNAL HELPER MACROS -- */ + +typedef struct { + void * call_history[FFF_CALL_HISTORY_LEN]; + unsigned int call_history_idx; +} fff_globals_t; + +FFF_EXTERN_C \ +extern fff_globals_t fff; +FFF_END_EXTERN_C \ + +#define DEFINE_FFF_GLOBALS \ + FFF_EXTERN_C \ + fff_globals_t fff; \ + FFF_END_EXTERN_C + +#define FFF_RESET_HISTORY() fff.call_history_idx = 0; + +#define REGISTER_CALL(function) \ + if(fff.call_history_idx < FFF_CALL_HISTORY_LEN) \ + fff.call_history[fff.call_history_idx++] = (void *)function; + +#define DECLARE_FAKE_VOID_FUNC0(FUNCNAME) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC0(FUNCNAME) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(){ \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC0(FUNCNAME) \ + DECLARE_FAKE_VOID_FUNC0(FUNCNAME) \ + DEFINE_FAKE_VOID_FUNC0(FUNCNAME) \ + + +#define DECLARE_FAKE_VOID_FUNC1(FUNCNAME, ARG0_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC1(FUNCNAME, ARG0_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0){ \ + SAVE_ARG(FUNCNAME, 0); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC1(FUNCNAME, ARG0_TYPE) \ + DECLARE_FAKE_VOID_FUNC1(FUNCNAME, ARG0_TYPE) \ + DEFINE_FAKE_VOID_FUNC1(FUNCNAME, ARG0_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC2(FUNCNAME, ARG0_TYPE, ARG1_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC2(FUNCNAME, ARG0_TYPE, ARG1_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC2(FUNCNAME, ARG0_TYPE, ARG1_TYPE) \ + DECLARE_FAKE_VOID_FUNC2(FUNCNAME, ARG0_TYPE, ARG1_TYPE) \ + DEFINE_FAKE_VOID_FUNC2(FUNCNAME, ARG0_TYPE, ARG1_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC3(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC3(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC3(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE) \ + DECLARE_FAKE_VOID_FUNC3(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE) \ + DEFINE_FAKE_VOID_FUNC3(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC4(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC4(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC4(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE) \ + DECLARE_FAKE_VOID_FUNC4(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE) \ + DEFINE_FAKE_VOID_FUNC4(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC5(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC5(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC5(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE) \ + DECLARE_FAKE_VOID_FUNC5(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE) \ + DEFINE_FAKE_VOID_FUNC5(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC6(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC6(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC6(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE) \ + DECLARE_FAKE_VOID_FUNC6(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE) \ + DEFINE_FAKE_VOID_FUNC6(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC7(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC7(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC7(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE) \ + DECLARE_FAKE_VOID_FUNC7(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE) \ + DEFINE_FAKE_VOID_FUNC7(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC8(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC8(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC8(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE) \ + DECLARE_FAKE_VOID_FUNC8(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE) \ + DEFINE_FAKE_VOID_FUNC8(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC9(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC9(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC9(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE) \ + DECLARE_FAKE_VOID_FUNC9(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE) \ + DEFINE_FAKE_VOID_FUNC9(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC10(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC10(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC10(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE) \ + DECLARE_FAKE_VOID_FUNC10(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE) \ + DEFINE_FAKE_VOID_FUNC10(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC11(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC11(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC11(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE) \ + DECLARE_FAKE_VOID_FUNC11(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE) \ + DEFINE_FAKE_VOID_FUNC11(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC12(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC12(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC12(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE) \ + DECLARE_FAKE_VOID_FUNC12(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE) \ + DEFINE_FAKE_VOID_FUNC12(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC13(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC13(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC13(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE) \ + DECLARE_FAKE_VOID_FUNC13(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE) \ + DEFINE_FAKE_VOID_FUNC13(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC14(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC14(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC14(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE) \ + DECLARE_FAKE_VOID_FUNC14(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE) \ + DEFINE_FAKE_VOID_FUNC14(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC15(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC15(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC15(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE) \ + DECLARE_FAKE_VOID_FUNC15(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE) \ + DEFINE_FAKE_VOID_FUNC15(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC16(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC16(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC16(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE) \ + DECLARE_FAKE_VOID_FUNC16(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE) \ + DEFINE_FAKE_VOID_FUNC16(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC17(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC17(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC17(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE) \ + DECLARE_FAKE_VOID_FUNC17(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE) \ + DEFINE_FAKE_VOID_FUNC17(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC18(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ARG(ARG17_TYPE, 17, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC18(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + SAVE_ARG(FUNCNAME, 17); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + SAVE_ARG_HISTORY(FUNCNAME, 17); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC18(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE) \ + DECLARE_FAKE_VOID_FUNC18(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE) \ + DEFINE_FAKE_VOID_FUNC18(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC19(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ARG(ARG17_TYPE, 17, FUNCNAME) \ + DECLARE_ARG(ARG18_TYPE, 18, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC19(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + SAVE_ARG(FUNCNAME, 17); \ + SAVE_ARG(FUNCNAME, 18); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + SAVE_ARG_HISTORY(FUNCNAME, 17); \ + SAVE_ARG_HISTORY(FUNCNAME, 18); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC19(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE) \ + DECLARE_FAKE_VOID_FUNC19(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE) \ + DEFINE_FAKE_VOID_FUNC19(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC20(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ARG(ARG17_TYPE, 17, FUNCNAME) \ + DECLARE_ARG(ARG18_TYPE, 18, FUNCNAME) \ + DECLARE_ARG(ARG19_TYPE, 19, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18, ARG19_TYPE arg19); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC20(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18, ARG19_TYPE arg19){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + SAVE_ARG(FUNCNAME, 17); \ + SAVE_ARG(FUNCNAME, 18); \ + SAVE_ARG(FUNCNAME, 19); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + SAVE_ARG_HISTORY(FUNCNAME, 17); \ + SAVE_ARG_HISTORY(FUNCNAME, 18); \ + SAVE_ARG_HISTORY(FUNCNAME, 19); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC20(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE) \ + DECLARE_FAKE_VOID_FUNC20(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE) \ + DEFINE_FAKE_VOID_FUNC20(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC0(RETURN_TYPE, FUNCNAME) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC0(RETURN_TYPE, FUNCNAME) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(){ \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC0(RETURN_TYPE, FUNCNAME) \ + DECLARE_FAKE_VALUE_FUNC0(RETURN_TYPE, FUNCNAME) \ + DEFINE_FAKE_VALUE_FUNC0(RETURN_TYPE, FUNCNAME) \ + + +#define DECLARE_FAKE_VALUE_FUNC1(RETURN_TYPE, FUNCNAME, ARG0_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC1(RETURN_TYPE, FUNCNAME, ARG0_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0){ \ + SAVE_ARG(FUNCNAME, 0); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC1(RETURN_TYPE, FUNCNAME, ARG0_TYPE) \ + DECLARE_FAKE_VALUE_FUNC1(RETURN_TYPE, FUNCNAME, ARG0_TYPE) \ + DEFINE_FAKE_VALUE_FUNC1(RETURN_TYPE, FUNCNAME, ARG0_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC2(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC2(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC2(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE) \ + DECLARE_FAKE_VALUE_FUNC2(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE) \ + DEFINE_FAKE_VALUE_FUNC2(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC3(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC3(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC3(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE) \ + DECLARE_FAKE_VALUE_FUNC3(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE) \ + DEFINE_FAKE_VALUE_FUNC3(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC4(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC4(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC4(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE) \ + DECLARE_FAKE_VALUE_FUNC4(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE) \ + DEFINE_FAKE_VALUE_FUNC4(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC5(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC5(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC5(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE) \ + DECLARE_FAKE_VALUE_FUNC5(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE) \ + DEFINE_FAKE_VALUE_FUNC5(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC6(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC6(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC6(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE) \ + DECLARE_FAKE_VALUE_FUNC6(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE) \ + DEFINE_FAKE_VALUE_FUNC6(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC7(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC7(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC7(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE) \ + DECLARE_FAKE_VALUE_FUNC7(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE) \ + DEFINE_FAKE_VALUE_FUNC7(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC8(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC8(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC8(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE) \ + DECLARE_FAKE_VALUE_FUNC8(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE) \ + DEFINE_FAKE_VALUE_FUNC8(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC9(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC9(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC9(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE) \ + DECLARE_FAKE_VALUE_FUNC9(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE) \ + DEFINE_FAKE_VALUE_FUNC9(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC10(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC10(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC10(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE) \ + DECLARE_FAKE_VALUE_FUNC10(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE) \ + DEFINE_FAKE_VALUE_FUNC10(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC11(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC11(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC11(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE) \ + DECLARE_FAKE_VALUE_FUNC11(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE) \ + DEFINE_FAKE_VALUE_FUNC11(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC12(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC12(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC12(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE) \ + DECLARE_FAKE_VALUE_FUNC12(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE) \ + DEFINE_FAKE_VALUE_FUNC12(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC13(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC13(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC13(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE) \ + DECLARE_FAKE_VALUE_FUNC13(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE) \ + DEFINE_FAKE_VALUE_FUNC13(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC14(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC14(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC14(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE) \ + DECLARE_FAKE_VALUE_FUNC14(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE) \ + DEFINE_FAKE_VALUE_FUNC14(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC15(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC15(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC15(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE) \ + DECLARE_FAKE_VALUE_FUNC15(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE) \ + DEFINE_FAKE_VALUE_FUNC15(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC16(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC16(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC16(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE) \ + DECLARE_FAKE_VALUE_FUNC16(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE) \ + DEFINE_FAKE_VALUE_FUNC16(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC17(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC17(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC17(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE) \ + DECLARE_FAKE_VALUE_FUNC17(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE) \ + DEFINE_FAKE_VALUE_FUNC17(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC18(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ARG(ARG17_TYPE, 17, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC18(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + SAVE_ARG(FUNCNAME, 17); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + SAVE_ARG_HISTORY(FUNCNAME, 17); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC18(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE) \ + DECLARE_FAKE_VALUE_FUNC18(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE) \ + DEFINE_FAKE_VALUE_FUNC18(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC19(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ARG(ARG17_TYPE, 17, FUNCNAME) \ + DECLARE_ARG(ARG18_TYPE, 18, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC19(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + SAVE_ARG(FUNCNAME, 17); \ + SAVE_ARG(FUNCNAME, 18); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + SAVE_ARG_HISTORY(FUNCNAME, 17); \ + SAVE_ARG_HISTORY(FUNCNAME, 18); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC19(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE) \ + DECLARE_FAKE_VALUE_FUNC19(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE) \ + DEFINE_FAKE_VALUE_FUNC19(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE) \ + + +#define DECLARE_FAKE_VALUE_FUNC20(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ARG(ARG17_TYPE, 17, FUNCNAME) \ + DECLARE_ARG(ARG18_TYPE, 18, FUNCNAME) \ + DECLARE_ARG(ARG19_TYPE, 19, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18, ARG19_TYPE arg19); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC20(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18, ARG19_TYPE arg19){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + SAVE_ARG(FUNCNAME, 17); \ + SAVE_ARG(FUNCNAME, 18); \ + SAVE_ARG(FUNCNAME, 19); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + SAVE_ARG_HISTORY(FUNCNAME, 17); \ + SAVE_ARG_HISTORY(FUNCNAME, 18); \ + SAVE_ARG_HISTORY(FUNCNAME, 19); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18, arg19); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC20(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE) \ + DECLARE_FAKE_VALUE_FUNC20(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE) \ + DEFINE_FAKE_VALUE_FUNC20(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ARG19_TYPE) \ + + +#define DECLARE_FAKE_VOID_FUNC2_VARARG(FUNCNAME, ARG0_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC2_VARARG(FUNCNAME, ARG0_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC2_VARARG(FUNCNAME, ARG0_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC2_VARARG(FUNCNAME, ARG0_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC2_VARARG(FUNCNAME, ARG0_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC3_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC3_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC3_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC3_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC3_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC4_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC4_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC4_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC4_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC4_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC5_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC5_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC5_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC5_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC5_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC6_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC6_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC6_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC6_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC6_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC7_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC7_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC7_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC7_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC7_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC8_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC8_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC8_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC8_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC8_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC9_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC9_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC9_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC9_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC9_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC10_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC10_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC10_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC10_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC10_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC11_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC11_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC11_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC11_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC11_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC12_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC12_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC12_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC12_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC12_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC13_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC13_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC13_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC13_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC13_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC14_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC14_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC14_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC14_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC14_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC15_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC15_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC15_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC15_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC15_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC16_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC16_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC16_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC16_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC16_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC17_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC17_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC17_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC17_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC17_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC18_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC18_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC18_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC18_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC18_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC19_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ARG(ARG17_TYPE, 17, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC19_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + SAVE_ARG(FUNCNAME, 17); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + SAVE_ARG_HISTORY(FUNCNAME, 17); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC19_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC19_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC19_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ...) \ + + +#define DECLARE_FAKE_VOID_FUNC20_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ARG(ARG17_TYPE, 17, FUNCNAME) \ + DECLARE_ARG(ARG18_TYPE, 18, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + void(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VOID_FUNC20_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + SAVE_ARG(FUNCNAME, 17); \ + SAVE_ARG(FUNCNAME, 18); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + SAVE_ARG_HISTORY(FUNCNAME, 17); \ + SAVE_ARG_HISTORY(FUNCNAME, 18); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18); \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VOID_FUNC20_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ...) \ + DECLARE_FAKE_VOID_FUNC20_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ...) \ + DEFINE_FAKE_VOID_FUNC20_VARARG(FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC2_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC2_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC2_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC2_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC2_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC3_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC3_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC3_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC3_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC3_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC4_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC4_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC4_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC4_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC4_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC5_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC5_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC5_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC5_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC5_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC6_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC6_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC6_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC6_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC6_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC7_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC7_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC7_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC7_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC7_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC8_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC8_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC8_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC8_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC8_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC9_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC9_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC9_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC9_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC9_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC10_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC10_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC10_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC10_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC10_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC11_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC11_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC11_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC11_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC11_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC12_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC12_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC12_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC12_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC12_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC13_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC13_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC13_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC13_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC13_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC14_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC14_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC14_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC14_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC14_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC15_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC15_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC15_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC15_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC15_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC16_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC16_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC16_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC16_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC16_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC17_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC17_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC17_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC17_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC17_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC18_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC18_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC18_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC18_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC18_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC19_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ARG(ARG17_TYPE, 17, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC19_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + SAVE_ARG(FUNCNAME, 17); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + SAVE_ARG_HISTORY(FUNCNAME, 17); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC19_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC19_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC19_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ...) \ + + +#define DECLARE_FAKE_VALUE_FUNC20_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ...) \ + FFF_EXTERN_C \ + typedef struct FUNCNAME##_Fake { \ + DECLARE_ARG(ARG0_TYPE, 0, FUNCNAME) \ + DECLARE_ARG(ARG1_TYPE, 1, FUNCNAME) \ + DECLARE_ARG(ARG2_TYPE, 2, FUNCNAME) \ + DECLARE_ARG(ARG3_TYPE, 3, FUNCNAME) \ + DECLARE_ARG(ARG4_TYPE, 4, FUNCNAME) \ + DECLARE_ARG(ARG5_TYPE, 5, FUNCNAME) \ + DECLARE_ARG(ARG6_TYPE, 6, FUNCNAME) \ + DECLARE_ARG(ARG7_TYPE, 7, FUNCNAME) \ + DECLARE_ARG(ARG8_TYPE, 8, FUNCNAME) \ + DECLARE_ARG(ARG9_TYPE, 9, FUNCNAME) \ + DECLARE_ARG(ARG10_TYPE, 10, FUNCNAME) \ + DECLARE_ARG(ARG11_TYPE, 11, FUNCNAME) \ + DECLARE_ARG(ARG12_TYPE, 12, FUNCNAME) \ + DECLARE_ARG(ARG13_TYPE, 13, FUNCNAME) \ + DECLARE_ARG(ARG14_TYPE, 14, FUNCNAME) \ + DECLARE_ARG(ARG15_TYPE, 15, FUNCNAME) \ + DECLARE_ARG(ARG16_TYPE, 16, FUNCNAME) \ + DECLARE_ARG(ARG17_TYPE, 17, FUNCNAME) \ + DECLARE_ARG(ARG18_TYPE, 18, FUNCNAME) \ + DECLARE_ALL_FUNC_COMMON \ + DECLARE_VALUE_FUNCTION_VARIABLES(RETURN_TYPE) \ + RETURN_TYPE(*custom_fake)(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18); \ + } FUNCNAME##_Fake;\ + extern FUNCNAME##_Fake FUNCNAME##_fake;\ + void FUNCNAME##_reset(); \ + FFF_END_EXTERN_C \ + +#define DEFINE_FAKE_VALUE_FUNC20_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ...) \ + FFF_EXTERN_C \ + FUNCNAME##_Fake FUNCNAME##_fake;\ + RETURN_TYPE FUNCNAME(ARG0_TYPE arg0, ARG1_TYPE arg1, ARG2_TYPE arg2, ARG3_TYPE arg3, ARG4_TYPE arg4, ARG5_TYPE arg5, ARG6_TYPE arg6, ARG7_TYPE arg7, ARG8_TYPE arg8, ARG9_TYPE arg9, ARG10_TYPE arg10, ARG11_TYPE arg11, ARG12_TYPE arg12, ARG13_TYPE arg13, ARG14_TYPE arg14, ARG15_TYPE arg15, ARG16_TYPE arg16, ARG17_TYPE arg17, ARG18_TYPE arg18, ...){ \ + SAVE_ARG(FUNCNAME, 0); \ + SAVE_ARG(FUNCNAME, 1); \ + SAVE_ARG(FUNCNAME, 2); \ + SAVE_ARG(FUNCNAME, 3); \ + SAVE_ARG(FUNCNAME, 4); \ + SAVE_ARG(FUNCNAME, 5); \ + SAVE_ARG(FUNCNAME, 6); \ + SAVE_ARG(FUNCNAME, 7); \ + SAVE_ARG(FUNCNAME, 8); \ + SAVE_ARG(FUNCNAME, 9); \ + SAVE_ARG(FUNCNAME, 10); \ + SAVE_ARG(FUNCNAME, 11); \ + SAVE_ARG(FUNCNAME, 12); \ + SAVE_ARG(FUNCNAME, 13); \ + SAVE_ARG(FUNCNAME, 14); \ + SAVE_ARG(FUNCNAME, 15); \ + SAVE_ARG(FUNCNAME, 16); \ + SAVE_ARG(FUNCNAME, 17); \ + SAVE_ARG(FUNCNAME, 18); \ + if(ROOM_FOR_MORE_HISTORY(FUNCNAME)){\ + SAVE_ARG_HISTORY(FUNCNAME, 0); \ + SAVE_ARG_HISTORY(FUNCNAME, 1); \ + SAVE_ARG_HISTORY(FUNCNAME, 2); \ + SAVE_ARG_HISTORY(FUNCNAME, 3); \ + SAVE_ARG_HISTORY(FUNCNAME, 4); \ + SAVE_ARG_HISTORY(FUNCNAME, 5); \ + SAVE_ARG_HISTORY(FUNCNAME, 6); \ + SAVE_ARG_HISTORY(FUNCNAME, 7); \ + SAVE_ARG_HISTORY(FUNCNAME, 8); \ + SAVE_ARG_HISTORY(FUNCNAME, 9); \ + SAVE_ARG_HISTORY(FUNCNAME, 10); \ + SAVE_ARG_HISTORY(FUNCNAME, 11); \ + SAVE_ARG_HISTORY(FUNCNAME, 12); \ + SAVE_ARG_HISTORY(FUNCNAME, 13); \ + SAVE_ARG_HISTORY(FUNCNAME, 14); \ + SAVE_ARG_HISTORY(FUNCNAME, 15); \ + SAVE_ARG_HISTORY(FUNCNAME, 16); \ + SAVE_ARG_HISTORY(FUNCNAME, 17); \ + SAVE_ARG_HISTORY(FUNCNAME, 18); \ + }\ + else{\ + HISTORY_DROPPED(FUNCNAME);\ + }\ + INCREMENT_CALL_COUNT(FUNCNAME); \ + REGISTER_CALL(FUNCNAME); \ + if (FUNCNAME##_fake.custom_fake) return FUNCNAME##_fake.custom_fake(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg17, arg18); \ + RETURN_FAKE_RESULT(FUNCNAME) \ + } \ + DEFINE_RESET_FUNCTION(FUNCNAME) \ + FFF_END_EXTERN_C \ + +#define FAKE_VALUE_FUNC20_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ...) \ + DECLARE_FAKE_VALUE_FUNC20_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ...) \ + DEFINE_FAKE_VALUE_FUNC20_VARARG(RETURN_TYPE, FUNCNAME, ARG0_TYPE, ARG1_TYPE, ARG2_TYPE, ARG3_TYPE, ARG4_TYPE, ARG5_TYPE, ARG6_TYPE, ARG7_TYPE, ARG8_TYPE, ARG9_TYPE, ARG10_TYPE, ARG11_TYPE, ARG12_TYPE, ARG13_TYPE, ARG14_TYPE, ARG15_TYPE, ARG16_TYPE, ARG17_TYPE, ARG18_TYPE, ...) \ + + +#define PP_NARG_MINUS2(...) PP_NARG_MINUS2_(__VA_ARGS__, PP_RSEQ_N_MINUS2()) + +#define PP_NARG_MINUS2_(...) PP_ARG_MINUS2_N(__VA_ARGS__) + +#define PP_ARG_MINUS2_N(returnVal, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, N, ...) N + +#define PP_RSEQ_N_MINUS2() 19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0 + + +#define FAKE_VALUE_FUNC(...) FUNC_VALUE_(PP_NARG_MINUS2(__VA_ARGS__), __VA_ARGS__) + +#define FUNC_VALUE_(N,...) FUNC_VALUE_N(N,__VA_ARGS__) + +#define FUNC_VALUE_N(N,...) FAKE_VALUE_FUNC ## N(__VA_ARGS__) + + + +#define PP_NARG_MINUS1(...) PP_NARG_MINUS1_(__VA_ARGS__, PP_RSEQ_N_MINUS1()) + +#define PP_NARG_MINUS1_(...) PP_ARG_MINUS1_N(__VA_ARGS__) + +#define PP_ARG_MINUS1_N(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, N, ...) N + +#define PP_RSEQ_N_MINUS1() 20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0 + +#define FAKE_VOID_FUNC(...) FUNC_VOID_(PP_NARG_MINUS1(__VA_ARGS__), __VA_ARGS__) + +#define FUNC_VOID_(N,...) FUNC_VOID_N(N,__VA_ARGS__) + +#define FUNC_VOID_N(N,...) FAKE_VOID_FUNC ## N(__VA_ARGS__) + + +#endif /* FAKE_FUNCTIONS */ diff --git a/plugins/fff/vendor/fff/gtest/Makefile b/plugins/fff/vendor/fff/gtest/Makefile new file mode 100644 index 000000000..efcfa0b83 --- /dev/null +++ b/plugins/fff/vendor/fff/gtest/Makefile @@ -0,0 +1,22 @@ + +BUILD_DIR = ../build +LIBS := -lpthread +CPP_OBJS=$(BUILD_DIR)/gtest-all.o $(BUILD_DIR)/gtest-main.o + +SOURCES=gtest-all.cc gtest-main.cc + + +# Each subdirectory must supply rules for building sources it contributes +$(BUILD_DIR)/%.o: %.cc + @echo 'Building file: $<' + @echo 'Invoking: GCC C++ Compiler' + g++ -I../ -O0 -g3 -Wall -DGTEST_USE_OWN_TR1_TUPLE=1 -c -o "$@" "$<" + @echo 'Finished building: $<' + @echo ' ' + +all: $(CPP_OBJS) + +clean: + -$(RM) $(CPP_OBJS) + -@echo ' ' + diff --git a/plugins/fff/vendor/fff/gtest/gtest-all.cc b/plugins/fff/vendor/fff/gtest/gtest-all.cc new file mode 100644 index 000000000..5ced66a90 --- /dev/null +++ b/plugins/fff/vendor/fff/gtest/gtest-all.cc @@ -0,0 +1,9118 @@ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: mheule@google.com (Markus Heule) +// +// Google C++ Testing Framework (Google Test) +// +// Sometimes it's desirable to build Google Test by compiling a single file. +// This file serves this purpose. + +// This line ensures that gtest.h can be compiled on its own, even +// when it's fused. +#include "gtest/gtest.h" + +// The following lines pull in the real gtest *.cc files. +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) +// +// The Google C++ Testing Framework (Google Test) + +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) +// +// Utilities for testing Google Test itself and code that uses Google Test +// (e.g. frameworks built on top of Google Test). + +#ifndef GTEST_INCLUDE_GTEST_GTEST_SPI_H_ +#define GTEST_INCLUDE_GTEST_GTEST_SPI_H_ + + +namespace testing { + +// This helper class can be used to mock out Google Test failure reporting +// so that we can test Google Test or code that builds on Google Test. +// +// An object of this class appends a TestPartResult object to the +// TestPartResultArray object given in the constructor whenever a Google Test +// failure is reported. It can either intercept only failures that are +// generated in the same thread that created this object or it can intercept +// all generated failures. The scope of this mock object can be controlled with +// the second argument to the two arguments constructor. +class GTEST_API_ ScopedFakeTestPartResultReporter + : public TestPartResultReporterInterface { + public: + // The two possible mocking modes of this object. + enum InterceptMode { + INTERCEPT_ONLY_CURRENT_THREAD, // Intercepts only thread local failures. + INTERCEPT_ALL_THREADS // Intercepts all failures. + }; + + // The c'tor sets this object as the test part result reporter used + // by Google Test. The 'result' parameter specifies where to report the + // results. This reporter will only catch failures generated in the current + // thread. DEPRECATED + explicit ScopedFakeTestPartResultReporter(TestPartResultArray* result); + + // Same as above, but you can choose the interception scope of this object. + ScopedFakeTestPartResultReporter(InterceptMode intercept_mode, + TestPartResultArray* result); + + // The d'tor restores the previous test part result reporter. + virtual ~ScopedFakeTestPartResultReporter(); + + // Appends the TestPartResult object to the TestPartResultArray + // received in the constructor. + // + // This method is from the TestPartResultReporterInterface + // interface. + virtual void ReportTestPartResult(const TestPartResult& result); + private: + void Init(); + + const InterceptMode intercept_mode_; + TestPartResultReporterInterface* old_reporter_; + TestPartResultArray* const result_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedFakeTestPartResultReporter); +}; + +namespace internal { + +// A helper class for implementing EXPECT_FATAL_FAILURE() and +// EXPECT_NONFATAL_FAILURE(). Its destructor verifies that the given +// TestPartResultArray contains exactly one failure that has the given +// type and contains the given substring. If that's not the case, a +// non-fatal failure will be generated. +class GTEST_API_ SingleFailureChecker { + public: + // The constructor remembers the arguments. + SingleFailureChecker(const TestPartResultArray* results, + TestPartResult::Type type, + const string& substr); + ~SingleFailureChecker(); + private: + const TestPartResultArray* const results_; + const TestPartResult::Type type_; + const string substr_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(SingleFailureChecker); +}; + +} // namespace internal + +} // namespace testing + +// A set of macros for testing Google Test assertions or code that's expected +// to generate Google Test fatal failures. It verifies that the given +// statement will cause exactly one fatal Google Test failure with 'substr' +// being part of the failure message. +// +// There are two different versions of this macro. EXPECT_FATAL_FAILURE only +// affects and considers failures generated in the current thread and +// EXPECT_FATAL_FAILURE_ON_ALL_THREADS does the same but for all threads. +// +// The verification of the assertion is done correctly even when the statement +// throws an exception or aborts the current function. +// +// Known restrictions: +// - 'statement' cannot reference local non-static variables or +// non-static members of the current object. +// - 'statement' cannot return a value. +// - You cannot stream a failure message to this macro. +// +// Note that even though the implementations of the following two +// macros are much alike, we cannot refactor them to use a common +// helper macro, due to some peculiarity in how the preprocessor +// works. The AcceptsMacroThatExpandsToUnprotectedComma test in +// gtest_unittest.cc will fail to compile if we do that. +#define EXPECT_FATAL_FAILURE(statement, substr) \ + do { \ + class GTestExpectFatalFailureHelper {\ + public:\ + static void Execute() { statement; }\ + };\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TestPartResult::kFatalFailure, (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter:: \ + INTERCEPT_ONLY_CURRENT_THREAD, >est_failures);\ + GTestExpectFatalFailureHelper::Execute();\ + }\ + } while (::testing::internal::AlwaysFalse()) + +#define EXPECT_FATAL_FAILURE_ON_ALL_THREADS(statement, substr) \ + do { \ + class GTestExpectFatalFailureHelper {\ + public:\ + static void Execute() { statement; }\ + };\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TestPartResult::kFatalFailure, (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter:: \ + INTERCEPT_ALL_THREADS, >est_failures);\ + GTestExpectFatalFailureHelper::Execute();\ + }\ + } while (::testing::internal::AlwaysFalse()) + +// A macro for testing Google Test assertions or code that's expected to +// generate Google Test non-fatal failures. It asserts that the given +// statement will cause exactly one non-fatal Google Test failure with 'substr' +// being part of the failure message. +// +// There are two different versions of this macro. EXPECT_NONFATAL_FAILURE only +// affects and considers failures generated in the current thread and +// EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS does the same but for all threads. +// +// 'statement' is allowed to reference local variables and members of +// the current object. +// +// The verification of the assertion is done correctly even when the statement +// throws an exception or aborts the current function. +// +// Known restrictions: +// - You cannot stream a failure message to this macro. +// +// Note that even though the implementations of the following two +// macros are much alike, we cannot refactor them to use a common +// helper macro, due to some peculiarity in how the preprocessor +// works. If we do that, the code won't compile when the user gives +// EXPECT_NONFATAL_FAILURE() a statement that contains a macro that +// expands to code containing an unprotected comma. The +// AcceptsMacroThatExpandsToUnprotectedComma test in gtest_unittest.cc +// catches that. +// +// For the same reason, we have to write +// if (::testing::internal::AlwaysTrue()) { statement; } +// instead of +// GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) +// to avoid an MSVC warning on unreachable code. +#define EXPECT_NONFATAL_FAILURE(statement, substr) \ + do {\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TestPartResult::kNonFatalFailure, \ + (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter:: \ + INTERCEPT_ONLY_CURRENT_THREAD, >est_failures);\ + if (::testing::internal::AlwaysTrue()) { statement; }\ + }\ + } while (::testing::internal::AlwaysFalse()) + +#define EXPECT_NONFATAL_FAILURE_ON_ALL_THREADS(statement, substr) \ + do {\ + ::testing::TestPartResultArray gtest_failures;\ + ::testing::internal::SingleFailureChecker gtest_checker(\ + >est_failures, ::testing::TestPartResult::kNonFatalFailure, \ + (substr));\ + {\ + ::testing::ScopedFakeTestPartResultReporter gtest_reporter(\ + ::testing::ScopedFakeTestPartResultReporter::INTERCEPT_ALL_THREADS,\ + >est_failures);\ + if (::testing::internal::AlwaysTrue()) { statement; }\ + }\ + } while (::testing::internal::AlwaysFalse()) + +#endif // GTEST_INCLUDE_GTEST_GTEST_SPI_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include // NOLINT +#include +#include + +#if GTEST_OS_LINUX + +// TODO(kenton@google.com): Use autoconf to detect availability of +// gettimeofday(). +# define GTEST_HAS_GETTIMEOFDAY_ 1 + +# include // NOLINT +# include // NOLINT +# include // NOLINT +// Declares vsnprintf(). This header is not available on Windows. +# include // NOLINT +# include // NOLINT +# include // NOLINT +# include // NOLINT +# include + +#elif GTEST_OS_SYMBIAN +# define GTEST_HAS_GETTIMEOFDAY_ 1 +# include // NOLINT + +#elif GTEST_OS_ZOS +# define GTEST_HAS_GETTIMEOFDAY_ 1 +# include // NOLINT + +// On z/OS we additionally need strings.h for strcasecmp. +# include // NOLINT + +#elif GTEST_OS_WINDOWS_MOBILE // We are on Windows CE. + +# include // NOLINT + +#elif GTEST_OS_WINDOWS // We are on Windows proper. + +# include // NOLINT +# include // NOLINT +# include // NOLINT +# include // NOLINT + +# if GTEST_OS_WINDOWS_MINGW +// MinGW has gettimeofday() but not _ftime64(). +// TODO(kenton@google.com): Use autoconf to detect availability of +// gettimeofday(). +// TODO(kenton@google.com): There are other ways to get the time on +// Windows, like GetTickCount() or GetSystemTimeAsFileTime(). MinGW +// supports these. consider using them instead. +# define GTEST_HAS_GETTIMEOFDAY_ 1 +# include // NOLINT +# endif // GTEST_OS_WINDOWS_MINGW + +// cpplint thinks that the header is already included, so we want to +// silence it. +# include // NOLINT + +#else + +// Assume other platforms have gettimeofday(). +// TODO(kenton@google.com): Use autoconf to detect availability of +// gettimeofday(). +# define GTEST_HAS_GETTIMEOFDAY_ 1 + +// cpplint thinks that the header is already included, so we want to +// silence it. +# include // NOLINT +# include // NOLINT + +#endif // GTEST_OS_LINUX + +#if GTEST_HAS_EXCEPTIONS +# include +#endif + +#if GTEST_CAN_STREAM_RESULTS_ +# include // NOLINT +# include // NOLINT +#endif + +// Indicates that this translation unit is part of Google Test's +// implementation. It must come before gtest-internal-inl.h is +// included, or there will be a compiler error. This trick is to +// prevent a user from accidentally including gtest-internal-inl.h in +// his code. +#define GTEST_IMPLEMENTATION_ 1 +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// Utility functions and classes used by the Google C++ testing framework. +// +// Author: wan@google.com (Zhanyong Wan) +// +// This file contains purely Google Test's internal implementation. Please +// DO NOT #INCLUDE IT IN A USER PROGRAM. + +#ifndef GTEST_SRC_GTEST_INTERNAL_INL_H_ +#define GTEST_SRC_GTEST_INTERNAL_INL_H_ + +// GTEST_IMPLEMENTATION_ is defined to 1 iff the current translation unit is +// part of Google Test's implementation; otherwise it's undefined. +#if !GTEST_IMPLEMENTATION_ +// A user is trying to include this from his code - just say no. +# error "gtest-internal-inl.h is part of Google Test's internal implementation." +# error "It must not be included except by Google Test itself." +#endif // GTEST_IMPLEMENTATION_ + +#ifndef _WIN32_WCE +# include +#endif // !_WIN32_WCE +#include +#include // For strtoll/_strtoul64/malloc/free. +#include // For memmove. + +#include +#include +#include + + +#if GTEST_OS_WINDOWS +# include // NOLINT +#endif // GTEST_OS_WINDOWS + + +namespace testing { + +// Declares the flags. +// +// We don't want the users to modify this flag in the code, but want +// Google Test's own unit tests to be able to access it. Therefore we +// declare it here as opposed to in gtest.h. +GTEST_DECLARE_bool_(death_test_use_fork); + +namespace internal { + +// The value of GetTestTypeId() as seen from within the Google Test +// library. This is solely for testing GetTestTypeId(). +GTEST_API_ extern const TypeId kTestTypeIdInGoogleTest; + +// Names of the flags (needed for parsing Google Test flags). +const char kAlsoRunDisabledTestsFlag[] = "also_run_disabled_tests"; +const char kBreakOnFailureFlag[] = "break_on_failure"; +const char kCatchExceptionsFlag[] = "catch_exceptions"; +const char kColorFlag[] = "color"; +const char kFilterFlag[] = "filter"; +const char kListTestsFlag[] = "list_tests"; +const char kOutputFlag[] = "output"; +const char kPrintTimeFlag[] = "print_time"; +const char kRandomSeedFlag[] = "random_seed"; +const char kRepeatFlag[] = "repeat"; +const char kShuffleFlag[] = "shuffle"; +const char kStackTraceDepthFlag[] = "stack_trace_depth"; +const char kStreamResultToFlag[] = "stream_result_to"; +const char kThrowOnFailureFlag[] = "throw_on_failure"; + +// A valid random seed must be in [1, kMaxRandomSeed]. +const int kMaxRandomSeed = 99999; + +// g_help_flag is true iff the --help flag or an equivalent form is +// specified on the command line. +GTEST_API_ extern bool g_help_flag; + +// Returns the current time in milliseconds. +GTEST_API_ TimeInMillis GetTimeInMillis(); + +// Returns true iff Google Test should use colors in the output. +GTEST_API_ bool ShouldUseColor(bool stdout_is_tty); + +// Formats the given time in milliseconds as seconds. +GTEST_API_ std::string FormatTimeInMillisAsSeconds(TimeInMillis ms); + +// Parses a string for an Int32 flag, in the form of "--flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +GTEST_API_ bool ParseInt32Flag( + const char* str, const char* flag, Int32* value); + +// Returns a random seed in range [1, kMaxRandomSeed] based on the +// given --gtest_random_seed flag value. +inline int GetRandomSeedFromFlag(Int32 random_seed_flag) { + const unsigned int raw_seed = (random_seed_flag == 0) ? + static_cast(GetTimeInMillis()) : + static_cast(random_seed_flag); + + // Normalizes the actual seed to range [1, kMaxRandomSeed] such that + // it's easy to type. + const int normalized_seed = + static_cast((raw_seed - 1U) % + static_cast(kMaxRandomSeed)) + 1; + return normalized_seed; +} + +// Returns the first valid random seed after 'seed'. The behavior is +// undefined if 'seed' is invalid. The seed after kMaxRandomSeed is +// considered to be 1. +inline int GetNextRandomSeed(int seed) { + GTEST_CHECK_(1 <= seed && seed <= kMaxRandomSeed) + << "Invalid random seed " << seed << " - must be in [1, " + << kMaxRandomSeed << "]."; + const int next_seed = seed + 1; + return (next_seed > kMaxRandomSeed) ? 1 : next_seed; +} + +// This class saves the values of all Google Test flags in its c'tor, and +// restores them in its d'tor. +class GTestFlagSaver { + public: + // The c'tor. + GTestFlagSaver() { + also_run_disabled_tests_ = GTEST_FLAG(also_run_disabled_tests); + break_on_failure_ = GTEST_FLAG(break_on_failure); + catch_exceptions_ = GTEST_FLAG(catch_exceptions); + color_ = GTEST_FLAG(color); + death_test_style_ = GTEST_FLAG(death_test_style); + death_test_use_fork_ = GTEST_FLAG(death_test_use_fork); + filter_ = GTEST_FLAG(filter); + internal_run_death_test_ = GTEST_FLAG(internal_run_death_test); + list_tests_ = GTEST_FLAG(list_tests); + output_ = GTEST_FLAG(output); + print_time_ = GTEST_FLAG(print_time); + random_seed_ = GTEST_FLAG(random_seed); + repeat_ = GTEST_FLAG(repeat); + shuffle_ = GTEST_FLAG(shuffle); + stack_trace_depth_ = GTEST_FLAG(stack_trace_depth); + stream_result_to_ = GTEST_FLAG(stream_result_to); + throw_on_failure_ = GTEST_FLAG(throw_on_failure); + } + + // The d'tor is not virtual. DO NOT INHERIT FROM THIS CLASS. + ~GTestFlagSaver() { + GTEST_FLAG(also_run_disabled_tests) = also_run_disabled_tests_; + GTEST_FLAG(break_on_failure) = break_on_failure_; + GTEST_FLAG(catch_exceptions) = catch_exceptions_; + GTEST_FLAG(color) = color_; + GTEST_FLAG(death_test_style) = death_test_style_; + GTEST_FLAG(death_test_use_fork) = death_test_use_fork_; + GTEST_FLAG(filter) = filter_; + GTEST_FLAG(internal_run_death_test) = internal_run_death_test_; + GTEST_FLAG(list_tests) = list_tests_; + GTEST_FLAG(output) = output_; + GTEST_FLAG(print_time) = print_time_; + GTEST_FLAG(random_seed) = random_seed_; + GTEST_FLAG(repeat) = repeat_; + GTEST_FLAG(shuffle) = shuffle_; + GTEST_FLAG(stack_trace_depth) = stack_trace_depth_; + GTEST_FLAG(stream_result_to) = stream_result_to_; + GTEST_FLAG(throw_on_failure) = throw_on_failure_; + } + private: + // Fields for saving the original values of flags. + bool also_run_disabled_tests_; + bool break_on_failure_; + bool catch_exceptions_; + String color_; + String death_test_style_; + bool death_test_use_fork_; + String filter_; + String internal_run_death_test_; + bool list_tests_; + String output_; + bool print_time_; + bool pretty_; + internal::Int32 random_seed_; + internal::Int32 repeat_; + bool shuffle_; + internal::Int32 stack_trace_depth_; + String stream_result_to_; + bool throw_on_failure_; +} GTEST_ATTRIBUTE_UNUSED_; + +// Converts a Unicode code point to a narrow string in UTF-8 encoding. +// code_point parameter is of type UInt32 because wchar_t may not be +// wide enough to contain a code point. +// The output buffer str must containt at least 32 characters. +// The function returns the address of the output buffer. +// If the code_point is not a valid Unicode code point +// (i.e. outside of Unicode range U+0 to U+10FFFF) it will be output +// as '(Invalid Unicode 0xXXXXXXXX)'. +GTEST_API_ char* CodePointToUtf8(UInt32 code_point, char* str); + +// Converts a wide string to a narrow string in UTF-8 encoding. +// The wide string is assumed to have the following encoding: +// UTF-16 if sizeof(wchar_t) == 2 (on Windows, Cygwin, Symbian OS) +// UTF-32 if sizeof(wchar_t) == 4 (on Linux) +// Parameter str points to a null-terminated wide string. +// Parameter num_chars may additionally limit the number +// of wchar_t characters processed. -1 is used when the entire string +// should be processed. +// If the string contains code points that are not valid Unicode code points +// (i.e. outside of Unicode range U+0 to U+10FFFF) they will be output +// as '(Invalid Unicode 0xXXXXXXXX)'. If the string is in UTF16 encoding +// and contains invalid UTF-16 surrogate pairs, values in those pairs +// will be encoded as individual Unicode characters from Basic Normal Plane. +GTEST_API_ String WideStringToUtf8(const wchar_t* str, int num_chars); + +// Reads the GTEST_SHARD_STATUS_FILE environment variable, and creates the file +// if the variable is present. If a file already exists at this location, this +// function will write over it. If the variable is present, but the file cannot +// be created, prints an error and exits. +void WriteToShardStatusFileIfNeeded(); + +// Checks whether sharding is enabled by examining the relevant +// environment variable values. If the variables are present, +// but inconsistent (e.g., shard_index >= total_shards), prints +// an error and exits. If in_subprocess_for_death_test, sharding is +// disabled because it must only be applied to the original test +// process. Otherwise, we could filter out death tests we intended to execute. +GTEST_API_ bool ShouldShard(const char* total_shards_str, + const char* shard_index_str, + bool in_subprocess_for_death_test); + +// Parses the environment variable var as an Int32. If it is unset, +// returns default_val. If it is not an Int32, prints an error and +// and aborts. +GTEST_API_ Int32 Int32FromEnvOrDie(const char* env_var, Int32 default_val); + +// Given the total number of shards, the shard index, and the test id, +// returns true iff the test should be run on this shard. The test id is +// some arbitrary but unique non-negative integer assigned to each test +// method. Assumes that 0 <= shard_index < total_shards. +GTEST_API_ bool ShouldRunTestOnShard( + int total_shards, int shard_index, int test_id); + +// STL container utilities. + +// Returns the number of elements in the given container that satisfy +// the given predicate. +template +inline int CountIf(const Container& c, Predicate predicate) { + // Implemented as an explicit loop since std::count_if() in libCstd on + // Solaris has a non-standard signature. + int count = 0; + for (typename Container::const_iterator it = c.begin(); it != c.end(); ++it) { + if (predicate(*it)) + ++count; + } + return count; +} + +// Applies a function/functor to each element in the container. +template +void ForEach(const Container& c, Functor functor) { + std::for_each(c.begin(), c.end(), functor); +} + +// Returns the i-th element of the vector, or default_value if i is not +// in range [0, v.size()). +template +inline E GetElementOr(const std::vector& v, int i, E default_value) { + return (i < 0 || i >= static_cast(v.size())) ? default_value : v[i]; +} + +// Performs an in-place shuffle of a range of the vector's elements. +// 'begin' and 'end' are element indices as an STL-style range; +// i.e. [begin, end) are shuffled, where 'end' == size() means to +// shuffle to the end of the vector. +template +void ShuffleRange(internal::Random* random, int begin, int end, + std::vector* v) { + const int size = static_cast(v->size()); + GTEST_CHECK_(0 <= begin && begin <= size) + << "Invalid shuffle range start " << begin << ": must be in range [0, " + << size << "]."; + GTEST_CHECK_(begin <= end && end <= size) + << "Invalid shuffle range finish " << end << ": must be in range [" + << begin << ", " << size << "]."; + + // Fisher-Yates shuffle, from + // http://en.wikipedia.org/wiki/Fisher-Yates_shuffle + for (int range_width = end - begin; range_width >= 2; range_width--) { + const int last_in_range = begin + range_width - 1; + const int selected = begin + random->Generate(range_width); + std::swap((*v)[selected], (*v)[last_in_range]); + } +} + +// Performs an in-place shuffle of the vector's elements. +template +inline void Shuffle(internal::Random* random, std::vector* v) { + ShuffleRange(random, 0, static_cast(v->size()), v); +} + +// A function for deleting an object. Handy for being used as a +// functor. +template +static void Delete(T* x) { + delete x; +} + +// A predicate that checks the key of a TestProperty against a known key. +// +// TestPropertyKeyIs is copyable. +class TestPropertyKeyIs { + public: + // Constructor. + // + // TestPropertyKeyIs has NO default constructor. + explicit TestPropertyKeyIs(const char* key) + : key_(key) {} + + // Returns true iff the test name of test property matches on key_. + bool operator()(const TestProperty& test_property) const { + return String(test_property.key()).Compare(key_) == 0; + } + + private: + String key_; +}; + +// Class UnitTestOptions. +// +// This class contains functions for processing options the user +// specifies when running the tests. It has only static members. +// +// In most cases, the user can specify an option using either an +// environment variable or a command line flag. E.g. you can set the +// test filter using either GTEST_FILTER or --gtest_filter. If both +// the variable and the flag are present, the latter overrides the +// former. +class GTEST_API_ UnitTestOptions { + public: + // Functions for processing the gtest_output flag. + + // Returns the output format, or "" for normal printed output. + static String GetOutputFormat(); + + // Returns the absolute path of the requested output file, or the + // default (test_detail.xml in the original working directory) if + // none was explicitly specified. + static String GetAbsolutePathToOutputFile(); + + // Functions for processing the gtest_filter flag. + + // Returns true iff the wildcard pattern matches the string. The + // first ':' or '\0' character in pattern marks the end of it. + // + // This recursive algorithm isn't very efficient, but is clear and + // works well enough for matching test names, which are short. + static bool PatternMatchesString(const char *pattern, const char *str); + + // Returns true iff the user-specified filter matches the test case + // name and the test name. + static bool FilterMatchesTest(const String &test_case_name, + const String &test_name); + +#if GTEST_OS_WINDOWS + // Function for supporting the gtest_catch_exception flag. + + // Returns EXCEPTION_EXECUTE_HANDLER if Google Test should handle the + // given SEH exception, or EXCEPTION_CONTINUE_SEARCH otherwise. + // This function is useful as an __except condition. + static int GTestShouldProcessSEH(DWORD exception_code); +#endif // GTEST_OS_WINDOWS + + // Returns true if "name" matches the ':' separated list of glob-style + // filters in "filter". + static bool MatchesFilter(const String& name, const char* filter); +}; + +// Returns the current application's name, removing directory path if that +// is present. Used by UnitTestOptions::GetOutputFile. +GTEST_API_ FilePath GetCurrentExecutableName(); + +// The role interface for getting the OS stack trace as a string. +class OsStackTraceGetterInterface { + public: + OsStackTraceGetterInterface() {} + virtual ~OsStackTraceGetterInterface() {} + + // Returns the current OS stack trace as a String. Parameters: + // + // max_depth - the maximum number of stack frames to be included + // in the trace. + // skip_count - the number of top frames to be skipped; doesn't count + // against max_depth. + virtual String CurrentStackTrace(int max_depth, int skip_count) = 0; + + // UponLeavingGTest() should be called immediately before Google Test calls + // user code. It saves some information about the current stack that + // CurrentStackTrace() will use to find and hide Google Test stack frames. + virtual void UponLeavingGTest() = 0; + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(OsStackTraceGetterInterface); +}; + +// A working implementation of the OsStackTraceGetterInterface interface. +class OsStackTraceGetter : public OsStackTraceGetterInterface { + public: + OsStackTraceGetter() : caller_frame_(NULL) {} + virtual String CurrentStackTrace(int max_depth, int skip_count); + virtual void UponLeavingGTest(); + + // This string is inserted in place of stack frames that are part of + // Google Test's implementation. + static const char* const kElidedFramesMarker; + + private: + Mutex mutex_; // protects all internal state + + // We save the stack frame below the frame that calls user code. + // We do this because the address of the frame immediately below + // the user code changes between the call to UponLeavingGTest() + // and any calls to CurrentStackTrace() from within the user code. + void* caller_frame_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(OsStackTraceGetter); +}; + +// Information about a Google Test trace point. +struct TraceInfo { + const char* file; + int line; + String message; +}; + +// This is the default global test part result reporter used in UnitTestImpl. +// This class should only be used by UnitTestImpl. +class DefaultGlobalTestPartResultReporter + : public TestPartResultReporterInterface { + public: + explicit DefaultGlobalTestPartResultReporter(UnitTestImpl* unit_test); + // Implements the TestPartResultReporterInterface. Reports the test part + // result in the current test. + virtual void ReportTestPartResult(const TestPartResult& result); + + private: + UnitTestImpl* const unit_test_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultGlobalTestPartResultReporter); +}; + +// This is the default per thread test part result reporter used in +// UnitTestImpl. This class should only be used by UnitTestImpl. +class DefaultPerThreadTestPartResultReporter + : public TestPartResultReporterInterface { + public: + explicit DefaultPerThreadTestPartResultReporter(UnitTestImpl* unit_test); + // Implements the TestPartResultReporterInterface. The implementation just + // delegates to the current global test part result reporter of *unit_test_. + virtual void ReportTestPartResult(const TestPartResult& result); + + private: + UnitTestImpl* const unit_test_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(DefaultPerThreadTestPartResultReporter); +}; + +// The private implementation of the UnitTest class. We don't protect +// the methods under a mutex, as this class is not accessible by a +// user and the UnitTest class that delegates work to this class does +// proper locking. +class GTEST_API_ UnitTestImpl { + public: + explicit UnitTestImpl(UnitTest* parent); + virtual ~UnitTestImpl(); + + // There are two different ways to register your own TestPartResultReporter. + // You can register your own repoter to listen either only for test results + // from the current thread or for results from all threads. + // By default, each per-thread test result repoter just passes a new + // TestPartResult to the global test result reporter, which registers the + // test part result for the currently running test. + + // Returns the global test part result reporter. + TestPartResultReporterInterface* GetGlobalTestPartResultReporter(); + + // Sets the global test part result reporter. + void SetGlobalTestPartResultReporter( + TestPartResultReporterInterface* reporter); + + // Returns the test part result reporter for the current thread. + TestPartResultReporterInterface* GetTestPartResultReporterForCurrentThread(); + + // Sets the test part result reporter for the current thread. + void SetTestPartResultReporterForCurrentThread( + TestPartResultReporterInterface* reporter); + + // Gets the number of successful test cases. + int successful_test_case_count() const; + + // Gets the number of failed test cases. + int failed_test_case_count() const; + + // Gets the number of all test cases. + int total_test_case_count() const; + + // Gets the number of all test cases that contain at least one test + // that should run. + int test_case_to_run_count() const; + + // Gets the number of successful tests. + int successful_test_count() const; + + // Gets the number of failed tests. + int failed_test_count() const; + + // Gets the number of disabled tests. + int disabled_test_count() const; + + // Gets the number of all tests. + int total_test_count() const; + + // Gets the number of tests that should run. + int test_to_run_count() const; + + // Gets the elapsed time, in milliseconds. + TimeInMillis elapsed_time() const { return elapsed_time_; } + + // Returns true iff the unit test passed (i.e. all test cases passed). + bool Passed() const { return !Failed(); } + + // Returns true iff the unit test failed (i.e. some test case failed + // or something outside of all tests failed). + bool Failed() const { + return failed_test_case_count() > 0 || ad_hoc_test_result()->Failed(); + } + + // Gets the i-th test case among all the test cases. i can range from 0 to + // total_test_case_count() - 1. If i is not in that range, returns NULL. + const TestCase* GetTestCase(int i) const { + const int index = GetElementOr(test_case_indices_, i, -1); + return index < 0 ? NULL : test_cases_[i]; + } + + // Gets the i-th test case among all the test cases. i can range from 0 to + // total_test_case_count() - 1. If i is not in that range, returns NULL. + TestCase* GetMutableTestCase(int i) { + const int index = GetElementOr(test_case_indices_, i, -1); + return index < 0 ? NULL : test_cases_[index]; + } + + // Provides access to the event listener list. + TestEventListeners* listeners() { return &listeners_; } + + // Returns the TestResult for the test that's currently running, or + // the TestResult for the ad hoc test if no test is running. + TestResult* current_test_result(); + + // Returns the TestResult for the ad hoc test. + const TestResult* ad_hoc_test_result() const { return &ad_hoc_test_result_; } + + // Sets the OS stack trace getter. + // + // Does nothing if the input and the current OS stack trace getter + // are the same; otherwise, deletes the old getter and makes the + // input the current getter. + void set_os_stack_trace_getter(OsStackTraceGetterInterface* getter); + + // Returns the current OS stack trace getter if it is not NULL; + // otherwise, creates an OsStackTraceGetter, makes it the current + // getter, and returns it. + OsStackTraceGetterInterface* os_stack_trace_getter(); + + // Returns the current OS stack trace as a String. + // + // The maximum number of stack frames to be included is specified by + // the gtest_stack_trace_depth flag. The skip_count parameter + // specifies the number of top frames to be skipped, which doesn't + // count against the number of frames to be included. + // + // For example, if Foo() calls Bar(), which in turn calls + // CurrentOsStackTraceExceptTop(1), Foo() will be included in the + // trace but Bar() and CurrentOsStackTraceExceptTop() won't. + String CurrentOsStackTraceExceptTop(int skip_count); + + // Finds and returns a TestCase with the given name. If one doesn't + // exist, creates one and returns it. + // + // Arguments: + // + // test_case_name: name of the test case + // type_param: the name of the test's type parameter, or NULL if + // this is not a typed or a type-parameterized test. + // set_up_tc: pointer to the function that sets up the test case + // tear_down_tc: pointer to the function that tears down the test case + TestCase* GetTestCase(const char* test_case_name, + const char* type_param, + Test::SetUpTestCaseFunc set_up_tc, + Test::TearDownTestCaseFunc tear_down_tc); + + // Adds a TestInfo to the unit test. + // + // Arguments: + // + // set_up_tc: pointer to the function that sets up the test case + // tear_down_tc: pointer to the function that tears down the test case + // test_info: the TestInfo object + void AddTestInfo(Test::SetUpTestCaseFunc set_up_tc, + Test::TearDownTestCaseFunc tear_down_tc, + TestInfo* test_info) { + // In order to support thread-safe death tests, we need to + // remember the original working directory when the test program + // was first invoked. We cannot do this in RUN_ALL_TESTS(), as + // the user may have changed the current directory before calling + // RUN_ALL_TESTS(). Therefore we capture the current directory in + // AddTestInfo(), which is called to register a TEST or TEST_F + // before main() is reached. + if (original_working_dir_.IsEmpty()) { + original_working_dir_.Set(FilePath::GetCurrentDir()); + GTEST_CHECK_(!original_working_dir_.IsEmpty()) + << "Failed to get the current working directory."; + } + + GetTestCase(test_info->test_case_name(), + test_info->type_param(), + set_up_tc, + tear_down_tc)->AddTestInfo(test_info); + } + +#if GTEST_HAS_PARAM_TEST + // Returns ParameterizedTestCaseRegistry object used to keep track of + // value-parameterized tests and instantiate and register them. + internal::ParameterizedTestCaseRegistry& parameterized_test_registry() { + return parameterized_test_registry_; + } +#endif // GTEST_HAS_PARAM_TEST + + // Sets the TestCase object for the test that's currently running. + void set_current_test_case(TestCase* a_current_test_case) { + current_test_case_ = a_current_test_case; + } + + // Sets the TestInfo object for the test that's currently running. If + // current_test_info is NULL, the assertion results will be stored in + // ad_hoc_test_result_. + void set_current_test_info(TestInfo* a_current_test_info) { + current_test_info_ = a_current_test_info; + } + + // Registers all parameterized tests defined using TEST_P and + // INSTANTIATE_TEST_CASE_P, creating regular tests for each test/parameter + // combination. This method can be called more then once; it has guards + // protecting from registering the tests more then once. If + // value-parameterized tests are disabled, RegisterParameterizedTests is + // present but does nothing. + void RegisterParameterizedTests(); + + // Runs all tests in this UnitTest object, prints the result, and + // returns true if all tests are successful. If any exception is + // thrown during a test, this test is considered to be failed, but + // the rest of the tests will still be run. + bool RunAllTests(); + + // Clears the results of all tests, except the ad hoc tests. + void ClearNonAdHocTestResult() { + ForEach(test_cases_, TestCase::ClearTestCaseResult); + } + + // Clears the results of ad-hoc test assertions. + void ClearAdHocTestResult() { + ad_hoc_test_result_.Clear(); + } + + enum ReactionToSharding { + HONOR_SHARDING_PROTOCOL, + IGNORE_SHARDING_PROTOCOL + }; + + // Matches the full name of each test against the user-specified + // filter to decide whether the test should run, then records the + // result in each TestCase and TestInfo object. + // If shard_tests == HONOR_SHARDING_PROTOCOL, further filters tests + // based on sharding variables in the environment. + // Returns the number of tests that should run. + int FilterTests(ReactionToSharding shard_tests); + + // Prints the names of the tests matching the user-specified filter flag. + void ListTestsMatchingFilter(); + + const TestCase* current_test_case() const { return current_test_case_; } + TestInfo* current_test_info() { return current_test_info_; } + const TestInfo* current_test_info() const { return current_test_info_; } + + // Returns the vector of environments that need to be set-up/torn-down + // before/after the tests are run. + std::vector& environments() { return environments_; } + + // Getters for the per-thread Google Test trace stack. + std::vector& gtest_trace_stack() { + return *(gtest_trace_stack_.pointer()); + } + const std::vector& gtest_trace_stack() const { + return gtest_trace_stack_.get(); + } + +#if GTEST_HAS_DEATH_TEST + void InitDeathTestSubprocessControlInfo() { + internal_run_death_test_flag_.reset(ParseInternalRunDeathTestFlag()); + } + // Returns a pointer to the parsed --gtest_internal_run_death_test + // flag, or NULL if that flag was not specified. + // This information is useful only in a death test child process. + // Must not be called before a call to InitGoogleTest. + const InternalRunDeathTestFlag* internal_run_death_test_flag() const { + return internal_run_death_test_flag_.get(); + } + + // Returns a pointer to the current death test factory. + internal::DeathTestFactory* death_test_factory() { + return death_test_factory_.get(); + } + + void SuppressTestEventsIfInSubprocess(); + + friend class ReplaceDeathTestFactory; +#endif // GTEST_HAS_DEATH_TEST + + // Initializes the event listener performing XML output as specified by + // UnitTestOptions. Must not be called before InitGoogleTest. + void ConfigureXmlOutput(); + +#if GTEST_CAN_STREAM_RESULTS_ + // Initializes the event listener for streaming test results to a socket. + // Must not be called before InitGoogleTest. + void ConfigureStreamingOutput(); +#endif + + // Performs initialization dependent upon flag values obtained in + // ParseGoogleTestFlagsOnly. Is called from InitGoogleTest after the call to + // ParseGoogleTestFlagsOnly. In case a user neglects to call InitGoogleTest + // this function is also called from RunAllTests. Since this function can be + // called more than once, it has to be idempotent. + void PostFlagParsingInit(); + + // Gets the random seed used at the start of the current test iteration. + int random_seed() const { return random_seed_; } + + // Gets the random number generator. + internal::Random* random() { return &random_; } + + // Shuffles all test cases, and the tests within each test case, + // making sure that death tests are still run first. + void ShuffleTests(); + + // Restores the test cases and tests to their order before the first shuffle. + void UnshuffleTests(); + + // Returns the value of GTEST_FLAG(catch_exceptions) at the moment + // UnitTest::Run() starts. + bool catch_exceptions() const { return catch_exceptions_; } + + private: + friend class ::testing::UnitTest; + + // Used by UnitTest::Run() to capture the state of + // GTEST_FLAG(catch_exceptions) at the moment it starts. + void set_catch_exceptions(bool value) { catch_exceptions_ = value; } + + // The UnitTest object that owns this implementation object. + UnitTest* const parent_; + + // The working directory when the first TEST() or TEST_F() was + // executed. + internal::FilePath original_working_dir_; + + // The default test part result reporters. + DefaultGlobalTestPartResultReporter default_global_test_part_result_reporter_; + DefaultPerThreadTestPartResultReporter + default_per_thread_test_part_result_reporter_; + + // Points to (but doesn't own) the global test part result reporter. + TestPartResultReporterInterface* global_test_part_result_repoter_; + + // Protects read and write access to global_test_part_result_reporter_. + internal::Mutex global_test_part_result_reporter_mutex_; + + // Points to (but doesn't own) the per-thread test part result reporter. + internal::ThreadLocal + per_thread_test_part_result_reporter_; + + // The vector of environments that need to be set-up/torn-down + // before/after the tests are run. + std::vector environments_; + + // The vector of TestCases in their original order. It owns the + // elements in the vector. + std::vector test_cases_; + + // Provides a level of indirection for the test case list to allow + // easy shuffling and restoring the test case order. The i-th + // element of this vector is the index of the i-th test case in the + // shuffled order. + std::vector test_case_indices_; + +#if GTEST_HAS_PARAM_TEST + // ParameterizedTestRegistry object used to register value-parameterized + // tests. + internal::ParameterizedTestCaseRegistry parameterized_test_registry_; + + // Indicates whether RegisterParameterizedTests() has been called already. + bool parameterized_tests_registered_; +#endif // GTEST_HAS_PARAM_TEST + + // Index of the last death test case registered. Initially -1. + int last_death_test_case_; + + // This points to the TestCase for the currently running test. It + // changes as Google Test goes through one test case after another. + // When no test is running, this is set to NULL and Google Test + // stores assertion results in ad_hoc_test_result_. Initially NULL. + TestCase* current_test_case_; + + // This points to the TestInfo for the currently running test. It + // changes as Google Test goes through one test after another. When + // no test is running, this is set to NULL and Google Test stores + // assertion results in ad_hoc_test_result_. Initially NULL. + TestInfo* current_test_info_; + + // Normally, a user only writes assertions inside a TEST or TEST_F, + // or inside a function called by a TEST or TEST_F. Since Google + // Test keeps track of which test is current running, it can + // associate such an assertion with the test it belongs to. + // + // If an assertion is encountered when no TEST or TEST_F is running, + // Google Test attributes the assertion result to an imaginary "ad hoc" + // test, and records the result in ad_hoc_test_result_. + TestResult ad_hoc_test_result_; + + // The list of event listeners that can be used to track events inside + // Google Test. + TestEventListeners listeners_; + + // The OS stack trace getter. Will be deleted when the UnitTest + // object is destructed. By default, an OsStackTraceGetter is used, + // but the user can set this field to use a custom getter if that is + // desired. + OsStackTraceGetterInterface* os_stack_trace_getter_; + + // True iff PostFlagParsingInit() has been called. + bool post_flag_parse_init_performed_; + + // The random number seed used at the beginning of the test run. + int random_seed_; + + // Our random number generator. + internal::Random random_; + + // How long the test took to run, in milliseconds. + TimeInMillis elapsed_time_; + +#if GTEST_HAS_DEATH_TEST + // The decomposed components of the gtest_internal_run_death_test flag, + // parsed when RUN_ALL_TESTS is called. + internal::scoped_ptr internal_run_death_test_flag_; + internal::scoped_ptr death_test_factory_; +#endif // GTEST_HAS_DEATH_TEST + + // A per-thread stack of traces created by the SCOPED_TRACE() macro. + internal::ThreadLocal > gtest_trace_stack_; + + // The value of GTEST_FLAG(catch_exceptions) at the moment RunAllTests() + // starts. + bool catch_exceptions_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(UnitTestImpl); +}; // class UnitTestImpl + +// Convenience function for accessing the global UnitTest +// implementation object. +inline UnitTestImpl* GetUnitTestImpl() { + return UnitTest::GetInstance()->impl(); +} + +#if GTEST_USES_SIMPLE_RE + +// Internal helper functions for implementing the simple regular +// expression matcher. +GTEST_API_ bool IsInSet(char ch, const char* str); +GTEST_API_ bool IsAsciiDigit(char ch); +GTEST_API_ bool IsAsciiPunct(char ch); +GTEST_API_ bool IsRepeat(char ch); +GTEST_API_ bool IsAsciiWhiteSpace(char ch); +GTEST_API_ bool IsAsciiWordChar(char ch); +GTEST_API_ bool IsValidEscape(char ch); +GTEST_API_ bool AtomMatchesChar(bool escaped, char pattern, char ch); +GTEST_API_ bool ValidateRegex(const char* regex); +GTEST_API_ bool MatchRegexAtHead(const char* regex, const char* str); +GTEST_API_ bool MatchRepetitionAndRegexAtHead( + bool escaped, char ch, char repeat, const char* regex, const char* str); +GTEST_API_ bool MatchRegexAnywhere(const char* regex, const char* str); + +#endif // GTEST_USES_SIMPLE_RE + +// Parses the command line for Google Test flags, without initializing +// other parts of Google Test. +GTEST_API_ void ParseGoogleTestFlagsOnly(int* argc, char** argv); +GTEST_API_ void ParseGoogleTestFlagsOnly(int* argc, wchar_t** argv); + +#if GTEST_HAS_DEATH_TEST + +// Returns the message describing the last system error, regardless of the +// platform. +GTEST_API_ String GetLastErrnoDescription(); + +# if GTEST_OS_WINDOWS +// Provides leak-safe Windows kernel handle ownership. +class AutoHandle { + public: + AutoHandle() : handle_(INVALID_HANDLE_VALUE) {} + explicit AutoHandle(HANDLE handle) : handle_(handle) {} + + ~AutoHandle() { Reset(); } + + HANDLE Get() const { return handle_; } + void Reset() { Reset(INVALID_HANDLE_VALUE); } + void Reset(HANDLE handle) { + if (handle != handle_) { + if (handle_ != INVALID_HANDLE_VALUE) + ::CloseHandle(handle_); + handle_ = handle; + } + } + + private: + HANDLE handle_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(AutoHandle); +}; +# endif // GTEST_OS_WINDOWS + +// Attempts to parse a string into a positive integer pointed to by the +// number parameter. Returns true if that is possible. +// GTEST_HAS_DEATH_TEST implies that we have ::std::string, so we can use +// it here. +template +bool ParseNaturalNumber(const ::std::string& str, Integer* number) { + // Fail fast if the given string does not begin with a digit; + // this bypasses strtoXXX's "optional leading whitespace and plus + // or minus sign" semantics, which are undesirable here. + if (str.empty() || !IsDigit(str[0])) { + return false; + } + errno = 0; + + char* end; + // BiggestConvertible is the largest integer type that system-provided + // string-to-number conversion routines can return. + +# if GTEST_OS_WINDOWS && !defined(__GNUC__) + + // MSVC and C++ Builder define __int64 instead of the standard long long. + typedef unsigned __int64 BiggestConvertible; + const BiggestConvertible parsed = _strtoui64(str.c_str(), &end, 10); + +# else + + typedef unsigned long long BiggestConvertible; // NOLINT + const BiggestConvertible parsed = strtoull(str.c_str(), &end, 10); + +# endif // GTEST_OS_WINDOWS && !defined(__GNUC__) + + const bool parse_success = *end == '\0' && errno == 0; + + // TODO(vladl@google.com): Convert this to compile time assertion when it is + // available. + GTEST_CHECK_(sizeof(Integer) <= sizeof(parsed)); + + const Integer result = static_cast(parsed); + if (parse_success && static_cast(result) == parsed) { + *number = result; + return true; + } + return false; +} +#endif // GTEST_HAS_DEATH_TEST + +// TestResult contains some private methods that should be hidden from +// Google Test user but are required for testing. This class allow our tests +// to access them. +// +// This class is supplied only for the purpose of testing Google Test's own +// constructs. Do not use it in user tests, either directly or indirectly. +class TestResultAccessor { + public: + static void RecordProperty(TestResult* test_result, + const TestProperty& property) { + test_result->RecordProperty(property); + } + + static void ClearTestPartResults(TestResult* test_result) { + test_result->ClearTestPartResults(); + } + + static const std::vector& test_part_results( + const TestResult& test_result) { + return test_result.test_part_results(); + } +}; + +} // namespace internal +} // namespace testing + +#endif // GTEST_SRC_GTEST_INTERNAL_INL_H_ +#undef GTEST_IMPLEMENTATION_ + +#if GTEST_OS_WINDOWS +# define vsnprintf _vsnprintf +#endif // GTEST_OS_WINDOWS + +namespace testing { + +using internal::CountIf; +using internal::ForEach; +using internal::GetElementOr; +using internal::Shuffle; + +// Constants. + +// A test whose test case name or test name matches this filter is +// disabled and not run. +static const char kDisableTestFilter[] = "DISABLED_*:*/DISABLED_*"; + +// A test case whose name matches this filter is considered a death +// test case and will be run before test cases whose name doesn't +// match this filter. +static const char kDeathTestCaseFilter[] = "*DeathTest:*DeathTest/*"; + +// A test filter that matches everything. +static const char kUniversalFilter[] = "*"; + +// The default output file for XML output. +static const char kDefaultOutputFile[] = "test_detail.xml"; + +// The environment variable name for the test shard index. +static const char kTestShardIndex[] = "GTEST_SHARD_INDEX"; +// The environment variable name for the total number of test shards. +static const char kTestTotalShards[] = "GTEST_TOTAL_SHARDS"; +// The environment variable name for the test shard status file. +static const char kTestShardStatusFile[] = "GTEST_SHARD_STATUS_FILE"; + +namespace internal { + +// The text used in failure messages to indicate the start of the +// stack trace. +const char kStackTraceMarker[] = "\nStack trace:\n"; + +// g_help_flag is true iff the --help flag or an equivalent form is +// specified on the command line. +bool g_help_flag = false; + +} // namespace internal + +GTEST_DEFINE_bool_( + also_run_disabled_tests, + internal::BoolFromGTestEnv("also_run_disabled_tests", false), + "Run disabled tests too, in addition to the tests normally being run."); + +GTEST_DEFINE_bool_( + break_on_failure, + internal::BoolFromGTestEnv("break_on_failure", false), + "True iff a failed assertion should be a debugger break-point."); + +GTEST_DEFINE_bool_( + catch_exceptions, + internal::BoolFromGTestEnv("catch_exceptions", true), + "True iff " GTEST_NAME_ + " should catch exceptions and treat them as test failures."); + +GTEST_DEFINE_string_( + color, + internal::StringFromGTestEnv("color", "auto"), + "Whether to use colors in the output. Valid values: yes, no, " + "and auto. 'auto' means to use colors if the output is " + "being sent to a terminal and the TERM environment variable " + "is set to xterm, xterm-color, xterm-256color, linux or cygwin."); + +GTEST_DEFINE_string_( + filter, + internal::StringFromGTestEnv("filter", kUniversalFilter), + "A colon-separated list of glob (not regex) patterns " + "for filtering the tests to run, optionally followed by a " + "'-' and a : separated list of negative patterns (tests to " + "exclude). A test is run if it matches one of the positive " + "patterns and does not match any of the negative patterns."); + +GTEST_DEFINE_bool_(list_tests, false, + "List all tests without running them."); + +GTEST_DEFINE_string_( + output, + internal::StringFromGTestEnv("output", ""), + "A format (currently must be \"xml\"), optionally followed " + "by a colon and an output file name or directory. A directory " + "is indicated by a trailing pathname separator. " + "Examples: \"xml:filename.xml\", \"xml::directoryname/\". " + "If a directory is specified, output files will be created " + "within that directory, with file-names based on the test " + "executable's name and, if necessary, made unique by adding " + "digits."); + +GTEST_DEFINE_bool_( + print_time, + internal::BoolFromGTestEnv("print_time", true), + "True iff " GTEST_NAME_ + " should display elapsed time in text output."); + +GTEST_DEFINE_int32_( + random_seed, + internal::Int32FromGTestEnv("random_seed", 0), + "Random number seed to use when shuffling test orders. Must be in range " + "[1, 99999], or 0 to use a seed based on the current time."); + +GTEST_DEFINE_int32_( + repeat, + internal::Int32FromGTestEnv("repeat", 1), + "How many times to repeat each test. Specify a negative number " + "for repeating forever. Useful for shaking out flaky tests."); + +GTEST_DEFINE_bool_( + show_internal_stack_frames, false, + "True iff " GTEST_NAME_ " should include internal stack frames when " + "printing test failure stack traces."); + +GTEST_DEFINE_bool_( + shuffle, + internal::BoolFromGTestEnv("shuffle", false), + "True iff " GTEST_NAME_ + " should randomize tests' order on every run."); + +GTEST_DEFINE_int32_( + stack_trace_depth, + internal::Int32FromGTestEnv("stack_trace_depth", kMaxStackTraceDepth), + "The maximum number of stack frames to print when an " + "assertion fails. The valid range is 0 through 100, inclusive."); + +GTEST_DEFINE_string_( + stream_result_to, + internal::StringFromGTestEnv("stream_result_to", ""), + "This flag specifies the host name and the port number on which to stream " + "test results. Example: \"localhost:555\". The flag is effective only on " + "Linux."); + +GTEST_DEFINE_bool_( + throw_on_failure, + internal::BoolFromGTestEnv("throw_on_failure", false), + "When this flag is specified, a failed assertion will throw an exception " + "if exceptions are enabled or exit the program with a non-zero code " + "otherwise."); + +namespace internal { + +// Generates a random number from [0, range), using a Linear +// Congruential Generator (LCG). Crashes if 'range' is 0 or greater +// than kMaxRange. +UInt32 Random::Generate(UInt32 range) { + // These constants are the same as are used in glibc's rand(3). + state_ = (1103515245U*state_ + 12345U) % kMaxRange; + + GTEST_CHECK_(range > 0) + << "Cannot generate a number in the range [0, 0)."; + GTEST_CHECK_(range <= kMaxRange) + << "Generation of a number in [0, " << range << ") was requested, " + << "but this can only generate numbers in [0, " << kMaxRange << ")."; + + // Converting via modulus introduces a bit of downward bias, but + // it's simple, and a linear congruential generator isn't too good + // to begin with. + return state_ % range; +} + +// GTestIsInitialized() returns true iff the user has initialized +// Google Test. Useful for catching the user mistake of not initializing +// Google Test before calling RUN_ALL_TESTS(). +// +// A user must call testing::InitGoogleTest() to initialize Google +// Test. g_init_gtest_count is set to the number of times +// InitGoogleTest() has been called. We don't protect this variable +// under a mutex as it is only accessed in the main thread. +int g_init_gtest_count = 0; +static bool GTestIsInitialized() { return g_init_gtest_count != 0; } + +// Iterates over a vector of TestCases, keeping a running sum of the +// results of calling a given int-returning method on each. +// Returns the sum. +static int SumOverTestCaseList(const std::vector& case_list, + int (TestCase::*method)() const) { + int sum = 0; + for (size_t i = 0; i < case_list.size(); i++) { + sum += (case_list[i]->*method)(); + } + return sum; +} + +// Returns true iff the test case passed. +static bool TestCasePassed(const TestCase* test_case) { + return test_case->should_run() && test_case->Passed(); +} + +// Returns true iff the test case failed. +static bool TestCaseFailed(const TestCase* test_case) { + return test_case->should_run() && test_case->Failed(); +} + +// Returns true iff test_case contains at least one test that should +// run. +static bool ShouldRunTestCase(const TestCase* test_case) { + return test_case->should_run(); +} + +// AssertHelper constructor. +AssertHelper::AssertHelper(TestPartResult::Type type, + const char* file, + int line, + const char* message) + : data_(new AssertHelperData(type, file, line, message)) { +} + +AssertHelper::~AssertHelper() { + delete data_; +} + +// Message assignment, for assertion streaming support. +void AssertHelper::operator=(const Message& message) const { + UnitTest::GetInstance()-> + AddTestPartResult(data_->type, data_->file, data_->line, + AppendUserMessage(data_->message, message), + UnitTest::GetInstance()->impl() + ->CurrentOsStackTraceExceptTop(1) + // Skips the stack frame for this function itself. + ); // NOLINT +} + +// Mutex for linked pointers. +GTEST_DEFINE_STATIC_MUTEX_(g_linked_ptr_mutex); + +// Application pathname gotten in InitGoogleTest. +String g_executable_path; + +// Returns the current application's name, removing directory path if that +// is present. +FilePath GetCurrentExecutableName() { + FilePath result; + +#if GTEST_OS_WINDOWS + result.Set(FilePath(g_executable_path).RemoveExtension("exe")); +#else + result.Set(FilePath(g_executable_path)); +#endif // GTEST_OS_WINDOWS + + return result.RemoveDirectoryName(); +} + +// Functions for processing the gtest_output flag. + +// Returns the output format, or "" for normal printed output. +String UnitTestOptions::GetOutputFormat() { + const char* const gtest_output_flag = GTEST_FLAG(output).c_str(); + if (gtest_output_flag == NULL) return String(""); + + const char* const colon = strchr(gtest_output_flag, ':'); + return (colon == NULL) ? + String(gtest_output_flag) : + String(gtest_output_flag, colon - gtest_output_flag); +} + +// Returns the name of the requested output file, or the default if none +// was explicitly specified. +String UnitTestOptions::GetAbsolutePathToOutputFile() { + const char* const gtest_output_flag = GTEST_FLAG(output).c_str(); + if (gtest_output_flag == NULL) + return String(""); + + const char* const colon = strchr(gtest_output_flag, ':'); + if (colon == NULL) + return String(internal::FilePath::ConcatPaths( + internal::FilePath( + UnitTest::GetInstance()->original_working_dir()), + internal::FilePath(kDefaultOutputFile)).ToString() ); + + internal::FilePath output_name(colon + 1); + if (!output_name.IsAbsolutePath()) + // TODO(wan@google.com): on Windows \some\path is not an absolute + // path (as its meaning depends on the current drive), yet the + // following logic for turning it into an absolute path is wrong. + // Fix it. + output_name = internal::FilePath::ConcatPaths( + internal::FilePath(UnitTest::GetInstance()->original_working_dir()), + internal::FilePath(colon + 1)); + + if (!output_name.IsDirectory()) + return output_name.ToString(); + + internal::FilePath result(internal::FilePath::GenerateUniqueFileName( + output_name, internal::GetCurrentExecutableName(), + GetOutputFormat().c_str())); + return result.ToString(); +} + +// Returns true iff the wildcard pattern matches the string. The +// first ':' or '\0' character in pattern marks the end of it. +// +// This recursive algorithm isn't very efficient, but is clear and +// works well enough for matching test names, which are short. +bool UnitTestOptions::PatternMatchesString(const char *pattern, + const char *str) { + switch (*pattern) { + case '\0': + case ':': // Either ':' or '\0' marks the end of the pattern. + return *str == '\0'; + case '?': // Matches any single character. + return *str != '\0' && PatternMatchesString(pattern + 1, str + 1); + case '*': // Matches any string (possibly empty) of characters. + return (*str != '\0' && PatternMatchesString(pattern, str + 1)) || + PatternMatchesString(pattern + 1, str); + default: // Non-special character. Matches itself. + return *pattern == *str && + PatternMatchesString(pattern + 1, str + 1); + } +} + +bool UnitTestOptions::MatchesFilter(const String& name, const char* filter) { + const char *cur_pattern = filter; + for (;;) { + if (PatternMatchesString(cur_pattern, name.c_str())) { + return true; + } + + // Finds the next pattern in the filter. + cur_pattern = strchr(cur_pattern, ':'); + + // Returns if no more pattern can be found. + if (cur_pattern == NULL) { + return false; + } + + // Skips the pattern separater (the ':' character). + cur_pattern++; + } +} + +// TODO(keithray): move String function implementations to gtest-string.cc. + +// Returns true iff the user-specified filter matches the test case +// name and the test name. +bool UnitTestOptions::FilterMatchesTest(const String &test_case_name, + const String &test_name) { + const String& full_name = String::Format("%s.%s", + test_case_name.c_str(), + test_name.c_str()); + + // Split --gtest_filter at '-', if there is one, to separate into + // positive filter and negative filter portions + const char* const p = GTEST_FLAG(filter).c_str(); + const char* const dash = strchr(p, '-'); + String positive; + String negative; + if (dash == NULL) { + positive = GTEST_FLAG(filter).c_str(); // Whole string is a positive filter + negative = String(""); + } else { + positive = String(p, dash - p); // Everything up to the dash + negative = String(dash+1); // Everything after the dash + if (positive.empty()) { + // Treat '-test1' as the same as '*-test1' + positive = kUniversalFilter; + } + } + + // A filter is a colon-separated list of patterns. It matches a + // test if any pattern in it matches the test. + return (MatchesFilter(full_name, positive.c_str()) && + !MatchesFilter(full_name, negative.c_str())); +} + +#if GTEST_HAS_SEH +// Returns EXCEPTION_EXECUTE_HANDLER if Google Test should handle the +// given SEH exception, or EXCEPTION_CONTINUE_SEARCH otherwise. +// This function is useful as an __except condition. +int UnitTestOptions::GTestShouldProcessSEH(DWORD exception_code) { + // Google Test should handle a SEH exception if: + // 1. the user wants it to, AND + // 2. this is not a breakpoint exception, AND + // 3. this is not a C++ exception (VC++ implements them via SEH, + // apparently). + // + // SEH exception code for C++ exceptions. + // (see http://support.microsoft.com/kb/185294 for more information). + const DWORD kCxxExceptionCode = 0xe06d7363; + + bool should_handle = true; + + if (!GTEST_FLAG(catch_exceptions)) + should_handle = false; + else if (exception_code == EXCEPTION_BREAKPOINT) + should_handle = false; + else if (exception_code == kCxxExceptionCode) + should_handle = false; + + return should_handle ? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH; +} +#endif // GTEST_HAS_SEH + +} // namespace internal + +// The c'tor sets this object as the test part result reporter used by +// Google Test. The 'result' parameter specifies where to report the +// results. Intercepts only failures from the current thread. +ScopedFakeTestPartResultReporter::ScopedFakeTestPartResultReporter( + TestPartResultArray* result) + : intercept_mode_(INTERCEPT_ONLY_CURRENT_THREAD), + result_(result) { + Init(); +} + +// The c'tor sets this object as the test part result reporter used by +// Google Test. The 'result' parameter specifies where to report the +// results. +ScopedFakeTestPartResultReporter::ScopedFakeTestPartResultReporter( + InterceptMode intercept_mode, TestPartResultArray* result) + : intercept_mode_(intercept_mode), + result_(result) { + Init(); +} + +void ScopedFakeTestPartResultReporter::Init() { + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + if (intercept_mode_ == INTERCEPT_ALL_THREADS) { + old_reporter_ = impl->GetGlobalTestPartResultReporter(); + impl->SetGlobalTestPartResultReporter(this); + } else { + old_reporter_ = impl->GetTestPartResultReporterForCurrentThread(); + impl->SetTestPartResultReporterForCurrentThread(this); + } +} + +// The d'tor restores the test part result reporter used by Google Test +// before. +ScopedFakeTestPartResultReporter::~ScopedFakeTestPartResultReporter() { + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + if (intercept_mode_ == INTERCEPT_ALL_THREADS) { + impl->SetGlobalTestPartResultReporter(old_reporter_); + } else { + impl->SetTestPartResultReporterForCurrentThread(old_reporter_); + } +} + +// Increments the test part result count and remembers the result. +// This method is from the TestPartResultReporterInterface interface. +void ScopedFakeTestPartResultReporter::ReportTestPartResult( + const TestPartResult& result) { + result_->Append(result); +} + +namespace internal { + +// Returns the type ID of ::testing::Test. We should always call this +// instead of GetTypeId< ::testing::Test>() to get the type ID of +// testing::Test. This is to work around a suspected linker bug when +// using Google Test as a framework on Mac OS X. The bug causes +// GetTypeId< ::testing::Test>() to return different values depending +// on whether the call is from the Google Test framework itself or +// from user test code. GetTestTypeId() is guaranteed to always +// return the same value, as it always calls GetTypeId<>() from the +// gtest.cc, which is within the Google Test framework. +TypeId GetTestTypeId() { + return GetTypeId(); +} + +// The value of GetTestTypeId() as seen from within the Google Test +// library. This is solely for testing GetTestTypeId(). +extern const TypeId kTestTypeIdInGoogleTest = GetTestTypeId(); + +// This predicate-formatter checks that 'results' contains a test part +// failure of the given type and that the failure message contains the +// given substring. +AssertionResult HasOneFailure(const char* /* results_expr */, + const char* /* type_expr */, + const char* /* substr_expr */, + const TestPartResultArray& results, + TestPartResult::Type type, + const string& substr) { + const String expected(type == TestPartResult::kFatalFailure ? + "1 fatal failure" : + "1 non-fatal failure"); + Message msg; + if (results.size() != 1) { + msg << "Expected: " << expected << "\n" + << " Actual: " << results.size() << " failures"; + for (int i = 0; i < results.size(); i++) { + msg << "\n" << results.GetTestPartResult(i); + } + return AssertionFailure() << msg; + } + + const TestPartResult& r = results.GetTestPartResult(0); + if (r.type() != type) { + return AssertionFailure() << "Expected: " << expected << "\n" + << " Actual:\n" + << r; + } + + if (strstr(r.message(), substr.c_str()) == NULL) { + return AssertionFailure() << "Expected: " << expected << " containing \"" + << substr << "\"\n" + << " Actual:\n" + << r; + } + + return AssertionSuccess(); +} + +// The constructor of SingleFailureChecker remembers where to look up +// test part results, what type of failure we expect, and what +// substring the failure message should contain. +SingleFailureChecker:: SingleFailureChecker( + const TestPartResultArray* results, + TestPartResult::Type type, + const string& substr) + : results_(results), + type_(type), + substr_(substr) {} + +// The destructor of SingleFailureChecker verifies that the given +// TestPartResultArray contains exactly one failure that has the given +// type and contains the given substring. If that's not the case, a +// non-fatal failure will be generated. +SingleFailureChecker::~SingleFailureChecker() { + EXPECT_PRED_FORMAT3(HasOneFailure, *results_, type_, substr_); +} + +DefaultGlobalTestPartResultReporter::DefaultGlobalTestPartResultReporter( + UnitTestImpl* unit_test) : unit_test_(unit_test) {} + +void DefaultGlobalTestPartResultReporter::ReportTestPartResult( + const TestPartResult& result) { + unit_test_->current_test_result()->AddTestPartResult(result); + unit_test_->listeners()->repeater()->OnTestPartResult(result); +} + +DefaultPerThreadTestPartResultReporter::DefaultPerThreadTestPartResultReporter( + UnitTestImpl* unit_test) : unit_test_(unit_test) {} + +void DefaultPerThreadTestPartResultReporter::ReportTestPartResult( + const TestPartResult& result) { + unit_test_->GetGlobalTestPartResultReporter()->ReportTestPartResult(result); +} + +// Returns the global test part result reporter. +TestPartResultReporterInterface* +UnitTestImpl::GetGlobalTestPartResultReporter() { + internal::MutexLock lock(&global_test_part_result_reporter_mutex_); + return global_test_part_result_repoter_; +} + +// Sets the global test part result reporter. +void UnitTestImpl::SetGlobalTestPartResultReporter( + TestPartResultReporterInterface* reporter) { + internal::MutexLock lock(&global_test_part_result_reporter_mutex_); + global_test_part_result_repoter_ = reporter; +} + +// Returns the test part result reporter for the current thread. +TestPartResultReporterInterface* +UnitTestImpl::GetTestPartResultReporterForCurrentThread() { + return per_thread_test_part_result_reporter_.get(); +} + +// Sets the test part result reporter for the current thread. +void UnitTestImpl::SetTestPartResultReporterForCurrentThread( + TestPartResultReporterInterface* reporter) { + per_thread_test_part_result_reporter_.set(reporter); +} + +// Gets the number of successful test cases. +int UnitTestImpl::successful_test_case_count() const { + return CountIf(test_cases_, TestCasePassed); +} + +// Gets the number of failed test cases. +int UnitTestImpl::failed_test_case_count() const { + return CountIf(test_cases_, TestCaseFailed); +} + +// Gets the number of all test cases. +int UnitTestImpl::total_test_case_count() const { + return static_cast(test_cases_.size()); +} + +// Gets the number of all test cases that contain at least one test +// that should run. +int UnitTestImpl::test_case_to_run_count() const { + return CountIf(test_cases_, ShouldRunTestCase); +} + +// Gets the number of successful tests. +int UnitTestImpl::successful_test_count() const { + return SumOverTestCaseList(test_cases_, &TestCase::successful_test_count); +} + +// Gets the number of failed tests. +int UnitTestImpl::failed_test_count() const { + return SumOverTestCaseList(test_cases_, &TestCase::failed_test_count); +} + +// Gets the number of disabled tests. +int UnitTestImpl::disabled_test_count() const { + return SumOverTestCaseList(test_cases_, &TestCase::disabled_test_count); +} + +// Gets the number of all tests. +int UnitTestImpl::total_test_count() const { + return SumOverTestCaseList(test_cases_, &TestCase::total_test_count); +} + +// Gets the number of tests that should run. +int UnitTestImpl::test_to_run_count() const { + return SumOverTestCaseList(test_cases_, &TestCase::test_to_run_count); +} + +// Returns the current OS stack trace as a String. +// +// The maximum number of stack frames to be included is specified by +// the gtest_stack_trace_depth flag. The skip_count parameter +// specifies the number of top frames to be skipped, which doesn't +// count against the number of frames to be included. +// +// For example, if Foo() calls Bar(), which in turn calls +// CurrentOsStackTraceExceptTop(1), Foo() will be included in the +// trace but Bar() and CurrentOsStackTraceExceptTop() won't. +String UnitTestImpl::CurrentOsStackTraceExceptTop(int skip_count) { + (void)skip_count; + return String(""); +} + +// Returns the current time in milliseconds. +TimeInMillis GetTimeInMillis() { +#if GTEST_OS_WINDOWS_MOBILE || defined(__BORLANDC__) + // Difference between 1970-01-01 and 1601-01-01 in milliseconds. + // http://analogous.blogspot.com/2005/04/epoch.html + const TimeInMillis kJavaEpochToWinFileTimeDelta = + static_cast(116444736UL) * 100000UL; + const DWORD kTenthMicrosInMilliSecond = 10000; + + SYSTEMTIME now_systime; + FILETIME now_filetime; + ULARGE_INTEGER now_int64; + // TODO(kenton@google.com): Shouldn't this just use + // GetSystemTimeAsFileTime()? + GetSystemTime(&now_systime); + if (SystemTimeToFileTime(&now_systime, &now_filetime)) { + now_int64.LowPart = now_filetime.dwLowDateTime; + now_int64.HighPart = now_filetime.dwHighDateTime; + now_int64.QuadPart = (now_int64.QuadPart / kTenthMicrosInMilliSecond) - + kJavaEpochToWinFileTimeDelta; + return now_int64.QuadPart; + } + return 0; +#elif GTEST_OS_WINDOWS && !GTEST_HAS_GETTIMEOFDAY_ + __timeb64 now; + +# ifdef _MSC_VER + + // MSVC 8 deprecates _ftime64(), so we want to suppress warning 4996 + // (deprecated function) there. + // TODO(kenton@google.com): Use GetTickCount()? Or use + // SystemTimeToFileTime() +# pragma warning(push) // Saves the current warning state. +# pragma warning(disable:4996) // Temporarily disables warning 4996. + _ftime64(&now); +# pragma warning(pop) // Restores the warning state. +# else + + _ftime64(&now); + +# endif // _MSC_VER + + return static_cast(now.time) * 1000 + now.millitm; +#elif GTEST_HAS_GETTIMEOFDAY_ + struct timeval now; + gettimeofday(&now, NULL); + return static_cast(now.tv_sec) * 1000 + now.tv_usec / 1000; +#else +# error "Don't know how to get the current time on your system." +#endif +} + +// Utilities + +// class String + +// Returns the input enclosed in double quotes if it's not NULL; +// otherwise returns "(null)". For example, "\"Hello\"" is returned +// for input "Hello". +// +// This is useful for printing a C string in the syntax of a literal. +// +// Known issue: escape sequences are not handled yet. +String String::ShowCStringQuoted(const char* c_str) { + return c_str ? String::Format("\"%s\"", c_str) : String("(null)"); +} + +// Copies at most length characters from str into a newly-allocated +// piece of memory of size length+1. The memory is allocated with new[]. +// A terminating null byte is written to the memory, and a pointer to it +// is returned. If str is NULL, NULL is returned. +static char* CloneString(const char* str, size_t length) { + if (str == NULL) { + return NULL; + } else { + char* const clone = new char[length + 1]; + posix::StrNCpy(clone, str, length); + clone[length] = '\0'; + return clone; + } +} + +// Clones a 0-terminated C string, allocating memory using new. The +// caller is responsible for deleting[] the return value. Returns the +// cloned string, or NULL if the input is NULL. +const char * String::CloneCString(const char* c_str) { + return (c_str == NULL) ? + NULL : CloneString(c_str, strlen(c_str)); +} + +#if GTEST_OS_WINDOWS_MOBILE +// Creates a UTF-16 wide string from the given ANSI string, allocating +// memory using new. The caller is responsible for deleting the return +// value using delete[]. Returns the wide string, or NULL if the +// input is NULL. +LPCWSTR String::AnsiToUtf16(const char* ansi) { + if (!ansi) return NULL; + const int length = strlen(ansi); + const int unicode_length = + MultiByteToWideChar(CP_ACP, 0, ansi, length, + NULL, 0); + WCHAR* unicode = new WCHAR[unicode_length + 1]; + MultiByteToWideChar(CP_ACP, 0, ansi, length, + unicode, unicode_length); + unicode[unicode_length] = 0; + return unicode; +} + +// Creates an ANSI string from the given wide string, allocating +// memory using new. The caller is responsible for deleting the return +// value using delete[]. Returns the ANSI string, or NULL if the +// input is NULL. +const char* String::Utf16ToAnsi(LPCWSTR utf16_str) { + if (!utf16_str) return NULL; + const int ansi_length = + WideCharToMultiByte(CP_ACP, 0, utf16_str, -1, + NULL, 0, NULL, NULL); + char* ansi = new char[ansi_length + 1]; + WideCharToMultiByte(CP_ACP, 0, utf16_str, -1, + ansi, ansi_length, NULL, NULL); + ansi[ansi_length] = 0; + return ansi; +} + +#endif // GTEST_OS_WINDOWS_MOBILE + +// Compares two C strings. Returns true iff they have the same content. +// +// Unlike strcmp(), this function can handle NULL argument(s). A NULL +// C string is considered different to any non-NULL C string, +// including the empty string. +bool String::CStringEquals(const char * lhs, const char * rhs) { + if ( lhs == NULL ) return rhs == NULL; + + if ( rhs == NULL ) return false; + + return strcmp(lhs, rhs) == 0; +} + +#if GTEST_HAS_STD_WSTRING || GTEST_HAS_GLOBAL_WSTRING + +// Converts an array of wide chars to a narrow string using the UTF-8 +// encoding, and streams the result to the given Message object. +static void StreamWideCharsToMessage(const wchar_t* wstr, size_t length, + Message* msg) { + // TODO(wan): consider allowing a testing::String object to + // contain '\0'. This will make it behave more like std::string, + // and will allow ToUtf8String() to return the correct encoding + // for '\0' s.t. we can get rid of the conditional here (and in + // several other places). + for (size_t i = 0; i != length; ) { // NOLINT + if (wstr[i] != L'\0') { + *msg << WideStringToUtf8(wstr + i, static_cast(length - i)); + while (i != length && wstr[i] != L'\0') + i++; + } else { + *msg << '\0'; + i++; + } + } +} + +#endif // GTEST_HAS_STD_WSTRING || GTEST_HAS_GLOBAL_WSTRING + +} // namespace internal + +#if GTEST_HAS_STD_WSTRING +// Converts the given wide string to a narrow string using the UTF-8 +// encoding, and streams the result to this Message object. +Message& Message::operator <<(const ::std::wstring& wstr) { + internal::StreamWideCharsToMessage(wstr.c_str(), wstr.length(), this); + return *this; +} +#endif // GTEST_HAS_STD_WSTRING + +#if GTEST_HAS_GLOBAL_WSTRING +// Converts the given wide string to a narrow string using the UTF-8 +// encoding, and streams the result to this Message object. +Message& Message::operator <<(const ::wstring& wstr) { + internal::StreamWideCharsToMessage(wstr.c_str(), wstr.length(), this); + return *this; +} +#endif // GTEST_HAS_GLOBAL_WSTRING + +// AssertionResult constructors. +// Used in EXPECT_TRUE/FALSE(assertion_result). +AssertionResult::AssertionResult(const AssertionResult& other) + : success_(other.success_), + message_(other.message_.get() != NULL ? + new ::std::string(*other.message_) : + static_cast< ::std::string*>(NULL)) { +} + +// Returns the assertion's negation. Used with EXPECT/ASSERT_FALSE. +AssertionResult AssertionResult::operator!() const { + AssertionResult negation(!success_); + if (message_.get() != NULL) + negation << *message_; + return negation; +} + +// Makes a successful assertion result. +AssertionResult AssertionSuccess() { + return AssertionResult(true); +} + +// Makes a failed assertion result. +AssertionResult AssertionFailure() { + return AssertionResult(false); +} + +// Makes a failed assertion result with the given failure message. +// Deprecated; use AssertionFailure() << message. +AssertionResult AssertionFailure(const Message& message) { + return AssertionFailure() << message; +} + +namespace internal { + +// Constructs and returns the message for an equality assertion +// (e.g. ASSERT_EQ, EXPECT_STREQ, etc) failure. +// +// The first four parameters are the expressions used in the assertion +// and their values, as strings. For example, for ASSERT_EQ(foo, bar) +// where foo is 5 and bar is 6, we have: +// +// expected_expression: "foo" +// actual_expression: "bar" +// expected_value: "5" +// actual_value: "6" +// +// The ignoring_case parameter is true iff the assertion is a +// *_STRCASEEQ*. When it's true, the string " (ignoring case)" will +// be inserted into the message. +AssertionResult EqFailure(const char* expected_expression, + const char* actual_expression, + const String& expected_value, + const String& actual_value, + bool ignoring_case) { + Message msg; + msg << "Value of: " << actual_expression; + if (actual_value != actual_expression) { + msg << "\n Actual: " << actual_value; + } + + msg << "\nExpected: " << expected_expression; + if (ignoring_case) { + msg << " (ignoring case)"; + } + if (expected_value != expected_expression) { + msg << "\nWhich is: " << expected_value; + } + + return AssertionFailure() << msg; +} + +// Constructs a failure message for Boolean assertions such as EXPECT_TRUE. +String GetBoolAssertionFailureMessage(const AssertionResult& assertion_result, + const char* expression_text, + const char* actual_predicate_value, + const char* expected_predicate_value) { + const char* actual_message = assertion_result.message(); + Message msg; + msg << "Value of: " << expression_text + << "\n Actual: " << actual_predicate_value; + if (actual_message[0] != '\0') + msg << " (" << actual_message << ")"; + msg << "\nExpected: " << expected_predicate_value; + return msg.GetString(); +} + +// Helper function for implementing ASSERT_NEAR. +AssertionResult DoubleNearPredFormat(const char* expr1, + const char* expr2, + const char* abs_error_expr, + double val1, + double val2, + double abs_error) { + const double diff = fabs(val1 - val2); + if (diff <= abs_error) return AssertionSuccess(); + + // TODO(wan): do not print the value of an expression if it's + // already a literal. + return AssertionFailure() + << "The difference between " << expr1 << " and " << expr2 + << " is " << diff << ", which exceeds " << abs_error_expr << ", where\n" + << expr1 << " evaluates to " << val1 << ",\n" + << expr2 << " evaluates to " << val2 << ", and\n" + << abs_error_expr << " evaluates to " << abs_error << "."; +} + + +// Helper template for implementing FloatLE() and DoubleLE(). +template +AssertionResult FloatingPointLE(const char* expr1, + const char* expr2, + RawType val1, + RawType val2) { + // Returns success if val1 is less than val2, + if (val1 < val2) { + return AssertionSuccess(); + } + + // or if val1 is almost equal to val2. + const FloatingPoint lhs(val1), rhs(val2); + if (lhs.AlmostEquals(rhs)) { + return AssertionSuccess(); + } + + // Note that the above two checks will both fail if either val1 or + // val2 is NaN, as the IEEE floating-point standard requires that + // any predicate involving a NaN must return false. + + ::std::stringstream val1_ss; + val1_ss << std::setprecision(std::numeric_limits::digits10 + 2) + << val1; + + ::std::stringstream val2_ss; + val2_ss << std::setprecision(std::numeric_limits::digits10 + 2) + << val2; + + return AssertionFailure() + << "Expected: (" << expr1 << ") <= (" << expr2 << ")\n" + << " Actual: " << StringStreamToString(&val1_ss) << " vs " + << StringStreamToString(&val2_ss); +} + +} // namespace internal + +// Asserts that val1 is less than, or almost equal to, val2. Fails +// otherwise. In particular, it fails if either val1 or val2 is NaN. +AssertionResult FloatLE(const char* expr1, const char* expr2, + float val1, float val2) { + return internal::FloatingPointLE(expr1, expr2, val1, val2); +} + +// Asserts that val1 is less than, or almost equal to, val2. Fails +// otherwise. In particular, it fails if either val1 or val2 is NaN. +AssertionResult DoubleLE(const char* expr1, const char* expr2, + double val1, double val2) { + return internal::FloatingPointLE(expr1, expr2, val1, val2); +} + +namespace internal { + +// The helper function for {ASSERT|EXPECT}_EQ with int or enum +// arguments. +AssertionResult CmpHelperEQ(const char* expected_expression, + const char* actual_expression, + BiggestInt expected, + BiggestInt actual) { + if (expected == actual) { + return AssertionSuccess(); + } + + return EqFailure(expected_expression, + actual_expression, + FormatForComparisonFailureMessage(expected, actual), + FormatForComparisonFailureMessage(actual, expected), + false); +} + +// A macro for implementing the helper functions needed to implement +// ASSERT_?? and EXPECT_?? with integer or enum arguments. It is here +// just to avoid copy-and-paste of similar code. +#define GTEST_IMPL_CMP_HELPER_(op_name, op)\ +AssertionResult CmpHelper##op_name(const char* expr1, const char* expr2, \ + BiggestInt val1, BiggestInt val2) {\ + if (val1 op val2) {\ + return AssertionSuccess();\ + } else {\ + return AssertionFailure() \ + << "Expected: (" << expr1 << ") " #op " (" << expr2\ + << "), actual: " << FormatForComparisonFailureMessage(val1, val2)\ + << " vs " << FormatForComparisonFailureMessage(val2, val1);\ + }\ +} + +// Implements the helper function for {ASSERT|EXPECT}_NE with int or +// enum arguments. +GTEST_IMPL_CMP_HELPER_(NE, !=) +// Implements the helper function for {ASSERT|EXPECT}_LE with int or +// enum arguments. +GTEST_IMPL_CMP_HELPER_(LE, <=) +// Implements the helper function for {ASSERT|EXPECT}_LT with int or +// enum arguments. +GTEST_IMPL_CMP_HELPER_(LT, < ) +// Implements the helper function for {ASSERT|EXPECT}_GE with int or +// enum arguments. +GTEST_IMPL_CMP_HELPER_(GE, >=) +// Implements the helper function for {ASSERT|EXPECT}_GT with int or +// enum arguments. +GTEST_IMPL_CMP_HELPER_(GT, > ) + +#undef GTEST_IMPL_CMP_HELPER_ + +// The helper function for {ASSERT|EXPECT}_STREQ. +AssertionResult CmpHelperSTREQ(const char* expected_expression, + const char* actual_expression, + const char* expected, + const char* actual) { + if (String::CStringEquals(expected, actual)) { + return AssertionSuccess(); + } + + return EqFailure(expected_expression, + actual_expression, + String::ShowCStringQuoted(expected), + String::ShowCStringQuoted(actual), + false); +} + +// The helper function for {ASSERT|EXPECT}_STRCASEEQ. +AssertionResult CmpHelperSTRCASEEQ(const char* expected_expression, + const char* actual_expression, + const char* expected, + const char* actual) { + if (String::CaseInsensitiveCStringEquals(expected, actual)) { + return AssertionSuccess(); + } + + return EqFailure(expected_expression, + actual_expression, + String::ShowCStringQuoted(expected), + String::ShowCStringQuoted(actual), + true); +} + +// The helper function for {ASSERT|EXPECT}_STRNE. +AssertionResult CmpHelperSTRNE(const char* s1_expression, + const char* s2_expression, + const char* s1, + const char* s2) { + if (!String::CStringEquals(s1, s2)) { + return AssertionSuccess(); + } else { + return AssertionFailure() << "Expected: (" << s1_expression << ") != (" + << s2_expression << "), actual: \"" + << s1 << "\" vs \"" << s2 << "\""; + } +} + +// The helper function for {ASSERT|EXPECT}_STRCASENE. +AssertionResult CmpHelperSTRCASENE(const char* s1_expression, + const char* s2_expression, + const char* s1, + const char* s2) { + if (!String::CaseInsensitiveCStringEquals(s1, s2)) { + return AssertionSuccess(); + } else { + return AssertionFailure() + << "Expected: (" << s1_expression << ") != (" + << s2_expression << ") (ignoring case), actual: \"" + << s1 << "\" vs \"" << s2 << "\""; + } +} + +} // namespace internal + +namespace { + +// Helper functions for implementing IsSubString() and IsNotSubstring(). + +// This group of overloaded functions return true iff needle is a +// substring of haystack. NULL is considered a substring of itself +// only. + +bool IsSubstringPred(const char* needle, const char* haystack) { + if (needle == NULL || haystack == NULL) + return needle == haystack; + + return strstr(haystack, needle) != NULL; +} + +bool IsSubstringPred(const wchar_t* needle, const wchar_t* haystack) { + if (needle == NULL || haystack == NULL) + return needle == haystack; + + return wcsstr(haystack, needle) != NULL; +} + +// StringType here can be either ::std::string or ::std::wstring. +template +bool IsSubstringPred(const StringType& needle, + const StringType& haystack) { + return haystack.find(needle) != StringType::npos; +} + +// This function implements either IsSubstring() or IsNotSubstring(), +// depending on the value of the expected_to_be_substring parameter. +// StringType here can be const char*, const wchar_t*, ::std::string, +// or ::std::wstring. +template +AssertionResult IsSubstringImpl( + bool expected_to_be_substring, + const char* needle_expr, const char* haystack_expr, + const StringType& needle, const StringType& haystack) { + if (IsSubstringPred(needle, haystack) == expected_to_be_substring) + return AssertionSuccess(); + + const bool is_wide_string = sizeof(needle[0]) > 1; + const char* const begin_string_quote = is_wide_string ? "L\"" : "\""; + return AssertionFailure() + << "Value of: " << needle_expr << "\n" + << " Actual: " << begin_string_quote << needle << "\"\n" + << "Expected: " << (expected_to_be_substring ? "" : "not ") + << "a substring of " << haystack_expr << "\n" + << "Which is: " << begin_string_quote << haystack << "\""; +} + +} // namespace + +// IsSubstring() and IsNotSubstring() check whether needle is a +// substring of haystack (NULL is considered a substring of itself +// only), and return an appropriate error message when they fail. + +AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const char* needle, const char* haystack) { + return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const wchar_t* needle, const wchar_t* haystack) { + return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const char* needle, const char* haystack) { + return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const wchar_t* needle, const wchar_t* haystack) { + return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::string& needle, const ::std::string& haystack) { + return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::string& needle, const ::std::string& haystack) { + return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); +} + +#if GTEST_HAS_STD_WSTRING +AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::wstring& needle, const ::std::wstring& haystack) { + return IsSubstringImpl(true, needle_expr, haystack_expr, needle, haystack); +} + +AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::wstring& needle, const ::std::wstring& haystack) { + return IsSubstringImpl(false, needle_expr, haystack_expr, needle, haystack); +} +#endif // GTEST_HAS_STD_WSTRING + +namespace internal { + +#if GTEST_OS_WINDOWS + +namespace { + +// Helper function for IsHRESULT{SuccessFailure} predicates +AssertionResult HRESULTFailureHelper(const char* expr, + const char* expected, + long hr) { // NOLINT +# if GTEST_OS_WINDOWS_MOBILE + + // Windows CE doesn't support FormatMessage. + const char error_text[] = ""; + +# else + + // Looks up the human-readable system message for the HRESULT code + // and since we're not passing any params to FormatMessage, we don't + // want inserts expanded. + const DWORD kFlags = FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS; + const DWORD kBufSize = 4096; // String::Format can't exceed this length. + // Gets the system's human readable message string for this HRESULT. + char error_text[kBufSize] = { '\0' }; + DWORD message_length = ::FormatMessageA(kFlags, + 0, // no source, we're asking system + hr, // the error + 0, // no line width restrictions + error_text, // output buffer + kBufSize, // buf size + NULL); // no arguments for inserts + // Trims tailing white space (FormatMessage leaves a trailing cr-lf) + for (; message_length && IsSpace(error_text[message_length - 1]); + --message_length) { + error_text[message_length - 1] = '\0'; + } + +# endif // GTEST_OS_WINDOWS_MOBILE + + const String error_hex(String::Format("0x%08X ", hr)); + return ::testing::AssertionFailure() + << "Expected: " << expr << " " << expected << ".\n" + << " Actual: " << error_hex << error_text << "\n"; +} + +} // namespace + +AssertionResult IsHRESULTSuccess(const char* expr, long hr) { // NOLINT + if (SUCCEEDED(hr)) { + return AssertionSuccess(); + } + return HRESULTFailureHelper(expr, "succeeds", hr); +} + +AssertionResult IsHRESULTFailure(const char* expr, long hr) { // NOLINT + if (FAILED(hr)) { + return AssertionSuccess(); + } + return HRESULTFailureHelper(expr, "fails", hr); +} + +#endif // GTEST_OS_WINDOWS + +// Utility functions for encoding Unicode text (wide strings) in +// UTF-8. + +// A Unicode code-point can have upto 21 bits, and is encoded in UTF-8 +// like this: +// +// Code-point length Encoding +// 0 - 7 bits 0xxxxxxx +// 8 - 11 bits 110xxxxx 10xxxxxx +// 12 - 16 bits 1110xxxx 10xxxxxx 10xxxxxx +// 17 - 21 bits 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + +// The maximum code-point a one-byte UTF-8 sequence can represent. +const UInt32 kMaxCodePoint1 = (static_cast(1) << 7) - 1; + +// The maximum code-point a two-byte UTF-8 sequence can represent. +const UInt32 kMaxCodePoint2 = (static_cast(1) << (5 + 6)) - 1; + +// The maximum code-point a three-byte UTF-8 sequence can represent. +const UInt32 kMaxCodePoint3 = (static_cast(1) << (4 + 2*6)) - 1; + +// The maximum code-point a four-byte UTF-8 sequence can represent. +const UInt32 kMaxCodePoint4 = (static_cast(1) << (3 + 3*6)) - 1; + +// Chops off the n lowest bits from a bit pattern. Returns the n +// lowest bits. As a side effect, the original bit pattern will be +// shifted to the right by n bits. +inline UInt32 ChopLowBits(UInt32* bits, int n) { + const UInt32 low_bits = *bits & ((static_cast(1) << n) - 1); + *bits >>= n; + return low_bits; +} + +// Converts a Unicode code point to a narrow string in UTF-8 encoding. +// code_point parameter is of type UInt32 because wchar_t may not be +// wide enough to contain a code point. +// The output buffer str must containt at least 32 characters. +// The function returns the address of the output buffer. +// If the code_point is not a valid Unicode code point +// (i.e. outside of Unicode range U+0 to U+10FFFF) it will be output +// as '(Invalid Unicode 0xXXXXXXXX)'. +char* CodePointToUtf8(UInt32 code_point, char* str) { + if (code_point <= kMaxCodePoint1) { + str[1] = '\0'; + str[0] = static_cast(code_point); // 0xxxxxxx + } else if (code_point <= kMaxCodePoint2) { + str[2] = '\0'; + str[1] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[0] = static_cast(0xC0 | code_point); // 110xxxxx + } else if (code_point <= kMaxCodePoint3) { + str[3] = '\0'; + str[2] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[1] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[0] = static_cast(0xE0 | code_point); // 1110xxxx + } else if (code_point <= kMaxCodePoint4) { + str[4] = '\0'; + str[3] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[2] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[1] = static_cast(0x80 | ChopLowBits(&code_point, 6)); // 10xxxxxx + str[0] = static_cast(0xF0 | code_point); // 11110xxx + } else { + // The longest string String::Format can produce when invoked + // with these parameters is 28 character long (not including + // the terminating nul character). We are asking for 32 character + // buffer just in case. This is also enough for strncpy to + // null-terminate the destination string. + posix::StrNCpy( + str, String::Format("(Invalid Unicode 0x%X)", code_point).c_str(), 32); + str[31] = '\0'; // Makes sure no change in the format to strncpy leaves + // the result unterminated. + } + return str; +} + +// The following two functions only make sense if the the system +// uses UTF-16 for wide string encoding. All supported systems +// with 16 bit wchar_t (Windows, Cygwin, Symbian OS) do use UTF-16. + +// Determines if the arguments constitute UTF-16 surrogate pair +// and thus should be combined into a single Unicode code point +// using CreateCodePointFromUtf16SurrogatePair. +inline bool IsUtf16SurrogatePair(wchar_t first, wchar_t second) { + return sizeof(wchar_t) == 2 && + (first & 0xFC00) == 0xD800 && (second & 0xFC00) == 0xDC00; +} + +// Creates a Unicode code point from UTF16 surrogate pair. +inline UInt32 CreateCodePointFromUtf16SurrogatePair(wchar_t first, + wchar_t second) { + const UInt32 mask = (1 << 10) - 1; + return (sizeof(wchar_t) == 2) ? + (((first & mask) << 10) | (second & mask)) + 0x10000 : + // This function should not be called when the condition is + // false, but we provide a sensible default in case it is. + static_cast(first); +} + +// Converts a wide string to a narrow string in UTF-8 encoding. +// The wide string is assumed to have the following encoding: +// UTF-16 if sizeof(wchar_t) == 2 (on Windows, Cygwin, Symbian OS) +// UTF-32 if sizeof(wchar_t) == 4 (on Linux) +// Parameter str points to a null-terminated wide string. +// Parameter num_chars may additionally limit the number +// of wchar_t characters processed. -1 is used when the entire string +// should be processed. +// If the string contains code points that are not valid Unicode code points +// (i.e. outside of Unicode range U+0 to U+10FFFF) they will be output +// as '(Invalid Unicode 0xXXXXXXXX)'. If the string is in UTF16 encoding +// and contains invalid UTF-16 surrogate pairs, values in those pairs +// will be encoded as individual Unicode characters from Basic Normal Plane. +String WideStringToUtf8(const wchar_t* str, int num_chars) { + if (num_chars == -1) + num_chars = static_cast(wcslen(str)); + + ::std::stringstream stream; + for (int i = 0; i < num_chars; ++i) { + UInt32 unicode_code_point; + + if (str[i] == L'\0') { + break; + } else if (i + 1 < num_chars && IsUtf16SurrogatePair(str[i], str[i + 1])) { + unicode_code_point = CreateCodePointFromUtf16SurrogatePair(str[i], + str[i + 1]); + i++; + } else { + unicode_code_point = static_cast(str[i]); + } + + char buffer[32]; // CodePointToUtf8 requires a buffer this big. + stream << CodePointToUtf8(unicode_code_point, buffer); + } + return StringStreamToString(&stream); +} + +// Converts a wide C string to a String using the UTF-8 encoding. +// NULL will be converted to "(null)". +String String::ShowWideCString(const wchar_t * wide_c_str) { + if (wide_c_str == NULL) return String("(null)"); + + return String(internal::WideStringToUtf8(wide_c_str, -1).c_str()); +} + +// Similar to ShowWideCString(), except that this function encloses +// the converted string in double quotes. +String String::ShowWideCStringQuoted(const wchar_t* wide_c_str) { + if (wide_c_str == NULL) return String("(null)"); + + return String::Format("L\"%s\"", + String::ShowWideCString(wide_c_str).c_str()); +} + +// Compares two wide C strings. Returns true iff they have the same +// content. +// +// Unlike wcscmp(), this function can handle NULL argument(s). A NULL +// C string is considered different to any non-NULL C string, +// including the empty string. +bool String::WideCStringEquals(const wchar_t * lhs, const wchar_t * rhs) { + if (lhs == NULL) return rhs == NULL; + + if (rhs == NULL) return false; + + return wcscmp(lhs, rhs) == 0; +} + +// Helper function for *_STREQ on wide strings. +AssertionResult CmpHelperSTREQ(const char* expected_expression, + const char* actual_expression, + const wchar_t* expected, + const wchar_t* actual) { + if (String::WideCStringEquals(expected, actual)) { + return AssertionSuccess(); + } + + return EqFailure(expected_expression, + actual_expression, + String::ShowWideCStringQuoted(expected), + String::ShowWideCStringQuoted(actual), + false); +} + +// Helper function for *_STRNE on wide strings. +AssertionResult CmpHelperSTRNE(const char* s1_expression, + const char* s2_expression, + const wchar_t* s1, + const wchar_t* s2) { + if (!String::WideCStringEquals(s1, s2)) { + return AssertionSuccess(); + } + + return AssertionFailure() << "Expected: (" << s1_expression << ") != (" + << s2_expression << "), actual: " + << String::ShowWideCStringQuoted(s1) + << " vs " << String::ShowWideCStringQuoted(s2); +} + +// Compares two C strings, ignoring case. Returns true iff they have +// the same content. +// +// Unlike strcasecmp(), this function can handle NULL argument(s). A +// NULL C string is considered different to any non-NULL C string, +// including the empty string. +bool String::CaseInsensitiveCStringEquals(const char * lhs, const char * rhs) { + if (lhs == NULL) + return rhs == NULL; + if (rhs == NULL) + return false; + return posix::StrCaseCmp(lhs, rhs) == 0; +} + + // Compares two wide C strings, ignoring case. Returns true iff they + // have the same content. + // + // Unlike wcscasecmp(), this function can handle NULL argument(s). + // A NULL C string is considered different to any non-NULL wide C string, + // including the empty string. + // NB: The implementations on different platforms slightly differ. + // On windows, this method uses _wcsicmp which compares according to LC_CTYPE + // environment variable. On GNU platform this method uses wcscasecmp + // which compares according to LC_CTYPE category of the current locale. + // On MacOS X, it uses towlower, which also uses LC_CTYPE category of the + // current locale. +bool String::CaseInsensitiveWideCStringEquals(const wchar_t* lhs, + const wchar_t* rhs) { + if (lhs == NULL) return rhs == NULL; + + if (rhs == NULL) return false; + +#if GTEST_OS_WINDOWS + return _wcsicmp(lhs, rhs) == 0; +#elif GTEST_OS_LINUX && !GTEST_OS_LINUX_ANDROID + return wcscasecmp(lhs, rhs) == 0; +#else + // Android, Mac OS X and Cygwin don't define wcscasecmp. + // Other unknown OSes may not define it either. + wint_t left, right; + do { + left = towlower(*lhs++); + right = towlower(*rhs++); + } while (left && left == right); + return left == right; +#endif // OS selector +} + +// Compares this with another String. +// Returns < 0 if this is less than rhs, 0 if this is equal to rhs, or > 0 +// if this is greater than rhs. +int String::Compare(const String & rhs) const { + const char* const lhs_c_str = c_str(); + const char* const rhs_c_str = rhs.c_str(); + + if (lhs_c_str == NULL) { + return rhs_c_str == NULL ? 0 : -1; // NULL < anything except NULL + } else if (rhs_c_str == NULL) { + return 1; + } + + const size_t shorter_str_len = + length() <= rhs.length() ? length() : rhs.length(); + for (size_t i = 0; i != shorter_str_len; i++) { + if (lhs_c_str[i] < rhs_c_str[i]) { + return -1; + } else if (lhs_c_str[i] > rhs_c_str[i]) { + return 1; + } + } + return (length() < rhs.length()) ? -1 : + (length() > rhs.length()) ? 1 : 0; +} + +// Returns true iff this String ends with the given suffix. *Any* +// String is considered to end with a NULL or empty suffix. +bool String::EndsWith(const char* suffix) const { + if (suffix == NULL || CStringEquals(suffix, "")) return true; + + if (c_str() == NULL) return false; + + const size_t this_len = strlen(c_str()); + const size_t suffix_len = strlen(suffix); + return (this_len >= suffix_len) && + CStringEquals(c_str() + this_len - suffix_len, suffix); +} + +// Returns true iff this String ends with the given suffix, ignoring case. +// Any String is considered to end with a NULL or empty suffix. +bool String::EndsWithCaseInsensitive(const char* suffix) const { + if (suffix == NULL || CStringEquals(suffix, "")) return true; + + if (c_str() == NULL) return false; + + const size_t this_len = strlen(c_str()); + const size_t suffix_len = strlen(suffix); + return (this_len >= suffix_len) && + CaseInsensitiveCStringEquals(c_str() + this_len - suffix_len, suffix); +} + +// Formats a list of arguments to a String, using the same format +// spec string as for printf. +// +// We do not use the StringPrintf class as it is not universally +// available. +// +// The result is limited to 4096 characters (including the tailing 0). +// If 4096 characters are not enough to format the input, or if +// there's an error, "" is +// returned. +String String::Format(const char * format, ...) { + va_list args; + va_start(args, format); + + char buffer[4096]; + const int kBufferSize = sizeof(buffer)/sizeof(buffer[0]); + + // MSVC 8 deprecates vsnprintf(), so we want to suppress warning + // 4996 (deprecated function) there. +#ifdef _MSC_VER // We are using MSVC. +# pragma warning(push) // Saves the current warning state. +# pragma warning(disable:4996) // Temporarily disables warning 4996. + + const int size = vsnprintf(buffer, kBufferSize, format, args); + +# pragma warning(pop) // Restores the warning state. +#else // We are not using MSVC. + const int size = vsnprintf(buffer, kBufferSize, format, args); +#endif // _MSC_VER + va_end(args); + + // vsnprintf()'s behavior is not portable. When the buffer is not + // big enough, it returns a negative value in MSVC, and returns the + // needed buffer size on Linux. When there is an output error, it + // always returns a negative value. For simplicity, we lump the two + // error cases together. + if (size < 0 || size >= kBufferSize) { + return String(""); + } else { + return String(buffer, size); + } +} + +// Converts the buffer in a stringstream to a String, converting NUL +// bytes to "\\0" along the way. +String StringStreamToString(::std::stringstream* ss) { + const ::std::string& str = ss->str(); + const char* const start = str.c_str(); + const char* const end = start + str.length(); + + // We need to use a helper stringstream to do this transformation + // because String doesn't support push_back(). + ::std::stringstream helper; + for (const char* ch = start; ch != end; ++ch) { + if (*ch == '\0') { + helper << "\\0"; // Replaces NUL with "\\0"; + } else { + helper.put(*ch); + } + } + + return String(helper.str().c_str()); +} + +// Appends the user-supplied message to the Google-Test-generated message. +String AppendUserMessage(const String& gtest_msg, + const Message& user_msg) { + // Appends the user message if it's non-empty. + const String user_msg_string = user_msg.GetString(); + if (user_msg_string.empty()) { + return gtest_msg; + } + + Message msg; + msg << gtest_msg << "\n" << user_msg_string; + + return msg.GetString(); +} + +} // namespace internal + +// class TestResult + +// Creates an empty TestResult. +TestResult::TestResult() + : death_test_count_(0), + elapsed_time_(0) { +} + +// D'tor. +TestResult::~TestResult() { +} + +// Returns the i-th test part result among all the results. i can +// range from 0 to total_part_count() - 1. If i is not in that range, +// aborts the program. +const TestPartResult& TestResult::GetTestPartResult(int i) const { + if (i < 0 || i >= total_part_count()) + internal::posix::Abort(); + return test_part_results_.at(i); +} + +// Returns the i-th test property. i can range from 0 to +// test_property_count() - 1. If i is not in that range, aborts the +// program. +const TestProperty& TestResult::GetTestProperty(int i) const { + if (i < 0 || i >= test_property_count()) + internal::posix::Abort(); + return test_properties_.at(i); +} + +// Clears the test part results. +void TestResult::ClearTestPartResults() { + test_part_results_.clear(); +} + +// Adds a test part result to the list. +void TestResult::AddTestPartResult(const TestPartResult& test_part_result) { + test_part_results_.push_back(test_part_result); +} + +// Adds a test property to the list. If a property with the same key as the +// supplied property is already represented, the value of this test_property +// replaces the old value for that key. +void TestResult::RecordProperty(const TestProperty& test_property) { + if (!ValidateTestProperty(test_property)) { + return; + } + internal::MutexLock lock(&test_properites_mutex_); + const std::vector::iterator property_with_matching_key = + std::find_if(test_properties_.begin(), test_properties_.end(), + internal::TestPropertyKeyIs(test_property.key())); + if (property_with_matching_key == test_properties_.end()) { + test_properties_.push_back(test_property); + return; + } + property_with_matching_key->SetValue(test_property.value()); +} + +// Adds a failure if the key is a reserved attribute of Google Test +// testcase tags. Returns true if the property is valid. +bool TestResult::ValidateTestProperty(const TestProperty& test_property) { + internal::String key(test_property.key()); + if (key == "name" || key == "status" || key == "time" || key == "classname") { + ADD_FAILURE() + << "Reserved key used in RecordProperty(): " + << key + << " ('name', 'status', 'time', and 'classname' are reserved by " + << GTEST_NAME_ << ")"; + return false; + } + return true; +} + +// Clears the object. +void TestResult::Clear() { + test_part_results_.clear(); + test_properties_.clear(); + death_test_count_ = 0; + elapsed_time_ = 0; +} + +// Returns true iff the test failed. +bool TestResult::Failed() const { + for (int i = 0; i < total_part_count(); ++i) { + if (GetTestPartResult(i).failed()) + return true; + } + return false; +} + +// Returns true iff the test part fatally failed. +static bool TestPartFatallyFailed(const TestPartResult& result) { + return result.fatally_failed(); +} + +// Returns true iff the test fatally failed. +bool TestResult::HasFatalFailure() const { + return CountIf(test_part_results_, TestPartFatallyFailed) > 0; +} + +// Returns true iff the test part non-fatally failed. +static bool TestPartNonfatallyFailed(const TestPartResult& result) { + return result.nonfatally_failed(); +} + +// Returns true iff the test has a non-fatal failure. +bool TestResult::HasNonfatalFailure() const { + return CountIf(test_part_results_, TestPartNonfatallyFailed) > 0; +} + +// Gets the number of all test parts. This is the sum of the number +// of successful test parts and the number of failed test parts. +int TestResult::total_part_count() const { + return static_cast(test_part_results_.size()); +} + +// Returns the number of the test properties. +int TestResult::test_property_count() const { + return static_cast(test_properties_.size()); +} + +// class Test + +// Creates a Test object. + +// The c'tor saves the values of all Google Test flags. +Test::Test() + : gtest_flag_saver_(new internal::GTestFlagSaver) { +} + +// The d'tor restores the values of all Google Test flags. +Test::~Test() { + delete gtest_flag_saver_; +} + +// Sets up the test fixture. +// +// A sub-class may override this. +void Test::SetUp() { +} + +// Tears down the test fixture. +// +// A sub-class may override this. +void Test::TearDown() { +} + +// Allows user supplied key value pairs to be recorded for later output. +void Test::RecordProperty(const char* key, const char* value) { + UnitTest::GetInstance()->RecordPropertyForCurrentTest(key, value); +} + +// Allows user supplied key value pairs to be recorded for later output. +void Test::RecordProperty(const char* key, int value) { + Message value_message; + value_message << value; + RecordProperty(key, value_message.GetString().c_str()); +} + +namespace internal { + +void ReportFailureInUnknownLocation(TestPartResult::Type result_type, + const String& message) { + // This function is a friend of UnitTest and as such has access to + // AddTestPartResult. + UnitTest::GetInstance()->AddTestPartResult( + result_type, + NULL, // No info about the source file where the exception occurred. + -1, // We have no info on which line caused the exception. + message, + String()); // No stack trace, either. +} + +} // namespace internal + +// Google Test requires all tests in the same test case to use the same test +// fixture class. This function checks if the current test has the +// same fixture class as the first test in the current test case. If +// yes, it returns true; otherwise it generates a Google Test failure and +// returns false. +bool Test::HasSameFixtureClass() { + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + const TestCase* const test_case = impl->current_test_case(); + + // Info about the first test in the current test case. + const TestInfo* const first_test_info = test_case->test_info_list()[0]; + const internal::TypeId first_fixture_id = first_test_info->fixture_class_id_; + const char* const first_test_name = first_test_info->name(); + + // Info about the current test. + const TestInfo* const this_test_info = impl->current_test_info(); + const internal::TypeId this_fixture_id = this_test_info->fixture_class_id_; + const char* const this_test_name = this_test_info->name(); + + if (this_fixture_id != first_fixture_id) { + // Is the first test defined using TEST? + const bool first_is_TEST = first_fixture_id == internal::GetTestTypeId(); + // Is this test defined using TEST? + const bool this_is_TEST = this_fixture_id == internal::GetTestTypeId(); + + if (first_is_TEST || this_is_TEST) { + // The user mixed TEST and TEST_F in this test case - we'll tell + // him/her how to fix it. + + // Gets the name of the TEST and the name of the TEST_F. Note + // that first_is_TEST and this_is_TEST cannot both be true, as + // the fixture IDs are different for the two tests. + const char* const TEST_name = + first_is_TEST ? first_test_name : this_test_name; + const char* const TEST_F_name = + first_is_TEST ? this_test_name : first_test_name; + + ADD_FAILURE() + << "All tests in the same test case must use the same test fixture\n" + << "class, so mixing TEST_F and TEST in the same test case is\n" + << "illegal. In test case " << this_test_info->test_case_name() + << ",\n" + << "test " << TEST_F_name << " is defined using TEST_F but\n" + << "test " << TEST_name << " is defined using TEST. You probably\n" + << "want to change the TEST to TEST_F or move it to another test\n" + << "case."; + } else { + // The user defined two fixture classes with the same name in + // two namespaces - we'll tell him/her how to fix it. + ADD_FAILURE() + << "All tests in the same test case must use the same test fixture\n" + << "class. However, in test case " + << this_test_info->test_case_name() << ",\n" + << "you defined test " << first_test_name + << " and test " << this_test_name << "\n" + << "using two different test fixture classes. This can happen if\n" + << "the two classes are from different namespaces or translation\n" + << "units and have the same name. You should probably rename one\n" + << "of the classes to put the tests into different test cases."; + } + return false; + } + + return true; +} + +#if GTEST_HAS_SEH + +// Adds an "exception thrown" fatal failure to the current test. This +// function returns its result via an output parameter pointer because VC++ +// prohibits creation of objects with destructors on stack in functions +// using __try (see error C2712). +static internal::String* FormatSehExceptionMessage(DWORD exception_code, + const char* location) { + Message message; + message << "SEH exception with code 0x" << std::setbase(16) << + exception_code << std::setbase(10) << " thrown in " << location << "."; + + return new internal::String(message.GetString()); +} + +#endif // GTEST_HAS_SEH + +#if GTEST_HAS_EXCEPTIONS + +// Adds an "exception thrown" fatal failure to the current test. +static internal::String FormatCxxExceptionMessage(const char* description, + const char* location) { + Message message; + if (description != NULL) { + message << "C++ exception with description \"" << description << "\""; + } else { + message << "Unknown C++ exception"; + } + message << " thrown in " << location << "."; + + return message.GetString(); +} + +static internal::String PrintTestPartResultToString( + const TestPartResult& test_part_result); + +// A failed Google Test assertion will throw an exception of this type when +// GTEST_FLAG(throw_on_failure) is true (if exceptions are enabled). We +// derive it from std::runtime_error, which is for errors presumably +// detectable only at run time. Since std::runtime_error inherits from +// std::exception, many testing frameworks know how to extract and print the +// message inside it. +class GoogleTestFailureException : public ::std::runtime_error { + public: + explicit GoogleTestFailureException(const TestPartResult& failure) + : ::std::runtime_error(PrintTestPartResultToString(failure).c_str()) {} +}; +#endif // GTEST_HAS_EXCEPTIONS + +namespace internal { +// We put these helper functions in the internal namespace as IBM's xlC +// compiler rejects the code if they were declared static. + +// Runs the given method and handles SEH exceptions it throws, when +// SEH is supported; returns the 0-value for type Result in case of an +// SEH exception. (Microsoft compilers cannot handle SEH and C++ +// exceptions in the same function. Therefore, we provide a separate +// wrapper function for handling SEH exceptions.) +template +Result HandleSehExceptionsInMethodIfSupported( + T* object, Result (T::*method)(), const char* location) { +#if GTEST_HAS_SEH + __try { + return (object->*method)(); + } __except (internal::UnitTestOptions::GTestShouldProcessSEH( // NOLINT + GetExceptionCode())) { + // We create the exception message on the heap because VC++ prohibits + // creation of objects with destructors on stack in functions using __try + // (see error C2712). + internal::String* exception_message = FormatSehExceptionMessage( + GetExceptionCode(), location); + internal::ReportFailureInUnknownLocation(TestPartResult::kFatalFailure, + *exception_message); + delete exception_message; + return static_cast(0); + } +#else + (void)location; + return (object->*method)(); +#endif // GTEST_HAS_SEH +} + +// Runs the given method and catches and reports C++ and/or SEH-style +// exceptions, if they are supported; returns the 0-value for type +// Result in case of an SEH exception. +template +Result HandleExceptionsInMethodIfSupported( + T* object, Result (T::*method)(), const char* location) { + // NOTE: The user code can affect the way in which Google Test handles + // exceptions by setting GTEST_FLAG(catch_exceptions), but only before + // RUN_ALL_TESTS() starts. It is technically possible to check the flag + // after the exception is caught and either report or re-throw the + // exception based on the flag's value: + // + // try { + // // Perform the test method. + // } catch (...) { + // if (GTEST_FLAG(catch_exceptions)) + // // Report the exception as failure. + // else + // throw; // Re-throws the original exception. + // } + // + // However, the purpose of this flag is to allow the program to drop into + // the debugger when the exception is thrown. On most platforms, once the + // control enters the catch block, the exception origin information is + // lost and the debugger will stop the program at the point of the + // re-throw in this function -- instead of at the point of the original + // throw statement in the code under test. For this reason, we perform + // the check early, sacrificing the ability to affect Google Test's + // exception handling in the method where the exception is thrown. + if (internal::GetUnitTestImpl()->catch_exceptions()) { +#if GTEST_HAS_EXCEPTIONS + try { + return HandleSehExceptionsInMethodIfSupported(object, method, location); + } catch (const GoogleTestFailureException&) { // NOLINT + // This exception doesn't originate in code under test. It makes no + // sense to report it as a test failure. + throw; + } catch (const std::exception& e) { // NOLINT + internal::ReportFailureInUnknownLocation( + TestPartResult::kFatalFailure, + FormatCxxExceptionMessage(e.what(), location)); + } catch (...) { // NOLINT + internal::ReportFailureInUnknownLocation( + TestPartResult::kFatalFailure, + FormatCxxExceptionMessage(NULL, location)); + } + return static_cast(0); +#else + return HandleSehExceptionsInMethodIfSupported(object, method, location); +#endif // GTEST_HAS_EXCEPTIONS + } else { + return (object->*method)(); + } +} + +} // namespace internal + +// Runs the test and updates the test result. +void Test::Run() { + if (!HasSameFixtureClass()) return; + + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + impl->os_stack_trace_getter()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported(this, &Test::SetUp, "SetUp()"); + // We will run the test only if SetUp() was successful. + if (!HasFatalFailure()) { + impl->os_stack_trace_getter()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported( + this, &Test::TestBody, "the test body"); + } + + // However, we want to clean up as much as possible. Hence we will + // always call TearDown(), even if SetUp() or the test body has + // failed. + impl->os_stack_trace_getter()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported( + this, &Test::TearDown, "TearDown()"); +} + +// Returns true iff the current test has a fatal failure. +bool Test::HasFatalFailure() { + return internal::GetUnitTestImpl()->current_test_result()->HasFatalFailure(); +} + +// Returns true iff the current test has a non-fatal failure. +bool Test::HasNonfatalFailure() { + return internal::GetUnitTestImpl()->current_test_result()-> + HasNonfatalFailure(); +} + +// class TestInfo + +// Constructs a TestInfo object. It assumes ownership of the test factory +// object. +// TODO(vladl@google.com): Make a_test_case_name and a_name const string&'s +// to signify they cannot be NULLs. +TestInfo::TestInfo(const char* a_test_case_name, + const char* a_name, + const char* a_type_param, + const char* a_value_param, + internal::TypeId fixture_class_id, + internal::TestFactoryBase* factory) + : test_case_name_(a_test_case_name), + name_(a_name), + type_param_(a_type_param ? new std::string(a_type_param) : NULL), + value_param_(a_value_param ? new std::string(a_value_param) : NULL), + fixture_class_id_(fixture_class_id), + should_run_(false), + is_disabled_(false), + matches_filter_(false), + factory_(factory), + result_() {} + +// Destructs a TestInfo object. +TestInfo::~TestInfo() { delete factory_; } + +namespace internal { + +// Creates a new TestInfo object and registers it with Google Test; +// returns the created object. +// +// Arguments: +// +// test_case_name: name of the test case +// name: name of the test +// type_param: the name of the test's type parameter, or NULL if +// this is not a typed or a type-parameterized test. +// value_param: text representation of the test's value parameter, +// or NULL if this is not a value-parameterized test. +// fixture_class_id: ID of the test fixture class +// set_up_tc: pointer to the function that sets up the test case +// tear_down_tc: pointer to the function that tears down the test case +// factory: pointer to the factory that creates a test object. +// The newly created TestInfo instance will assume +// ownership of the factory object. +TestInfo* MakeAndRegisterTestInfo( + const char* test_case_name, const char* name, + const char* type_param, + const char* value_param, + TypeId fixture_class_id, + SetUpTestCaseFunc set_up_tc, + TearDownTestCaseFunc tear_down_tc, + TestFactoryBase* factory) { + TestInfo* const test_info = + new TestInfo(test_case_name, name, type_param, value_param, + fixture_class_id, factory); + GetUnitTestImpl()->AddTestInfo(set_up_tc, tear_down_tc, test_info); + return test_info; +} + +#if GTEST_HAS_PARAM_TEST +void ReportInvalidTestCaseType(const char* test_case_name, + const char* file, int line) { + Message errors; + errors + << "Attempted redefinition of test case " << test_case_name << ".\n" + << "All tests in the same test case must use the same test fixture\n" + << "class. However, in test case " << test_case_name << ", you tried\n" + << "to define a test using a fixture class different from the one\n" + << "used earlier. This can happen if the two fixture classes are\n" + << "from different namespaces and have the same name. You should\n" + << "probably rename one of the classes to put the tests into different\n" + << "test cases."; + + fprintf(stderr, "%s %s", FormatFileLocation(file, line).c_str(), + errors.GetString().c_str()); +} +#endif // GTEST_HAS_PARAM_TEST + +} // namespace internal + +namespace { + +// A predicate that checks the test name of a TestInfo against a known +// value. +// +// This is used for implementation of the TestCase class only. We put +// it in the anonymous namespace to prevent polluting the outer +// namespace. +// +// TestNameIs is copyable. +class TestNameIs { + public: + // Constructor. + // + // TestNameIs has NO default constructor. + explicit TestNameIs(const char* name) + : name_(name) {} + + // Returns true iff the test name of test_info matches name_. + bool operator()(const TestInfo * test_info) const { + return test_info && internal::String(test_info->name()).Compare(name_) == 0; + } + + private: + internal::String name_; +}; + +} // namespace + +namespace internal { + +// This method expands all parameterized tests registered with macros TEST_P +// and INSTANTIATE_TEST_CASE_P into regular tests and registers those. +// This will be done just once during the program runtime. +void UnitTestImpl::RegisterParameterizedTests() { +#if GTEST_HAS_PARAM_TEST + if (!parameterized_tests_registered_) { + parameterized_test_registry_.RegisterTests(); + parameterized_tests_registered_ = true; + } +#endif +} + +} // namespace internal + +// Creates the test object, runs it, records its result, and then +// deletes it. +void TestInfo::Run() { + if (!should_run_) return; + + // Tells UnitTest where to store test result. + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + impl->set_current_test_info(this); + + TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater(); + + // Notifies the unit test event listeners that a test is about to start. + repeater->OnTestStart(*this); + + const TimeInMillis start = internal::GetTimeInMillis(); + + impl->os_stack_trace_getter()->UponLeavingGTest(); + + // Creates the test object. + Test* const test = internal::HandleExceptionsInMethodIfSupported( + factory_, &internal::TestFactoryBase::CreateTest, + "the test fixture's constructor"); + + // Runs the test only if the test object was created and its + // constructor didn't generate a fatal failure. + if ((test != NULL) && !Test::HasFatalFailure()) { + // This doesn't throw as all user code that can throw are wrapped into + // exception handling code. + test->Run(); + } + + // Deletes the test object. + impl->os_stack_trace_getter()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported( + test, &Test::DeleteSelf_, "the test fixture's destructor"); + + result_.set_elapsed_time(internal::GetTimeInMillis() - start); + + // Notifies the unit test event listener that a test has just finished. + repeater->OnTestEnd(*this); + + // Tells UnitTest to stop associating assertion results to this + // test. + impl->set_current_test_info(NULL); +} + +// class TestCase + +// Gets the number of successful tests in this test case. +int TestCase::successful_test_count() const { + return CountIf(test_info_list_, TestPassed); +} + +// Gets the number of failed tests in this test case. +int TestCase::failed_test_count() const { + return CountIf(test_info_list_, TestFailed); +} + +int TestCase::disabled_test_count() const { + return CountIf(test_info_list_, TestDisabled); +} + +// Get the number of tests in this test case that should run. +int TestCase::test_to_run_count() const { + return CountIf(test_info_list_, ShouldRunTest); +} + +// Gets the number of all tests. +int TestCase::total_test_count() const { + return static_cast(test_info_list_.size()); +} + +// Creates a TestCase with the given name. +// +// Arguments: +// +// name: name of the test case +// a_type_param: the name of the test case's type parameter, or NULL if +// this is not a typed or a type-parameterized test case. +// set_up_tc: pointer to the function that sets up the test case +// tear_down_tc: pointer to the function that tears down the test case +TestCase::TestCase(const char* a_name, const char* a_type_param, + Test::SetUpTestCaseFunc set_up_tc, + Test::TearDownTestCaseFunc tear_down_tc) + : name_(a_name), + type_param_(a_type_param ? new std::string(a_type_param) : NULL), + set_up_tc_(set_up_tc), + tear_down_tc_(tear_down_tc), + should_run_(false), + elapsed_time_(0) { +} + +// Destructor of TestCase. +TestCase::~TestCase() { + // Deletes every Test in the collection. + ForEach(test_info_list_, internal::Delete); +} + +// Returns the i-th test among all the tests. i can range from 0 to +// total_test_count() - 1. If i is not in that range, returns NULL. +const TestInfo* TestCase::GetTestInfo(int i) const { + const int index = GetElementOr(test_indices_, i, -1); + return index < 0 ? NULL : test_info_list_[index]; +} + +// Returns the i-th test among all the tests. i can range from 0 to +// total_test_count() - 1. If i is not in that range, returns NULL. +TestInfo* TestCase::GetMutableTestInfo(int i) { + const int index = GetElementOr(test_indices_, i, -1); + return index < 0 ? NULL : test_info_list_[index]; +} + +// Adds a test to this test case. Will delete the test upon +// destruction of the TestCase object. +void TestCase::AddTestInfo(TestInfo * test_info) { + test_info_list_.push_back(test_info); + test_indices_.push_back(static_cast(test_indices_.size())); +} + +// Runs every test in this TestCase. +void TestCase::Run() { + if (!should_run_) return; + + internal::UnitTestImpl* const impl = internal::GetUnitTestImpl(); + impl->set_current_test_case(this); + + TestEventListener* repeater = UnitTest::GetInstance()->listeners().repeater(); + + repeater->OnTestCaseStart(*this); + impl->os_stack_trace_getter()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported( + this, &TestCase::RunSetUpTestCase, "SetUpTestCase()"); + + const internal::TimeInMillis start = internal::GetTimeInMillis(); + for (int i = 0; i < total_test_count(); i++) { + GetMutableTestInfo(i)->Run(); + } + elapsed_time_ = internal::GetTimeInMillis() - start; + + impl->os_stack_trace_getter()->UponLeavingGTest(); + internal::HandleExceptionsInMethodIfSupported( + this, &TestCase::RunTearDownTestCase, "TearDownTestCase()"); + + repeater->OnTestCaseEnd(*this); + impl->set_current_test_case(NULL); +} + +// Clears the results of all tests in this test case. +void TestCase::ClearResult() { + ForEach(test_info_list_, TestInfo::ClearTestResult); +} + +// Shuffles the tests in this test case. +void TestCase::ShuffleTests(internal::Random* random) { + Shuffle(random, &test_indices_); +} + +// Restores the test order to before the first shuffle. +void TestCase::UnshuffleTests() { + for (size_t i = 0; i < test_indices_.size(); i++) { + test_indices_[i] = static_cast(i); + } +} + +// Formats a countable noun. Depending on its quantity, either the +// singular form or the plural form is used. e.g. +// +// FormatCountableNoun(1, "formula", "formuli") returns "1 formula". +// FormatCountableNoun(5, "book", "books") returns "5 books". +static internal::String FormatCountableNoun(int count, + const char * singular_form, + const char * plural_form) { + return internal::String::Format("%d %s", count, + count == 1 ? singular_form : plural_form); +} + +// Formats the count of tests. +static internal::String FormatTestCount(int test_count) { + return FormatCountableNoun(test_count, "test", "tests"); +} + +// Formats the count of test cases. +static internal::String FormatTestCaseCount(int test_case_count) { + return FormatCountableNoun(test_case_count, "test case", "test cases"); +} + +// Converts a TestPartResult::Type enum to human-friendly string +// representation. Both kNonFatalFailure and kFatalFailure are translated +// to "Failure", as the user usually doesn't care about the difference +// between the two when viewing the test result. +static const char * TestPartResultTypeToString(TestPartResult::Type type) { + switch (type) { + case TestPartResult::kSuccess: + return "Success"; + + case TestPartResult::kNonFatalFailure: + case TestPartResult::kFatalFailure: +#ifdef _MSC_VER + return "error: "; +#else + return "Failure\n"; +#endif + default: + return "Unknown result type"; + } +} + +// Prints a TestPartResult to a String. +static internal::String PrintTestPartResultToString( + const TestPartResult& test_part_result) { + return (Message() + << internal::FormatFileLocation(test_part_result.file_name(), + test_part_result.line_number()) + << " " << TestPartResultTypeToString(test_part_result.type()) + << test_part_result.message()).GetString(); +} + +// Prints a TestPartResult. +static void PrintTestPartResult(const TestPartResult& test_part_result) { + const internal::String& result = + PrintTestPartResultToString(test_part_result); + printf("%s\n", result.c_str()); + fflush(stdout); + // If the test program runs in Visual Studio or a debugger, the + // following statements add the test part result message to the Output + // window such that the user can double-click on it to jump to the + // corresponding source code location; otherwise they do nothing. +#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE + // We don't call OutputDebugString*() on Windows Mobile, as printing + // to stdout is done by OutputDebugString() there already - we don't + // want the same message printed twice. + ::OutputDebugStringA(result.c_str()); + ::OutputDebugStringA("\n"); +#endif +} + +// class PrettyUnitTestResultPrinter + +namespace internal { + +enum GTestColor { + COLOR_DEFAULT, + COLOR_RED, + COLOR_GREEN, + COLOR_YELLOW +}; + +#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE + +// Returns the character attribute for the given color. +WORD GetColorAttribute(GTestColor color) { + switch (color) { + case COLOR_RED: return FOREGROUND_RED; + case COLOR_GREEN: return FOREGROUND_GREEN; + case COLOR_YELLOW: return FOREGROUND_RED | FOREGROUND_GREEN; + default: return 0; + } +} + +#else + +// Returns the ANSI color code for the given color. COLOR_DEFAULT is +// an invalid input. +const char* GetAnsiColorCode(GTestColor color) { + switch (color) { + case COLOR_RED: return "1"; + case COLOR_GREEN: return "2"; + case COLOR_YELLOW: return "3"; + default: return NULL; + }; +} + +#endif // GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE + +// Returns true iff Google Test should use colors in the output. +bool ShouldUseColor(bool stdout_is_tty) { + const char* const gtest_color = GTEST_FLAG(color).c_str(); + + if (String::CaseInsensitiveCStringEquals(gtest_color, "auto")) { +#if GTEST_OS_WINDOWS + // On Windows the TERM variable is usually not set, but the + // console there does support colors. + return stdout_is_tty; +#else + // On non-Windows platforms, we rely on the TERM variable. + const char* const term = posix::GetEnv("TERM"); + const bool term_supports_color = + String::CStringEquals(term, "xterm") || + String::CStringEquals(term, "xterm-color") || + String::CStringEquals(term, "xterm-256color") || + String::CStringEquals(term, "screen") || + String::CStringEquals(term, "linux") || + String::CStringEquals(term, "cygwin"); + return stdout_is_tty && term_supports_color; +#endif // GTEST_OS_WINDOWS + } + + return String::CaseInsensitiveCStringEquals(gtest_color, "yes") || + String::CaseInsensitiveCStringEquals(gtest_color, "true") || + String::CaseInsensitiveCStringEquals(gtest_color, "t") || + String::CStringEquals(gtest_color, "1"); + // We take "yes", "true", "t", and "1" as meaning "yes". If the + // value is neither one of these nor "auto", we treat it as "no" to + // be conservative. +} + +// Helpers for printing colored strings to stdout. Note that on Windows, we +// cannot simply emit special characters and have the terminal change colors. +// This routine must actually emit the characters rather than return a string +// that would be colored when printed, as can be done on Linux. +void ColoredPrintf(GTestColor color, const char* fmt, ...) { + va_list args; + va_start(args, fmt); + +#if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_SYMBIAN || GTEST_OS_ZOS + const bool use_color = false; +#else + static const bool in_color_mode = + ShouldUseColor(posix::IsATTY(posix::FileNo(stdout)) != 0); + const bool use_color = in_color_mode && (color != COLOR_DEFAULT); +#endif // GTEST_OS_WINDOWS_MOBILE || GTEST_OS_SYMBIAN || GTEST_OS_ZOS + // The '!= 0' comparison is necessary to satisfy MSVC 7.1. + + if (!use_color) { + vprintf(fmt, args); + va_end(args); + return; + } + +#if GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE + const HANDLE stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE); + + // Gets the current text color. + CONSOLE_SCREEN_BUFFER_INFO buffer_info; + GetConsoleScreenBufferInfo(stdout_handle, &buffer_info); + const WORD old_color_attrs = buffer_info.wAttributes; + + // We need to flush the stream buffers into the console before each + // SetConsoleTextAttribute call lest it affect the text that is already + // printed but has not yet reached the console. + fflush(stdout); + SetConsoleTextAttribute(stdout_handle, + GetColorAttribute(color) | FOREGROUND_INTENSITY); + vprintf(fmt, args); + + fflush(stdout); + // Restores the text color. + SetConsoleTextAttribute(stdout_handle, old_color_attrs); +#else + printf("\033[0;3%sm", GetAnsiColorCode(color)); + vprintf(fmt, args); + printf("\033[m"); // Resets the terminal to default. +#endif // GTEST_OS_WINDOWS && !GTEST_OS_WINDOWS_MOBILE + va_end(args); +} + +void PrintFullTestCommentIfPresent(const TestInfo& test_info) { + const char* const type_param = test_info.type_param(); + const char* const value_param = test_info.value_param(); + + if (type_param != NULL || value_param != NULL) { + printf(", where "); + if (type_param != NULL) { + printf("TypeParam = %s", type_param); + if (value_param != NULL) + printf(" and "); + } + if (value_param != NULL) { + printf("GetParam() = %s", value_param); + } + } +} + +// This class implements the TestEventListener interface. +// +// Class PrettyUnitTestResultPrinter is copyable. +class PrettyUnitTestResultPrinter : public TestEventListener { + public: + PrettyUnitTestResultPrinter() {} + static void PrintTestName(const char * test_case, const char * test) { + printf("%s.%s", test_case, test); + } + + // The following methods override what's in the TestEventListener class. + virtual void OnTestProgramStart(const UnitTest& /*unit_test*/) {} + virtual void OnTestIterationStart(const UnitTest& unit_test, int iteration); + virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test); + virtual void OnEnvironmentsSetUpEnd(const UnitTest& /*unit_test*/) {} + virtual void OnTestCaseStart(const TestCase& test_case); + virtual void OnTestStart(const TestInfo& test_info); + virtual void OnTestPartResult(const TestPartResult& result); + virtual void OnTestEnd(const TestInfo& test_info); + virtual void OnTestCaseEnd(const TestCase& test_case); + virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test); + virtual void OnEnvironmentsTearDownEnd(const UnitTest& /*unit_test*/) {} + virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration); + virtual void OnTestProgramEnd(const UnitTest& /*unit_test*/) {} + + private: + static void PrintFailedTests(const UnitTest& unit_test); + + internal::String test_case_name_; +}; + + // Fired before each iteration of tests starts. +void PrettyUnitTestResultPrinter::OnTestIterationStart( + const UnitTest& unit_test, int iteration) { + if (GTEST_FLAG(repeat) != 1) + printf("\nRepeating all tests (iteration %d) . . .\n\n", iteration + 1); + + const char* const filter = GTEST_FLAG(filter).c_str(); + + // Prints the filter if it's not *. This reminds the user that some + // tests may be skipped. + if (!internal::String::CStringEquals(filter, kUniversalFilter)) { + ColoredPrintf(COLOR_YELLOW, + "Note: %s filter = %s\n", GTEST_NAME_, filter); + } + + if (internal::ShouldShard(kTestTotalShards, kTestShardIndex, false)) { + const Int32 shard_index = Int32FromEnvOrDie(kTestShardIndex, -1); + ColoredPrintf(COLOR_YELLOW, + "Note: This is test shard %d of %s.\n", + static_cast(shard_index) + 1, + internal::posix::GetEnv(kTestTotalShards)); + } + + if (GTEST_FLAG(shuffle)) { + ColoredPrintf(COLOR_YELLOW, + "Note: Randomizing tests' orders with a seed of %d .\n", + unit_test.random_seed()); + } + + ColoredPrintf(COLOR_GREEN, "[==========] "); + printf("Running %s from %s.\n", + FormatTestCount(unit_test.test_to_run_count()).c_str(), + FormatTestCaseCount(unit_test.test_case_to_run_count()).c_str()); + fflush(stdout); +} + +void PrettyUnitTestResultPrinter::OnEnvironmentsSetUpStart( + const UnitTest& /*unit_test*/) { + ColoredPrintf(COLOR_GREEN, "[----------] "); + printf("Global test environment set-up.\n"); + fflush(stdout); +} + +void PrettyUnitTestResultPrinter::OnTestCaseStart(const TestCase& test_case) { + test_case_name_ = test_case.name(); + const internal::String counts = + FormatCountableNoun(test_case.test_to_run_count(), "test", "tests"); + ColoredPrintf(COLOR_GREEN, "[----------] "); + printf("%s from %s", counts.c_str(), test_case_name_.c_str()); + if (test_case.type_param() == NULL) { + printf("\n"); + } else { + printf(", where TypeParam = %s\n", test_case.type_param()); + } + fflush(stdout); +} + +void PrettyUnitTestResultPrinter::OnTestStart(const TestInfo& test_info) { + ColoredPrintf(COLOR_GREEN, "[ RUN ] "); + PrintTestName(test_case_name_.c_str(), test_info.name()); + printf("\n"); + fflush(stdout); +} + +// Called after an assertion failure. +void PrettyUnitTestResultPrinter::OnTestPartResult( + const TestPartResult& result) { + // If the test part succeeded, we don't need to do anything. + if (result.type() == TestPartResult::kSuccess) + return; + + // Print failure message from the assertion (e.g. expected this and got that). + PrintTestPartResult(result); + fflush(stdout); +} + +void PrettyUnitTestResultPrinter::OnTestEnd(const TestInfo& test_info) { + if (test_info.result()->Passed()) { + ColoredPrintf(COLOR_GREEN, "[ OK ] "); + } else { + ColoredPrintf(COLOR_RED, "[ FAILED ] "); + } + PrintTestName(test_case_name_.c_str(), test_info.name()); + if (test_info.result()->Failed()) + PrintFullTestCommentIfPresent(test_info); + + if (GTEST_FLAG(print_time)) { + printf(" (%s ms)\n", internal::StreamableToString( + test_info.result()->elapsed_time()).c_str()); + } else { + printf("\n"); + } + fflush(stdout); +} + +void PrettyUnitTestResultPrinter::OnTestCaseEnd(const TestCase& test_case) { + if (!GTEST_FLAG(print_time)) return; + + test_case_name_ = test_case.name(); + const internal::String counts = + FormatCountableNoun(test_case.test_to_run_count(), "test", "tests"); + ColoredPrintf(COLOR_GREEN, "[----------] "); + printf("%s from %s (%s ms total)\n\n", + counts.c_str(), test_case_name_.c_str(), + internal::StreamableToString(test_case.elapsed_time()).c_str()); + fflush(stdout); +} + +void PrettyUnitTestResultPrinter::OnEnvironmentsTearDownStart( + const UnitTest& /*unit_test*/) { + ColoredPrintf(COLOR_GREEN, "[----------] "); + printf("Global test environment tear-down\n"); + fflush(stdout); +} + +// Internal helper for printing the list of failed tests. +void PrettyUnitTestResultPrinter::PrintFailedTests(const UnitTest& unit_test) { + const int failed_test_count = unit_test.failed_test_count(); + if (failed_test_count == 0) { + return; + } + + for (int i = 0; i < unit_test.total_test_case_count(); ++i) { + const TestCase& test_case = *unit_test.GetTestCase(i); + if (!test_case.should_run() || (test_case.failed_test_count() == 0)) { + continue; + } + for (int j = 0; j < test_case.total_test_count(); ++j) { + const TestInfo& test_info = *test_case.GetTestInfo(j); + if (!test_info.should_run() || test_info.result()->Passed()) { + continue; + } + ColoredPrintf(COLOR_RED, "[ FAILED ] "); + printf("%s.%s", test_case.name(), test_info.name()); + PrintFullTestCommentIfPresent(test_info); + printf("\n"); + } + } +} + +void PrettyUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, + int /*iteration*/) { + ColoredPrintf(COLOR_GREEN, "[==========] "); + printf("%s from %s ran.", + FormatTestCount(unit_test.test_to_run_count()).c_str(), + FormatTestCaseCount(unit_test.test_case_to_run_count()).c_str()); + if (GTEST_FLAG(print_time)) { + printf(" (%s ms total)", + internal::StreamableToString(unit_test.elapsed_time()).c_str()); + } + printf("\n"); + ColoredPrintf(COLOR_GREEN, "[ PASSED ] "); + printf("%s.\n", FormatTestCount(unit_test.successful_test_count()).c_str()); + + int num_failures = unit_test.failed_test_count(); + if (!unit_test.Passed()) { + const int failed_test_count = unit_test.failed_test_count(); + ColoredPrintf(COLOR_RED, "[ FAILED ] "); + printf("%s, listed below:\n", FormatTestCount(failed_test_count).c_str()); + PrintFailedTests(unit_test); + printf("\n%2d FAILED %s\n", num_failures, + num_failures == 1 ? "TEST" : "TESTS"); + } + + int num_disabled = unit_test.disabled_test_count(); + if (num_disabled && !GTEST_FLAG(also_run_disabled_tests)) { + if (!num_failures) { + printf("\n"); // Add a spacer if no FAILURE banner is displayed. + } + ColoredPrintf(COLOR_YELLOW, + " YOU HAVE %d DISABLED %s\n\n", + num_disabled, + num_disabled == 1 ? "TEST" : "TESTS"); + } + // Ensure that Google Test output is printed before, e.g., heapchecker output. + fflush(stdout); +} + +// End PrettyUnitTestResultPrinter + +// class TestEventRepeater +// +// This class forwards events to other event listeners. +class TestEventRepeater : public TestEventListener { + public: + TestEventRepeater() : forwarding_enabled_(true) {} + virtual ~TestEventRepeater(); + void Append(TestEventListener *listener); + TestEventListener* Release(TestEventListener* listener); + + // Controls whether events will be forwarded to listeners_. Set to false + // in death test child processes. + bool forwarding_enabled() const { return forwarding_enabled_; } + void set_forwarding_enabled(bool enable) { forwarding_enabled_ = enable; } + + virtual void OnTestProgramStart(const UnitTest& unit_test); + virtual void OnTestIterationStart(const UnitTest& unit_test, int iteration); + virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test); + virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test); + virtual void OnTestCaseStart(const TestCase& test_case); + virtual void OnTestStart(const TestInfo& test_info); + virtual void OnTestPartResult(const TestPartResult& result); + virtual void OnTestEnd(const TestInfo& test_info); + virtual void OnTestCaseEnd(const TestCase& test_case); + virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test); + virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test); + virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration); + virtual void OnTestProgramEnd(const UnitTest& unit_test); + + private: + // Controls whether events will be forwarded to listeners_. Set to false + // in death test child processes. + bool forwarding_enabled_; + // The list of listeners that receive events. + std::vector listeners_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestEventRepeater); +}; + +TestEventRepeater::~TestEventRepeater() { + ForEach(listeners_, Delete); +} + +void TestEventRepeater::Append(TestEventListener *listener) { + listeners_.push_back(listener); +} + +// TODO(vladl@google.com): Factor the search functionality into Vector::Find. +TestEventListener* TestEventRepeater::Release(TestEventListener *listener) { + for (size_t i = 0; i < listeners_.size(); ++i) { + if (listeners_[i] == listener) { + listeners_.erase(listeners_.begin() + i); + return listener; + } + } + + return NULL; +} + +// Since most methods are very similar, use macros to reduce boilerplate. +// This defines a member that forwards the call to all listeners. +#define GTEST_REPEATER_METHOD_(Name, Type) \ +void TestEventRepeater::Name(const Type& parameter) { \ + if (forwarding_enabled_) { \ + for (size_t i = 0; i < listeners_.size(); i++) { \ + listeners_[i]->Name(parameter); \ + } \ + } \ +} +// This defines a member that forwards the call to all listeners in reverse +// order. +#define GTEST_REVERSE_REPEATER_METHOD_(Name, Type) \ +void TestEventRepeater::Name(const Type& parameter) { \ + if (forwarding_enabled_) { \ + for (int i = static_cast(listeners_.size()) - 1; i >= 0; i--) { \ + listeners_[i]->Name(parameter); \ + } \ + } \ +} + +GTEST_REPEATER_METHOD_(OnTestProgramStart, UnitTest) +GTEST_REPEATER_METHOD_(OnEnvironmentsSetUpStart, UnitTest) +GTEST_REPEATER_METHOD_(OnTestCaseStart, TestCase) +GTEST_REPEATER_METHOD_(OnTestStart, TestInfo) +GTEST_REPEATER_METHOD_(OnTestPartResult, TestPartResult) +GTEST_REPEATER_METHOD_(OnEnvironmentsTearDownStart, UnitTest) +GTEST_REVERSE_REPEATER_METHOD_(OnEnvironmentsSetUpEnd, UnitTest) +GTEST_REVERSE_REPEATER_METHOD_(OnEnvironmentsTearDownEnd, UnitTest) +GTEST_REVERSE_REPEATER_METHOD_(OnTestEnd, TestInfo) +GTEST_REVERSE_REPEATER_METHOD_(OnTestCaseEnd, TestCase) +GTEST_REVERSE_REPEATER_METHOD_(OnTestProgramEnd, UnitTest) + +#undef GTEST_REPEATER_METHOD_ +#undef GTEST_REVERSE_REPEATER_METHOD_ + +void TestEventRepeater::OnTestIterationStart(const UnitTest& unit_test, + int iteration) { + if (forwarding_enabled_) { + for (size_t i = 0; i < listeners_.size(); i++) { + listeners_[i]->OnTestIterationStart(unit_test, iteration); + } + } +} + +void TestEventRepeater::OnTestIterationEnd(const UnitTest& unit_test, + int iteration) { + if (forwarding_enabled_) { + for (int i = static_cast(listeners_.size()) - 1; i >= 0; i--) { + listeners_[i]->OnTestIterationEnd(unit_test, iteration); + } + } +} + +// End TestEventRepeater + +// This class generates an XML output file. +class XmlUnitTestResultPrinter : public EmptyTestEventListener { + public: + explicit XmlUnitTestResultPrinter(const char* output_file); + + virtual void OnTestIterationEnd(const UnitTest& unit_test, int iteration); + + private: + // Is c a whitespace character that is normalized to a space character + // when it appears in an XML attribute value? + static bool IsNormalizableWhitespace(char c) { + return c == 0x9 || c == 0xA || c == 0xD; + } + + // May c appear in a well-formed XML document? + static bool IsValidXmlCharacter(char c) { + return IsNormalizableWhitespace(c) || c >= 0x20; + } + + // Returns an XML-escaped copy of the input string str. If + // is_attribute is true, the text is meant to appear as an attribute + // value, and normalizable whitespace is preserved by replacing it + // with character references. + static String EscapeXml(const char* str, bool is_attribute); + + // Returns the given string with all characters invalid in XML removed. + static string RemoveInvalidXmlCharacters(const string& str); + + // Convenience wrapper around EscapeXml when str is an attribute value. + static String EscapeXmlAttribute(const char* str) { + return EscapeXml(str, true); + } + + // Convenience wrapper around EscapeXml when str is not an attribute value. + static String EscapeXmlText(const char* str) { return EscapeXml(str, false); } + + // Streams an XML CDATA section, escaping invalid CDATA sequences as needed. + static void OutputXmlCDataSection(::std::ostream* stream, const char* data); + + // Streams an XML representation of a TestInfo object. + static void OutputXmlTestInfo(::std::ostream* stream, + const char* test_case_name, + const TestInfo& test_info); + + // Prints an XML representation of a TestCase object + static void PrintXmlTestCase(FILE* out, const TestCase& test_case); + + // Prints an XML summary of unit_test to output stream out. + static void PrintXmlUnitTest(FILE* out, const UnitTest& unit_test); + + // Produces a string representing the test properties in a result as space + // delimited XML attributes based on the property key="value" pairs. + // When the String is not empty, it includes a space at the beginning, + // to delimit this attribute from prior attributes. + static String TestPropertiesAsXmlAttributes(const TestResult& result); + + // The output file. + const String output_file_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(XmlUnitTestResultPrinter); +}; + +// Creates a new XmlUnitTestResultPrinter. +XmlUnitTestResultPrinter::XmlUnitTestResultPrinter(const char* output_file) + : output_file_(output_file) { + if (output_file_.c_str() == NULL || output_file_.empty()) { + fprintf(stderr, "XML output file may not be null\n"); + fflush(stderr); + exit(EXIT_FAILURE); + } +} + +// Called after the unit test ends. +void XmlUnitTestResultPrinter::OnTestIterationEnd(const UnitTest& unit_test, + int /*iteration*/) { + FILE* xmlout = NULL; + FilePath output_file(output_file_); + FilePath output_dir(output_file.RemoveFileName()); + + if (output_dir.CreateDirectoriesRecursively()) { + xmlout = posix::FOpen(output_file_.c_str(), "w"); + } + if (xmlout == NULL) { + // TODO(wan): report the reason of the failure. + // + // We don't do it for now as: + // + // 1. There is no urgent need for it. + // 2. It's a bit involved to make the errno variable thread-safe on + // all three operating systems (Linux, Windows, and Mac OS). + // 3. To interpret the meaning of errno in a thread-safe way, + // we need the strerror_r() function, which is not available on + // Windows. + fprintf(stderr, + "Unable to open file \"%s\"\n", + output_file_.c_str()); + fflush(stderr); + exit(EXIT_FAILURE); + } + PrintXmlUnitTest(xmlout, unit_test); + fclose(xmlout); +} + +// Returns an XML-escaped copy of the input string str. If is_attribute +// is true, the text is meant to appear as an attribute value, and +// normalizable whitespace is preserved by replacing it with character +// references. +// +// Invalid XML characters in str, if any, are stripped from the output. +// It is expected that most, if not all, of the text processed by this +// module will consist of ordinary English text. +// If this module is ever modified to produce version 1.1 XML output, +// most invalid characters can be retained using character references. +// TODO(wan): It might be nice to have a minimally invasive, human-readable +// escaping scheme for invalid characters, rather than dropping them. +String XmlUnitTestResultPrinter::EscapeXml(const char* str, bool is_attribute) { + Message m; + + if (str != NULL) { + for (const char* src = str; *src; ++src) { + switch (*src) { + case '<': + m << "<"; + break; + case '>': + m << ">"; + break; + case '&': + m << "&"; + break; + case '\'': + if (is_attribute) + m << "'"; + else + m << '\''; + break; + case '"': + if (is_attribute) + m << """; + else + m << '"'; + break; + default: + if (IsValidXmlCharacter(*src)) { + if (is_attribute && IsNormalizableWhitespace(*src)) + m << String::Format("&#x%02X;", unsigned(*src)); + else + m << *src; + } + break; + } + } + } + + return m.GetString(); +} + +// Returns the given string with all characters invalid in XML removed. +// Currently invalid characters are dropped from the string. An +// alternative is to replace them with certain characters such as . or ?. +string XmlUnitTestResultPrinter::RemoveInvalidXmlCharacters(const string& str) { + string output; + output.reserve(str.size()); + for (string::const_iterator it = str.begin(); it != str.end(); ++it) + if (IsValidXmlCharacter(*it)) + output.push_back(*it); + + return output; +} + +// The following routines generate an XML representation of a UnitTest +// object. +// +// This is how Google Test concepts map to the DTD: +// +// <-- corresponds to a UnitTest object +// <-- corresponds to a TestCase object +// <-- corresponds to a TestInfo object +// ... +// ... +// ... +// <-- individual assertion failures +// +// +// + +// Formats the given time in milliseconds as seconds. +std::string FormatTimeInMillisAsSeconds(TimeInMillis ms) { + ::std::stringstream ss; + ss << ms/1000.0; + return ss.str(); +} + +// Streams an XML CDATA section, escaping invalid CDATA sequences as needed. +void XmlUnitTestResultPrinter::OutputXmlCDataSection(::std::ostream* stream, + const char* data) { + const char* segment = data; + *stream << ""); + if (next_segment != NULL) { + stream->write( + segment, static_cast(next_segment - segment)); + *stream << "]]>]]>"); + } else { + *stream << segment; + break; + } + } + *stream << "]]>"; +} + +// Prints an XML representation of a TestInfo object. +// TODO(wan): There is also value in printing properties with the plain printer. +void XmlUnitTestResultPrinter::OutputXmlTestInfo(::std::ostream* stream, + const char* test_case_name, + const TestInfo& test_info) { + const TestResult& result = *test_info.result(); + *stream << " \n"; + *stream << " "; + const string location = internal::FormatCompilerIndependentFileLocation( + part.file_name(), part.line_number()); + const string message = location + "\n" + part.message(); + OutputXmlCDataSection(stream, + RemoveInvalidXmlCharacters(message).c_str()); + *stream << "\n"; + } + } + + if (failures == 0) + *stream << " />\n"; + else + *stream << " \n"; +} + +// Prints an XML representation of a TestCase object +void XmlUnitTestResultPrinter::PrintXmlTestCase(FILE* out, + const TestCase& test_case) { + fprintf(out, + " \n", + FormatTimeInMillisAsSeconds(test_case.elapsed_time()).c_str()); + for (int i = 0; i < test_case.total_test_count(); ++i) { + ::std::stringstream stream; + OutputXmlTestInfo(&stream, test_case.name(), *test_case.GetTestInfo(i)); + fprintf(out, "%s", StringStreamToString(&stream).c_str()); + } + fprintf(out, " \n"); +} + +// Prints an XML summary of unit_test to output stream out. +void XmlUnitTestResultPrinter::PrintXmlUnitTest(FILE* out, + const UnitTest& unit_test) { + fprintf(out, "\n"); + fprintf(out, + "\n"); + for (int i = 0; i < unit_test.total_test_case_count(); ++i) + PrintXmlTestCase(out, *unit_test.GetTestCase(i)); + fprintf(out, "\n"); +} + +// Produces a string representing the test properties in a result as space +// delimited XML attributes based on the property key="value" pairs. +String XmlUnitTestResultPrinter::TestPropertiesAsXmlAttributes( + const TestResult& result) { + Message attributes; + for (int i = 0; i < result.test_property_count(); ++i) { + const TestProperty& property = result.GetTestProperty(i); + attributes << " " << property.key() << "=" + << "\"" << EscapeXmlAttribute(property.value()) << "\""; + } + return attributes.GetString(); +} + +// End XmlUnitTestResultPrinter + +#if GTEST_CAN_STREAM_RESULTS_ + +// Streams test results to the given port on the given host machine. +class StreamingListener : public EmptyTestEventListener { + public: + // Escapes '=', '&', '%', and '\n' characters in str as "%xx". + static string UrlEncode(const char* str); + + StreamingListener(const string& host, const string& port) + : sockfd_(-1), host_name_(host), port_num_(port) { + MakeConnection(); + Send("gtest_streaming_protocol_version=1.0\n"); + } + + virtual ~StreamingListener() { + if (sockfd_ != -1) + CloseConnection(); + } + + void OnTestProgramStart(const UnitTest& /* unit_test */) { + Send("event=TestProgramStart\n"); + } + + void OnTestProgramEnd(const UnitTest& unit_test) { + // Note that Google Test current only report elapsed time for each + // test iteration, not for the entire test program. + Send(String::Format("event=TestProgramEnd&passed=%d\n", + unit_test.Passed())); + + // Notify the streaming server to stop. + CloseConnection(); + } + + void OnTestIterationStart(const UnitTest& /* unit_test */, int iteration) { + Send(String::Format("event=TestIterationStart&iteration=%d\n", + iteration)); + } + + void OnTestIterationEnd(const UnitTest& unit_test, int /* iteration */) { + Send(String::Format("event=TestIterationEnd&passed=%d&elapsed_time=%sms\n", + unit_test.Passed(), + StreamableToString(unit_test.elapsed_time()).c_str())); + } + + void OnTestCaseStart(const TestCase& test_case) { + Send(String::Format("event=TestCaseStart&name=%s\n", test_case.name())); + } + + void OnTestCaseEnd(const TestCase& test_case) { + Send(String::Format("event=TestCaseEnd&passed=%d&elapsed_time=%sms\n", + test_case.Passed(), + StreamableToString(test_case.elapsed_time()).c_str())); + } + + void OnTestStart(const TestInfo& test_info) { + Send(String::Format("event=TestStart&name=%s\n", test_info.name())); + } + + void OnTestEnd(const TestInfo& test_info) { + Send(String::Format( + "event=TestEnd&passed=%d&elapsed_time=%sms\n", + (test_info.result())->Passed(), + StreamableToString((test_info.result())->elapsed_time()).c_str())); + } + + void OnTestPartResult(const TestPartResult& test_part_result) { + const char* file_name = test_part_result.file_name(); + if (file_name == NULL) + file_name = ""; + Send(String::Format("event=TestPartResult&file=%s&line=%d&message=", + UrlEncode(file_name).c_str(), + test_part_result.line_number())); + Send(UrlEncode(test_part_result.message()) + "\n"); + } + + private: + // Creates a client socket and connects to the server. + void MakeConnection(); + + // Closes the socket. + void CloseConnection() { + GTEST_CHECK_(sockfd_ != -1) + << "CloseConnection() can be called only when there is a connection."; + + close(sockfd_); + sockfd_ = -1; + } + + // Sends a string to the socket. + void Send(const string& message) { + GTEST_CHECK_(sockfd_ != -1) + << "Send() can be called only when there is a connection."; + + const int len = static_cast(message.length()); + if (write(sockfd_, message.c_str(), len) != len) { + GTEST_LOG_(WARNING) + << "stream_result_to: failed to stream to " + << host_name_ << ":" << port_num_; + } + } + + int sockfd_; // socket file descriptor + const string host_name_; + const string port_num_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(StreamingListener); +}; // class StreamingListener + +// Checks if str contains '=', '&', '%' or '\n' characters. If yes, +// replaces them by "%xx" where xx is their hexadecimal value. For +// example, replaces "=" with "%3D". This algorithm is O(strlen(str)) +// in both time and space -- important as the input str may contain an +// arbitrarily long test failure message and stack trace. +string StreamingListener::UrlEncode(const char* str) { + string result; + result.reserve(strlen(str) + 1); + for (char ch = *str; ch != '\0'; ch = *++str) { + switch (ch) { + case '%': + case '=': + case '&': + case '\n': + result.append(String::Format("%%%02x", static_cast(ch))); + break; + default: + result.push_back(ch); + break; + } + } + return result; +} + +void StreamingListener::MakeConnection() { + GTEST_CHECK_(sockfd_ == -1) + << "MakeConnection() can't be called when there is already a connection."; + + addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; // To allow both IPv4 and IPv6 addresses. + hints.ai_socktype = SOCK_STREAM; + addrinfo* servinfo = NULL; + + // Use the getaddrinfo() to get a linked list of IP addresses for + // the given host name. + const int error_num = getaddrinfo( + host_name_.c_str(), port_num_.c_str(), &hints, &servinfo); + if (error_num != 0) { + GTEST_LOG_(WARNING) << "stream_result_to: getaddrinfo() failed: " + << gai_strerror(error_num); + } + + // Loop through all the results and connect to the first we can. + for (addrinfo* cur_addr = servinfo; sockfd_ == -1 && cur_addr != NULL; + cur_addr = cur_addr->ai_next) { + sockfd_ = socket( + cur_addr->ai_family, cur_addr->ai_socktype, cur_addr->ai_protocol); + if (sockfd_ != -1) { + // Connect the client socket to the server socket. + if (connect(sockfd_, cur_addr->ai_addr, cur_addr->ai_addrlen) == -1) { + close(sockfd_); + sockfd_ = -1; + } + } + } + + freeaddrinfo(servinfo); // all done with this structure + + if (sockfd_ == -1) { + GTEST_LOG_(WARNING) << "stream_result_to: failed to connect to " + << host_name_ << ":" << port_num_; + } +} + +// End of class Streaming Listener +#endif // GTEST_CAN_STREAM_RESULTS__ + +// Class ScopedTrace + +// Pushes the given source file location and message onto a per-thread +// trace stack maintained by Google Test. +// L < UnitTest::mutex_ +ScopedTrace::ScopedTrace(const char* file, int line, const Message& message) { + TraceInfo trace; + trace.file = file; + trace.line = line; + trace.message = message.GetString(); + + UnitTest::GetInstance()->PushGTestTrace(trace); +} + +// Pops the info pushed by the c'tor. +// L < UnitTest::mutex_ +ScopedTrace::~ScopedTrace() { + UnitTest::GetInstance()->PopGTestTrace(); +} + + +// class OsStackTraceGetter + +// Returns the current OS stack trace as a String. Parameters: +// +// max_depth - the maximum number of stack frames to be included +// in the trace. +// skip_count - the number of top frames to be skipped; doesn't count +// against max_depth. +// +// L < mutex_ +// We use "L < mutex_" to denote that the function may acquire mutex_. +String OsStackTraceGetter::CurrentStackTrace(int, int) { + return String(""); +} + +// L < mutex_ +void OsStackTraceGetter::UponLeavingGTest() { +} + +const char* const +OsStackTraceGetter::kElidedFramesMarker = + "... " GTEST_NAME_ " internal frames ..."; + +} // namespace internal + +// class TestEventListeners + +TestEventListeners::TestEventListeners() + : repeater_(new internal::TestEventRepeater()), + default_result_printer_(NULL), + default_xml_generator_(NULL) { +} + +TestEventListeners::~TestEventListeners() { delete repeater_; } + +// Returns the standard listener responsible for the default console +// output. Can be removed from the listeners list to shut down default +// console output. Note that removing this object from the listener list +// with Release transfers its ownership to the user. +void TestEventListeners::Append(TestEventListener* listener) { + repeater_->Append(listener); +} + +// Removes the given event listener from the list and returns it. It then +// becomes the caller's responsibility to delete the listener. Returns +// NULL if the listener is not found in the list. +TestEventListener* TestEventListeners::Release(TestEventListener* listener) { + if (listener == default_result_printer_) + default_result_printer_ = NULL; + else if (listener == default_xml_generator_) + default_xml_generator_ = NULL; + return repeater_->Release(listener); +} + +// Returns repeater that broadcasts the TestEventListener events to all +// subscribers. +TestEventListener* TestEventListeners::repeater() { return repeater_; } + +// Sets the default_result_printer attribute to the provided listener. +// The listener is also added to the listener list and previous +// default_result_printer is removed from it and deleted. The listener can +// also be NULL in which case it will not be added to the list. Does +// nothing if the previous and the current listener objects are the same. +void TestEventListeners::SetDefaultResultPrinter(TestEventListener* listener) { + if (default_result_printer_ != listener) { + // It is an error to pass this method a listener that is already in the + // list. + delete Release(default_result_printer_); + default_result_printer_ = listener; + if (listener != NULL) + Append(listener); + } +} + +// Sets the default_xml_generator attribute to the provided listener. The +// listener is also added to the listener list and previous +// default_xml_generator is removed from it and deleted. The listener can +// also be NULL in which case it will not be added to the list. Does +// nothing if the previous and the current listener objects are the same. +void TestEventListeners::SetDefaultXmlGenerator(TestEventListener* listener) { + if (default_xml_generator_ != listener) { + // It is an error to pass this method a listener that is already in the + // list. + delete Release(default_xml_generator_); + default_xml_generator_ = listener; + if (listener != NULL) + Append(listener); + } +} + +// Controls whether events will be forwarded by the repeater to the +// listeners in the list. +bool TestEventListeners::EventForwardingEnabled() const { + return repeater_->forwarding_enabled(); +} + +void TestEventListeners::SuppressEventForwarding() { + repeater_->set_forwarding_enabled(false); +} + +// class UnitTest + +// Gets the singleton UnitTest object. The first time this method is +// called, a UnitTest object is constructed and returned. Consecutive +// calls will return the same object. +// +// We don't protect this under mutex_ as a user is not supposed to +// call this before main() starts, from which point on the return +// value will never change. +UnitTest * UnitTest::GetInstance() { + // When compiled with MSVC 7.1 in optimized mode, destroying the + // UnitTest object upon exiting the program messes up the exit code, + // causing successful tests to appear failed. We have to use a + // different implementation in this case to bypass the compiler bug. + // This implementation makes the compiler happy, at the cost of + // leaking the UnitTest object. + + // CodeGear C++Builder insists on a public destructor for the + // default implementation. Use this implementation to keep good OO + // design with private destructor. + +#if (_MSC_VER == 1310 && !defined(_DEBUG)) || defined(__BORLANDC__) + static UnitTest* const instance = new UnitTest; + return instance; +#else + static UnitTest instance; + return &instance; +#endif // (_MSC_VER == 1310 && !defined(_DEBUG)) || defined(__BORLANDC__) +} + +// Gets the number of successful test cases. +int UnitTest::successful_test_case_count() const { + return impl()->successful_test_case_count(); +} + +// Gets the number of failed test cases. +int UnitTest::failed_test_case_count() const { + return impl()->failed_test_case_count(); +} + +// Gets the number of all test cases. +int UnitTest::total_test_case_count() const { + return impl()->total_test_case_count(); +} + +// Gets the number of all test cases that contain at least one test +// that should run. +int UnitTest::test_case_to_run_count() const { + return impl()->test_case_to_run_count(); +} + +// Gets the number of successful tests. +int UnitTest::successful_test_count() const { + return impl()->successful_test_count(); +} + +// Gets the number of failed tests. +int UnitTest::failed_test_count() const { return impl()->failed_test_count(); } + +// Gets the number of disabled tests. +int UnitTest::disabled_test_count() const { + return impl()->disabled_test_count(); +} + +// Gets the number of all tests. +int UnitTest::total_test_count() const { return impl()->total_test_count(); } + +// Gets the number of tests that should run. +int UnitTest::test_to_run_count() const { return impl()->test_to_run_count(); } + +// Gets the elapsed time, in milliseconds. +internal::TimeInMillis UnitTest::elapsed_time() const { + return impl()->elapsed_time(); +} + +// Returns true iff the unit test passed (i.e. all test cases passed). +bool UnitTest::Passed() const { return impl()->Passed(); } + +// Returns true iff the unit test failed (i.e. some test case failed +// or something outside of all tests failed). +bool UnitTest::Failed() const { return impl()->Failed(); } + +// Gets the i-th test case among all the test cases. i can range from 0 to +// total_test_case_count() - 1. If i is not in that range, returns NULL. +const TestCase* UnitTest::GetTestCase(int i) const { + return impl()->GetTestCase(i); +} + +// Gets the i-th test case among all the test cases. i can range from 0 to +// total_test_case_count() - 1. If i is not in that range, returns NULL. +TestCase* UnitTest::GetMutableTestCase(int i) { + return impl()->GetMutableTestCase(i); +} + +// Returns the list of event listeners that can be used to track events +// inside Google Test. +TestEventListeners& UnitTest::listeners() { + return *impl()->listeners(); +} + +// Registers and returns a global test environment. When a test +// program is run, all global test environments will be set-up in the +// order they were registered. After all tests in the program have +// finished, all global test environments will be torn-down in the +// *reverse* order they were registered. +// +// The UnitTest object takes ownership of the given environment. +// +// We don't protect this under mutex_, as we only support calling it +// from the main thread. +Environment* UnitTest::AddEnvironment(Environment* env) { + if (env == NULL) { + return NULL; + } + + impl_->environments().push_back(env); + return env; +} + +// Adds a TestPartResult to the current TestResult object. All Google Test +// assertion macros (e.g. ASSERT_TRUE, EXPECT_EQ, etc) eventually call +// this to report their results. The user code should use the +// assertion macros instead of calling this directly. +// L < mutex_ +void UnitTest::AddTestPartResult(TestPartResult::Type result_type, + const char* file_name, + int line_number, + const internal::String& message, + const internal::String& os_stack_trace) { + Message msg; + msg << message; + + internal::MutexLock lock(&mutex_); + if (impl_->gtest_trace_stack().size() > 0) { + msg << "\n" << GTEST_NAME_ << " trace:"; + + for (int i = static_cast(impl_->gtest_trace_stack().size()); + i > 0; --i) { + const internal::TraceInfo& trace = impl_->gtest_trace_stack()[i - 1]; + msg << "\n" << internal::FormatFileLocation(trace.file, trace.line) + << " " << trace.message; + } + } + + if (os_stack_trace.c_str() != NULL && !os_stack_trace.empty()) { + msg << internal::kStackTraceMarker << os_stack_trace; + } + + const TestPartResult result = + TestPartResult(result_type, file_name, line_number, + msg.GetString().c_str()); + impl_->GetTestPartResultReporterForCurrentThread()-> + ReportTestPartResult(result); + + if (result_type != TestPartResult::kSuccess) { + // gtest_break_on_failure takes precedence over + // gtest_throw_on_failure. This allows a user to set the latter + // in the code (perhaps in order to use Google Test assertions + // with another testing framework) and specify the former on the + // command line for debugging. + if (GTEST_FLAG(break_on_failure)) { +#if GTEST_OS_WINDOWS + // Using DebugBreak on Windows allows gtest to still break into a debugger + // when a failure happens and both the --gtest_break_on_failure and + // the --gtest_catch_exceptions flags are specified. + DebugBreak(); +#else + // Dereference NULL through a volatile pointer to prevent the compiler + // from removing. We use this rather than abort() or __builtin_trap() for + // portability: Symbian doesn't implement abort() well, and some debuggers + // don't correctly trap abort(). + *static_cast(NULL) = 1; +#endif // GTEST_OS_WINDOWS + } else if (GTEST_FLAG(throw_on_failure)) { +#if GTEST_HAS_EXCEPTIONS + throw GoogleTestFailureException(result); +#else + // We cannot call abort() as it generates a pop-up in debug mode + // that cannot be suppressed in VC 7.1 or below. + exit(1); +#endif + } + } +} + +// Creates and adds a property to the current TestResult. If a property matching +// the supplied value already exists, updates its value instead. +void UnitTest::RecordPropertyForCurrentTest(const char* key, + const char* value) { + const TestProperty test_property(key, value); + impl_->current_test_result()->RecordProperty(test_property); +} + +// Runs all tests in this UnitTest object and prints the result. +// Returns 0 if successful, or 1 otherwise. +// +// We don't protect this under mutex_, as we only support calling it +// from the main thread. +int UnitTest::Run() { + // Captures the value of GTEST_FLAG(catch_exceptions). This value will be + // used for the duration of the program. + impl()->set_catch_exceptions(GTEST_FLAG(catch_exceptions)); + +#if GTEST_HAS_SEH + const bool in_death_test_child_process = + internal::GTEST_FLAG(internal_run_death_test).length() > 0; + + // Either the user wants Google Test to catch exceptions thrown by the + // tests or this is executing in the context of death test child + // process. In either case the user does not want to see pop-up dialogs + // about crashes - they are expected. + if (impl()->catch_exceptions() || in_death_test_child_process) { + +# if !GTEST_OS_WINDOWS_MOBILE + // SetErrorMode doesn't exist on CE. + SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOALIGNMENTFAULTEXCEPT | + SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); +# endif // !GTEST_OS_WINDOWS_MOBILE + +# if (defined(_MSC_VER) || GTEST_OS_WINDOWS_MINGW) && !GTEST_OS_WINDOWS_MOBILE + // Death test children can be terminated with _abort(). On Windows, + // _abort() can show a dialog with a warning message. This forces the + // abort message to go to stderr instead. + _set_error_mode(_OUT_TO_STDERR); +# endif + +# if _MSC_VER >= 1400 && !GTEST_OS_WINDOWS_MOBILE + // In the debug version, Visual Studio pops up a separate dialog + // offering a choice to debug the aborted program. We need to suppress + // this dialog or it will pop up for every EXPECT/ASSERT_DEATH statement + // executed. Google Test will notify the user of any unexpected + // failure via stderr. + // + // VC++ doesn't define _set_abort_behavior() prior to the version 8.0. + // Users of prior VC versions shall suffer the agony and pain of + // clicking through the countless debug dialogs. + // TODO(vladl@google.com): find a way to suppress the abort dialog() in the + // debug mode when compiled with VC 7.1 or lower. + if (!GTEST_FLAG(break_on_failure)) + _set_abort_behavior( + 0x0, // Clear the following flags: + _WRITE_ABORT_MSG | _CALL_REPORTFAULT); // pop-up window, core dump. +# endif + + } +#endif // GTEST_HAS_SEH + + return internal::HandleExceptionsInMethodIfSupported( + impl(), + &internal::UnitTestImpl::RunAllTests, + "auxiliary test code (environments or event listeners)") ? 0 : 1; +} + +// Returns the working directory when the first TEST() or TEST_F() was +// executed. +const char* UnitTest::original_working_dir() const { + return impl_->original_working_dir_.c_str(); +} + +// Returns the TestCase object for the test that's currently running, +// or NULL if no test is running. +// L < mutex_ +const TestCase* UnitTest::current_test_case() const { + internal::MutexLock lock(&mutex_); + return impl_->current_test_case(); +} + +// Returns the TestInfo object for the test that's currently running, +// or NULL if no test is running. +// L < mutex_ +const TestInfo* UnitTest::current_test_info() const { + internal::MutexLock lock(&mutex_); + return impl_->current_test_info(); +} + +// Returns the random seed used at the start of the current test run. +int UnitTest::random_seed() const { return impl_->random_seed(); } + +#if GTEST_HAS_PARAM_TEST +// Returns ParameterizedTestCaseRegistry object used to keep track of +// value-parameterized tests and instantiate and register them. +// L < mutex_ +internal::ParameterizedTestCaseRegistry& + UnitTest::parameterized_test_registry() { + return impl_->parameterized_test_registry(); +} +#endif // GTEST_HAS_PARAM_TEST + +// Creates an empty UnitTest. +UnitTest::UnitTest() { + impl_ = new internal::UnitTestImpl(this); +} + +// Destructor of UnitTest. +UnitTest::~UnitTest() { + delete impl_; +} + +// Pushes a trace defined by SCOPED_TRACE() on to the per-thread +// Google Test trace stack. +// L < mutex_ +void UnitTest::PushGTestTrace(const internal::TraceInfo& trace) { + internal::MutexLock lock(&mutex_); + impl_->gtest_trace_stack().push_back(trace); +} + +// Pops a trace from the per-thread Google Test trace stack. +// L < mutex_ +void UnitTest::PopGTestTrace() { + internal::MutexLock lock(&mutex_); + impl_->gtest_trace_stack().pop_back(); +} + +namespace internal { + +UnitTestImpl::UnitTestImpl(UnitTest* parent) + : parent_(parent), +#ifdef _MSC_VER +# pragma warning(push) // Saves the current warning state. +# pragma warning(disable:4355) // Temporarily disables warning 4355 + // (using this in initializer). + default_global_test_part_result_reporter_(this), + default_per_thread_test_part_result_reporter_(this), +# pragma warning(pop) // Restores the warning state again. +#else + default_global_test_part_result_reporter_(this), + default_per_thread_test_part_result_reporter_(this), +#endif // _MSC_VER + global_test_part_result_repoter_( + &default_global_test_part_result_reporter_), + per_thread_test_part_result_reporter_( + &default_per_thread_test_part_result_reporter_), +#if GTEST_HAS_PARAM_TEST + parameterized_test_registry_(), + parameterized_tests_registered_(false), +#endif // GTEST_HAS_PARAM_TEST + last_death_test_case_(-1), + current_test_case_(NULL), + current_test_info_(NULL), + ad_hoc_test_result_(), + os_stack_trace_getter_(NULL), + post_flag_parse_init_performed_(false), + random_seed_(0), // Will be overridden by the flag before first use. + random_(0), // Will be reseeded before first use. + elapsed_time_(0), +#if GTEST_HAS_DEATH_TEST + internal_run_death_test_flag_(NULL), + death_test_factory_(new DefaultDeathTestFactory), +#endif + // Will be overridden by the flag before first use. + catch_exceptions_(false) { + listeners()->SetDefaultResultPrinter(new PrettyUnitTestResultPrinter); +} + +UnitTestImpl::~UnitTestImpl() { + // Deletes every TestCase. + ForEach(test_cases_, internal::Delete); + + // Deletes every Environment. + ForEach(environments_, internal::Delete); + + delete os_stack_trace_getter_; +} + +#if GTEST_HAS_DEATH_TEST +// Disables event forwarding if the control is currently in a death test +// subprocess. Must not be called before InitGoogleTest. +void UnitTestImpl::SuppressTestEventsIfInSubprocess() { + if (internal_run_death_test_flag_.get() != NULL) + listeners()->SuppressEventForwarding(); +} +#endif // GTEST_HAS_DEATH_TEST + +// Initializes event listeners performing XML output as specified by +// UnitTestOptions. Must not be called before InitGoogleTest. +void UnitTestImpl::ConfigureXmlOutput() { + const String& output_format = UnitTestOptions::GetOutputFormat(); + if (output_format == "xml") { + listeners()->SetDefaultXmlGenerator(new XmlUnitTestResultPrinter( + UnitTestOptions::GetAbsolutePathToOutputFile().c_str())); + } else if (output_format != "") { + printf("WARNING: unrecognized output format \"%s\" ignored.\n", + output_format.c_str()); + fflush(stdout); + } +} + +#if GTEST_CAN_STREAM_RESULTS_ +// Initializes event listeners for streaming test results in String form. +// Must not be called before InitGoogleTest. +void UnitTestImpl::ConfigureStreamingOutput() { + const string& target = GTEST_FLAG(stream_result_to); + if (!target.empty()) { + const size_t pos = target.find(':'); + if (pos != string::npos) { + listeners()->Append(new StreamingListener(target.substr(0, pos), + target.substr(pos+1))); + } else { + printf("WARNING: unrecognized streaming target \"%s\" ignored.\n", + target.c_str()); + fflush(stdout); + } + } +} +#endif // GTEST_CAN_STREAM_RESULTS_ + +// Performs initialization dependent upon flag values obtained in +// ParseGoogleTestFlagsOnly. Is called from InitGoogleTest after the call to +// ParseGoogleTestFlagsOnly. In case a user neglects to call InitGoogleTest +// this function is also called from RunAllTests. Since this function can be +// called more than once, it has to be idempotent. +void UnitTestImpl::PostFlagParsingInit() { + // Ensures that this function does not execute more than once. + if (!post_flag_parse_init_performed_) { + post_flag_parse_init_performed_ = true; + +#if GTEST_HAS_DEATH_TEST + InitDeathTestSubprocessControlInfo(); + SuppressTestEventsIfInSubprocess(); +#endif // GTEST_HAS_DEATH_TEST + + // Registers parameterized tests. This makes parameterized tests + // available to the UnitTest reflection API without running + // RUN_ALL_TESTS. + RegisterParameterizedTests(); + + // Configures listeners for XML output. This makes it possible for users + // to shut down the default XML output before invoking RUN_ALL_TESTS. + ConfigureXmlOutput(); + +#if GTEST_CAN_STREAM_RESULTS_ + // Configures listeners for streaming test results to the specified server. + ConfigureStreamingOutput(); +#endif // GTEST_CAN_STREAM_RESULTS_ + } +} + +// A predicate that checks the name of a TestCase against a known +// value. +// +// This is used for implementation of the UnitTest class only. We put +// it in the anonymous namespace to prevent polluting the outer +// namespace. +// +// TestCaseNameIs is copyable. +class TestCaseNameIs { + public: + // Constructor. + explicit TestCaseNameIs(const String& name) + : name_(name) {} + + // Returns true iff the name of test_case matches name_. + bool operator()(const TestCase* test_case) const { + return test_case != NULL && strcmp(test_case->name(), name_.c_str()) == 0; + } + + private: + String name_; +}; + +// Finds and returns a TestCase with the given name. If one doesn't +// exist, creates one and returns it. It's the CALLER'S +// RESPONSIBILITY to ensure that this function is only called WHEN THE +// TESTS ARE NOT SHUFFLED. +// +// Arguments: +// +// test_case_name: name of the test case +// type_param: the name of the test case's type parameter, or NULL if +// this is not a typed or a type-parameterized test case. +// set_up_tc: pointer to the function that sets up the test case +// tear_down_tc: pointer to the function that tears down the test case +TestCase* UnitTestImpl::GetTestCase(const char* test_case_name, + const char* type_param, + Test::SetUpTestCaseFunc set_up_tc, + Test::TearDownTestCaseFunc tear_down_tc) { + // Can we find a TestCase with the given name? + const std::vector::const_iterator test_case = + std::find_if(test_cases_.begin(), test_cases_.end(), + TestCaseNameIs(test_case_name)); + + if (test_case != test_cases_.end()) + return *test_case; + + // No. Let's create one. + TestCase* const new_test_case = + new TestCase(test_case_name, type_param, set_up_tc, tear_down_tc); + + // Is this a death test case? + if (internal::UnitTestOptions::MatchesFilter(String(test_case_name), + kDeathTestCaseFilter)) { + // Yes. Inserts the test case after the last death test case + // defined so far. This only works when the test cases haven't + // been shuffled. Otherwise we may end up running a death test + // after a non-death test. + ++last_death_test_case_; + test_cases_.insert(test_cases_.begin() + last_death_test_case_, + new_test_case); + } else { + // No. Appends to the end of the list. + test_cases_.push_back(new_test_case); + } + + test_case_indices_.push_back(static_cast(test_case_indices_.size())); + return new_test_case; +} + +// Helpers for setting up / tearing down the given environment. They +// are for use in the ForEach() function. +static void SetUpEnvironment(Environment* env) { env->SetUp(); } +static void TearDownEnvironment(Environment* env) { env->TearDown(); } + +// Runs all tests in this UnitTest object, prints the result, and +// returns true if all tests are successful. If any exception is +// thrown during a test, the test is considered to be failed, but the +// rest of the tests will still be run. +// +// When parameterized tests are enabled, it expands and registers +// parameterized tests first in RegisterParameterizedTests(). +// All other functions called from RunAllTests() may safely assume that +// parameterized tests are ready to be counted and run. +bool UnitTestImpl::RunAllTests() { + // Makes sure InitGoogleTest() was called. + if (!GTestIsInitialized()) { + printf("%s", + "\nThis test program did NOT call ::testing::InitGoogleTest " + "before calling RUN_ALL_TESTS(). Please fix it.\n"); + return false; + } + + // Do not run any test if the --help flag was specified. + if (g_help_flag) + return true; + + // Repeats the call to the post-flag parsing initialization in case the + // user didn't call InitGoogleTest. + PostFlagParsingInit(); + + // Even if sharding is not on, test runners may want to use the + // GTEST_SHARD_STATUS_FILE to query whether the test supports the sharding + // protocol. + internal::WriteToShardStatusFileIfNeeded(); + + // True iff we are in a subprocess for running a thread-safe-style + // death test. + bool in_subprocess_for_death_test = false; + +#if GTEST_HAS_DEATH_TEST + in_subprocess_for_death_test = (internal_run_death_test_flag_.get() != NULL); +#endif // GTEST_HAS_DEATH_TEST + + const bool should_shard = ShouldShard(kTestTotalShards, kTestShardIndex, + in_subprocess_for_death_test); + + // Compares the full test names with the filter to decide which + // tests to run. + const bool has_tests_to_run = FilterTests(should_shard + ? HONOR_SHARDING_PROTOCOL + : IGNORE_SHARDING_PROTOCOL) > 0; + + // Lists the tests and exits if the --gtest_list_tests flag was specified. + if (GTEST_FLAG(list_tests)) { + // This must be called *after* FilterTests() has been called. + ListTestsMatchingFilter(); + return true; + } + + random_seed_ = GTEST_FLAG(shuffle) ? + GetRandomSeedFromFlag(GTEST_FLAG(random_seed)) : 0; + + // True iff at least one test has failed. + bool failed = false; + + TestEventListener* repeater = listeners()->repeater(); + + repeater->OnTestProgramStart(*parent_); + + // How many times to repeat the tests? We don't want to repeat them + // when we are inside the subprocess of a death test. + const int repeat = in_subprocess_for_death_test ? 1 : GTEST_FLAG(repeat); + // Repeats forever if the repeat count is negative. + const bool forever = repeat < 0; + for (int i = 0; forever || i != repeat; i++) { + // We want to preserve failures generated by ad-hoc test + // assertions executed before RUN_ALL_TESTS(). + ClearNonAdHocTestResult(); + + const TimeInMillis start = GetTimeInMillis(); + + // Shuffles test cases and tests if requested. + if (has_tests_to_run && GTEST_FLAG(shuffle)) { + random()->Reseed(random_seed_); + // This should be done before calling OnTestIterationStart(), + // such that a test event listener can see the actual test order + // in the event. + ShuffleTests(); + } + + // Tells the unit test event listeners that the tests are about to start. + repeater->OnTestIterationStart(*parent_, i); + + // Runs each test case if there is at least one test to run. + if (has_tests_to_run) { + // Sets up all environments beforehand. + repeater->OnEnvironmentsSetUpStart(*parent_); + ForEach(environments_, SetUpEnvironment); + repeater->OnEnvironmentsSetUpEnd(*parent_); + + // Runs the tests only if there was no fatal failure during global + // set-up. + if (!Test::HasFatalFailure()) { + for (int test_index = 0; test_index < total_test_case_count(); + test_index++) { + GetMutableTestCase(test_index)->Run(); + } + } + + // Tears down all environments in reverse order afterwards. + repeater->OnEnvironmentsTearDownStart(*parent_); + std::for_each(environments_.rbegin(), environments_.rend(), + TearDownEnvironment); + repeater->OnEnvironmentsTearDownEnd(*parent_); + } + + elapsed_time_ = GetTimeInMillis() - start; + + // Tells the unit test event listener that the tests have just finished. + repeater->OnTestIterationEnd(*parent_, i); + + // Gets the result and clears it. + if (!Passed()) { + failed = true; + } + + // Restores the original test order after the iteration. This + // allows the user to quickly repro a failure that happens in the + // N-th iteration without repeating the first (N - 1) iterations. + // This is not enclosed in "if (GTEST_FLAG(shuffle)) { ... }", in + // case the user somehow changes the value of the flag somewhere + // (it's always safe to unshuffle the tests). + UnshuffleTests(); + + if (GTEST_FLAG(shuffle)) { + // Picks a new random seed for each iteration. + random_seed_ = GetNextRandomSeed(random_seed_); + } + } + + repeater->OnTestProgramEnd(*parent_); + + return !failed; +} + +// Reads the GTEST_SHARD_STATUS_FILE environment variable, and creates the file +// if the variable is present. If a file already exists at this location, this +// function will write over it. If the variable is present, but the file cannot +// be created, prints an error and exits. +void WriteToShardStatusFileIfNeeded() { + const char* const test_shard_file = posix::GetEnv(kTestShardStatusFile); + if (test_shard_file != NULL) { + FILE* const file = posix::FOpen(test_shard_file, "w"); + if (file == NULL) { + ColoredPrintf(COLOR_RED, + "Could not write to the test shard status file \"%s\" " + "specified by the %s environment variable.\n", + test_shard_file, kTestShardStatusFile); + fflush(stdout); + exit(EXIT_FAILURE); + } + fclose(file); + } +} + +// Checks whether sharding is enabled by examining the relevant +// environment variable values. If the variables are present, +// but inconsistent (i.e., shard_index >= total_shards), prints +// an error and exits. If in_subprocess_for_death_test, sharding is +// disabled because it must only be applied to the original test +// process. Otherwise, we could filter out death tests we intended to execute. +bool ShouldShard(const char* total_shards_env, + const char* shard_index_env, + bool in_subprocess_for_death_test) { + if (in_subprocess_for_death_test) { + return false; + } + + const Int32 total_shards = Int32FromEnvOrDie(total_shards_env, -1); + const Int32 shard_index = Int32FromEnvOrDie(shard_index_env, -1); + + if (total_shards == -1 && shard_index == -1) { + return false; + } else if (total_shards == -1 && shard_index != -1) { + const Message msg = Message() + << "Invalid environment variables: you have " + << kTestShardIndex << " = " << shard_index + << ", but have left " << kTestTotalShards << " unset.\n"; + ColoredPrintf(COLOR_RED, msg.GetString().c_str()); + fflush(stdout); + exit(EXIT_FAILURE); + } else if (total_shards != -1 && shard_index == -1) { + const Message msg = Message() + << "Invalid environment variables: you have " + << kTestTotalShards << " = " << total_shards + << ", but have left " << kTestShardIndex << " unset.\n"; + ColoredPrintf(COLOR_RED, msg.GetString().c_str()); + fflush(stdout); + exit(EXIT_FAILURE); + } else if (shard_index < 0 || shard_index >= total_shards) { + const Message msg = Message() + << "Invalid environment variables: we require 0 <= " + << kTestShardIndex << " < " << kTestTotalShards + << ", but you have " << kTestShardIndex << "=" << shard_index + << ", " << kTestTotalShards << "=" << total_shards << ".\n"; + ColoredPrintf(COLOR_RED, msg.GetString().c_str()); + fflush(stdout); + exit(EXIT_FAILURE); + } + + return total_shards > 1; +} + +// Parses the environment variable var as an Int32. If it is unset, +// returns default_val. If it is not an Int32, prints an error +// and aborts. +Int32 Int32FromEnvOrDie(const char* var, Int32 default_val) { + const char* str_val = posix::GetEnv(var); + if (str_val == NULL) { + return default_val; + } + + Int32 result; + if (!ParseInt32(Message() << "The value of environment variable " << var, + str_val, &result)) { + exit(EXIT_FAILURE); + } + return result; +} + +// Given the total number of shards, the shard index, and the test id, +// returns true iff the test should be run on this shard. The test id is +// some arbitrary but unique non-negative integer assigned to each test +// method. Assumes that 0 <= shard_index < total_shards. +bool ShouldRunTestOnShard(int total_shards, int shard_index, int test_id) { + return (test_id % total_shards) == shard_index; +} + +// Compares the name of each test with the user-specified filter to +// decide whether the test should be run, then records the result in +// each TestCase and TestInfo object. +// If shard_tests == true, further filters tests based on sharding +// variables in the environment - see +// http://code.google.com/p/googletest/wiki/GoogleTestAdvancedGuide. +// Returns the number of tests that should run. +int UnitTestImpl::FilterTests(ReactionToSharding shard_tests) { + const Int32 total_shards = shard_tests == HONOR_SHARDING_PROTOCOL ? + Int32FromEnvOrDie(kTestTotalShards, -1) : -1; + const Int32 shard_index = shard_tests == HONOR_SHARDING_PROTOCOL ? + Int32FromEnvOrDie(kTestShardIndex, -1) : -1; + + // num_runnable_tests are the number of tests that will + // run across all shards (i.e., match filter and are not disabled). + // num_selected_tests are the number of tests to be run on + // this shard. + int num_runnable_tests = 0; + int num_selected_tests = 0; + for (size_t i = 0; i < test_cases_.size(); i++) { + TestCase* const test_case = test_cases_[i]; + const String &test_case_name = test_case->name(); + test_case->set_should_run(false); + + for (size_t j = 0; j < test_case->test_info_list().size(); j++) { + TestInfo* const test_info = test_case->test_info_list()[j]; + const String test_name(test_info->name()); + // A test is disabled if test case name or test name matches + // kDisableTestFilter. + const bool is_disabled = + internal::UnitTestOptions::MatchesFilter(test_case_name, + kDisableTestFilter) || + internal::UnitTestOptions::MatchesFilter(test_name, + kDisableTestFilter); + test_info->is_disabled_ = is_disabled; + + const bool matches_filter = + internal::UnitTestOptions::FilterMatchesTest(test_case_name, + test_name); + test_info->matches_filter_ = matches_filter; + + const bool is_runnable = + (GTEST_FLAG(also_run_disabled_tests) || !is_disabled) && + matches_filter; + + const bool is_selected = is_runnable && + (shard_tests == IGNORE_SHARDING_PROTOCOL || + ShouldRunTestOnShard(total_shards, shard_index, + num_runnable_tests)); + + num_runnable_tests += is_runnable; + num_selected_tests += is_selected; + + test_info->should_run_ = is_selected; + test_case->set_should_run(test_case->should_run() || is_selected); + } + } + return num_selected_tests; +} + +// Prints the names of the tests matching the user-specified filter flag. +void UnitTestImpl::ListTestsMatchingFilter() { + for (size_t i = 0; i < test_cases_.size(); i++) { + const TestCase* const test_case = test_cases_[i]; + bool printed_test_case_name = false; + + for (size_t j = 0; j < test_case->test_info_list().size(); j++) { + const TestInfo* const test_info = + test_case->test_info_list()[j]; + if (test_info->matches_filter_) { + if (!printed_test_case_name) { + printed_test_case_name = true; + printf("%s.\n", test_case->name()); + } + printf(" %s\n", test_info->name()); + } + } + } + fflush(stdout); +} + +// Sets the OS stack trace getter. +// +// Does nothing if the input and the current OS stack trace getter are +// the same; otherwise, deletes the old getter and makes the input the +// current getter. +void UnitTestImpl::set_os_stack_trace_getter( + OsStackTraceGetterInterface* getter) { + if (os_stack_trace_getter_ != getter) { + delete os_stack_trace_getter_; + os_stack_trace_getter_ = getter; + } +} + +// Returns the current OS stack trace getter if it is not NULL; +// otherwise, creates an OsStackTraceGetter, makes it the current +// getter, and returns it. +OsStackTraceGetterInterface* UnitTestImpl::os_stack_trace_getter() { + if (os_stack_trace_getter_ == NULL) { + os_stack_trace_getter_ = new OsStackTraceGetter; + } + + return os_stack_trace_getter_; +} + +// Returns the TestResult for the test that's currently running, or +// the TestResult for the ad hoc test if no test is running. +TestResult* UnitTestImpl::current_test_result() { + return current_test_info_ ? + &(current_test_info_->result_) : &ad_hoc_test_result_; +} + +// Shuffles all test cases, and the tests within each test case, +// making sure that death tests are still run first. +void UnitTestImpl::ShuffleTests() { + // Shuffles the death test cases. + ShuffleRange(random(), 0, last_death_test_case_ + 1, &test_case_indices_); + + // Shuffles the non-death test cases. + ShuffleRange(random(), last_death_test_case_ + 1, + static_cast(test_cases_.size()), &test_case_indices_); + + // Shuffles the tests inside each test case. + for (size_t i = 0; i < test_cases_.size(); i++) { + test_cases_[i]->ShuffleTests(random()); + } +} + +// Restores the test cases and tests to their order before the first shuffle. +void UnitTestImpl::UnshuffleTests() { + for (size_t i = 0; i < test_cases_.size(); i++) { + // Unshuffles the tests in each test case. + test_cases_[i]->UnshuffleTests(); + // Resets the index of each test case. + test_case_indices_[i] = static_cast(i); + } +} + +// Returns the current OS stack trace as a String. +// +// The maximum number of stack frames to be included is specified by +// the gtest_stack_trace_depth flag. The skip_count parameter +// specifies the number of top frames to be skipped, which doesn't +// count against the number of frames to be included. +// +// For example, if Foo() calls Bar(), which in turn calls +// GetCurrentOsStackTraceExceptTop(..., 1), Foo() will be included in +// the trace but Bar() and GetCurrentOsStackTraceExceptTop() won't. +String GetCurrentOsStackTraceExceptTop(UnitTest* /*unit_test*/, + int skip_count) { + // We pass skip_count + 1 to skip this wrapper function in addition + // to what the user really wants to skip. + return GetUnitTestImpl()->CurrentOsStackTraceExceptTop(skip_count + 1); +} + +// Used by the GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_ macro to +// suppress unreachable code warnings. +namespace { +class ClassUniqueToAlwaysTrue {}; +} + +bool IsTrue(bool condition) { return condition; } + +bool AlwaysTrue() { +#if GTEST_HAS_EXCEPTIONS + // This condition is always false so AlwaysTrue() never actually throws, + // but it makes the compiler think that it may throw. + if (IsTrue(false)) + throw ClassUniqueToAlwaysTrue(); +#endif // GTEST_HAS_EXCEPTIONS + return true; +} + +// If *pstr starts with the given prefix, modifies *pstr to be right +// past the prefix and returns true; otherwise leaves *pstr unchanged +// and returns false. None of pstr, *pstr, and prefix can be NULL. +bool SkipPrefix(const char* prefix, const char** pstr) { + const size_t prefix_len = strlen(prefix); + if (strncmp(*pstr, prefix, prefix_len) == 0) { + *pstr += prefix_len; + return true; + } + return false; +} + +// Parses a string as a command line flag. The string should have +// the format "--flag=value". When def_optional is true, the "=value" +// part can be omitted. +// +// Returns the value of the flag, or NULL if the parsing failed. +const char* ParseFlagValue(const char* str, + const char* flag, + bool def_optional) { + // str and flag must not be NULL. + if (str == NULL || flag == NULL) return NULL; + + // The flag must start with "--" followed by GTEST_FLAG_PREFIX_. + const String flag_str = String::Format("--%s%s", GTEST_FLAG_PREFIX_, flag); + const size_t flag_len = flag_str.length(); + if (strncmp(str, flag_str.c_str(), flag_len) != 0) return NULL; + + // Skips the flag name. + const char* flag_end = str + flag_len; + + // When def_optional is true, it's OK to not have a "=value" part. + if (def_optional && (flag_end[0] == '\0')) { + return flag_end; + } + + // If def_optional is true and there are more characters after the + // flag name, or if def_optional is false, there must be a '=' after + // the flag name. + if (flag_end[0] != '=') return NULL; + + // Returns the string after "=". + return flag_end + 1; +} + +// Parses a string for a bool flag, in the form of either +// "--flag=value" or "--flag". +// +// In the former case, the value is taken as true as long as it does +// not start with '0', 'f', or 'F'. +// +// In the latter case, the value is taken as true. +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +bool ParseBoolFlag(const char* str, const char* flag, bool* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseFlagValue(str, flag, true); + + // Aborts if the parsing failed. + if (value_str == NULL) return false; + + // Converts the string value to a bool. + *value = !(*value_str == '0' || *value_str == 'f' || *value_str == 'F'); + return true; +} + +// Parses a string for an Int32 flag, in the form of +// "--flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +bool ParseInt32Flag(const char* str, const char* flag, Int32* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseFlagValue(str, flag, false); + + // Aborts if the parsing failed. + if (value_str == NULL) return false; + + // Sets *value to the value of the flag. + return ParseInt32(Message() << "The value of flag --" << flag, + value_str, value); +} + +// Parses a string for a string flag, in the form of +// "--flag=value". +// +// On success, stores the value of the flag in *value, and returns +// true. On failure, returns false without changing *value. +bool ParseStringFlag(const char* str, const char* flag, String* value) { + // Gets the value of the flag as a string. + const char* const value_str = ParseFlagValue(str, flag, false); + + // Aborts if the parsing failed. + if (value_str == NULL) return false; + + // Sets *value to the value of the flag. + *value = value_str; + return true; +} + +// Determines whether a string has a prefix that Google Test uses for its +// flags, i.e., starts with GTEST_FLAG_PREFIX_ or GTEST_FLAG_PREFIX_DASH_. +// If Google Test detects that a command line flag has its prefix but is not +// recognized, it will print its help message. Flags starting with +// GTEST_INTERNAL_PREFIX_ followed by "internal_" are considered Google Test +// internal flags and do not trigger the help message. +static bool HasGoogleTestFlagPrefix(const char* str) { + return (SkipPrefix("--", &str) || + SkipPrefix("-", &str) || + SkipPrefix("/", &str)) && + !SkipPrefix(GTEST_FLAG_PREFIX_ "internal_", &str) && + (SkipPrefix(GTEST_FLAG_PREFIX_, &str) || + SkipPrefix(GTEST_FLAG_PREFIX_DASH_, &str)); +} + +// Prints a string containing code-encoded text. The following escape +// sequences can be used in the string to control the text color: +// +// @@ prints a single '@' character. +// @R changes the color to red. +// @G changes the color to green. +// @Y changes the color to yellow. +// @D changes to the default terminal text color. +// +// TODO(wan@google.com): Write tests for this once we add stdout +// capturing to Google Test. +static void PrintColorEncoded(const char* str) { + GTestColor color = COLOR_DEFAULT; // The current color. + + // Conceptually, we split the string into segments divided by escape + // sequences. Then we print one segment at a time. At the end of + // each iteration, the str pointer advances to the beginning of the + // next segment. + for (;;) { + const char* p = strchr(str, '@'); + if (p == NULL) { + ColoredPrintf(color, "%s", str); + return; + } + + ColoredPrintf(color, "%s", String(str, p - str).c_str()); + + const char ch = p[1]; + str = p + 2; + if (ch == '@') { + ColoredPrintf(color, "@"); + } else if (ch == 'D') { + color = COLOR_DEFAULT; + } else if (ch == 'R') { + color = COLOR_RED; + } else if (ch == 'G') { + color = COLOR_GREEN; + } else if (ch == 'Y') { + color = COLOR_YELLOW; + } else { + --str; + } + } +} + +static const char kColorEncodedHelpMessage[] = +"This program contains tests written using " GTEST_NAME_ ". You can use the\n" +"following command line flags to control its behavior:\n" +"\n" +"Test Selection:\n" +" @G--" GTEST_FLAG_PREFIX_ "list_tests@D\n" +" List the names of all tests instead of running them. The name of\n" +" TEST(Foo, Bar) is \"Foo.Bar\".\n" +" @G--" GTEST_FLAG_PREFIX_ "filter=@YPOSTIVE_PATTERNS" + "[@G-@YNEGATIVE_PATTERNS]@D\n" +" Run only the tests whose name matches one of the positive patterns but\n" +" none of the negative patterns. '?' matches any single character; '*'\n" +" matches any substring; ':' separates two patterns.\n" +" @G--" GTEST_FLAG_PREFIX_ "also_run_disabled_tests@D\n" +" Run all disabled tests too.\n" +"\n" +"Test Execution:\n" +" @G--" GTEST_FLAG_PREFIX_ "repeat=@Y[COUNT]@D\n" +" Run the tests repeatedly; use a negative count to repeat forever.\n" +" @G--" GTEST_FLAG_PREFIX_ "shuffle@D\n" +" Randomize tests' orders on every iteration.\n" +" @G--" GTEST_FLAG_PREFIX_ "random_seed=@Y[NUMBER]@D\n" +" Random number seed to use for shuffling test orders (between 1 and\n" +" 99999, or 0 to use a seed based on the current time).\n" +"\n" +"Test Output:\n" +" @G--" GTEST_FLAG_PREFIX_ "color=@Y(@Gyes@Y|@Gno@Y|@Gauto@Y)@D\n" +" Enable/disable colored output. The default is @Gauto@D.\n" +" -@G-" GTEST_FLAG_PREFIX_ "print_time=0@D\n" +" Don't print the elapsed time of each test.\n" +" @G--" GTEST_FLAG_PREFIX_ "output=xml@Y[@G:@YDIRECTORY_PATH@G" + GTEST_PATH_SEP_ "@Y|@G:@YFILE_PATH]@D\n" +" Generate an XML report in the given directory or with the given file\n" +" name. @YFILE_PATH@D defaults to @Gtest_details.xml@D.\n" +#if GTEST_CAN_STREAM_RESULTS_ +" @G--" GTEST_FLAG_PREFIX_ "stream_result_to=@YHOST@G:@YPORT@D\n" +" Stream test results to the given server.\n" +#endif // GTEST_CAN_STREAM_RESULTS_ +"\n" +"Assertion Behavior:\n" +#if GTEST_HAS_DEATH_TEST && !GTEST_OS_WINDOWS +" @G--" GTEST_FLAG_PREFIX_ "death_test_style=@Y(@Gfast@Y|@Gthreadsafe@Y)@D\n" +" Set the default death test style.\n" +#endif // GTEST_HAS_DEATH_TEST && !GTEST_OS_WINDOWS +" @G--" GTEST_FLAG_PREFIX_ "break_on_failure@D\n" +" Turn assertion failures into debugger break-points.\n" +" @G--" GTEST_FLAG_PREFIX_ "throw_on_failure@D\n" +" Turn assertion failures into C++ exceptions.\n" +" @G--" GTEST_FLAG_PREFIX_ "catch_exceptions=0@D\n" +" Do not report exceptions as test failures. Instead, allow them\n" +" to crash the program or throw a pop-up (on Windows).\n" +"\n" +"Except for @G--" GTEST_FLAG_PREFIX_ "list_tests@D, you can alternatively set " + "the corresponding\n" +"environment variable of a flag (all letters in upper-case). For example, to\n" +"disable colored text output, you can either specify @G--" GTEST_FLAG_PREFIX_ + "color=no@D or set\n" +"the @G" GTEST_FLAG_PREFIX_UPPER_ "COLOR@D environment variable to @Gno@D.\n" +"\n" +"For more information, please read the " GTEST_NAME_ " documentation at\n" +"@G" GTEST_PROJECT_URL_ "@D. If you find a bug in " GTEST_NAME_ "\n" +"(not one in your own code or tests), please report it to\n" +"@G<" GTEST_DEV_EMAIL_ ">@D.\n"; + +// Parses the command line for Google Test flags, without initializing +// other parts of Google Test. The type parameter CharType can be +// instantiated to either char or wchar_t. +template +void ParseGoogleTestFlagsOnlyImpl(int* argc, CharType** argv) { + for (int i = 1; i < *argc; i++) { + const String arg_string = StreamableToString(argv[i]); + const char* const arg = arg_string.c_str(); + + using internal::ParseBoolFlag; + using internal::ParseInt32Flag; + using internal::ParseStringFlag; + + // Do we see a Google Test flag? + if (ParseBoolFlag(arg, kAlsoRunDisabledTestsFlag, + >EST_FLAG(also_run_disabled_tests)) || + ParseBoolFlag(arg, kBreakOnFailureFlag, + >EST_FLAG(break_on_failure)) || + ParseBoolFlag(arg, kCatchExceptionsFlag, + >EST_FLAG(catch_exceptions)) || + ParseStringFlag(arg, kColorFlag, >EST_FLAG(color)) || + ParseStringFlag(arg, kDeathTestStyleFlag, + >EST_FLAG(death_test_style)) || + ParseBoolFlag(arg, kDeathTestUseFork, + >EST_FLAG(death_test_use_fork)) || + ParseStringFlag(arg, kFilterFlag, >EST_FLAG(filter)) || + ParseStringFlag(arg, kInternalRunDeathTestFlag, + >EST_FLAG(internal_run_death_test)) || + ParseBoolFlag(arg, kListTestsFlag, >EST_FLAG(list_tests)) || + ParseStringFlag(arg, kOutputFlag, >EST_FLAG(output)) || + ParseBoolFlag(arg, kPrintTimeFlag, >EST_FLAG(print_time)) || + ParseInt32Flag(arg, kRandomSeedFlag, >EST_FLAG(random_seed)) || + ParseInt32Flag(arg, kRepeatFlag, >EST_FLAG(repeat)) || + ParseBoolFlag(arg, kShuffleFlag, >EST_FLAG(shuffle)) || + ParseInt32Flag(arg, kStackTraceDepthFlag, + >EST_FLAG(stack_trace_depth)) || + ParseStringFlag(arg, kStreamResultToFlag, + >EST_FLAG(stream_result_to)) || + ParseBoolFlag(arg, kThrowOnFailureFlag, + >EST_FLAG(throw_on_failure)) + ) { + // Yes. Shift the remainder of the argv list left by one. Note + // that argv has (*argc + 1) elements, the last one always being + // NULL. The following loop moves the trailing NULL element as + // well. + for (int j = i; j != *argc; j++) { + argv[j] = argv[j + 1]; + } + + // Decrements the argument count. + (*argc)--; + + // We also need to decrement the iterator as we just removed + // an element. + i--; + } else if (arg_string == "--help" || arg_string == "-h" || + arg_string == "-?" || arg_string == "/?" || + HasGoogleTestFlagPrefix(arg)) { + // Both help flag and unrecognized Google Test flags (excluding + // internal ones) trigger help display. + g_help_flag = true; + } + } + + if (g_help_flag) { + // We print the help here instead of in RUN_ALL_TESTS(), as the + // latter may not be called at all if the user is using Google + // Test with another testing framework. + PrintColorEncoded(kColorEncodedHelpMessage); + } +} + +// Parses the command line for Google Test flags, without initializing +// other parts of Google Test. +void ParseGoogleTestFlagsOnly(int* argc, char** argv) { + ParseGoogleTestFlagsOnlyImpl(argc, argv); +} +void ParseGoogleTestFlagsOnly(int* argc, wchar_t** argv) { + ParseGoogleTestFlagsOnlyImpl(argc, argv); +} + +// The internal implementation of InitGoogleTest(). +// +// The type parameter CharType can be instantiated to either char or +// wchar_t. +template +void InitGoogleTestImpl(int* argc, CharType** argv) { + g_init_gtest_count++; + + // We don't want to run the initialization code twice. + if (g_init_gtest_count != 1) return; + + if (*argc <= 0) return; + + internal::g_executable_path = internal::StreamableToString(argv[0]); + +#if GTEST_HAS_DEATH_TEST + + g_argvs.clear(); + for (int i = 0; i != *argc; i++) { + g_argvs.push_back(StreamableToString(argv[i])); + } + +#endif // GTEST_HAS_DEATH_TEST + + ParseGoogleTestFlagsOnly(argc, argv); + GetUnitTestImpl()->PostFlagParsingInit(); +} + +} // namespace internal + +// Initializes Google Test. This must be called before calling +// RUN_ALL_TESTS(). In particular, it parses a command line for the +// flags that Google Test recognizes. Whenever a Google Test flag is +// seen, it is removed from argv, and *argc is decremented. +// +// No value is returned. Instead, the Google Test flag variables are +// updated. +// +// Calling the function for the second time has no user-visible effect. +void InitGoogleTest(int* argc, char** argv) { + internal::InitGoogleTestImpl(argc, argv); +} + +// This overloaded version can be used in Windows programs compiled in +// UNICODE mode. +void InitGoogleTest(int* argc, wchar_t** argv) { + internal::InitGoogleTestImpl(argc, argv); +} + +} // namespace testing +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan), vladl@google.com (Vlad Losev) +// +// This file implements death tests. + + +#if GTEST_HAS_DEATH_TEST + +# if GTEST_OS_MAC +# include +# endif // GTEST_OS_MAC + +# include +# include +# include +# include + +# if GTEST_OS_WINDOWS +# include +# else +# include +# include +# endif // GTEST_OS_WINDOWS + +#endif // GTEST_HAS_DEATH_TEST + + +// Indicates that this translation unit is part of Google Test's +// implementation. It must come before gtest-internal-inl.h is +// included, or there will be a compiler error. This trick is to +// prevent a user from accidentally including gtest-internal-inl.h in +// his code. +#define GTEST_IMPLEMENTATION_ 1 +#undef GTEST_IMPLEMENTATION_ + +namespace testing { + +// Constants. + +// The default death test style. +static const char kDefaultDeathTestStyle[] = "fast"; + +GTEST_DEFINE_string_( + death_test_style, + internal::StringFromGTestEnv("death_test_style", kDefaultDeathTestStyle), + "Indicates how to run a death test in a forked child process: " + "\"threadsafe\" (child process re-executes the test binary " + "from the beginning, running only the specific death test) or " + "\"fast\" (child process runs the death test immediately " + "after forking)."); + +GTEST_DEFINE_bool_( + death_test_use_fork, + internal::BoolFromGTestEnv("death_test_use_fork", false), + "Instructs to use fork()/_exit() instead of clone() in death tests. " + "Ignored and always uses fork() on POSIX systems where clone() is not " + "implemented. Useful when running under valgrind or similar tools if " + "those do not support clone(). Valgrind 3.3.1 will just fail if " + "it sees an unsupported combination of clone() flags. " + "It is not recommended to use this flag w/o valgrind though it will " + "work in 99% of the cases. Once valgrind is fixed, this flag will " + "most likely be removed."); + +namespace internal { +GTEST_DEFINE_string_( + internal_run_death_test, "", + "Indicates the file, line number, temporal index of " + "the single death test to run, and a file descriptor to " + "which a success code may be sent, all separated by " + "colons. This flag is specified if and only if the current " + "process is a sub-process launched for running a thread-safe " + "death test. FOR INTERNAL USE ONLY."); +} // namespace internal + +#if GTEST_HAS_DEATH_TEST + +// ExitedWithCode constructor. +ExitedWithCode::ExitedWithCode(int exit_code) : exit_code_(exit_code) { +} + +// ExitedWithCode function-call operator. +bool ExitedWithCode::operator()(int exit_status) const { +# if GTEST_OS_WINDOWS + + return exit_status == exit_code_; + +# else + + return WIFEXITED(exit_status) && WEXITSTATUS(exit_status) == exit_code_; + +# endif // GTEST_OS_WINDOWS +} + +# if !GTEST_OS_WINDOWS +// KilledBySignal constructor. +KilledBySignal::KilledBySignal(int signum) : signum_(signum) { +} + +// KilledBySignal function-call operator. +bool KilledBySignal::operator()(int exit_status) const { + return WIFSIGNALED(exit_status) && WTERMSIG(exit_status) == signum_; +} +# endif // !GTEST_OS_WINDOWS + +namespace internal { + +// Utilities needed for death tests. + +// Generates a textual description of a given exit code, in the format +// specified by wait(2). +static String ExitSummary(int exit_code) { + Message m; + +# if GTEST_OS_WINDOWS + + m << "Exited with exit status " << exit_code; + +# else + + if (WIFEXITED(exit_code)) { + m << "Exited with exit status " << WEXITSTATUS(exit_code); + } else if (WIFSIGNALED(exit_code)) { + m << "Terminated by signal " << WTERMSIG(exit_code); + } +# ifdef WCOREDUMP + if (WCOREDUMP(exit_code)) { + m << " (core dumped)"; + } +# endif +# endif // GTEST_OS_WINDOWS + + return m.GetString(); +} + +// Returns true if exit_status describes a process that was terminated +// by a signal, or exited normally with a nonzero exit code. +bool ExitedUnsuccessfully(int exit_status) { + return !ExitedWithCode(0)(exit_status); +} + +# if !GTEST_OS_WINDOWS +// Generates a textual failure message when a death test finds more than +// one thread running, or cannot determine the number of threads, prior +// to executing the given statement. It is the responsibility of the +// caller not to pass a thread_count of 1. +static String DeathTestThreadWarning(size_t thread_count) { + Message msg; + msg << "Death tests use fork(), which is unsafe particularly" + << " in a threaded context. For this test, " << GTEST_NAME_ << " "; + if (thread_count == 0) + msg << "couldn't detect the number of threads."; + else + msg << "detected " << thread_count << " threads."; + return msg.GetString(); +} +# endif // !GTEST_OS_WINDOWS + +// Flag characters for reporting a death test that did not die. +static const char kDeathTestLived = 'L'; +static const char kDeathTestReturned = 'R'; +static const char kDeathTestThrew = 'T'; +static const char kDeathTestInternalError = 'I'; + +// An enumeration describing all of the possible ways that a death test can +// conclude. DIED means that the process died while executing the test +// code; LIVED means that process lived beyond the end of the test code; +// RETURNED means that the test statement attempted to execute a return +// statement, which is not allowed; THREW means that the test statement +// returned control by throwing an exception. IN_PROGRESS means the test +// has not yet concluded. +// TODO(vladl@google.com): Unify names and possibly values for +// AbortReason, DeathTestOutcome, and flag characters above. +enum DeathTestOutcome { IN_PROGRESS, DIED, LIVED, RETURNED, THREW }; + +// Routine for aborting the program which is safe to call from an +// exec-style death test child process, in which case the error +// message is propagated back to the parent process. Otherwise, the +// message is simply printed to stderr. In either case, the program +// then exits with status 1. +void DeathTestAbort(const String& message) { + // On a POSIX system, this function may be called from a threadsafe-style + // death test child process, which operates on a very small stack. Use + // the heap for any additional non-minuscule memory requirements. + const InternalRunDeathTestFlag* const flag = + GetUnitTestImpl()->internal_run_death_test_flag(); + if (flag != NULL) { + FILE* parent = posix::FDOpen(flag->write_fd(), "w"); + fputc(kDeathTestInternalError, parent); + fprintf(parent, "%s", message.c_str()); + fflush(parent); + _exit(1); + } else { + fprintf(stderr, "%s", message.c_str()); + fflush(stderr); + posix::Abort(); + } +} + +// A replacement for CHECK that calls DeathTestAbort if the assertion +// fails. +# define GTEST_DEATH_TEST_CHECK_(expression) \ + do { \ + if (!::testing::internal::IsTrue(expression)) { \ + DeathTestAbort(::testing::internal::String::Format( \ + "CHECK failed: File %s, line %d: %s", \ + __FILE__, __LINE__, #expression)); \ + } \ + } while (::testing::internal::AlwaysFalse()) + +// This macro is similar to GTEST_DEATH_TEST_CHECK_, but it is meant for +// evaluating any system call that fulfills two conditions: it must return +// -1 on failure, and set errno to EINTR when it is interrupted and +// should be tried again. The macro expands to a loop that repeatedly +// evaluates the expression as long as it evaluates to -1 and sets +// errno to EINTR. If the expression evaluates to -1 but errno is +// something other than EINTR, DeathTestAbort is called. +# define GTEST_DEATH_TEST_CHECK_SYSCALL_(expression) \ + do { \ + int gtest_retval; \ + do { \ + gtest_retval = (expression); \ + } while (gtest_retval == -1 && errno == EINTR); \ + if (gtest_retval == -1) { \ + DeathTestAbort(::testing::internal::String::Format( \ + "CHECK failed: File %s, line %d: %s != -1", \ + __FILE__, __LINE__, #expression)); \ + } \ + } while (::testing::internal::AlwaysFalse()) + +// Returns the message describing the last system error in errno. +String GetLastErrnoDescription() { + return String(errno == 0 ? "" : posix::StrError(errno)); +} + +// This is called from a death test parent process to read a failure +// message from the death test child process and log it with the FATAL +// severity. On Windows, the message is read from a pipe handle. On other +// platforms, it is read from a file descriptor. +static void FailFromInternalError(int fd) { + Message error; + char buffer[256]; + int num_read; + + do { + while ((num_read = posix::Read(fd, buffer, 255)) > 0) { + buffer[num_read] = '\0'; + error << buffer; + } + } while (num_read == -1 && errno == EINTR); + + if (num_read == 0) { + GTEST_LOG_(FATAL) << error.GetString(); + } else { + const int last_error = errno; + GTEST_LOG_(FATAL) << "Error while reading death test internal: " + << GetLastErrnoDescription() << " [" << last_error << "]"; + } +} + +// Death test constructor. Increments the running death test count +// for the current test. +DeathTest::DeathTest() { + TestInfo* const info = GetUnitTestImpl()->current_test_info(); + if (info == NULL) { + DeathTestAbort("Cannot run a death test outside of a TEST or " + "TEST_F construct"); + } +} + +// Creates and returns a death test by dispatching to the current +// death test factory. +bool DeathTest::Create(const char* statement, const RE* regex, + const char* file, int line, DeathTest** test) { + return GetUnitTestImpl()->death_test_factory()->Create( + statement, regex, file, line, test); +} + +const char* DeathTest::LastMessage() { + return last_death_test_message_.c_str(); +} + +void DeathTest::set_last_death_test_message(const String& message) { + last_death_test_message_ = message; +} + +String DeathTest::last_death_test_message_; + +// Provides cross platform implementation for some death functionality. +class DeathTestImpl : public DeathTest { + protected: + DeathTestImpl(const char* a_statement, const RE* a_regex) + : statement_(a_statement), + regex_(a_regex), + spawned_(false), + status_(-1), + outcome_(IN_PROGRESS), + read_fd_(-1), + write_fd_(-1) {} + + // read_fd_ is expected to be closed and cleared by a derived class. + ~DeathTestImpl() { GTEST_DEATH_TEST_CHECK_(read_fd_ == -1); } + + void Abort(AbortReason reason); + virtual bool Passed(bool status_ok); + + const char* statement() const { return statement_; } + const RE* regex() const { return regex_; } + bool spawned() const { return spawned_; } + void set_spawned(bool is_spawned) { spawned_ = is_spawned; } + int status() const { return status_; } + void set_status(int a_status) { status_ = a_status; } + DeathTestOutcome outcome() const { return outcome_; } + void set_outcome(DeathTestOutcome an_outcome) { outcome_ = an_outcome; } + int read_fd() const { return read_fd_; } + void set_read_fd(int fd) { read_fd_ = fd; } + int write_fd() const { return write_fd_; } + void set_write_fd(int fd) { write_fd_ = fd; } + + // Called in the parent process only. Reads the result code of the death + // test child process via a pipe, interprets it to set the outcome_ + // member, and closes read_fd_. Outputs diagnostics and terminates in + // case of unexpected codes. + void ReadAndInterpretStatusByte(); + + private: + // The textual content of the code this object is testing. This class + // doesn't own this string and should not attempt to delete it. + const char* const statement_; + // The regular expression which test output must match. DeathTestImpl + // doesn't own this object and should not attempt to delete it. + const RE* const regex_; + // True if the death test child process has been successfully spawned. + bool spawned_; + // The exit status of the child process. + int status_; + // How the death test concluded. + DeathTestOutcome outcome_; + // Descriptor to the read end of the pipe to the child process. It is + // always -1 in the child process. The child keeps its write end of the + // pipe in write_fd_. + int read_fd_; + // Descriptor to the child's write end of the pipe to the parent process. + // It is always -1 in the parent process. The parent keeps its end of the + // pipe in read_fd_. + int write_fd_; +}; + +// Called in the parent process only. Reads the result code of the death +// test child process via a pipe, interprets it to set the outcome_ +// member, and closes read_fd_. Outputs diagnostics and terminates in +// case of unexpected codes. +void DeathTestImpl::ReadAndInterpretStatusByte() { + char flag; + int bytes_read; + + // The read() here blocks until data is available (signifying the + // failure of the death test) or until the pipe is closed (signifying + // its success), so it's okay to call this in the parent before + // the child process has exited. + do { + bytes_read = posix::Read(read_fd(), &flag, 1); + } while (bytes_read == -1 && errno == EINTR); + + if (bytes_read == 0) { + set_outcome(DIED); + } else if (bytes_read == 1) { + switch (flag) { + case kDeathTestReturned: + set_outcome(RETURNED); + break; + case kDeathTestThrew: + set_outcome(THREW); + break; + case kDeathTestLived: + set_outcome(LIVED); + break; + case kDeathTestInternalError: + FailFromInternalError(read_fd()); // Does not return. + break; + default: + GTEST_LOG_(FATAL) << "Death test child process reported " + << "unexpected status byte (" + << static_cast(flag) << ")"; + } + } else { + GTEST_LOG_(FATAL) << "Read from death test child process failed: " + << GetLastErrnoDescription(); + } + GTEST_DEATH_TEST_CHECK_SYSCALL_(posix::Close(read_fd())); + set_read_fd(-1); +} + +// Signals that the death test code which should have exited, didn't. +// Should be called only in a death test child process. +// Writes a status byte to the child's status file descriptor, then +// calls _exit(1). +void DeathTestImpl::Abort(AbortReason reason) { + // The parent process considers the death test to be a failure if + // it finds any data in our pipe. So, here we write a single flag byte + // to the pipe, then exit. + const char status_ch = + reason == TEST_DID_NOT_DIE ? kDeathTestLived : + reason == TEST_THREW_EXCEPTION ? kDeathTestThrew : kDeathTestReturned; + + GTEST_DEATH_TEST_CHECK_SYSCALL_(posix::Write(write_fd(), &status_ch, 1)); + // We are leaking the descriptor here because on some platforms (i.e., + // when built as Windows DLL), destructors of global objects will still + // run after calling _exit(). On such systems, write_fd_ will be + // indirectly closed from the destructor of UnitTestImpl, causing double + // close if it is also closed here. On debug configurations, double close + // may assert. As there are no in-process buffers to flush here, we are + // relying on the OS to close the descriptor after the process terminates + // when the destructors are not run. + _exit(1); // Exits w/o any normal exit hooks (we were supposed to crash) +} + +// Returns an indented copy of stderr output for a death test. +// This makes distinguishing death test output lines from regular log lines +// much easier. +static ::std::string FormatDeathTestOutput(const ::std::string& output) { + ::std::string ret; + for (size_t at = 0; ; ) { + const size_t line_end = output.find('\n', at); + ret += "[ DEATH ] "; + if (line_end == ::std::string::npos) { + ret += output.substr(at); + break; + } + ret += output.substr(at, line_end + 1 - at); + at = line_end + 1; + } + return ret; +} + +// Assesses the success or failure of a death test, using both private +// members which have previously been set, and one argument: +// +// Private data members: +// outcome: An enumeration describing how the death test +// concluded: DIED, LIVED, THREW, or RETURNED. The death test +// fails in the latter three cases. +// status: The exit status of the child process. On *nix, it is in the +// in the format specified by wait(2). On Windows, this is the +// value supplied to the ExitProcess() API or a numeric code +// of the exception that terminated the program. +// regex: A regular expression object to be applied to +// the test's captured standard error output; the death test +// fails if it does not match. +// +// Argument: +// status_ok: true if exit_status is acceptable in the context of +// this particular death test, which fails if it is false +// +// Returns true iff all of the above conditions are met. Otherwise, the +// first failing condition, in the order given above, is the one that is +// reported. Also sets the last death test message string. +bool DeathTestImpl::Passed(bool status_ok) { + if (!spawned()) + return false; + + const String error_message = GetCapturedStderr(); + + bool success = false; + Message buffer; + + buffer << "Death test: " << statement() << "\n"; + switch (outcome()) { + case LIVED: + buffer << " Result: failed to die.\n" + << " Error msg:\n" << FormatDeathTestOutput(error_message); + break; + case THREW: + buffer << " Result: threw an exception.\n" + << " Error msg:\n" << FormatDeathTestOutput(error_message); + break; + case RETURNED: + buffer << " Result: illegal return in test statement.\n" + << " Error msg:\n" << FormatDeathTestOutput(error_message); + break; + case DIED: + if (status_ok) { + const bool matched = RE::PartialMatch(error_message.c_str(), *regex()); + if (matched) { + success = true; + } else { + buffer << " Result: died but not with expected error.\n" + << " Expected: " << regex()->pattern() << "\n" + << "Actual msg:\n" << FormatDeathTestOutput(error_message); + } + } else { + buffer << " Result: died but not with expected exit code:\n" + << " " << ExitSummary(status()) << "\n" + << "Actual msg:\n" << FormatDeathTestOutput(error_message); + } + break; + case IN_PROGRESS: + default: + GTEST_LOG_(FATAL) + << "DeathTest::Passed somehow called before conclusion of test"; + } + + DeathTest::set_last_death_test_message(buffer.GetString()); + return success; +} + +# if GTEST_OS_WINDOWS +// WindowsDeathTest implements death tests on Windows. Due to the +// specifics of starting new processes on Windows, death tests there are +// always threadsafe, and Google Test considers the +// --gtest_death_test_style=fast setting to be equivalent to +// --gtest_death_test_style=threadsafe there. +// +// A few implementation notes: Like the Linux version, the Windows +// implementation uses pipes for child-to-parent communication. But due to +// the specifics of pipes on Windows, some extra steps are required: +// +// 1. The parent creates a communication pipe and stores handles to both +// ends of it. +// 2. The parent starts the child and provides it with the information +// necessary to acquire the handle to the write end of the pipe. +// 3. The child acquires the write end of the pipe and signals the parent +// using a Windows event. +// 4. Now the parent can release the write end of the pipe on its side. If +// this is done before step 3, the object's reference count goes down to +// 0 and it is destroyed, preventing the child from acquiring it. The +// parent now has to release it, or read operations on the read end of +// the pipe will not return when the child terminates. +// 5. The parent reads child's output through the pipe (outcome code and +// any possible error messages) from the pipe, and its stderr and then +// determines whether to fail the test. +// +// Note: to distinguish Win32 API calls from the local method and function +// calls, the former are explicitly resolved in the global namespace. +// +class WindowsDeathTest : public DeathTestImpl { + public: + WindowsDeathTest(const char* a_statement, + const RE* a_regex, + const char* file, + int line) + : DeathTestImpl(a_statement, a_regex), file_(file), line_(line) {} + + // All of these virtual functions are inherited from DeathTest. + virtual int Wait(); + virtual TestRole AssumeRole(); + + private: + // The name of the file in which the death test is located. + const char* const file_; + // The line number on which the death test is located. + const int line_; + // Handle to the write end of the pipe to the child process. + AutoHandle write_handle_; + // Child process handle. + AutoHandle child_handle_; + // Event the child process uses to signal the parent that it has + // acquired the handle to the write end of the pipe. After seeing this + // event the parent can release its own handles to make sure its + // ReadFile() calls return when the child terminates. + AutoHandle event_handle_; +}; + +// Waits for the child in a death test to exit, returning its exit +// status, or 0 if no child process exists. As a side effect, sets the +// outcome data member. +int WindowsDeathTest::Wait() { + if (!spawned()) + return 0; + + // Wait until the child either signals that it has acquired the write end + // of the pipe or it dies. + const HANDLE wait_handles[2] = { child_handle_.Get(), event_handle_.Get() }; + switch (::WaitForMultipleObjects(2, + wait_handles, + FALSE, // Waits for any of the handles. + INFINITE)) { + case WAIT_OBJECT_0: + case WAIT_OBJECT_0 + 1: + break; + default: + GTEST_DEATH_TEST_CHECK_(false); // Should not get here. + } + + // The child has acquired the write end of the pipe or exited. + // We release the handle on our side and continue. + write_handle_.Reset(); + event_handle_.Reset(); + + ReadAndInterpretStatusByte(); + + // Waits for the child process to exit if it haven't already. This + // returns immediately if the child has already exited, regardless of + // whether previous calls to WaitForMultipleObjects synchronized on this + // handle or not. + GTEST_DEATH_TEST_CHECK_( + WAIT_OBJECT_0 == ::WaitForSingleObject(child_handle_.Get(), + INFINITE)); + DWORD status_code; + GTEST_DEATH_TEST_CHECK_( + ::GetExitCodeProcess(child_handle_.Get(), &status_code) != FALSE); + child_handle_.Reset(); + set_status(static_cast(status_code)); + return status(); +} + +// The AssumeRole process for a Windows death test. It creates a child +// process with the same executable as the current process to run the +// death test. The child process is given the --gtest_filter and +// --gtest_internal_run_death_test flags such that it knows to run the +// current death test only. +DeathTest::TestRole WindowsDeathTest::AssumeRole() { + const UnitTestImpl* const impl = GetUnitTestImpl(); + const InternalRunDeathTestFlag* const flag = + impl->internal_run_death_test_flag(); + const TestInfo* const info = impl->current_test_info(); + const int death_test_index = info->result()->death_test_count(); + + if (flag != NULL) { + // ParseInternalRunDeathTestFlag() has performed all the necessary + // processing. + set_write_fd(flag->write_fd()); + return EXECUTE_TEST; + } + + // WindowsDeathTest uses an anonymous pipe to communicate results of + // a death test. + SECURITY_ATTRIBUTES handles_are_inheritable = { + sizeof(SECURITY_ATTRIBUTES), NULL, TRUE }; + HANDLE read_handle, write_handle; + GTEST_DEATH_TEST_CHECK_( + ::CreatePipe(&read_handle, &write_handle, &handles_are_inheritable, + 0) // Default buffer size. + != FALSE); + set_read_fd(::_open_osfhandle(reinterpret_cast(read_handle), + O_RDONLY)); + write_handle_.Reset(write_handle); + event_handle_.Reset(::CreateEvent( + &handles_are_inheritable, + TRUE, // The event will automatically reset to non-signaled state. + FALSE, // The initial state is non-signalled. + NULL)); // The even is unnamed. + GTEST_DEATH_TEST_CHECK_(event_handle_.Get() != NULL); + const String filter_flag = String::Format("--%s%s=%s.%s", + GTEST_FLAG_PREFIX_, kFilterFlag, + info->test_case_name(), + info->name()); + const String internal_flag = String::Format( + "--%s%s=%s|%d|%d|%u|%Iu|%Iu", + GTEST_FLAG_PREFIX_, + kInternalRunDeathTestFlag, + file_, line_, + death_test_index, + static_cast(::GetCurrentProcessId()), + // size_t has the same with as pointers on both 32-bit and 64-bit + // Windows platforms. + // See http://msdn.microsoft.com/en-us/library/tcxf1dw6.aspx. + reinterpret_cast(write_handle), + reinterpret_cast(event_handle_.Get())); + + char executable_path[_MAX_PATH + 1]; // NOLINT + GTEST_DEATH_TEST_CHECK_( + _MAX_PATH + 1 != ::GetModuleFileNameA(NULL, + executable_path, + _MAX_PATH)); + + String command_line = String::Format("%s %s \"%s\"", + ::GetCommandLineA(), + filter_flag.c_str(), + internal_flag.c_str()); + + DeathTest::set_last_death_test_message(""); + + CaptureStderr(); + // Flush the log buffers since the log streams are shared with the child. + FlushInfoLog(); + + // The child process will share the standard handles with the parent. + STARTUPINFOA startup_info; + memset(&startup_info, 0, sizeof(STARTUPINFO)); + startup_info.dwFlags = STARTF_USESTDHANDLES; + startup_info.hStdInput = ::GetStdHandle(STD_INPUT_HANDLE); + startup_info.hStdOutput = ::GetStdHandle(STD_OUTPUT_HANDLE); + startup_info.hStdError = ::GetStdHandle(STD_ERROR_HANDLE); + + PROCESS_INFORMATION process_info; + GTEST_DEATH_TEST_CHECK_(::CreateProcessA( + executable_path, + const_cast(command_line.c_str()), + NULL, // Retuned process handle is not inheritable. + NULL, // Retuned thread handle is not inheritable. + TRUE, // Child inherits all inheritable handles (for write_handle_). + 0x0, // Default creation flags. + NULL, // Inherit the parent's environment. + UnitTest::GetInstance()->original_working_dir(), + &startup_info, + &process_info) != FALSE); + child_handle_.Reset(process_info.hProcess); + ::CloseHandle(process_info.hThread); + set_spawned(true); + return OVERSEE_TEST; +} +# else // We are not on Windows. + +// ForkingDeathTest provides implementations for most of the abstract +// methods of the DeathTest interface. Only the AssumeRole method is +// left undefined. +class ForkingDeathTest : public DeathTestImpl { + public: + ForkingDeathTest(const char* statement, const RE* regex); + + // All of these virtual functions are inherited from DeathTest. + virtual int Wait(); + + protected: + void set_child_pid(pid_t child_pid) { child_pid_ = child_pid; } + + private: + // PID of child process during death test; 0 in the child process itself. + pid_t child_pid_; +}; + +// Constructs a ForkingDeathTest. +ForkingDeathTest::ForkingDeathTest(const char* a_statement, const RE* a_regex) + : DeathTestImpl(a_statement, a_regex), + child_pid_(-1) {} + +// Waits for the child in a death test to exit, returning its exit +// status, or 0 if no child process exists. As a side effect, sets the +// outcome data member. +int ForkingDeathTest::Wait() { + if (!spawned()) + return 0; + + ReadAndInterpretStatusByte(); + + int status_value; + GTEST_DEATH_TEST_CHECK_SYSCALL_(waitpid(child_pid_, &status_value, 0)); + set_status(status_value); + return status_value; +} + +// A concrete death test class that forks, then immediately runs the test +// in the child process. +class NoExecDeathTest : public ForkingDeathTest { + public: + NoExecDeathTest(const char* a_statement, const RE* a_regex) : + ForkingDeathTest(a_statement, a_regex) { } + virtual TestRole AssumeRole(); +}; + +// The AssumeRole process for a fork-and-run death test. It implements a +// straightforward fork, with a simple pipe to transmit the status byte. +DeathTest::TestRole NoExecDeathTest::AssumeRole() { + const size_t thread_count = GetThreadCount(); + if (thread_count != 1) { + GTEST_LOG_(WARNING) << DeathTestThreadWarning(thread_count); + } + + int pipe_fd[2]; + GTEST_DEATH_TEST_CHECK_(pipe(pipe_fd) != -1); + + DeathTest::set_last_death_test_message(""); + CaptureStderr(); + // When we fork the process below, the log file buffers are copied, but the + // file descriptors are shared. We flush all log files here so that closing + // the file descriptors in the child process doesn't throw off the + // synchronization between descriptors and buffers in the parent process. + // This is as close to the fork as possible to avoid a race condition in case + // there are multiple threads running before the death test, and another + // thread writes to the log file. + FlushInfoLog(); + + const pid_t child_pid = fork(); + GTEST_DEATH_TEST_CHECK_(child_pid != -1); + set_child_pid(child_pid); + if (child_pid == 0) { + GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[0])); + set_write_fd(pipe_fd[1]); + // Redirects all logging to stderr in the child process to prevent + // concurrent writes to the log files. We capture stderr in the parent + // process and append the child process' output to a log. + LogToStderr(); + // Event forwarding to the listeners of event listener API mush be shut + // down in death test subprocesses. + GetUnitTestImpl()->listeners()->SuppressEventForwarding(); + return EXECUTE_TEST; + } else { + GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[1])); + set_read_fd(pipe_fd[0]); + set_spawned(true); + return OVERSEE_TEST; + } +} + +// A concrete death test class that forks and re-executes the main +// program from the beginning, with command-line flags set that cause +// only this specific death test to be run. +class ExecDeathTest : public ForkingDeathTest { + public: + ExecDeathTest(const char* a_statement, const RE* a_regex, + const char* file, int line) : + ForkingDeathTest(a_statement, a_regex), file_(file), line_(line) { } + virtual TestRole AssumeRole(); + private: + // The name of the file in which the death test is located. + const char* const file_; + // The line number on which the death test is located. + const int line_; +}; + +// Utility class for accumulating command-line arguments. +class Arguments { + public: + Arguments() { + args_.push_back(NULL); + } + + ~Arguments() { + for (std::vector::iterator i = args_.begin(); i != args_.end(); + ++i) { + free(*i); + } + } + void AddArgument(const char* argument) { + args_.insert(args_.end() - 1, posix::StrDup(argument)); + } + + template + void AddArguments(const ::std::vector& arguments) { + for (typename ::std::vector::const_iterator i = arguments.begin(); + i != arguments.end(); + ++i) { + args_.insert(args_.end() - 1, posix::StrDup(i->c_str())); + } + } + char* const* Argv() { + return &args_[0]; + } + private: + std::vector args_; +}; + +// A struct that encompasses the arguments to the child process of a +// threadsafe-style death test process. +struct ExecDeathTestArgs { + char* const* argv; // Command-line arguments for the child's call to exec + int close_fd; // File descriptor to close; the read end of a pipe +}; + +# if GTEST_OS_MAC +inline char** GetEnviron() { + // When Google Test is built as a framework on MacOS X, the environ variable + // is unavailable. Apple's documentation (man environ) recommends using + // _NSGetEnviron() instead. + return *_NSGetEnviron(); +} +# else +// Some POSIX platforms expect you to declare environ. extern "C" makes +// it reside in the global namespace. +extern "C" char** environ; +inline char** GetEnviron() { return environ; } +# endif // GTEST_OS_MAC + +// The main function for a threadsafe-style death test child process. +// This function is called in a clone()-ed process and thus must avoid +// any potentially unsafe operations like malloc or libc functions. +static int ExecDeathTestChildMain(void* child_arg) { + ExecDeathTestArgs* const args = static_cast(child_arg); + GTEST_DEATH_TEST_CHECK_SYSCALL_(close(args->close_fd)); + + // We need to execute the test program in the same environment where + // it was originally invoked. Therefore we change to the original + // working directory first. + const char* const original_dir = + UnitTest::GetInstance()->original_working_dir(); + // We can safely call chdir() as it's a direct system call. + if (chdir(original_dir) != 0) { + DeathTestAbort(String::Format("chdir(\"%s\") failed: %s", + original_dir, + GetLastErrnoDescription().c_str())); + return EXIT_FAILURE; + } + + // We can safely call execve() as it's a direct system call. We + // cannot use execvp() as it's a libc function and thus potentially + // unsafe. Since execve() doesn't search the PATH, the user must + // invoke the test program via a valid path that contains at least + // one path separator. + execve(args->argv[0], args->argv, GetEnviron()); + DeathTestAbort(String::Format("execve(%s, ...) in %s failed: %s", + args->argv[0], + original_dir, + GetLastErrnoDescription().c_str())); + return EXIT_FAILURE; +} + +// Two utility routines that together determine the direction the stack +// grows. +// This could be accomplished more elegantly by a single recursive +// function, but we want to guard against the unlikely possibility of +// a smart compiler optimizing the recursion away. +// +// GTEST_NO_INLINE_ is required to prevent GCC 4.6 from inlining +// StackLowerThanAddress into StackGrowsDown, which then doesn't give +// correct answer. +bool StackLowerThanAddress(const void* ptr) GTEST_NO_INLINE_; +bool StackLowerThanAddress(const void* ptr) { + int dummy; + return &dummy < ptr; +} + +bool StackGrowsDown() { + int dummy; + return StackLowerThanAddress(&dummy); +} + +// A threadsafe implementation of fork(2) for threadsafe-style death tests +// that uses clone(2). It dies with an error message if anything goes +// wrong. +static pid_t ExecDeathTestFork(char* const* argv, int close_fd) { + ExecDeathTestArgs args = { argv, close_fd }; + pid_t child_pid = -1; + +# if GTEST_HAS_CLONE + const bool use_fork = GTEST_FLAG(death_test_use_fork); + + if (!use_fork) { + static const bool stack_grows_down = StackGrowsDown(); + const size_t stack_size = getpagesize(); + // MMAP_ANONYMOUS is not defined on Mac, so we use MAP_ANON instead. + void* const stack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE, + MAP_ANON | MAP_PRIVATE, -1, 0); + GTEST_DEATH_TEST_CHECK_(stack != MAP_FAILED); + void* const stack_top = + static_cast(stack) + (stack_grows_down ? stack_size : 0); + + child_pid = clone(&ExecDeathTestChildMain, stack_top, SIGCHLD, &args); + + GTEST_DEATH_TEST_CHECK_(munmap(stack, stack_size) != -1); + } +# else + const bool use_fork = true; +# endif // GTEST_HAS_CLONE + + if (use_fork && (child_pid = fork()) == 0) { + ExecDeathTestChildMain(&args); + _exit(0); + } + + GTEST_DEATH_TEST_CHECK_(child_pid != -1); + return child_pid; +} + +// The AssumeRole process for a fork-and-exec death test. It re-executes the +// main program from the beginning, setting the --gtest_filter +// and --gtest_internal_run_death_test flags to cause only the current +// death test to be re-run. +DeathTest::TestRole ExecDeathTest::AssumeRole() { + const UnitTestImpl* const impl = GetUnitTestImpl(); + const InternalRunDeathTestFlag* const flag = + impl->internal_run_death_test_flag(); + const TestInfo* const info = impl->current_test_info(); + const int death_test_index = info->result()->death_test_count(); + + if (flag != NULL) { + set_write_fd(flag->write_fd()); + return EXECUTE_TEST; + } + + int pipe_fd[2]; + GTEST_DEATH_TEST_CHECK_(pipe(pipe_fd) != -1); + // Clear the close-on-exec flag on the write end of the pipe, lest + // it be closed when the child process does an exec: + GTEST_DEATH_TEST_CHECK_(fcntl(pipe_fd[1], F_SETFD, 0) != -1); + + const String filter_flag = + String::Format("--%s%s=%s.%s", + GTEST_FLAG_PREFIX_, kFilterFlag, + info->test_case_name(), info->name()); + const String internal_flag = + String::Format("--%s%s=%s|%d|%d|%d", + GTEST_FLAG_PREFIX_, kInternalRunDeathTestFlag, + file_, line_, death_test_index, pipe_fd[1]); + Arguments args; + args.AddArguments(GetArgvs()); + args.AddArgument(filter_flag.c_str()); + args.AddArgument(internal_flag.c_str()); + + DeathTest::set_last_death_test_message(""); + + CaptureStderr(); + // See the comment in NoExecDeathTest::AssumeRole for why the next line + // is necessary. + FlushInfoLog(); + + const pid_t child_pid = ExecDeathTestFork(args.Argv(), pipe_fd[0]); + GTEST_DEATH_TEST_CHECK_SYSCALL_(close(pipe_fd[1])); + set_child_pid(child_pid); + set_read_fd(pipe_fd[0]); + set_spawned(true); + return OVERSEE_TEST; +} + +# endif // !GTEST_OS_WINDOWS + +// Creates a concrete DeathTest-derived class that depends on the +// --gtest_death_test_style flag, and sets the pointer pointed to +// by the "test" argument to its address. If the test should be +// skipped, sets that pointer to NULL. Returns true, unless the +// flag is set to an invalid value. +bool DefaultDeathTestFactory::Create(const char* statement, const RE* regex, + const char* file, int line, + DeathTest** test) { + UnitTestImpl* const impl = GetUnitTestImpl(); + const InternalRunDeathTestFlag* const flag = + impl->internal_run_death_test_flag(); + const int death_test_index = impl->current_test_info() + ->increment_death_test_count(); + + if (flag != NULL) { + if (death_test_index > flag->index()) { + DeathTest::set_last_death_test_message(String::Format( + "Death test count (%d) somehow exceeded expected maximum (%d)", + death_test_index, flag->index())); + return false; + } + + if (!(flag->file() == file && flag->line() == line && + flag->index() == death_test_index)) { + *test = NULL; + return true; + } + } + +# if GTEST_OS_WINDOWS + + if (GTEST_FLAG(death_test_style) == "threadsafe" || + GTEST_FLAG(death_test_style) == "fast") { + *test = new WindowsDeathTest(statement, regex, file, line); + } + +# else + + if (GTEST_FLAG(death_test_style) == "threadsafe") { + *test = new ExecDeathTest(statement, regex, file, line); + } else if (GTEST_FLAG(death_test_style) == "fast") { + *test = new NoExecDeathTest(statement, regex); + } + +# endif // GTEST_OS_WINDOWS + + else { // NOLINT - this is more readable than unbalanced brackets inside #if. + DeathTest::set_last_death_test_message(String::Format( + "Unknown death test style \"%s\" encountered", + GTEST_FLAG(death_test_style).c_str())); + return false; + } + + return true; +} + +// Splits a given string on a given delimiter, populating a given +// vector with the fields. GTEST_HAS_DEATH_TEST implies that we have +// ::std::string, so we can use it here. +static void SplitString(const ::std::string& str, char delimiter, + ::std::vector< ::std::string>* dest) { + ::std::vector< ::std::string> parsed; + ::std::string::size_type pos = 0; + while (::testing::internal::AlwaysTrue()) { + const ::std::string::size_type colon = str.find(delimiter, pos); + if (colon == ::std::string::npos) { + parsed.push_back(str.substr(pos)); + break; + } else { + parsed.push_back(str.substr(pos, colon - pos)); + pos = colon + 1; + } + } + dest->swap(parsed); +} + +# if GTEST_OS_WINDOWS +// Recreates the pipe and event handles from the provided parameters, +// signals the event, and returns a file descriptor wrapped around the pipe +// handle. This function is called in the child process only. +int GetStatusFileDescriptor(unsigned int parent_process_id, + size_t write_handle_as_size_t, + size_t event_handle_as_size_t) { + AutoHandle parent_process_handle(::OpenProcess(PROCESS_DUP_HANDLE, + FALSE, // Non-inheritable. + parent_process_id)); + if (parent_process_handle.Get() == INVALID_HANDLE_VALUE) { + DeathTestAbort(String::Format("Unable to open parent process %u", + parent_process_id)); + } + + // TODO(vladl@google.com): Replace the following check with a + // compile-time assertion when available. + GTEST_CHECK_(sizeof(HANDLE) <= sizeof(size_t)); + + const HANDLE write_handle = + reinterpret_cast(write_handle_as_size_t); + HANDLE dup_write_handle; + + // The newly initialized handle is accessible only in in the parent + // process. To obtain one accessible within the child, we need to use + // DuplicateHandle. + if (!::DuplicateHandle(parent_process_handle.Get(), write_handle, + ::GetCurrentProcess(), &dup_write_handle, + 0x0, // Requested privileges ignored since + // DUPLICATE_SAME_ACCESS is used. + FALSE, // Request non-inheritable handler. + DUPLICATE_SAME_ACCESS)) { + DeathTestAbort(String::Format( + "Unable to duplicate the pipe handle %Iu from the parent process %u", + write_handle_as_size_t, parent_process_id)); + } + + const HANDLE event_handle = reinterpret_cast(event_handle_as_size_t); + HANDLE dup_event_handle; + + if (!::DuplicateHandle(parent_process_handle.Get(), event_handle, + ::GetCurrentProcess(), &dup_event_handle, + 0x0, + FALSE, + DUPLICATE_SAME_ACCESS)) { + DeathTestAbort(String::Format( + "Unable to duplicate the event handle %Iu from the parent process %u", + event_handle_as_size_t, parent_process_id)); + } + + const int write_fd = + ::_open_osfhandle(reinterpret_cast(dup_write_handle), O_APPEND); + if (write_fd == -1) { + DeathTestAbort(String::Format( + "Unable to convert pipe handle %Iu to a file descriptor", + write_handle_as_size_t)); + } + + // Signals the parent that the write end of the pipe has been acquired + // so the parent can release its own write end. + ::SetEvent(dup_event_handle); + + return write_fd; +} +# endif // GTEST_OS_WINDOWS + +// Returns a newly created InternalRunDeathTestFlag object with fields +// initialized from the GTEST_FLAG(internal_run_death_test) flag if +// the flag is specified; otherwise returns NULL. +InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag() { + if (GTEST_FLAG(internal_run_death_test) == "") return NULL; + + // GTEST_HAS_DEATH_TEST implies that we have ::std::string, so we + // can use it here. + int line = -1; + int index = -1; + ::std::vector< ::std::string> fields; + SplitString(GTEST_FLAG(internal_run_death_test).c_str(), '|', &fields); + int write_fd = -1; + +# if GTEST_OS_WINDOWS + + unsigned int parent_process_id = 0; + size_t write_handle_as_size_t = 0; + size_t event_handle_as_size_t = 0; + + if (fields.size() != 6 + || !ParseNaturalNumber(fields[1], &line) + || !ParseNaturalNumber(fields[2], &index) + || !ParseNaturalNumber(fields[3], &parent_process_id) + || !ParseNaturalNumber(fields[4], &write_handle_as_size_t) + || !ParseNaturalNumber(fields[5], &event_handle_as_size_t)) { + DeathTestAbort(String::Format( + "Bad --gtest_internal_run_death_test flag: %s", + GTEST_FLAG(internal_run_death_test).c_str())); + } + write_fd = GetStatusFileDescriptor(parent_process_id, + write_handle_as_size_t, + event_handle_as_size_t); +# else + + if (fields.size() != 4 + || !ParseNaturalNumber(fields[1], &line) + || !ParseNaturalNumber(fields[2], &index) + || !ParseNaturalNumber(fields[3], &write_fd)) { + DeathTestAbort(String::Format( + "Bad --gtest_internal_run_death_test flag: %s", + GTEST_FLAG(internal_run_death_test).c_str())); + } + +# endif // GTEST_OS_WINDOWS + + return new InternalRunDeathTestFlag(fields[0], line, index, write_fd); +} + +} // namespace internal + +#endif // GTEST_HAS_DEATH_TEST + +} // namespace testing +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Authors: keith.ray@gmail.com (Keith Ray) + + +#include + +#if GTEST_OS_WINDOWS_MOBILE +# include +#elif GTEST_OS_WINDOWS +# include +# include +#elif GTEST_OS_SYMBIAN || GTEST_OS_NACL +// Symbian OpenC and NaCl have PATH_MAX in sys/syslimits.h +# include +#else +# include +# include // Some Linux distributions define PATH_MAX here. +#endif // GTEST_OS_WINDOWS_MOBILE + +#if GTEST_OS_WINDOWS +# define GTEST_PATH_MAX_ _MAX_PATH +#elif defined(PATH_MAX) +# define GTEST_PATH_MAX_ PATH_MAX +#elif defined(_XOPEN_PATH_MAX) +# define GTEST_PATH_MAX_ _XOPEN_PATH_MAX +#else +# define GTEST_PATH_MAX_ _POSIX_PATH_MAX +#endif // GTEST_OS_WINDOWS + + +namespace testing { +namespace internal { + +#if GTEST_OS_WINDOWS +// On Windows, '\\' is the standard path separator, but many tools and the +// Windows API also accept '/' as an alternate path separator. Unless otherwise +// noted, a file path can contain either kind of path separators, or a mixture +// of them. +const char kPathSeparator = '\\'; +const char kAlternatePathSeparator = '/'; +const char kPathSeparatorString[] = "\\"; +const char kAlternatePathSeparatorString[] = "/"; +# if GTEST_OS_WINDOWS_MOBILE +// Windows CE doesn't have a current directory. You should not use +// the current directory in tests on Windows CE, but this at least +// provides a reasonable fallback. +const char kCurrentDirectoryString[] = "\\"; +// Windows CE doesn't define INVALID_FILE_ATTRIBUTES +const DWORD kInvalidFileAttributes = 0xffffffff; +# else +const char kCurrentDirectoryString[] = ".\\"; +# endif // GTEST_OS_WINDOWS_MOBILE +#else +const char kPathSeparator = '/'; +const char kPathSeparatorString[] = "/"; +const char kCurrentDirectoryString[] = "./"; +#endif // GTEST_OS_WINDOWS + +// Returns whether the given character is a valid path separator. +static bool IsPathSeparator(char c) { +#if GTEST_HAS_ALT_PATH_SEP_ + return (c == kPathSeparator) || (c == kAlternatePathSeparator); +#else + return c == kPathSeparator; +#endif +} + +// Returns the current working directory, or "" if unsuccessful. +FilePath FilePath::GetCurrentDir() { +#if GTEST_OS_WINDOWS_MOBILE + // Windows CE doesn't have a current directory, so we just return + // something reasonable. + return FilePath(kCurrentDirectoryString); +#elif GTEST_OS_WINDOWS + char cwd[GTEST_PATH_MAX_ + 1] = { '\0' }; + return FilePath(_getcwd(cwd, sizeof(cwd)) == NULL ? "" : cwd); +#else + char cwd[GTEST_PATH_MAX_ + 1] = { '\0' }; + return FilePath(getcwd(cwd, sizeof(cwd)) == NULL ? "" : cwd); +#endif // GTEST_OS_WINDOWS_MOBILE +} + +// Returns a copy of the FilePath with the case-insensitive extension removed. +// Example: FilePath("dir/file.exe").RemoveExtension("EXE") returns +// FilePath("dir/file"). If a case-insensitive extension is not +// found, returns a copy of the original FilePath. +FilePath FilePath::RemoveExtension(const char* extension) const { + String dot_extension(String::Format(".%s", extension)); + if (pathname_.EndsWithCaseInsensitive(dot_extension.c_str())) { + return FilePath(String(pathname_.c_str(), pathname_.length() - 4)); + } + return *this; +} + +// Returns a pointer to the last occurence of a valid path separator in +// the FilePath. On Windows, for example, both '/' and '\' are valid path +// separators. Returns NULL if no path separator was found. +const char* FilePath::FindLastPathSeparator() const { + const char* const last_sep = strrchr(c_str(), kPathSeparator); +#if GTEST_HAS_ALT_PATH_SEP_ + const char* const last_alt_sep = strrchr(c_str(), kAlternatePathSeparator); + // Comparing two pointers of which only one is NULL is undefined. + if (last_alt_sep != NULL && + (last_sep == NULL || last_alt_sep > last_sep)) { + return last_alt_sep; + } +#endif + return last_sep; +} + +// Returns a copy of the FilePath with the directory part removed. +// Example: FilePath("path/to/file").RemoveDirectoryName() returns +// FilePath("file"). If there is no directory part ("just_a_file"), it returns +// the FilePath unmodified. If there is no file part ("just_a_dir/") it +// returns an empty FilePath (""). +// On Windows platform, '\' is the path separator, otherwise it is '/'. +FilePath FilePath::RemoveDirectoryName() const { + const char* const last_sep = FindLastPathSeparator(); + return last_sep ? FilePath(String(last_sep + 1)) : *this; +} + +// RemoveFileName returns the directory path with the filename removed. +// Example: FilePath("path/to/file").RemoveFileName() returns "path/to/". +// If the FilePath is "a_file" or "/a_file", RemoveFileName returns +// FilePath("./") or, on Windows, FilePath(".\\"). If the filepath does +// not have a file, like "just/a/dir/", it returns the FilePath unmodified. +// On Windows platform, '\' is the path separator, otherwise it is '/'. +FilePath FilePath::RemoveFileName() const { + const char* const last_sep = FindLastPathSeparator(); + String dir; + if (last_sep) { + dir = String(c_str(), last_sep + 1 - c_str()); + } else { + dir = kCurrentDirectoryString; + } + return FilePath(dir); +} + +// Helper functions for naming files in a directory for xml output. + +// Given directory = "dir", base_name = "test", number = 0, +// extension = "xml", returns "dir/test.xml". If number is greater +// than zero (e.g., 12), returns "dir/test_12.xml". +// On Windows platform, uses \ as the separator rather than /. +FilePath FilePath::MakeFileName(const FilePath& directory, + const FilePath& base_name, + int number, + const char* extension) { + String file; + if (number == 0) { + file = String::Format("%s.%s", base_name.c_str(), extension); + } else { + file = String::Format("%s_%d.%s", base_name.c_str(), number, extension); + } + return ConcatPaths(directory, FilePath(file)); +} + +// Given directory = "dir", relative_path = "test.xml", returns "dir/test.xml". +// On Windows, uses \ as the separator rather than /. +FilePath FilePath::ConcatPaths(const FilePath& directory, + const FilePath& relative_path) { + if (directory.IsEmpty()) + return relative_path; + const FilePath dir(directory.RemoveTrailingPathSeparator()); + return FilePath(String::Format("%s%c%s", dir.c_str(), kPathSeparator, + relative_path.c_str())); +} + +// Returns true if pathname describes something findable in the file-system, +// either a file, directory, or whatever. +bool FilePath::FileOrDirectoryExists() const { +#if GTEST_OS_WINDOWS_MOBILE + LPCWSTR unicode = String::AnsiToUtf16(pathname_.c_str()); + const DWORD attributes = GetFileAttributes(unicode); + delete [] unicode; + return attributes != kInvalidFileAttributes; +#else + posix::StatStruct file_stat; + return posix::Stat(pathname_.c_str(), &file_stat) == 0; +#endif // GTEST_OS_WINDOWS_MOBILE +} + +// Returns true if pathname describes a directory in the file-system +// that exists. +bool FilePath::DirectoryExists() const { + bool result = false; +#if GTEST_OS_WINDOWS + // Don't strip off trailing separator if path is a root directory on + // Windows (like "C:\\"). + const FilePath& path(IsRootDirectory() ? *this : + RemoveTrailingPathSeparator()); +#else + const FilePath& path(*this); +#endif + +#if GTEST_OS_WINDOWS_MOBILE + LPCWSTR unicode = String::AnsiToUtf16(path.c_str()); + const DWORD attributes = GetFileAttributes(unicode); + delete [] unicode; + if ((attributes != kInvalidFileAttributes) && + (attributes & FILE_ATTRIBUTE_DIRECTORY)) { + result = true; + } +#else + posix::StatStruct file_stat; + result = posix::Stat(path.c_str(), &file_stat) == 0 && + posix::IsDir(file_stat); +#endif // GTEST_OS_WINDOWS_MOBILE + + return result; +} + +// Returns true if pathname describes a root directory. (Windows has one +// root directory per disk drive.) +bool FilePath::IsRootDirectory() const { +#if GTEST_OS_WINDOWS + // TODO(wan@google.com): on Windows a network share like + // \\server\share can be a root directory, although it cannot be the + // current directory. Handle this properly. + return pathname_.length() == 3 && IsAbsolutePath(); +#else + return pathname_.length() == 1 && IsPathSeparator(pathname_.c_str()[0]); +#endif +} + +// Returns true if pathname describes an absolute path. +bool FilePath::IsAbsolutePath() const { + const char* const name = pathname_.c_str(); +#if GTEST_OS_WINDOWS + return pathname_.length() >= 3 && + ((name[0] >= 'a' && name[0] <= 'z') || + (name[0] >= 'A' && name[0] <= 'Z')) && + name[1] == ':' && + IsPathSeparator(name[2]); +#else + return IsPathSeparator(name[0]); +#endif +} + +// Returns a pathname for a file that does not currently exist. The pathname +// will be directory/base_name.extension or +// directory/base_name_.extension if directory/base_name.extension +// already exists. The number will be incremented until a pathname is found +// that does not already exist. +// Examples: 'dir/foo_test.xml' or 'dir/foo_test_1.xml'. +// There could be a race condition if two or more processes are calling this +// function at the same time -- they could both pick the same filename. +FilePath FilePath::GenerateUniqueFileName(const FilePath& directory, + const FilePath& base_name, + const char* extension) { + FilePath full_pathname; + int number = 0; + do { + full_pathname.Set(MakeFileName(directory, base_name, number++, extension)); + } while (full_pathname.FileOrDirectoryExists()); + return full_pathname; +} + +// Returns true if FilePath ends with a path separator, which indicates that +// it is intended to represent a directory. Returns false otherwise. +// This does NOT check that a directory (or file) actually exists. +bool FilePath::IsDirectory() const { + return !pathname_.empty() && + IsPathSeparator(pathname_.c_str()[pathname_.length() - 1]); +} + +// Create directories so that path exists. Returns true if successful or if +// the directories already exist; returns false if unable to create directories +// for any reason. +bool FilePath::CreateDirectoriesRecursively() const { + if (!this->IsDirectory()) { + return false; + } + + if (pathname_.length() == 0 || this->DirectoryExists()) { + return true; + } + + const FilePath parent(this->RemoveTrailingPathSeparator().RemoveFileName()); + return parent.CreateDirectoriesRecursively() && this->CreateFolder(); +} + +// Create the directory so that path exists. Returns true if successful or +// if the directory already exists; returns false if unable to create the +// directory for any reason, including if the parent directory does not +// exist. Not named "CreateDirectory" because that's a macro on Windows. +bool FilePath::CreateFolder() const { +#if GTEST_OS_WINDOWS_MOBILE + FilePath removed_sep(this->RemoveTrailingPathSeparator()); + LPCWSTR unicode = String::AnsiToUtf16(removed_sep.c_str()); + int result = CreateDirectory(unicode, NULL) ? 0 : -1; + delete [] unicode; +#elif GTEST_OS_WINDOWS + int result = _mkdir(pathname_.c_str()); +#else + int result = mkdir(pathname_.c_str(), 0777); +#endif // GTEST_OS_WINDOWS_MOBILE + + if (result == -1) { + return this->DirectoryExists(); // An error is OK if the directory exists. + } + return true; // No error. +} + +// If input name has a trailing separator character, remove it and return the +// name, otherwise return the name string unmodified. +// On Windows platform, uses \ as the separator, other platforms use /. +FilePath FilePath::RemoveTrailingPathSeparator() const { + return IsDirectory() + ? FilePath(String(pathname_.c_str(), pathname_.length() - 1)) + : *this; +} + +// Removes any redundant separators that might be in the pathname. +// For example, "bar///foo" becomes "bar/foo". Does not eliminate other +// redundancies that might be in a pathname involving "." or "..". +// TODO(wan@google.com): handle Windows network shares (e.g. \\server\share). +void FilePath::Normalize() { + if (pathname_.c_str() == NULL) { + pathname_ = ""; + return; + } + const char* src = pathname_.c_str(); + char* const dest = new char[pathname_.length() + 1]; + char* dest_ptr = dest; + memset(dest_ptr, 0, pathname_.length() + 1); + + while (*src != '\0') { + *dest_ptr = *src; + if (!IsPathSeparator(*src)) { + src++; + } else { +#if GTEST_HAS_ALT_PATH_SEP_ + if (*dest_ptr == kAlternatePathSeparator) { + *dest_ptr = kPathSeparator; + } +#endif + while (IsPathSeparator(*src)) + src++; + } + dest_ptr++; + } + *dest_ptr = '\0'; + pathname_ = dest; + delete[] dest; +} + +} // namespace internal +} // namespace testing +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + + +#include +#include +#include +#include + +#if GTEST_OS_WINDOWS_MOBILE +# include // For TerminateProcess() +#elif GTEST_OS_WINDOWS +# include +# include +#else +# include +#endif // GTEST_OS_WINDOWS_MOBILE + +#if GTEST_OS_MAC +# include +# include +# include +#endif // GTEST_OS_MAC + + +// Indicates that this translation unit is part of Google Test's +// implementation. It must come before gtest-internal-inl.h is +// included, or there will be a compiler error. This trick is to +// prevent a user from accidentally including gtest-internal-inl.h in +// his code. +#define GTEST_IMPLEMENTATION_ 1 +#undef GTEST_IMPLEMENTATION_ + +namespace testing { +namespace internal { + +#if defined(_MSC_VER) || defined(__BORLANDC__) +// MSVC and C++Builder do not provide a definition of STDERR_FILENO. +const int kStdOutFileno = 1; +const int kStdErrFileno = 2; +#else +const int kStdOutFileno = STDOUT_FILENO; +const int kStdErrFileno = STDERR_FILENO; +#endif // _MSC_VER + +#if GTEST_OS_MAC + +// Returns the number of threads running in the process, or 0 to indicate that +// we cannot detect it. +size_t GetThreadCount() { + const task_t task = mach_task_self(); + mach_msg_type_number_t thread_count; + thread_act_array_t thread_list; + const kern_return_t status = task_threads(task, &thread_list, &thread_count); + if (status == KERN_SUCCESS) { + // task_threads allocates resources in thread_list and we need to free them + // to avoid leaks. + vm_deallocate(task, + reinterpret_cast(thread_list), + sizeof(thread_t) * thread_count); + return static_cast(thread_count); + } else { + return 0; + } +} + +#else + +size_t GetThreadCount() { + // There's no portable way to detect the number of threads, so we just + // return 0 to indicate that we cannot detect it. + return 0; +} + +#endif // GTEST_OS_MAC + +#if GTEST_USES_POSIX_RE + +// Implements RE. Currently only needed for death tests. + +RE::~RE() { + if (is_valid_) { + // regfree'ing an invalid regex might crash because the content + // of the regex is undefined. Since the regex's are essentially + // the same, one cannot be valid (or invalid) without the other + // being so too. + regfree(&partial_regex_); + regfree(&full_regex_); + } + free(const_cast(pattern_)); +} + +// Returns true iff regular expression re matches the entire str. +bool RE::FullMatch(const char* str, const RE& re) { + if (!re.is_valid_) return false; + + regmatch_t match; + return regexec(&re.full_regex_, str, 1, &match, 0) == 0; +} + +// Returns true iff regular expression re matches a substring of str +// (including str itself). +bool RE::PartialMatch(const char* str, const RE& re) { + if (!re.is_valid_) return false; + + regmatch_t match; + return regexec(&re.partial_regex_, str, 1, &match, 0) == 0; +} + +// Initializes an RE from its string representation. +void RE::Init(const char* regex) { + pattern_ = posix::StrDup(regex); + + // Reserves enough bytes to hold the regular expression used for a + // full match. + const size_t full_regex_len = strlen(regex) + 10; + char* const full_pattern = new char[full_regex_len]; + + snprintf(full_pattern, full_regex_len, "^(%s)$", regex); + is_valid_ = regcomp(&full_regex_, full_pattern, REG_EXTENDED) == 0; + // We want to call regcomp(&partial_regex_, ...) even if the + // previous expression returns false. Otherwise partial_regex_ may + // not be properly initialized can may cause trouble when it's + // freed. + // + // Some implementation of POSIX regex (e.g. on at least some + // versions of Cygwin) doesn't accept the empty string as a valid + // regex. We change it to an equivalent form "()" to be safe. + if (is_valid_) { + const char* const partial_regex = (*regex == '\0') ? "()" : regex; + is_valid_ = regcomp(&partial_regex_, partial_regex, REG_EXTENDED) == 0; + } + EXPECT_TRUE(is_valid_) + << "Regular expression \"" << regex + << "\" is not a valid POSIX Extended regular expression."; + + delete[] full_pattern; +} + +#elif GTEST_USES_SIMPLE_RE + +// Returns true iff ch appears anywhere in str (excluding the +// terminating '\0' character). +bool IsInSet(char ch, const char* str) { + return ch != '\0' && strchr(str, ch) != NULL; +} + +// Returns true iff ch belongs to the given classification. Unlike +// similar functions in , these aren't affected by the +// current locale. +bool IsAsciiDigit(char ch) { return '0' <= ch && ch <= '9'; } +bool IsAsciiPunct(char ch) { + return IsInSet(ch, "^-!\"#$%&'()*+,./:;<=>?@[\\]_`{|}~"); +} +bool IsRepeat(char ch) { return IsInSet(ch, "?*+"); } +bool IsAsciiWhiteSpace(char ch) { return IsInSet(ch, " \f\n\r\t\v"); } +bool IsAsciiWordChar(char ch) { + return ('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || + ('0' <= ch && ch <= '9') || ch == '_'; +} + +// Returns true iff "\\c" is a supported escape sequence. +bool IsValidEscape(char c) { + return (IsAsciiPunct(c) || IsInSet(c, "dDfnrsStvwW")); +} + +// Returns true iff the given atom (specified by escaped and pattern) +// matches ch. The result is undefined if the atom is invalid. +bool AtomMatchesChar(bool escaped, char pattern_char, char ch) { + if (escaped) { // "\\p" where p is pattern_char. + switch (pattern_char) { + case 'd': return IsAsciiDigit(ch); + case 'D': return !IsAsciiDigit(ch); + case 'f': return ch == '\f'; + case 'n': return ch == '\n'; + case 'r': return ch == '\r'; + case 's': return IsAsciiWhiteSpace(ch); + case 'S': return !IsAsciiWhiteSpace(ch); + case 't': return ch == '\t'; + case 'v': return ch == '\v'; + case 'w': return IsAsciiWordChar(ch); + case 'W': return !IsAsciiWordChar(ch); + } + return IsAsciiPunct(pattern_char) && pattern_char == ch; + } + + return (pattern_char == '.' && ch != '\n') || pattern_char == ch; +} + +// Helper function used by ValidateRegex() to format error messages. +String FormatRegexSyntaxError(const char* regex, int index) { + return (Message() << "Syntax error at index " << index + << " in simple regular expression \"" << regex << "\": ").GetString(); +} + +// Generates non-fatal failures and returns false if regex is invalid; +// otherwise returns true. +bool ValidateRegex(const char* regex) { + if (regex == NULL) { + // TODO(wan@google.com): fix the source file location in the + // assertion failures to match where the regex is used in user + // code. + ADD_FAILURE() << "NULL is not a valid simple regular expression."; + return false; + } + + bool is_valid = true; + + // True iff ?, *, or + can follow the previous atom. + bool prev_repeatable = false; + for (int i = 0; regex[i]; i++) { + if (regex[i] == '\\') { // An escape sequence + i++; + if (regex[i] == '\0') { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i - 1) + << "'\\' cannot appear at the end."; + return false; + } + + if (!IsValidEscape(regex[i])) { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i - 1) + << "invalid escape sequence \"\\" << regex[i] << "\"."; + is_valid = false; + } + prev_repeatable = true; + } else { // Not an escape sequence. + const char ch = regex[i]; + + if (ch == '^' && i > 0) { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i) + << "'^' can only appear at the beginning."; + is_valid = false; + } else if (ch == '$' && regex[i + 1] != '\0') { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i) + << "'$' can only appear at the end."; + is_valid = false; + } else if (IsInSet(ch, "()[]{}|")) { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i) + << "'" << ch << "' is unsupported."; + is_valid = false; + } else if (IsRepeat(ch) && !prev_repeatable) { + ADD_FAILURE() << FormatRegexSyntaxError(regex, i) + << "'" << ch << "' can only follow a repeatable token."; + is_valid = false; + } + + prev_repeatable = !IsInSet(ch, "^$?*+"); + } + } + + return is_valid; +} + +// Matches a repeated regex atom followed by a valid simple regular +// expression. The regex atom is defined as c if escaped is false, +// or \c otherwise. repeat is the repetition meta character (?, *, +// or +). The behavior is undefined if str contains too many +// characters to be indexable by size_t, in which case the test will +// probably time out anyway. We are fine with this limitation as +// std::string has it too. +bool MatchRepetitionAndRegexAtHead( + bool escaped, char c, char repeat, const char* regex, + const char* str) { + const size_t min_count = (repeat == '+') ? 1 : 0; + const size_t max_count = (repeat == '?') ? 1 : + static_cast(-1) - 1; + // We cannot call numeric_limits::max() as it conflicts with the + // max() macro on Windows. + + for (size_t i = 0; i <= max_count; ++i) { + // We know that the atom matches each of the first i characters in str. + if (i >= min_count && MatchRegexAtHead(regex, str + i)) { + // We have enough matches at the head, and the tail matches too. + // Since we only care about *whether* the pattern matches str + // (as opposed to *how* it matches), there is no need to find a + // greedy match. + return true; + } + if (str[i] == '\0' || !AtomMatchesChar(escaped, c, str[i])) + return false; + } + return false; +} + +// Returns true iff regex matches a prefix of str. regex must be a +// valid simple regular expression and not start with "^", or the +// result is undefined. +bool MatchRegexAtHead(const char* regex, const char* str) { + if (*regex == '\0') // An empty regex matches a prefix of anything. + return true; + + // "$" only matches the end of a string. Note that regex being + // valid guarantees that there's nothing after "$" in it. + if (*regex == '$') + return *str == '\0'; + + // Is the first thing in regex an escape sequence? + const bool escaped = *regex == '\\'; + if (escaped) + ++regex; + if (IsRepeat(regex[1])) { + // MatchRepetitionAndRegexAtHead() calls MatchRegexAtHead(), so + // here's an indirect recursion. It terminates as the regex gets + // shorter in each recursion. + return MatchRepetitionAndRegexAtHead( + escaped, regex[0], regex[1], regex + 2, str); + } else { + // regex isn't empty, isn't "$", and doesn't start with a + // repetition. We match the first atom of regex with the first + // character of str and recurse. + return (*str != '\0') && AtomMatchesChar(escaped, *regex, *str) && + MatchRegexAtHead(regex + 1, str + 1); + } +} + +// Returns true iff regex matches any substring of str. regex must be +// a valid simple regular expression, or the result is undefined. +// +// The algorithm is recursive, but the recursion depth doesn't exceed +// the regex length, so we won't need to worry about running out of +// stack space normally. In rare cases the time complexity can be +// exponential with respect to the regex length + the string length, +// but usually it's must faster (often close to linear). +bool MatchRegexAnywhere(const char* regex, const char* str) { + if (regex == NULL || str == NULL) + return false; + + if (*regex == '^') + return MatchRegexAtHead(regex + 1, str); + + // A successful match can be anywhere in str. + do { + if (MatchRegexAtHead(regex, str)) + return true; + } while (*str++ != '\0'); + return false; +} + +// Implements the RE class. + +RE::~RE() { + free(const_cast(pattern_)); + free(const_cast(full_pattern_)); +} + +// Returns true iff regular expression re matches the entire str. +bool RE::FullMatch(const char* str, const RE& re) { + return re.is_valid_ && MatchRegexAnywhere(re.full_pattern_, str); +} + +// Returns true iff regular expression re matches a substring of str +// (including str itself). +bool RE::PartialMatch(const char* str, const RE& re) { + return re.is_valid_ && MatchRegexAnywhere(re.pattern_, str); +} + +// Initializes an RE from its string representation. +void RE::Init(const char* regex) { + pattern_ = full_pattern_ = NULL; + if (regex != NULL) { + pattern_ = posix::StrDup(regex); + } + + is_valid_ = ValidateRegex(regex); + if (!is_valid_) { + // No need to calculate the full pattern when the regex is invalid. + return; + } + + const size_t len = strlen(regex); + // Reserves enough bytes to hold the regular expression used for a + // full match: we need space to prepend a '^', append a '$', and + // terminate the string with '\0'. + char* buffer = static_cast(malloc(len + 3)); + full_pattern_ = buffer; + + if (*regex != '^') + *buffer++ = '^'; // Makes sure full_pattern_ starts with '^'. + + // We don't use snprintf or strncpy, as they trigger a warning when + // compiled with VC++ 8.0. + memcpy(buffer, regex, len); + buffer += len; + + if (len == 0 || regex[len - 1] != '$') + *buffer++ = '$'; // Makes sure full_pattern_ ends with '$'. + + *buffer = '\0'; +} + +#endif // GTEST_USES_POSIX_RE + +const char kUnknownFile[] = "unknown file"; + +// Formats a source file path and a line number as they would appear +// in an error message from the compiler used to compile this code. +GTEST_API_ ::std::string FormatFileLocation(const char* file, int line) { + const char* const file_name = file == NULL ? kUnknownFile : file; + + if (line < 0) { + return String::Format("%s:", file_name).c_str(); + } +#ifdef _MSC_VER + return String::Format("%s(%d):", file_name, line).c_str(); +#else + return String::Format("%s:%d:", file_name, line).c_str(); +#endif // _MSC_VER +} + +// Formats a file location for compiler-independent XML output. +// Although this function is not platform dependent, we put it next to +// FormatFileLocation in order to contrast the two functions. +// Note that FormatCompilerIndependentFileLocation() does NOT append colon +// to the file location it produces, unlike FormatFileLocation(). +GTEST_API_ ::std::string FormatCompilerIndependentFileLocation( + const char* file, int line) { + const char* const file_name = file == NULL ? kUnknownFile : file; + + if (line < 0) + return file_name; + else + return String::Format("%s:%d", file_name, line).c_str(); +} + + +GTestLog::GTestLog(GTestLogSeverity severity, const char* file, int line) + : severity_(severity) { + const char* const marker = + severity == GTEST_INFO ? "[ INFO ]" : + severity == GTEST_WARNING ? "[WARNING]" : + severity == GTEST_ERROR ? "[ ERROR ]" : "[ FATAL ]"; + GetStream() << ::std::endl << marker << " " + << FormatFileLocation(file, line).c_str() << ": "; +} + +// Flushes the buffers and, if severity is GTEST_FATAL, aborts the program. +GTestLog::~GTestLog() { + GetStream() << ::std::endl; + if (severity_ == GTEST_FATAL) { + fflush(stderr); + posix::Abort(); + } +} +// Disable Microsoft deprecation warnings for POSIX functions called from +// this class (creat, dup, dup2, and close) +#ifdef _MSC_VER +# pragma warning(push) +# pragma warning(disable: 4996) +#endif // _MSC_VER + +#if GTEST_HAS_STREAM_REDIRECTION + +// Object that captures an output stream (stdout/stderr). +class CapturedStream { + public: + // The ctor redirects the stream to a temporary file. + CapturedStream(int fd) : fd_(fd), uncaptured_fd_(dup(fd)) { + +# if GTEST_OS_WINDOWS + char temp_dir_path[MAX_PATH + 1] = { '\0' }; // NOLINT + char temp_file_path[MAX_PATH + 1] = { '\0' }; // NOLINT + + ::GetTempPathA(sizeof(temp_dir_path), temp_dir_path); + const UINT success = ::GetTempFileNameA(temp_dir_path, + "gtest_redir", + 0, // Generate unique file name. + temp_file_path); + GTEST_CHECK_(success != 0) + << "Unable to create a temporary file in " << temp_dir_path; + const int captured_fd = creat(temp_file_path, _S_IREAD | _S_IWRITE); + GTEST_CHECK_(captured_fd != -1) << "Unable to open temporary file " + << temp_file_path; + filename_ = temp_file_path; +# else + // There's no guarantee that a test has write access to the + // current directory, so we create the temporary file in the /tmp + // directory instead. + char name_template[] = "/tmp/captured_stream.XXXXXX"; + const int captured_fd = mkstemp(name_template); + filename_ = name_template; +# endif // GTEST_OS_WINDOWS + fflush(NULL); + dup2(captured_fd, fd_); + close(captured_fd); + } + + ~CapturedStream() { + remove(filename_.c_str()); + } + + String GetCapturedString() { + if (uncaptured_fd_ != -1) { + // Restores the original stream. + fflush(NULL); + dup2(uncaptured_fd_, fd_); + close(uncaptured_fd_); + uncaptured_fd_ = -1; + } + + FILE* const file = posix::FOpen(filename_.c_str(), "r"); + const String content = ReadEntireFile(file); + posix::FClose(file); + return content; + } + + private: + // Reads the entire content of a file as a String. + static String ReadEntireFile(FILE* file); + + // Returns the size (in bytes) of a file. + static size_t GetFileSize(FILE* file); + + const int fd_; // A stream to capture. + int uncaptured_fd_; + // Name of the temporary file holding the stderr output. + ::std::string filename_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(CapturedStream); +}; + +// Returns the size (in bytes) of a file. +size_t CapturedStream::GetFileSize(FILE* file) { + fseek(file, 0, SEEK_END); + return static_cast(ftell(file)); +} + +// Reads the entire content of a file as a string. +String CapturedStream::ReadEntireFile(FILE* file) { + const size_t file_size = GetFileSize(file); + char* const buffer = new char[file_size]; + + size_t bytes_last_read = 0; // # of bytes read in the last fread() + size_t bytes_read = 0; // # of bytes read so far + + fseek(file, 0, SEEK_SET); + + // Keeps reading the file until we cannot read further or the + // pre-determined file size is reached. + do { + bytes_last_read = fread(buffer+bytes_read, 1, file_size-bytes_read, file); + bytes_read += bytes_last_read; + } while (bytes_last_read > 0 && bytes_read < file_size); + + const String content(buffer, bytes_read); + delete[] buffer; + + return content; +} + +# ifdef _MSC_VER +# pragma warning(pop) +# endif // _MSC_VER + +static CapturedStream* g_captured_stderr = NULL; +static CapturedStream* g_captured_stdout = NULL; + +// Starts capturing an output stream (stdout/stderr). +void CaptureStream(int fd, const char* stream_name, CapturedStream** stream) { + if (*stream != NULL) { + GTEST_LOG_(FATAL) << "Only one " << stream_name + << " capturer can exist at a time."; + } + *stream = new CapturedStream(fd); +} + +// Stops capturing the output stream and returns the captured string. +String GetCapturedStream(CapturedStream** captured_stream) { + const String content = (*captured_stream)->GetCapturedString(); + + delete *captured_stream; + *captured_stream = NULL; + + return content; +} + +// Starts capturing stdout. +void CaptureStdout() { + CaptureStream(kStdOutFileno, "stdout", &g_captured_stdout); +} + +// Starts capturing stderr. +void CaptureStderr() { + CaptureStream(kStdErrFileno, "stderr", &g_captured_stderr); +} + +// Stops capturing stdout and returns the captured string. +String GetCapturedStdout() { return GetCapturedStream(&g_captured_stdout); } + +// Stops capturing stderr and returns the captured string. +String GetCapturedStderr() { return GetCapturedStream(&g_captured_stderr); } + +#endif // GTEST_HAS_STREAM_REDIRECTION + +#if GTEST_HAS_DEATH_TEST + +// A copy of all command line arguments. Set by InitGoogleTest(). +::std::vector g_argvs; + +// Returns the command line as a vector of strings. +const ::std::vector& GetArgvs() { return g_argvs; } + +#endif // GTEST_HAS_DEATH_TEST + +#if GTEST_OS_WINDOWS_MOBILE +namespace posix { +void Abort() { + DebugBreak(); + TerminateProcess(GetCurrentProcess(), 1); +} +} // namespace posix +#endif // GTEST_OS_WINDOWS_MOBILE + +// Returns the name of the environment variable corresponding to the +// given flag. For example, FlagToEnvVar("foo") will return +// "GTEST_FOO" in the open-source version. +static String FlagToEnvVar(const char* flag) { + const String full_flag = + (Message() << GTEST_FLAG_PREFIX_ << flag).GetString(); + + Message env_var; + for (size_t i = 0; i != full_flag.length(); i++) { + env_var << ToUpper(full_flag.c_str()[i]); + } + + return env_var.GetString(); +} + +// Parses 'str' for a 32-bit signed integer. If successful, writes +// the result to *value and returns true; otherwise leaves *value +// unchanged and returns false. +bool ParseInt32(const Message& src_text, const char* str, Int32* value) { + // Parses the environment variable as a decimal integer. + char* end = NULL; + const long long_value = strtol(str, &end, 10); // NOLINT + + // Has strtol() consumed all characters in the string? + if (*end != '\0') { + // No - an invalid character was encountered. + Message msg; + msg << "WARNING: " << src_text + << " is expected to be a 32-bit integer, but actually" + << " has value \"" << str << "\".\n"; + printf("%s", msg.GetString().c_str()); + fflush(stdout); + return false; + } + + // Is the parsed value in the range of an Int32? + const Int32 result = static_cast(long_value); + if (long_value == LONG_MAX || long_value == LONG_MIN || + // The parsed value overflows as a long. (strtol() returns + // LONG_MAX or LONG_MIN when the input overflows.) + result != long_value + // The parsed value overflows as an Int32. + ) { + Message msg; + msg << "WARNING: " << src_text + << " is expected to be a 32-bit integer, but actually" + << " has value " << str << ", which overflows.\n"; + printf("%s", msg.GetString().c_str()); + fflush(stdout); + return false; + } + + *value = result; + return true; +} + +// Reads and returns the Boolean environment variable corresponding to +// the given flag; if it's not set, returns default_value. +// +// The value is considered true iff it's not "0". +bool BoolFromGTestEnv(const char* flag, bool default_value) { + const String env_var = FlagToEnvVar(flag); + const char* const string_value = posix::GetEnv(env_var.c_str()); + return string_value == NULL ? + default_value : strcmp(string_value, "0") != 0; +} + +// Reads and returns a 32-bit integer stored in the environment +// variable corresponding to the given flag; if it isn't set or +// doesn't represent a valid 32-bit integer, returns default_value. +Int32 Int32FromGTestEnv(const char* flag, Int32 default_value) { + const String env_var = FlagToEnvVar(flag); + const char* const string_value = posix::GetEnv(env_var.c_str()); + if (string_value == NULL) { + // The environment variable is not set. + return default_value; + } + + Int32 result = default_value; + if (!ParseInt32(Message() << "Environment variable " << env_var, + string_value, &result)) { + printf("The default value %s is used.\n", + (Message() << default_value).GetString().c_str()); + fflush(stdout); + return default_value; + } + + return result; +} + +// Reads and returns the string environment variable corresponding to +// the given flag; if it's not set, returns default_value. +const char* StringFromGTestEnv(const char* flag, const char* default_value) { + const String env_var = FlagToEnvVar(flag); + const char* const value = posix::GetEnv(env_var.c_str()); + return value == NULL ? default_value : value; +} + +} // namespace internal +} // namespace testing +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Test - The Google C++ Testing Framework +// +// This file implements a universal value printer that can print a +// value of any type T: +// +// void ::testing::internal::UniversalPrinter::Print(value, ostream_ptr); +// +// It uses the << operator when possible, and prints the bytes in the +// object otherwise. A user can override its behavior for a class +// type Foo by defining either operator<<(::std::ostream&, const Foo&) +// or void PrintTo(const Foo&, ::std::ostream*) in the namespace that +// defines Foo. + +#include +#include +#include // NOLINT +#include + +namespace testing { + +namespace { + +using ::std::ostream; + +#if GTEST_OS_WINDOWS_MOBILE // Windows CE does not define _snprintf_s. +# define snprintf _snprintf +#elif _MSC_VER >= 1400 // VC 8.0 and later deprecate snprintf and _snprintf. +# define snprintf _snprintf_s +#elif _MSC_VER +# define snprintf _snprintf +#endif // GTEST_OS_WINDOWS_MOBILE + +// Prints a segment of bytes in the given object. +void PrintByteSegmentInObjectTo(const unsigned char* obj_bytes, size_t start, + size_t count, ostream* os) { + char text[5] = ""; + for (size_t i = 0; i != count; i++) { + const size_t j = start + i; + if (i != 0) { + // Organizes the bytes into groups of 2 for easy parsing by + // human. + if ((j % 2) == 0) + *os << ' '; + else + *os << '-'; + } + snprintf(text, sizeof(text), "%02X", obj_bytes[j]); + *os << text; + } +} + +// Prints the bytes in the given value to the given ostream. +void PrintBytesInObjectToImpl(const unsigned char* obj_bytes, size_t count, + ostream* os) { + // Tells the user how big the object is. + *os << count << "-byte object <"; + + const size_t kThreshold = 132; + const size_t kChunkSize = 64; + // If the object size is bigger than kThreshold, we'll have to omit + // some details by printing only the first and the last kChunkSize + // bytes. + // TODO(wan): let the user control the threshold using a flag. + if (count < kThreshold) { + PrintByteSegmentInObjectTo(obj_bytes, 0, count, os); + } else { + PrintByteSegmentInObjectTo(obj_bytes, 0, kChunkSize, os); + *os << " ... "; + // Rounds up to 2-byte boundary. + const size_t resume_pos = (count - kChunkSize + 1)/2*2; + PrintByteSegmentInObjectTo(obj_bytes, resume_pos, count - resume_pos, os); + } + *os << ">"; +} + +} // namespace + +namespace internal2 { + +// Delegates to PrintBytesInObjectToImpl() to print the bytes in the +// given object. The delegation simplifies the implementation, which +// uses the << operator and thus is easier done outside of the +// ::testing::internal namespace, which contains a << operator that +// sometimes conflicts with the one in STL. +void PrintBytesInObjectTo(const unsigned char* obj_bytes, size_t count, + ostream* os) { + PrintBytesInObjectToImpl(obj_bytes, count, os); +} + +} // namespace internal2 + +namespace internal { + +// Depending on the value of a char (or wchar_t), we print it in one +// of three formats: +// - as is if it's a printable ASCII (e.g. 'a', '2', ' '), +// - as a hexidecimal escape sequence (e.g. '\x7F'), or +// - as a special escape sequence (e.g. '\r', '\n'). +enum CharFormat { + kAsIs, + kHexEscape, + kSpecialEscape +}; + +// Returns true if c is a printable ASCII character. We test the +// value of c directly instead of calling isprint(), which is buggy on +// Windows Mobile. +inline bool IsPrintableAscii(wchar_t c) { + return 0x20 <= c && c <= 0x7E; +} + +// Prints a wide or narrow char c as a character literal without the +// quotes, escaping it when necessary; returns how c was formatted. +// The template argument UnsignedChar is the unsigned version of Char, +// which is the type of c. +template +static CharFormat PrintAsCharLiteralTo(Char c, ostream* os) { + switch (static_cast(c)) { + case L'\0': + *os << "\\0"; + break; + case L'\'': + *os << "\\'"; + break; + case L'\\': + *os << "\\\\"; + break; + case L'\a': + *os << "\\a"; + break; + case L'\b': + *os << "\\b"; + break; + case L'\f': + *os << "\\f"; + break; + case L'\n': + *os << "\\n"; + break; + case L'\r': + *os << "\\r"; + break; + case L'\t': + *os << "\\t"; + break; + case L'\v': + *os << "\\v"; + break; + default: + if (IsPrintableAscii(c)) { + *os << static_cast(c); + return kAsIs; + } else { + *os << String::Format("\\x%X", static_cast(c)); + return kHexEscape; + } + } + return kSpecialEscape; +} + +// Prints a char c as if it's part of a string literal, escaping it when +// necessary; returns how c was formatted. +static CharFormat PrintAsWideStringLiteralTo(wchar_t c, ostream* os) { + switch (c) { + case L'\'': + *os << "'"; + return kAsIs; + case L'"': + *os << "\\\""; + return kSpecialEscape; + default: + return PrintAsCharLiteralTo(c, os); + } +} + +// Prints a char c as if it's part of a string literal, escaping it when +// necessary; returns how c was formatted. +static CharFormat PrintAsNarrowStringLiteralTo(char c, ostream* os) { + return PrintAsWideStringLiteralTo(static_cast(c), os); +} + +// Prints a wide or narrow character c and its code. '\0' is printed +// as "'\\0'", other unprintable characters are also properly escaped +// using the standard C++ escape sequence. The template argument +// UnsignedChar is the unsigned version of Char, which is the type of c. +template +void PrintCharAndCodeTo(Char c, ostream* os) { + // First, print c as a literal in the most readable form we can find. + *os << ((sizeof(c) > 1) ? "L'" : "'"); + const CharFormat format = PrintAsCharLiteralTo(c, os); + *os << "'"; + + // To aid user debugging, we also print c's code in decimal, unless + // it's 0 (in which case c was printed as '\\0', making the code + // obvious). + if (c == 0) + return; + *os << " (" << String::Format("%d", c).c_str(); + + // For more convenience, we print c's code again in hexidecimal, + // unless c was already printed in the form '\x##' or the code is in + // [1, 9]. + if (format == kHexEscape || (1 <= c && c <= 9)) { + // Do nothing. + } else { + *os << String::Format(", 0x%X", + static_cast(c)).c_str(); + } + *os << ")"; +} + +void PrintTo(unsigned char c, ::std::ostream* os) { + PrintCharAndCodeTo(c, os); +} +void PrintTo(signed char c, ::std::ostream* os) { + PrintCharAndCodeTo(c, os); +} + +// Prints a wchar_t as a symbol if it is printable or as its internal +// code otherwise and also as its code. L'\0' is printed as "L'\\0'". +void PrintTo(wchar_t wc, ostream* os) { + PrintCharAndCodeTo(wc, os); +} + +// Prints the given array of characters to the ostream. +// The array starts at *begin, the length is len, it may include '\0' characters +// and may not be null-terminated. +static void PrintCharsAsStringTo(const char* begin, size_t len, ostream* os) { + *os << "\""; + bool is_previous_hex = false; + for (size_t index = 0; index < len; ++index) { + const char cur = begin[index]; + if (is_previous_hex && IsXDigit(cur)) { + // Previous character is of '\x..' form and this character can be + // interpreted as another hexadecimal digit in its number. Break string to + // disambiguate. + *os << "\" \""; + } + is_previous_hex = PrintAsNarrowStringLiteralTo(cur, os) == kHexEscape; + } + *os << "\""; +} + +// Prints a (const) char array of 'len' elements, starting at address 'begin'. +void UniversalPrintArray(const char* begin, size_t len, ostream* os) { + PrintCharsAsStringTo(begin, len, os); +} + +// Prints the given array of wide characters to the ostream. +// The array starts at *begin, the length is len, it may include L'\0' +// characters and may not be null-terminated. +static void PrintWideCharsAsStringTo(const wchar_t* begin, size_t len, + ostream* os) { + *os << "L\""; + bool is_previous_hex = false; + for (size_t index = 0; index < len; ++index) { + const wchar_t cur = begin[index]; + if (is_previous_hex && isascii(cur) && IsXDigit(static_cast(cur))) { + // Previous character is of '\x..' form and this character can be + // interpreted as another hexadecimal digit in its number. Break string to + // disambiguate. + *os << "\" L\""; + } + is_previous_hex = PrintAsWideStringLiteralTo(cur, os) == kHexEscape; + } + *os << "\""; +} + +// Prints the given C string to the ostream. +void PrintTo(const char* s, ostream* os) { + if (s == NULL) { + *os << "NULL"; + } else { + *os << ImplicitCast_(s) << " pointing to "; + PrintCharsAsStringTo(s, strlen(s), os); + } +} + +// MSVC compiler can be configured to define whar_t as a typedef +// of unsigned short. Defining an overload for const wchar_t* in that case +// would cause pointers to unsigned shorts be printed as wide strings, +// possibly accessing more memory than intended and causing invalid +// memory accesses. MSVC defines _NATIVE_WCHAR_T_DEFINED symbol when +// wchar_t is implemented as a native type. +#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) +// Prints the given wide C string to the ostream. +void PrintTo(const wchar_t* s, ostream* os) { + if (s == NULL) { + *os << "NULL"; + } else { + *os << ImplicitCast_(s) << " pointing to "; + PrintWideCharsAsStringTo(s, wcslen(s), os); + } +} +#endif // wchar_t is native + +// Prints a ::string object. +#if GTEST_HAS_GLOBAL_STRING +void PrintStringTo(const ::string& s, ostream* os) { + PrintCharsAsStringTo(s.data(), s.size(), os); +} +#endif // GTEST_HAS_GLOBAL_STRING + +void PrintStringTo(const ::std::string& s, ostream* os) { + PrintCharsAsStringTo(s.data(), s.size(), os); +} + +// Prints a ::wstring object. +#if GTEST_HAS_GLOBAL_WSTRING +void PrintWideStringTo(const ::wstring& s, ostream* os) { + PrintWideCharsAsStringTo(s.data(), s.size(), os); +} +#endif // GTEST_HAS_GLOBAL_WSTRING + +#if GTEST_HAS_STD_WSTRING +void PrintWideStringTo(const ::std::wstring& s, ostream* os) { + PrintWideCharsAsStringTo(s.data(), s.size(), os); +} +#endif // GTEST_HAS_STD_WSTRING + +} // namespace internal + +} // namespace testing +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: mheule@google.com (Markus Heule) +// +// The Google C++ Testing Framework (Google Test) + + +// Indicates that this translation unit is part of Google Test's +// implementation. It must come before gtest-internal-inl.h is +// included, or there will be a compiler error. This trick is to +// prevent a user from accidentally including gtest-internal-inl.h in +// his code. +#define GTEST_IMPLEMENTATION_ 1 +#undef GTEST_IMPLEMENTATION_ + +namespace testing { + +using internal::GetUnitTestImpl; + +// Gets the summary of the failure message by omitting the stack trace +// in it. +internal::String TestPartResult::ExtractSummary(const char* message) { + const char* const stack_trace = strstr(message, internal::kStackTraceMarker); + return stack_trace == NULL ? internal::String(message) : + internal::String(message, stack_trace - message); +} + +// Prints a TestPartResult object. +std::ostream& operator<<(std::ostream& os, const TestPartResult& result) { + return os + << result.file_name() << ":" << result.line_number() << ": " + << (result.type() == TestPartResult::kSuccess ? "Success" : + result.type() == TestPartResult::kFatalFailure ? "Fatal failure" : + "Non-fatal failure") << ":\n" + << result.message() << std::endl; +} + +// Appends a TestPartResult to the array. +void TestPartResultArray::Append(const TestPartResult& result) { + array_.push_back(result); +} + +// Returns the TestPartResult at the given index (0-based). +const TestPartResult& TestPartResultArray::GetTestPartResult(int index) const { + if (index < 0 || index >= size()) { + printf("\nInvalid index (%d) into TestPartResultArray.\n", index); + internal::posix::Abort(); + } + + return array_[index]; +} + +// Returns the number of TestPartResult objects in the array. +int TestPartResultArray::size() const { + return static_cast(array_.size()); +} + +namespace internal { + +HasNewFatalFailureHelper::HasNewFatalFailureHelper() + : has_new_fatal_failure_(false), + original_reporter_(GetUnitTestImpl()-> + GetTestPartResultReporterForCurrentThread()) { + GetUnitTestImpl()->SetTestPartResultReporterForCurrentThread(this); +} + +HasNewFatalFailureHelper::~HasNewFatalFailureHelper() { + GetUnitTestImpl()->SetTestPartResultReporterForCurrentThread( + original_reporter_); +} + +void HasNewFatalFailureHelper::ReportTestPartResult( + const TestPartResult& result) { + if (result.fatally_failed()) + has_new_fatal_failure_ = true; + original_reporter_->ReportTestPartResult(result); +} + +} // namespace internal + +} // namespace testing +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + + +namespace testing { +namespace internal { + +#if GTEST_HAS_TYPED_TEST_P + +// Skips to the first non-space char in str. Returns an empty string if str +// contains only whitespace characters. +static const char* SkipSpaces(const char* str) { + while (IsSpace(*str)) + str++; + return str; +} + +// Verifies that registered_tests match the test names in +// defined_test_names_; returns registered_tests if successful, or +// aborts the program otherwise. +const char* TypedTestCasePState::VerifyRegisteredTestNames( + const char* file, int line, const char* registered_tests) { + typedef ::std::set::const_iterator DefinedTestIter; + registered_ = true; + + // Skip initial whitespace in registered_tests since some + // preprocessors prefix stringizied literals with whitespace. + registered_tests = SkipSpaces(registered_tests); + + Message errors; + ::std::set tests; + for (const char* names = registered_tests; names != NULL; + names = SkipComma(names)) { + const String name = GetPrefixUntilComma(names); + if (tests.count(name) != 0) { + errors << "Test " << name << " is listed more than once.\n"; + continue; + } + + bool found = false; + for (DefinedTestIter it = defined_test_names_.begin(); + it != defined_test_names_.end(); + ++it) { + if (name == *it) { + found = true; + break; + } + } + + if (found) { + tests.insert(name); + } else { + errors << "No test named " << name + << " can be found in this test case.\n"; + } + } + + for (DefinedTestIter it = defined_test_names_.begin(); + it != defined_test_names_.end(); + ++it) { + if (tests.count(*it) == 0) { + errors << "You forgot to list test " << *it << ".\n"; + } + } + + const String& errors_str = errors.GetString(); + if (errors_str != "") { + fprintf(stderr, "%s %s", FormatFileLocation(file, line).c_str(), + errors_str.c_str()); + fflush(stderr); + posix::Abort(); + } + + return registered_tests; +} + +#endif // GTEST_HAS_TYPED_TEST_P + +} // namespace internal +} // namespace testing diff --git a/plugins/fff/vendor/fff/gtest/gtest-main.cc b/plugins/fff/vendor/fff/gtest/gtest-main.cc new file mode 100644 index 000000000..4f5bbb280 --- /dev/null +++ b/plugins/fff/vendor/fff/gtest/gtest-main.cc @@ -0,0 +1,6 @@ +#include "gtest.h" + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/plugins/fff/vendor/fff/gtest/gtest.h b/plugins/fff/vendor/fff/gtest/gtest.h new file mode 100644 index 000000000..875b4f829 --- /dev/null +++ b/plugins/fff/vendor/fff/gtest/gtest.h @@ -0,0 +1,19547 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + + + + +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file defines the public API for Google Test. It should be +// included by any test program that uses Google Test. +// +// IMPORTANT NOTE: Due to limitation of the C++ language, we have to +// leave some internal implementation details in this header file. +// They are clearly marked by comments like this: +// +// // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +// +// Such code is NOT meant to be used by a user directly, and is subject +// to CHANGE WITHOUT NOTICE. Therefore DO NOT DEPEND ON IT in a user +// program! +// +// Acknowledgment: Google Test borrowed the idea of automatic test +// registration from Barthelemy Dagenais' (barthelemy@prologique.com) +// easyUnit framework. + +#ifndef GTEST_INCLUDE_GTEST_GTEST_H_ +#define GTEST_INCLUDE_GTEST_GTEST_H_ + +#include +#include + +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Authors: wan@google.com (Zhanyong Wan), eefacm@gmail.com (Sean Mcafee) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file declares functions and macros used internally by +// Google Test. They are subject to change without notice. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_ + +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Authors: wan@google.com (Zhanyong Wan) +// +// Low-level types and utilities for porting Google Test to various +// platforms. They are subject to change without notice. DO NOT USE +// THEM IN USER CODE. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ + +// The user can define the following macros in the build script to +// control Google Test's behavior. If the user doesn't define a macro +// in this list, Google Test will define it. +// +// GTEST_HAS_CLONE - Define it to 1/0 to indicate that clone(2) +// is/isn't available. +// GTEST_HAS_EXCEPTIONS - Define it to 1/0 to indicate that exceptions +// are enabled. +// GTEST_HAS_GLOBAL_STRING - Define it to 1/0 to indicate that ::string +// is/isn't available (some systems define +// ::string, which is different to std::string). +// GTEST_HAS_GLOBAL_WSTRING - Define it to 1/0 to indicate that ::string +// is/isn't available (some systems define +// ::wstring, which is different to std::wstring). +// GTEST_HAS_POSIX_RE - Define it to 1/0 to indicate that POSIX regular +// expressions are/aren't available. +// GTEST_HAS_PTHREAD - Define it to 1/0 to indicate that +// is/isn't available. +// GTEST_HAS_RTTI - Define it to 1/0 to indicate that RTTI is/isn't +// enabled. +// GTEST_HAS_STD_WSTRING - Define it to 1/0 to indicate that +// std::wstring does/doesn't work (Google Test can +// be used where std::wstring is unavailable). +// GTEST_HAS_TR1_TUPLE - Define it to 1/0 to indicate tr1::tuple +// is/isn't available. +// GTEST_HAS_SEH - Define it to 1/0 to indicate whether the +// compiler supports Microsoft's "Structured +// Exception Handling". +// GTEST_HAS_STREAM_REDIRECTION +// - Define it to 1/0 to indicate whether the +// platform supports I/O stream redirection using +// dup() and dup2(). +// GTEST_USE_OWN_TR1_TUPLE - Define it to 1/0 to indicate whether Google +// Test's own tr1 tuple implementation should be +// used. Unused when the user sets +// GTEST_HAS_TR1_TUPLE to 0. +// GTEST_LINKED_AS_SHARED_LIBRARY +// - Define to 1 when compiling tests that use +// Google Test as a shared library (known as +// DLL on Windows). +// GTEST_CREATE_SHARED_LIBRARY +// - Define to 1 when compiling Google Test itself +// as a shared library. + +// This header defines the following utilities: +// +// Macros indicating the current platform (defined to 1 if compiled on +// the given platform; otherwise undefined): +// GTEST_OS_AIX - IBM AIX +// GTEST_OS_CYGWIN - Cygwin +// GTEST_OS_HPUX - HP-UX +// GTEST_OS_LINUX - Linux +// GTEST_OS_LINUX_ANDROID - Google Android +// GTEST_OS_MAC - Mac OS X +// GTEST_OS_NACL - Google Native Client (NaCl) +// GTEST_OS_SOLARIS - Sun Solaris +// GTEST_OS_SYMBIAN - Symbian +// GTEST_OS_WINDOWS - Windows (Desktop, MinGW, or Mobile) +// GTEST_OS_WINDOWS_DESKTOP - Windows Desktop +// GTEST_OS_WINDOWS_MINGW - MinGW +// GTEST_OS_WINDOWS_MOBILE - Windows Mobile +// GTEST_OS_ZOS - z/OS +// +// Among the platforms, Cygwin, Linux, Max OS X, and Windows have the +// most stable support. Since core members of the Google Test project +// don't have access to other platforms, support for them may be less +// stable. If you notice any problems on your platform, please notify +// googletestframework@googlegroups.com (patches for fixing them are +// even more welcome!). +// +// Note that it is possible that none of the GTEST_OS_* macros are defined. +// +// Macros indicating available Google Test features (defined to 1 if +// the corresponding feature is supported; otherwise undefined): +// GTEST_HAS_COMBINE - the Combine() function (for value-parameterized +// tests) +// GTEST_HAS_DEATH_TEST - death tests +// GTEST_HAS_PARAM_TEST - value-parameterized tests +// GTEST_HAS_TYPED_TEST - typed tests +// GTEST_HAS_TYPED_TEST_P - type-parameterized tests +// GTEST_USES_POSIX_RE - enhanced POSIX regex is used. Do not confuse with +// GTEST_HAS_POSIX_RE (see above) which users can +// define themselves. +// GTEST_USES_SIMPLE_RE - our own simple regex is used; +// the above two are mutually exclusive. +// GTEST_CAN_COMPARE_NULL - accepts untyped NULL in EXPECT_EQ(). +// +// Macros for basic C++ coding: +// GTEST_AMBIGUOUS_ELSE_BLOCKER_ - for disabling a gcc warning. +// GTEST_ATTRIBUTE_UNUSED_ - declares that a class' instances or a +// variable don't have to be used. +// GTEST_DISALLOW_ASSIGN_ - disables operator=. +// GTEST_DISALLOW_COPY_AND_ASSIGN_ - disables copy ctor and operator=. +// GTEST_MUST_USE_RESULT_ - declares that a function's result must be used. +// +// Synchronization: +// Mutex, MutexLock, ThreadLocal, GetThreadCount() +// - synchronization primitives. +// GTEST_IS_THREADSAFE - defined to 1 to indicate that the above +// synchronization primitives have real implementations +// and Google Test is thread-safe; or 0 otherwise. +// +// Template meta programming: +// is_pointer - as in TR1; needed on Symbian and IBM XL C/C++ only. +// IteratorTraits - partial implementation of std::iterator_traits, which +// is not available in libCstd when compiled with Sun C++. +// +// Smart pointers: +// scoped_ptr - as in TR2. +// +// Regular expressions: +// RE - a simple regular expression class using the POSIX +// Extended Regular Expression syntax on UNIX-like +// platforms, or a reduced regular exception syntax on +// other platforms, including Windows. +// +// Logging: +// GTEST_LOG_() - logs messages at the specified severity level. +// LogToStderr() - directs all log messages to stderr. +// FlushInfoLog() - flushes informational log messages. +// +// Stdout and stderr capturing: +// CaptureStdout() - starts capturing stdout. +// GetCapturedStdout() - stops capturing stdout and returns the captured +// string. +// CaptureStderr() - starts capturing stderr. +// GetCapturedStderr() - stops capturing stderr and returns the captured +// string. +// +// Integer types: +// TypeWithSize - maps an integer to a int type. +// Int32, UInt32, Int64, UInt64, TimeInMillis +// - integers of known sizes. +// BiggestInt - the biggest signed integer type. +// +// Command-line utilities: +// GTEST_FLAG() - references a flag. +// GTEST_DECLARE_*() - declares a flag. +// GTEST_DEFINE_*() - defines a flag. +// GetArgvs() - returns the command line as a vector of strings. +// +// Environment variable utilities: +// GetEnv() - gets the value of an environment variable. +// BoolFromGTestEnv() - parses a bool environment variable. +// Int32FromGTestEnv() - parses an Int32 environment variable. +// StringFromGTestEnv() - parses a string environment variable. + +#include // for isspace, etc +#include // for ptrdiff_t +#include +#include +#include +#ifndef _WIN32_WCE +# include +# include +#endif // !_WIN32_WCE + +#include // NOLINT +#include // NOLINT +#include // NOLINT + +#define GTEST_DEV_EMAIL_ "googletestframework@@googlegroups.com" +#define GTEST_FLAG_PREFIX_ "gtest_" +#define GTEST_FLAG_PREFIX_DASH_ "gtest-" +#define GTEST_FLAG_PREFIX_UPPER_ "GTEST_" +#define GTEST_NAME_ "Google Test" +#define GTEST_PROJECT_URL_ "http://code.google.com/p/googletest/" + +// Determines the version of gcc that is used to compile this. +#ifdef __GNUC__ +// 40302 means version 4.3.2. +# define GTEST_GCC_VER_ \ + (__GNUC__*10000 + __GNUC_MINOR__*100 + __GNUC_PATCHLEVEL__) +#endif // __GNUC__ + +// Determines the platform on which Google Test is compiled. +#ifdef __CYGWIN__ +# define GTEST_OS_CYGWIN 1 +#elif defined __SYMBIAN32__ +# define GTEST_OS_SYMBIAN 1 +#elif defined _WIN32 +# define GTEST_OS_WINDOWS 1 +# ifdef _WIN32_WCE +# define GTEST_OS_WINDOWS_MOBILE 1 +# elif defined(__MINGW__) || defined(__MINGW32__) +# define GTEST_OS_WINDOWS_MINGW 1 +# else +# define GTEST_OS_WINDOWS_DESKTOP 1 +# endif // _WIN32_WCE +#elif defined __APPLE__ +# define GTEST_OS_MAC 1 +#elif defined __linux__ +# define GTEST_OS_LINUX 1 +# ifdef ANDROID +# define GTEST_OS_LINUX_ANDROID 1 +# endif // ANDROID +#elif defined __MVS__ +# define GTEST_OS_ZOS 1 +#elif defined(__sun) && defined(__SVR4) +# define GTEST_OS_SOLARIS 1 +#elif defined(_AIX) +# define GTEST_OS_AIX 1 +#elif defined(__hpux) +# define GTEST_OS_HPUX 1 +#elif defined __native_client__ +# define GTEST_OS_NACL 1 +#endif // __CYGWIN__ + +// Brings in definitions for functions used in the testing::internal::posix +// namespace (read, write, close, chdir, isatty, stat). We do not currently +// use them on Windows Mobile. +#if !GTEST_OS_WINDOWS +// This assumes that non-Windows OSes provide unistd.h. For OSes where this +// is not the case, we need to include headers that provide the functions +// mentioned above. +# include +# if !GTEST_OS_NACL +// TODO(vladl@google.com): Remove this condition when Native Client SDK adds +// strings.h (tracked in +// http://code.google.com/p/nativeclient/issues/detail?id=1175). +# include // Native Client doesn't provide strings.h. +# endif +#elif !GTEST_OS_WINDOWS_MOBILE +# include +# include +#endif + +// Defines this to true iff Google Test can use POSIX regular expressions. +#ifndef GTEST_HAS_POSIX_RE +# define GTEST_HAS_POSIX_RE (!GTEST_OS_WINDOWS) +#endif + +#if GTEST_HAS_POSIX_RE + +// On some platforms, needs someone to define size_t, and +// won't compile otherwise. We can #include it here as we already +// included , which is guaranteed to define size_t through +// . +# include // NOLINT + +# define GTEST_USES_POSIX_RE 1 + +#elif GTEST_OS_WINDOWS + +// is not available on Windows. Use our own simple regex +// implementation instead. +# define GTEST_USES_SIMPLE_RE 1 + +#else + +// may not be available on this platform. Use our own +// simple regex implementation instead. +# define GTEST_USES_SIMPLE_RE 1 + +#endif // GTEST_HAS_POSIX_RE + +#ifndef GTEST_HAS_EXCEPTIONS +// The user didn't tell us whether exceptions are enabled, so we need +// to figure it out. +# if defined(_MSC_VER) || defined(__BORLANDC__) +// MSVC's and C++Builder's implementations of the STL use the _HAS_EXCEPTIONS +// macro to enable exceptions, so we'll do the same. +// Assumes that exceptions are enabled by default. +# ifndef _HAS_EXCEPTIONS +# define _HAS_EXCEPTIONS 1 +# endif // _HAS_EXCEPTIONS +# define GTEST_HAS_EXCEPTIONS _HAS_EXCEPTIONS +# elif defined(__GNUC__) && __EXCEPTIONS +// gcc defines __EXCEPTIONS to 1 iff exceptions are enabled. +# define GTEST_HAS_EXCEPTIONS 1 +# elif defined(__SUNPRO_CC) +// Sun Pro CC supports exceptions. However, there is no compile-time way of +// detecting whether they are enabled or not. Therefore, we assume that +// they are enabled unless the user tells us otherwise. +# define GTEST_HAS_EXCEPTIONS 1 +# elif defined(__IBMCPP__) && __EXCEPTIONS +// xlC defines __EXCEPTIONS to 1 iff exceptions are enabled. +# define GTEST_HAS_EXCEPTIONS 1 +# elif defined(__HP_aCC) +// Exception handling is in effect by default in HP aCC compiler. It has to +// be turned of by +noeh compiler option if desired. +# define GTEST_HAS_EXCEPTIONS 1 +# else +// For other compilers, we assume exceptions are disabled to be +// conservative. +# define GTEST_HAS_EXCEPTIONS 0 +# endif // defined(_MSC_VER) || defined(__BORLANDC__) +#endif // GTEST_HAS_EXCEPTIONS + +#if !defined(GTEST_HAS_STD_STRING) +// Even though we don't use this macro any longer, we keep it in case +// some clients still depend on it. +# define GTEST_HAS_STD_STRING 1 +#elif !GTEST_HAS_STD_STRING +// The user told us that ::std::string isn't available. +# error "Google Test cannot be used where ::std::string isn't available." +#endif // !defined(GTEST_HAS_STD_STRING) + +#ifndef GTEST_HAS_GLOBAL_STRING +// The user didn't tell us whether ::string is available, so we need +// to figure it out. + +# define GTEST_HAS_GLOBAL_STRING 0 + +#endif // GTEST_HAS_GLOBAL_STRING + +#ifndef GTEST_HAS_STD_WSTRING +// The user didn't tell us whether ::std::wstring is available, so we need +// to figure it out. +// TODO(wan@google.com): uses autoconf to detect whether ::std::wstring +// is available. + +// Cygwin 1.7 and below doesn't support ::std::wstring. +// Solaris' libc++ doesn't support it either. Android has +// no support for it at least as recent as Froyo (2.2). +# define GTEST_HAS_STD_WSTRING \ + (!(GTEST_OS_LINUX_ANDROID || GTEST_OS_CYGWIN || GTEST_OS_SOLARIS)) + +#endif // GTEST_HAS_STD_WSTRING + +#ifndef GTEST_HAS_GLOBAL_WSTRING +// The user didn't tell us whether ::wstring is available, so we need +// to figure it out. +# define GTEST_HAS_GLOBAL_WSTRING \ + (GTEST_HAS_STD_WSTRING && GTEST_HAS_GLOBAL_STRING) +#endif // GTEST_HAS_GLOBAL_WSTRING + +// Determines whether RTTI is available. +#ifndef GTEST_HAS_RTTI +// The user didn't tell us whether RTTI is enabled, so we need to +// figure it out. + +# ifdef _MSC_VER + +# ifdef _CPPRTTI // MSVC defines this macro iff RTTI is enabled. +# define GTEST_HAS_RTTI 1 +# else +# define GTEST_HAS_RTTI 0 +# endif + +// Starting with version 4.3.2, gcc defines __GXX_RTTI iff RTTI is enabled. +# elif defined(__GNUC__) && (GTEST_GCC_VER_ >= 40302) + +# ifdef __GXX_RTTI +# define GTEST_HAS_RTTI 1 +# else +# define GTEST_HAS_RTTI 0 +# endif // __GXX_RTTI + +// Starting with version 9.0 IBM Visual Age defines __RTTI_ALL__ to 1 if +// both the typeid and dynamic_cast features are present. +# elif defined(__IBMCPP__) && (__IBMCPP__ >= 900) + +# ifdef __RTTI_ALL__ +# define GTEST_HAS_RTTI 1 +# else +# define GTEST_HAS_RTTI 0 +# endif + +# else + +// For all other compilers, we assume RTTI is enabled. +# define GTEST_HAS_RTTI 1 + +# endif // _MSC_VER + +#endif // GTEST_HAS_RTTI + +// It's this header's responsibility to #include when RTTI +// is enabled. +#if GTEST_HAS_RTTI +# include +#endif + +// Determines whether Google Test can use the pthreads library. +#ifndef GTEST_HAS_PTHREAD +// The user didn't tell us explicitly, so we assume pthreads support is +// available on Linux and Mac. +// +// To disable threading support in Google Test, add -DGTEST_HAS_PTHREAD=0 +// to your compiler flags. +# define GTEST_HAS_PTHREAD (GTEST_OS_LINUX || GTEST_OS_MAC || GTEST_OS_HPUX) +#endif // GTEST_HAS_PTHREAD + +#if GTEST_HAS_PTHREAD +// gtest-port.h guarantees to #include when GTEST_HAS_PTHREAD is +// true. +# include // NOLINT + +// For timespec and nanosleep, used below. +# include // NOLINT +#endif + +// Determines whether Google Test can use tr1/tuple. You can define +// this macro to 0 to prevent Google Test from using tuple (any +// feature depending on tuple with be disabled in this mode). +#ifndef GTEST_HAS_TR1_TUPLE +// The user didn't tell us not to do it, so we assume it's OK. +# define GTEST_HAS_TR1_TUPLE 1 +#endif // GTEST_HAS_TR1_TUPLE + +// Determines whether Google Test's own tr1 tuple implementation +// should be used. +#ifndef GTEST_USE_OWN_TR1_TUPLE +// The user didn't tell us, so we need to figure it out. + +// We use our own TR1 tuple if we aren't sure the user has an +// implementation of it already. At this time, GCC 4.0.0+ and MSVC +// 2010 are the only mainstream compilers that come with a TR1 tuple +// implementation. NVIDIA's CUDA NVCC compiler pretends to be GCC by +// defining __GNUC__ and friends, but cannot compile GCC's tuple +// implementation. MSVC 2008 (9.0) provides TR1 tuple in a 323 MB +// Feature Pack download, which we cannot assume the user has. +# if (defined(__GNUC__) && !defined(__CUDACC__) && (GTEST_GCC_VER_ >= 40000)) \ + || _MSC_VER >= 1600 +# define GTEST_USE_OWN_TR1_TUPLE 0 +# else +# define GTEST_USE_OWN_TR1_TUPLE 1 +# endif + +#endif // GTEST_USE_OWN_TR1_TUPLE + +// To avoid conditional compilation everywhere, we make it +// gtest-port.h's responsibility to #include the header implementing +// tr1/tuple. +#if GTEST_HAS_TR1_TUPLE + +# if GTEST_USE_OWN_TR1_TUPLE +// This file was GENERATED by a script. DO NOT EDIT BY HAND!!! + +// Copyright 2009 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Implements a subset of TR1 tuple needed by Google Test and Google Mock. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TUPLE_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TUPLE_H_ + +#include // For ::std::pair. + +// The compiler used in Symbian has a bug that prevents us from declaring the +// tuple template as a friend (it complains that tuple is redefined). This +// hack bypasses the bug by declaring the members that should otherwise be +// private as public. +// Sun Studio versions < 12 also have the above bug. +#if defined(__SYMBIAN32__) || (defined(__SUNPRO_CC) && __SUNPRO_CC < 0x590) +# define GTEST_DECLARE_TUPLE_AS_FRIEND_ public: +#else +# define GTEST_DECLARE_TUPLE_AS_FRIEND_ \ + template friend class tuple; \ + private: +#endif + +// GTEST_n_TUPLE_(T) is the type of an n-tuple. +#define GTEST_0_TUPLE_(T) tuple<> +#define GTEST_1_TUPLE_(T) tuple +#define GTEST_2_TUPLE_(T) tuple +#define GTEST_3_TUPLE_(T) tuple +#define GTEST_4_TUPLE_(T) tuple +#define GTEST_5_TUPLE_(T) tuple +#define GTEST_6_TUPLE_(T) tuple +#define GTEST_7_TUPLE_(T) tuple +#define GTEST_8_TUPLE_(T) tuple +#define GTEST_9_TUPLE_(T) tuple +#define GTEST_10_TUPLE_(T) tuple + +// GTEST_n_TYPENAMES_(T) declares a list of n typenames. +#define GTEST_0_TYPENAMES_(T) +#define GTEST_1_TYPENAMES_(T) typename T##0 +#define GTEST_2_TYPENAMES_(T) typename T##0, typename T##1 +#define GTEST_3_TYPENAMES_(T) typename T##0, typename T##1, typename T##2 +#define GTEST_4_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \ + typename T##3 +#define GTEST_5_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \ + typename T##3, typename T##4 +#define GTEST_6_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \ + typename T##3, typename T##4, typename T##5 +#define GTEST_7_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \ + typename T##3, typename T##4, typename T##5, typename T##6 +#define GTEST_8_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \ + typename T##3, typename T##4, typename T##5, typename T##6, typename T##7 +#define GTEST_9_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \ + typename T##3, typename T##4, typename T##5, typename T##6, \ + typename T##7, typename T##8 +#define GTEST_10_TYPENAMES_(T) typename T##0, typename T##1, typename T##2, \ + typename T##3, typename T##4, typename T##5, typename T##6, \ + typename T##7, typename T##8, typename T##9 + +// In theory, defining stuff in the ::std namespace is undefined +// behavior. We can do this as we are playing the role of a standard +// library vendor. +namespace std { +namespace tr1 { + +template +class tuple; + +// Anything in namespace gtest_internal is Google Test's INTERNAL +// IMPLEMENTATION DETAIL and MUST NOT BE USED DIRECTLY in user code. +namespace gtest_internal { + +// ByRef::type is T if T is a reference; otherwise it's const T&. +template +struct ByRef { typedef const T& type; }; // NOLINT +template +struct ByRef { typedef T& type; }; // NOLINT + +// A handy wrapper for ByRef. +#define GTEST_BY_REF_(T) typename ::std::tr1::gtest_internal::ByRef::type + +// AddRef::type is T if T is a reference; otherwise it's T&. This +// is the same as tr1::add_reference::type. +template +struct AddRef { typedef T& type; }; // NOLINT +template +struct AddRef { typedef T& type; }; // NOLINT + +// A handy wrapper for AddRef. +#define GTEST_ADD_REF_(T) typename ::std::tr1::gtest_internal::AddRef::type + +// A helper for implementing get(). +template class Get; + +// A helper for implementing tuple_element. kIndexValid is true +// iff k < the number of fields in tuple type T. +template +struct TupleElement; + +template +struct TupleElement { typedef T0 type; }; + +template +struct TupleElement { typedef T1 type; }; + +template +struct TupleElement { typedef T2 type; }; + +template +struct TupleElement { typedef T3 type; }; + +template +struct TupleElement { typedef T4 type; }; + +template +struct TupleElement { typedef T5 type; }; + +template +struct TupleElement { typedef T6 type; }; + +template +struct TupleElement { typedef T7 type; }; + +template +struct TupleElement { typedef T8 type; }; + +template +struct TupleElement { typedef T9 type; }; + +} // namespace gtest_internal + +template <> +class tuple<> { + public: + tuple() {} + tuple(const tuple& /* t */) {} + tuple& operator=(const tuple& /* t */) { return *this; } +}; + +template +class GTEST_1_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0) : f0_(f0) {} + + tuple(const tuple& t) : f0_(t.f0_) {} + + template + tuple(const GTEST_1_TUPLE_(U)& t) : f0_(t.f0_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_1_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_1_TUPLE_(U)& t) { + f0_ = t.f0_; + return *this; + } + + T0 f0_; +}; + +template +class GTEST_2_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1) : f0_(f0), + f1_(f1) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_) {} + + template + tuple(const GTEST_2_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_) {} + template + tuple(const ::std::pair& p) : f0_(p.first), f1_(p.second) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_2_TUPLE_(U)& t) { + return CopyFrom(t); + } + template + tuple& operator=(const ::std::pair& p) { + f0_ = p.first; + f1_ = p.second; + return *this; + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_2_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + return *this; + } + + T0 f0_; + T1 f1_; +}; + +template +class GTEST_3_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2) : f0_(f0), f1_(f1), f2_(f2) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_) {} + + template + tuple(const GTEST_3_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_3_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_3_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; +}; + +template +class GTEST_4_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_(), f3_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3) : f0_(f0), f1_(f1), f2_(f2), + f3_(f3) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_) {} + + template + tuple(const GTEST_4_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), + f3_(t.f3_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_4_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_4_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + f3_ = t.f3_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; + T3 f3_; +}; + +template +class GTEST_5_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_(), f3_(), f4_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3, + GTEST_BY_REF_(T4) f4) : f0_(f0), f1_(f1), f2_(f2), f3_(f3), f4_(f4) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_), + f4_(t.f4_) {} + + template + tuple(const GTEST_5_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), + f3_(t.f3_), f4_(t.f4_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_5_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_5_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + f3_ = t.f3_; + f4_ = t.f4_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; + T3 f3_; + T4 f4_; +}; + +template +class GTEST_6_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_(), f3_(), f4_(), f5_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3, GTEST_BY_REF_(T4) f4, + GTEST_BY_REF_(T5) f5) : f0_(f0), f1_(f1), f2_(f2), f3_(f3), f4_(f4), + f5_(f5) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_), + f4_(t.f4_), f5_(t.f5_) {} + + template + tuple(const GTEST_6_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), + f3_(t.f3_), f4_(t.f4_), f5_(t.f5_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_6_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_6_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + f3_ = t.f3_; + f4_ = t.f4_; + f5_ = t.f5_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; + T3 f3_; + T4 f4_; + T5 f5_; +}; + +template +class GTEST_7_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_(), f3_(), f4_(), f5_(), f6_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3, GTEST_BY_REF_(T4) f4, + GTEST_BY_REF_(T5) f5, GTEST_BY_REF_(T6) f6) : f0_(f0), f1_(f1), f2_(f2), + f3_(f3), f4_(f4), f5_(f5), f6_(f6) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_), + f4_(t.f4_), f5_(t.f5_), f6_(t.f6_) {} + + template + tuple(const GTEST_7_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), + f3_(t.f3_), f4_(t.f4_), f5_(t.f5_), f6_(t.f6_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_7_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_7_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + f3_ = t.f3_; + f4_ = t.f4_; + f5_ = t.f5_; + f6_ = t.f6_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; + T3 f3_; + T4 f4_; + T5 f5_; + T6 f6_; +}; + +template +class GTEST_8_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_(), f3_(), f4_(), f5_(), f6_(), f7_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3, GTEST_BY_REF_(T4) f4, + GTEST_BY_REF_(T5) f5, GTEST_BY_REF_(T6) f6, + GTEST_BY_REF_(T7) f7) : f0_(f0), f1_(f1), f2_(f2), f3_(f3), f4_(f4), + f5_(f5), f6_(f6), f7_(f7) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_), + f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_) {} + + template + tuple(const GTEST_8_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), + f3_(t.f3_), f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_8_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_8_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + f3_ = t.f3_; + f4_ = t.f4_; + f5_ = t.f5_; + f6_ = t.f6_; + f7_ = t.f7_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; + T3 f3_; + T4 f4_; + T5 f5_; + T6 f6_; + T7 f7_; +}; + +template +class GTEST_9_TUPLE_(T) { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_(), f3_(), f4_(), f5_(), f6_(), f7_(), f8_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3, GTEST_BY_REF_(T4) f4, + GTEST_BY_REF_(T5) f5, GTEST_BY_REF_(T6) f6, GTEST_BY_REF_(T7) f7, + GTEST_BY_REF_(T8) f8) : f0_(f0), f1_(f1), f2_(f2), f3_(f3), f4_(f4), + f5_(f5), f6_(f6), f7_(f7), f8_(f8) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_), + f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_), f8_(t.f8_) {} + + template + tuple(const GTEST_9_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), + f3_(t.f3_), f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_), f8_(t.f8_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_9_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_9_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + f3_ = t.f3_; + f4_ = t.f4_; + f5_ = t.f5_; + f6_ = t.f6_; + f7_ = t.f7_; + f8_ = t.f8_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; + T3 f3_; + T4 f4_; + T5 f5_; + T6 f6_; + T7 f7_; + T8 f8_; +}; + +template +class tuple { + public: + template friend class gtest_internal::Get; + + tuple() : f0_(), f1_(), f2_(), f3_(), f4_(), f5_(), f6_(), f7_(), f8_(), + f9_() {} + + explicit tuple(GTEST_BY_REF_(T0) f0, GTEST_BY_REF_(T1) f1, + GTEST_BY_REF_(T2) f2, GTEST_BY_REF_(T3) f3, GTEST_BY_REF_(T4) f4, + GTEST_BY_REF_(T5) f5, GTEST_BY_REF_(T6) f6, GTEST_BY_REF_(T7) f7, + GTEST_BY_REF_(T8) f8, GTEST_BY_REF_(T9) f9) : f0_(f0), f1_(f1), f2_(f2), + f3_(f3), f4_(f4), f5_(f5), f6_(f6), f7_(f7), f8_(f8), f9_(f9) {} + + tuple(const tuple& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), f3_(t.f3_), + f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_), f8_(t.f8_), f9_(t.f9_) {} + + template + tuple(const GTEST_10_TUPLE_(U)& t) : f0_(t.f0_), f1_(t.f1_), f2_(t.f2_), + f3_(t.f3_), f4_(t.f4_), f5_(t.f5_), f6_(t.f6_), f7_(t.f7_), f8_(t.f8_), + f9_(t.f9_) {} + + tuple& operator=(const tuple& t) { return CopyFrom(t); } + + template + tuple& operator=(const GTEST_10_TUPLE_(U)& t) { + return CopyFrom(t); + } + + GTEST_DECLARE_TUPLE_AS_FRIEND_ + + template + tuple& CopyFrom(const GTEST_10_TUPLE_(U)& t) { + f0_ = t.f0_; + f1_ = t.f1_; + f2_ = t.f2_; + f3_ = t.f3_; + f4_ = t.f4_; + f5_ = t.f5_; + f6_ = t.f6_; + f7_ = t.f7_; + f8_ = t.f8_; + f9_ = t.f9_; + return *this; + } + + T0 f0_; + T1 f1_; + T2 f2_; + T3 f3_; + T4 f4_; + T5 f5_; + T6 f6_; + T7 f7_; + T8 f8_; + T9 f9_; +}; + +// 6.1.3.2 Tuple creation functions. + +// Known limitations: we don't support passing an +// std::tr1::reference_wrapper to make_tuple(). And we don't +// implement tie(). + +inline tuple<> make_tuple() { return tuple<>(); } + +template +inline GTEST_1_TUPLE_(T) make_tuple(const T0& f0) { + return GTEST_1_TUPLE_(T)(f0); +} + +template +inline GTEST_2_TUPLE_(T) make_tuple(const T0& f0, const T1& f1) { + return GTEST_2_TUPLE_(T)(f0, f1); +} + +template +inline GTEST_3_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2) { + return GTEST_3_TUPLE_(T)(f0, f1, f2); +} + +template +inline GTEST_4_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2, + const T3& f3) { + return GTEST_4_TUPLE_(T)(f0, f1, f2, f3); +} + +template +inline GTEST_5_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2, + const T3& f3, const T4& f4) { + return GTEST_5_TUPLE_(T)(f0, f1, f2, f3, f4); +} + +template +inline GTEST_6_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2, + const T3& f3, const T4& f4, const T5& f5) { + return GTEST_6_TUPLE_(T)(f0, f1, f2, f3, f4, f5); +} + +template +inline GTEST_7_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2, + const T3& f3, const T4& f4, const T5& f5, const T6& f6) { + return GTEST_7_TUPLE_(T)(f0, f1, f2, f3, f4, f5, f6); +} + +template +inline GTEST_8_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2, + const T3& f3, const T4& f4, const T5& f5, const T6& f6, const T7& f7) { + return GTEST_8_TUPLE_(T)(f0, f1, f2, f3, f4, f5, f6, f7); +} + +template +inline GTEST_9_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2, + const T3& f3, const T4& f4, const T5& f5, const T6& f6, const T7& f7, + const T8& f8) { + return GTEST_9_TUPLE_(T)(f0, f1, f2, f3, f4, f5, f6, f7, f8); +} + +template +inline GTEST_10_TUPLE_(T) make_tuple(const T0& f0, const T1& f1, const T2& f2, + const T3& f3, const T4& f4, const T5& f5, const T6& f6, const T7& f7, + const T8& f8, const T9& f9) { + return GTEST_10_TUPLE_(T)(f0, f1, f2, f3, f4, f5, f6, f7, f8, f9); +} + +// 6.1.3.3 Tuple helper classes. + +template struct tuple_size; + +template +struct tuple_size { static const int value = 0; }; + +template +struct tuple_size { static const int value = 1; }; + +template +struct tuple_size { static const int value = 2; }; + +template +struct tuple_size { static const int value = 3; }; + +template +struct tuple_size { static const int value = 4; }; + +template +struct tuple_size { static const int value = 5; }; + +template +struct tuple_size { static const int value = 6; }; + +template +struct tuple_size { static const int value = 7; }; + +template +struct tuple_size { static const int value = 8; }; + +template +struct tuple_size { static const int value = 9; }; + +template +struct tuple_size { static const int value = 10; }; + +template +struct tuple_element { + typedef typename gtest_internal::TupleElement< + k < (tuple_size::value), k, Tuple>::type type; +}; + +#define GTEST_TUPLE_ELEMENT_(k, Tuple) typename tuple_element::type + +// 6.1.3.4 Element access. + +namespace gtest_internal { + +template <> +class Get<0> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(0, Tuple)) + Field(Tuple& t) { return t.f0_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(0, Tuple)) + ConstField(const Tuple& t) { return t.f0_; } +}; + +template <> +class Get<1> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(1, Tuple)) + Field(Tuple& t) { return t.f1_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(1, Tuple)) + ConstField(const Tuple& t) { return t.f1_; } +}; + +template <> +class Get<2> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(2, Tuple)) + Field(Tuple& t) { return t.f2_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(2, Tuple)) + ConstField(const Tuple& t) { return t.f2_; } +}; + +template <> +class Get<3> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(3, Tuple)) + Field(Tuple& t) { return t.f3_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(3, Tuple)) + ConstField(const Tuple& t) { return t.f3_; } +}; + +template <> +class Get<4> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(4, Tuple)) + Field(Tuple& t) { return t.f4_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(4, Tuple)) + ConstField(const Tuple& t) { return t.f4_; } +}; + +template <> +class Get<5> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(5, Tuple)) + Field(Tuple& t) { return t.f5_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(5, Tuple)) + ConstField(const Tuple& t) { return t.f5_; } +}; + +template <> +class Get<6> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(6, Tuple)) + Field(Tuple& t) { return t.f6_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(6, Tuple)) + ConstField(const Tuple& t) { return t.f6_; } +}; + +template <> +class Get<7> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(7, Tuple)) + Field(Tuple& t) { return t.f7_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(7, Tuple)) + ConstField(const Tuple& t) { return t.f7_; } +}; + +template <> +class Get<8> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(8, Tuple)) + Field(Tuple& t) { return t.f8_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(8, Tuple)) + ConstField(const Tuple& t) { return t.f8_; } +}; + +template <> +class Get<9> { + public: + template + static GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(9, Tuple)) + Field(Tuple& t) { return t.f9_; } // NOLINT + + template + static GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(9, Tuple)) + ConstField(const Tuple& t) { return t.f9_; } +}; + +} // namespace gtest_internal + +template +GTEST_ADD_REF_(GTEST_TUPLE_ELEMENT_(k, GTEST_10_TUPLE_(T))) +get(GTEST_10_TUPLE_(T)& t) { + return gtest_internal::Get::Field(t); +} + +template +GTEST_BY_REF_(GTEST_TUPLE_ELEMENT_(k, GTEST_10_TUPLE_(T))) +get(const GTEST_10_TUPLE_(T)& t) { + return gtest_internal::Get::ConstField(t); +} + +// 6.1.3.5 Relational operators + +// We only implement == and !=, as we don't have a need for the rest yet. + +namespace gtest_internal { + +// SameSizeTuplePrefixComparator::Eq(t1, t2) returns true if the +// first k fields of t1 equals the first k fields of t2. +// SameSizeTuplePrefixComparator(k1, k2) would be a compiler error if +// k1 != k2. +template +struct SameSizeTuplePrefixComparator; + +template <> +struct SameSizeTuplePrefixComparator<0, 0> { + template + static bool Eq(const Tuple1& /* t1 */, const Tuple2& /* t2 */) { + return true; + } +}; + +template +struct SameSizeTuplePrefixComparator { + template + static bool Eq(const Tuple1& t1, const Tuple2& t2) { + return SameSizeTuplePrefixComparator::Eq(t1, t2) && + ::std::tr1::get(t1) == ::std::tr1::get(t2); + } +}; + +} // namespace gtest_internal + +template +inline bool operator==(const GTEST_10_TUPLE_(T)& t, + const GTEST_10_TUPLE_(U)& u) { + return gtest_internal::SameSizeTuplePrefixComparator< + tuple_size::value, + tuple_size::value>::Eq(t, u); +} + +template +inline bool operator!=(const GTEST_10_TUPLE_(T)& t, + const GTEST_10_TUPLE_(U)& u) { return !(t == u); } + +// 6.1.4 Pairs. +// Unimplemented. + +} // namespace tr1 +} // namespace std + +#undef GTEST_0_TUPLE_ +#undef GTEST_1_TUPLE_ +#undef GTEST_2_TUPLE_ +#undef GTEST_3_TUPLE_ +#undef GTEST_4_TUPLE_ +#undef GTEST_5_TUPLE_ +#undef GTEST_6_TUPLE_ +#undef GTEST_7_TUPLE_ +#undef GTEST_8_TUPLE_ +#undef GTEST_9_TUPLE_ +#undef GTEST_10_TUPLE_ + +#undef GTEST_0_TYPENAMES_ +#undef GTEST_1_TYPENAMES_ +#undef GTEST_2_TYPENAMES_ +#undef GTEST_3_TYPENAMES_ +#undef GTEST_4_TYPENAMES_ +#undef GTEST_5_TYPENAMES_ +#undef GTEST_6_TYPENAMES_ +#undef GTEST_7_TYPENAMES_ +#undef GTEST_8_TYPENAMES_ +#undef GTEST_9_TYPENAMES_ +#undef GTEST_10_TYPENAMES_ + +#undef GTEST_DECLARE_TUPLE_AS_FRIEND_ +#undef GTEST_BY_REF_ +#undef GTEST_ADD_REF_ +#undef GTEST_TUPLE_ELEMENT_ + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TUPLE_H_ +# elif GTEST_OS_SYMBIAN + +// On Symbian, BOOST_HAS_TR1_TUPLE causes Boost's TR1 tuple library to +// use STLport's tuple implementation, which unfortunately doesn't +// work as the copy of STLport distributed with Symbian is incomplete. +// By making sure BOOST_HAS_TR1_TUPLE is undefined, we force Boost to +// use its own tuple implementation. +# ifdef BOOST_HAS_TR1_TUPLE +# undef BOOST_HAS_TR1_TUPLE +# endif // BOOST_HAS_TR1_TUPLE + +// This prevents , which defines +// BOOST_HAS_TR1_TUPLE, from being #included by Boost's . +# define BOOST_TR1_DETAIL_CONFIG_HPP_INCLUDED +# include + +# elif defined(__GNUC__) && (GTEST_GCC_VER_ >= 40000) +// GCC 4.0+ implements tr1/tuple in the header. This does +// not conform to the TR1 spec, which requires the header to be . + +# if !GTEST_HAS_RTTI && GTEST_GCC_VER_ < 40302 +// Until version 4.3.2, gcc has a bug that causes , +// which is #included by , to not compile when RTTI is +// disabled. _TR1_FUNCTIONAL is the header guard for +// . Hence the following #define is a hack to prevent +// from being included. +# define _TR1_FUNCTIONAL 1 +# include +# undef _TR1_FUNCTIONAL // Allows the user to #include + // if he chooses to. +# else +# include // NOLINT +# endif // !GTEST_HAS_RTTI && GTEST_GCC_VER_ < 40302 + +# else +// If the compiler is not GCC 4.0+, we assume the user is using a +// spec-conforming TR1 implementation. +# include // NOLINT +# endif // GTEST_USE_OWN_TR1_TUPLE + +#endif // GTEST_HAS_TR1_TUPLE + +// Determines whether clone(2) is supported. +// Usually it will only be available on Linux, excluding +// Linux on the Itanium architecture. +// Also see http://linux.die.net/man/2/clone. +#ifndef GTEST_HAS_CLONE +// The user didn't tell us, so we need to figure it out. + +# if GTEST_OS_LINUX && !defined(__ia64__) +# define GTEST_HAS_CLONE 1 +# else +# define GTEST_HAS_CLONE 0 +# endif // GTEST_OS_LINUX && !defined(__ia64__) + +#endif // GTEST_HAS_CLONE + +// Determines whether to support stream redirection. This is used to test +// output correctness and to implement death tests. +#ifndef GTEST_HAS_STREAM_REDIRECTION +// By default, we assume that stream redirection is supported on all +// platforms except known mobile ones. +# if GTEST_OS_WINDOWS_MOBILE || GTEST_OS_SYMBIAN +# define GTEST_HAS_STREAM_REDIRECTION 0 +# else +# define GTEST_HAS_STREAM_REDIRECTION 1 +# endif // !GTEST_OS_WINDOWS_MOBILE && !GTEST_OS_SYMBIAN +#endif // GTEST_HAS_STREAM_REDIRECTION + +// Determines whether to support death tests. +// Google Test does not support death tests for VC 7.1 and earlier as +// abort() in a VC 7.1 application compiled as GUI in debug config +// pops up a dialog window that cannot be suppressed programmatically. +#if (GTEST_OS_LINUX || GTEST_OS_MAC || GTEST_OS_CYGWIN || GTEST_OS_SOLARIS || \ + (GTEST_OS_WINDOWS_DESKTOP && _MSC_VER >= 1400) || \ + GTEST_OS_WINDOWS_MINGW || GTEST_OS_AIX || GTEST_OS_HPUX) +# define GTEST_HAS_DEATH_TEST 1 +# include // NOLINT +#endif + +// We don't support MSVC 7.1 with exceptions disabled now. Therefore +// all the compilers we care about are adequate for supporting +// value-parameterized tests. +#define GTEST_HAS_PARAM_TEST 1 + +// Determines whether to support type-driven tests. + +// Typed tests need and variadic macros, which GCC, VC++ 8.0, +// Sun Pro CC, IBM Visual Age, and HP aCC support. +#if defined(__GNUC__) || (_MSC_VER >= 1400) || defined(__SUNPRO_CC) || \ + defined(__IBMCPP__) || defined(__HP_aCC) +# define GTEST_HAS_TYPED_TEST 1 +# define GTEST_HAS_TYPED_TEST_P 1 +#endif + +// Determines whether to support Combine(). This only makes sense when +// value-parameterized tests are enabled. The implementation doesn't +// work on Sun Studio since it doesn't understand templated conversion +// operators. +#if GTEST_HAS_PARAM_TEST && GTEST_HAS_TR1_TUPLE && !defined(__SUNPRO_CC) +# define GTEST_HAS_COMBINE 1 +#endif + +// Determines whether the system compiler uses UTF-16 for encoding wide strings. +#define GTEST_WIDE_STRING_USES_UTF16_ \ + (GTEST_OS_WINDOWS || GTEST_OS_CYGWIN || GTEST_OS_SYMBIAN || GTEST_OS_AIX) + +// Determines whether test results can be streamed to a socket. +#if GTEST_OS_LINUX +# define GTEST_CAN_STREAM_RESULTS_ 1 +#endif + +// Defines some utility macros. + +// The GNU compiler emits a warning if nested "if" statements are followed by +// an "else" statement and braces are not used to explicitly disambiguate the +// "else" binding. This leads to problems with code like: +// +// if (gate) +// ASSERT_*(condition) << "Some message"; +// +// The "switch (0) case 0:" idiom is used to suppress this. +#ifdef __INTEL_COMPILER +# define GTEST_AMBIGUOUS_ELSE_BLOCKER_ +#else +# define GTEST_AMBIGUOUS_ELSE_BLOCKER_ switch (0) case 0: default: // NOLINT +#endif + +// Use this annotation at the end of a struct/class definition to +// prevent the compiler from optimizing away instances that are never +// used. This is useful when all interesting logic happens inside the +// c'tor and / or d'tor. Example: +// +// struct Foo { +// Foo() { ... } +// } GTEST_ATTRIBUTE_UNUSED_; +// +// Also use it after a variable or parameter declaration to tell the +// compiler the variable/parameter does not have to be used. +#if defined(__GNUC__) && !defined(COMPILER_ICC) +# define GTEST_ATTRIBUTE_UNUSED_ __attribute__ ((unused)) +#else +# define GTEST_ATTRIBUTE_UNUSED_ +#endif + +// A macro to disallow operator= +// This should be used in the private: declarations for a class. +#define GTEST_DISALLOW_ASSIGN_(type)\ + void operator=(type const &) + +// A macro to disallow copy constructor and operator= +// This should be used in the private: declarations for a class. +#define GTEST_DISALLOW_COPY_AND_ASSIGN_(type)\ + type(type const &);\ + GTEST_DISALLOW_ASSIGN_(type) + +// Tell the compiler to warn about unused return values for functions declared +// with this macro. The macro should be used on function declarations +// following the argument list: +// +// Sprocket* AllocateSprocket() GTEST_MUST_USE_RESULT_; +#if defined(__GNUC__) && (GTEST_GCC_VER_ >= 30400) && !defined(COMPILER_ICC) +# define GTEST_MUST_USE_RESULT_ __attribute__ ((warn_unused_result)) +#else +# define GTEST_MUST_USE_RESULT_ +#endif // __GNUC__ && (GTEST_GCC_VER_ >= 30400) && !COMPILER_ICC + +// Determine whether the compiler supports Microsoft's Structured Exception +// Handling. This is supported by several Windows compilers but generally +// does not exist on any other system. +#ifndef GTEST_HAS_SEH +// The user didn't tell us, so we need to figure it out. + +# if defined(_MSC_VER) || defined(__BORLANDC__) +// These two compilers are known to support SEH. +# define GTEST_HAS_SEH 1 +# else +// Assume no SEH. +# define GTEST_HAS_SEH 0 +# endif + +#endif // GTEST_HAS_SEH + +#ifdef _MSC_VER + +# if GTEST_LINKED_AS_SHARED_LIBRARY +# define GTEST_API_ __declspec(dllimport) +# elif GTEST_CREATE_SHARED_LIBRARY +# define GTEST_API_ __declspec(dllexport) +# endif + +#endif // _MSC_VER + +#ifndef GTEST_API_ +# define GTEST_API_ +#endif + +#ifdef __GNUC__ +// Ask the compiler to never inline a given function. +# define GTEST_NO_INLINE_ __attribute__((noinline)) +#else +# define GTEST_NO_INLINE_ +#endif + +namespace testing { + +class Message; + +namespace internal { + +class String; + +// The GTEST_COMPILE_ASSERT_ macro can be used to verify that a compile time +// expression is true. For example, you could use it to verify the +// size of a static array: +// +// GTEST_COMPILE_ASSERT_(ARRAYSIZE(content_type_names) == CONTENT_NUM_TYPES, +// content_type_names_incorrect_size); +// +// or to make sure a struct is smaller than a certain size: +// +// GTEST_COMPILE_ASSERT_(sizeof(foo) < 128, foo_too_large); +// +// The second argument to the macro is the name of the variable. If +// the expression is false, most compilers will issue a warning/error +// containing the name of the variable. + +template +struct CompileAssert { +}; + +#define GTEST_COMPILE_ASSERT_(expr, msg) \ + typedef ::testing::internal::CompileAssert<(bool(expr))> \ + msg[bool(expr) ? 1 : -1] + +// Implementation details of GTEST_COMPILE_ASSERT_: +// +// - GTEST_COMPILE_ASSERT_ works by defining an array type that has -1 +// elements (and thus is invalid) when the expression is false. +// +// - The simpler definition +// +// #define GTEST_COMPILE_ASSERT_(expr, msg) typedef char msg[(expr) ? 1 : -1] +// +// does not work, as gcc supports variable-length arrays whose sizes +// are determined at run-time (this is gcc's extension and not part +// of the C++ standard). As a result, gcc fails to reject the +// following code with the simple definition: +// +// int foo; +// GTEST_COMPILE_ASSERT_(foo, msg); // not supposed to compile as foo is +// // not a compile-time constant. +// +// - By using the type CompileAssert<(bool(expr))>, we ensures that +// expr is a compile-time constant. (Template arguments must be +// determined at compile-time.) +// +// - The outter parentheses in CompileAssert<(bool(expr))> are necessary +// to work around a bug in gcc 3.4.4 and 4.0.1. If we had written +// +// CompileAssert +// +// instead, these compilers will refuse to compile +// +// GTEST_COMPILE_ASSERT_(5 > 0, some_message); +// +// (They seem to think the ">" in "5 > 0" marks the end of the +// template argument list.) +// +// - The array size is (bool(expr) ? 1 : -1), instead of simply +// +// ((expr) ? 1 : -1). +// +// This is to avoid running into a bug in MS VC 7.1, which +// causes ((0.0) ? 1 : -1) to incorrectly evaluate to 1. + +// StaticAssertTypeEqHelper is used by StaticAssertTypeEq defined in gtest.h. +// +// This template is declared, but intentionally undefined. +template +struct StaticAssertTypeEqHelper; + +template +struct StaticAssertTypeEqHelper {}; + +#if GTEST_HAS_GLOBAL_STRING +typedef ::string string; +#else +typedef ::std::string string; +#endif // GTEST_HAS_GLOBAL_STRING + +#if GTEST_HAS_GLOBAL_WSTRING +typedef ::wstring wstring; +#elif GTEST_HAS_STD_WSTRING +typedef ::std::wstring wstring; +#endif // GTEST_HAS_GLOBAL_WSTRING + +// A helper for suppressing warnings on constant condition. It just +// returns 'condition'. +GTEST_API_ bool IsTrue(bool condition); + +// Defines scoped_ptr. + +// This implementation of scoped_ptr is PARTIAL - it only contains +// enough stuff to satisfy Google Test's need. +template +class scoped_ptr { + public: + typedef T element_type; + + explicit scoped_ptr(T* p = NULL) : ptr_(p) {} + ~scoped_ptr() { reset(); } + + T& operator*() const { return *ptr_; } + T* operator->() const { return ptr_; } + T* get() const { return ptr_; } + + T* release() { + T* const ptr = ptr_; + ptr_ = NULL; + return ptr; + } + + void reset(T* p = NULL) { + if (p != ptr_) { + if (IsTrue(sizeof(T) > 0)) { // Makes sure T is a complete type. + delete ptr_; + } + ptr_ = p; + } + } + private: + T* ptr_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(scoped_ptr); +}; + +// Defines RE. + +// A simple C++ wrapper for . It uses the POSIX Extended +// Regular Expression syntax. +class GTEST_API_ RE { + public: + // A copy constructor is required by the Standard to initialize object + // references from r-values. + RE(const RE& other) { Init(other.pattern()); } + + // Constructs an RE from a string. + RE(const ::std::string& regex) { Init(regex.c_str()); } // NOLINT + +#if GTEST_HAS_GLOBAL_STRING + + RE(const ::string& regex) { Init(regex.c_str()); } // NOLINT + +#endif // GTEST_HAS_GLOBAL_STRING + + RE(const char* regex) { Init(regex); } // NOLINT + ~RE(); + + // Returns the string representation of the regex. + const char* pattern() const { return pattern_; } + + // FullMatch(str, re) returns true iff regular expression re matches + // the entire str. + // PartialMatch(str, re) returns true iff regular expression re + // matches a substring of str (including str itself). + // + // TODO(wan@google.com): make FullMatch() and PartialMatch() work + // when str contains NUL characters. + static bool FullMatch(const ::std::string& str, const RE& re) { + return FullMatch(str.c_str(), re); + } + static bool PartialMatch(const ::std::string& str, const RE& re) { + return PartialMatch(str.c_str(), re); + } + +#if GTEST_HAS_GLOBAL_STRING + + static bool FullMatch(const ::string& str, const RE& re) { + return FullMatch(str.c_str(), re); + } + static bool PartialMatch(const ::string& str, const RE& re) { + return PartialMatch(str.c_str(), re); + } + +#endif // GTEST_HAS_GLOBAL_STRING + + static bool FullMatch(const char* str, const RE& re); + static bool PartialMatch(const char* str, const RE& re); + + private: + void Init(const char* regex); + + // We use a const char* instead of a string, as Google Test may be used + // where string is not available. We also do not use Google Test's own + // String type here, in order to simplify dependencies between the + // files. + const char* pattern_; + bool is_valid_; + +#if GTEST_USES_POSIX_RE + + regex_t full_regex_; // For FullMatch(). + regex_t partial_regex_; // For PartialMatch(). + +#else // GTEST_USES_SIMPLE_RE + + const char* full_pattern_; // For FullMatch(); + +#endif + + GTEST_DISALLOW_ASSIGN_(RE); +}; + +// Formats a source file path and a line number as they would appear +// in an error message from the compiler used to compile this code. +GTEST_API_ ::std::string FormatFileLocation(const char* file, int line); + +// Formats a file location for compiler-independent XML output. +// Although this function is not platform dependent, we put it next to +// FormatFileLocation in order to contrast the two functions. +GTEST_API_ ::std::string FormatCompilerIndependentFileLocation(const char* file, + int line); + +// Defines logging utilities: +// GTEST_LOG_(severity) - logs messages at the specified severity level. The +// message itself is streamed into the macro. +// LogToStderr() - directs all log messages to stderr. +// FlushInfoLog() - flushes informational log messages. + +enum GTestLogSeverity { + GTEST_INFO, + GTEST_WARNING, + GTEST_ERROR, + GTEST_FATAL +}; + +// Formats log entry severity, provides a stream object for streaming the +// log message, and terminates the message with a newline when going out of +// scope. +class GTEST_API_ GTestLog { + public: + GTestLog(GTestLogSeverity severity, const char* file, int line); + + // Flushes the buffers and, if severity is GTEST_FATAL, aborts the program. + ~GTestLog(); + + ::std::ostream& GetStream() { return ::std::cerr; } + + private: + const GTestLogSeverity severity_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(GTestLog); +}; + +#define GTEST_LOG_(severity) \ + ::testing::internal::GTestLog(::testing::internal::GTEST_##severity, \ + __FILE__, __LINE__).GetStream() + +inline void LogToStderr() {} +inline void FlushInfoLog() { fflush(NULL); } + +// INTERNAL IMPLEMENTATION - DO NOT USE. +// +// GTEST_CHECK_ is an all-mode assert. It aborts the program if the condition +// is not satisfied. +// Synopsys: +// GTEST_CHECK_(boolean_condition); +// or +// GTEST_CHECK_(boolean_condition) << "Additional message"; +// +// This checks the condition and if the condition is not satisfied +// it prints message about the condition violation, including the +// condition itself, plus additional message streamed into it, if any, +// and then it aborts the program. It aborts the program irrespective of +// whether it is built in the debug mode or not. +#define GTEST_CHECK_(condition) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::IsTrue(condition)) \ + ; \ + else \ + GTEST_LOG_(FATAL) << "Condition " #condition " failed. " + +// An all-mode assert to verify that the given POSIX-style function +// call returns 0 (indicating success). Known limitation: this +// doesn't expand to a balanced 'if' statement, so enclose the macro +// in {} if you need to use it as the only statement in an 'if' +// branch. +#define GTEST_CHECK_POSIX_SUCCESS_(posix_call) \ + if (const int gtest_error = (posix_call)) \ + GTEST_LOG_(FATAL) << #posix_call << "failed with error " \ + << gtest_error + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Use ImplicitCast_ as a safe version of static_cast for upcasting in +// the type hierarchy (e.g. casting a Foo* to a SuperclassOfFoo* or a +// const Foo*). When you use ImplicitCast_, the compiler checks that +// the cast is safe. Such explicit ImplicitCast_s are necessary in +// surprisingly many situations where C++ demands an exact type match +// instead of an argument type convertable to a target type. +// +// The syntax for using ImplicitCast_ is the same as for static_cast: +// +// ImplicitCast_(expr) +// +// ImplicitCast_ would have been part of the C++ standard library, +// but the proposal was submitted too late. It will probably make +// its way into the language in the future. +// +// This relatively ugly name is intentional. It prevents clashes with +// similar functions users may have (e.g., implicit_cast). The internal +// namespace alone is not enough because the function can be found by ADL. +template +inline To ImplicitCast_(To x) { return x; } + +// When you upcast (that is, cast a pointer from type Foo to type +// SuperclassOfFoo), it's fine to use ImplicitCast_<>, since upcasts +// always succeed. When you downcast (that is, cast a pointer from +// type Foo to type SubclassOfFoo), static_cast<> isn't safe, because +// how do you know the pointer is really of type SubclassOfFoo? It +// could be a bare Foo, or of type DifferentSubclassOfFoo. Thus, +// when you downcast, you should use this macro. In debug mode, we +// use dynamic_cast<> to double-check the downcast is legal (we die +// if it's not). In normal mode, we do the efficient static_cast<> +// instead. Thus, it's important to test in debug mode to make sure +// the cast is legal! +// This is the only place in the code we should use dynamic_cast<>. +// In particular, you SHOULDN'T be using dynamic_cast<> in order to +// do RTTI (eg code like this: +// if (dynamic_cast(foo)) HandleASubclass1Object(foo); +// if (dynamic_cast(foo)) HandleASubclass2Object(foo); +// You should design the code some other way not to need this. +// +// This relatively ugly name is intentional. It prevents clashes with +// similar functions users may have (e.g., down_cast). The internal +// namespace alone is not enough because the function can be found by ADL. +template // use like this: DownCast_(foo); +inline To DownCast_(From* f) { // so we only accept pointers + // Ensures that To is a sub-type of From *. This test is here only + // for compile-time type checking, and has no overhead in an + // optimized build at run-time, as it will be optimized away + // completely. + if (false) { + const To to = NULL; + ::testing::internal::ImplicitCast_(to); + } + +#if GTEST_HAS_RTTI + // RTTI: debug mode only! + GTEST_CHECK_(f == NULL || dynamic_cast(f) != NULL); +#endif + return static_cast(f); +} + +// Downcasts the pointer of type Base to Derived. +// Derived must be a subclass of Base. The parameter MUST +// point to a class of type Derived, not any subclass of it. +// When RTTI is available, the function performs a runtime +// check to enforce this. +template +Derived* CheckedDowncastToActualType(Base* base) { +#if GTEST_HAS_RTTI + GTEST_CHECK_(typeid(*base) == typeid(Derived)); + return dynamic_cast(base); // NOLINT +#else + return static_cast(base); // Poor man's downcast. +#endif +} + +#if GTEST_HAS_STREAM_REDIRECTION + +// Defines the stderr capturer: +// CaptureStdout - starts capturing stdout. +// GetCapturedStdout - stops capturing stdout and returns the captured string. +// CaptureStderr - starts capturing stderr. +// GetCapturedStderr - stops capturing stderr and returns the captured string. +// +GTEST_API_ void CaptureStdout(); +GTEST_API_ String GetCapturedStdout(); +GTEST_API_ void CaptureStderr(); +GTEST_API_ String GetCapturedStderr(); + +#endif // GTEST_HAS_STREAM_REDIRECTION + + +#if GTEST_HAS_DEATH_TEST + +// A copy of all command line arguments. Set by InitGoogleTest(). +extern ::std::vector g_argvs; + +// GTEST_HAS_DEATH_TEST implies we have ::std::string. +const ::std::vector& GetArgvs(); + +#endif // GTEST_HAS_DEATH_TEST + +// Defines synchronization primitives. + +#if GTEST_HAS_PTHREAD + +// Sleeps for (roughly) n milli-seconds. This function is only for +// testing Google Test's own constructs. Don't use it in user tests, +// either directly or indirectly. +inline void SleepMilliseconds(int n) { + const timespec time = { + 0, // 0 seconds. + n * 1000L * 1000L, // And n ms. + }; + nanosleep(&time, NULL); +} + +// Allows a controller thread to pause execution of newly created +// threads until notified. Instances of this class must be created +// and destroyed in the controller thread. +// +// This class is only for testing Google Test's own constructs. Do not +// use it in user tests, either directly or indirectly. +class Notification { + public: + Notification() : notified_(false) {} + + // Notifies all threads created with this notification to start. Must + // be called from the controller thread. + void Notify() { notified_ = true; } + + // Blocks until the controller thread notifies. Must be called from a test + // thread. + void WaitForNotification() { + while(!notified_) { + SleepMilliseconds(10); + } + } + + private: + volatile bool notified_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(Notification); +}; + +// As a C-function, ThreadFuncWithCLinkage cannot be templated itself. +// Consequently, it cannot select a correct instantiation of ThreadWithParam +// in order to call its Run(). Introducing ThreadWithParamBase as a +// non-templated base class for ThreadWithParam allows us to bypass this +// problem. +class ThreadWithParamBase { + public: + virtual ~ThreadWithParamBase() {} + virtual void Run() = 0; +}; + +// pthread_create() accepts a pointer to a function type with the C linkage. +// According to the Standard (7.5/1), function types with different linkages +// are different even if they are otherwise identical. Some compilers (for +// example, SunStudio) treat them as different types. Since class methods +// cannot be defined with C-linkage we need to define a free C-function to +// pass into pthread_create(). +extern "C" inline void* ThreadFuncWithCLinkage(void* thread) { + static_cast(thread)->Run(); + return NULL; +} + +// Helper class for testing Google Test's multi-threading constructs. +// To use it, write: +// +// void ThreadFunc(int param) { /* Do things with param */ } +// Notification thread_can_start; +// ... +// // The thread_can_start parameter is optional; you can supply NULL. +// ThreadWithParam thread(&ThreadFunc, 5, &thread_can_start); +// thread_can_start.Notify(); +// +// These classes are only for testing Google Test's own constructs. Do +// not use them in user tests, either directly or indirectly. +template +class ThreadWithParam : public ThreadWithParamBase { + public: + typedef void (*UserThreadFunc)(T); + + ThreadWithParam( + UserThreadFunc func, T param, Notification* thread_can_start) + : func_(func), + param_(param), + thread_can_start_(thread_can_start), + finished_(false) { + ThreadWithParamBase* const base = this; + // The thread can be created only after all fields except thread_ + // have been initialized. + GTEST_CHECK_POSIX_SUCCESS_( + pthread_create(&thread_, 0, &ThreadFuncWithCLinkage, base)); + } + ~ThreadWithParam() { Join(); } + + void Join() { + if (!finished_) { + GTEST_CHECK_POSIX_SUCCESS_(pthread_join(thread_, 0)); + finished_ = true; + } + } + + virtual void Run() { + if (thread_can_start_ != NULL) + thread_can_start_->WaitForNotification(); + func_(param_); + } + + private: + const UserThreadFunc func_; // User-supplied thread function. + const T param_; // User-supplied parameter to the thread function. + // When non-NULL, used to block execution until the controller thread + // notifies. + Notification* const thread_can_start_; + bool finished_; // true iff we know that the thread function has finished. + pthread_t thread_; // The native thread object. + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadWithParam); +}; + +// MutexBase and Mutex implement mutex on pthreads-based platforms. They +// are used in conjunction with class MutexLock: +// +// Mutex mutex; +// ... +// MutexLock lock(&mutex); // Acquires the mutex and releases it at the end +// // of the current scope. +// +// MutexBase implements behavior for both statically and dynamically +// allocated mutexes. Do not use MutexBase directly. Instead, write +// the following to define a static mutex: +// +// GTEST_DEFINE_STATIC_MUTEX_(g_some_mutex); +// +// You can forward declare a static mutex like this: +// +// GTEST_DECLARE_STATIC_MUTEX_(g_some_mutex); +// +// To create a dynamic mutex, just define an object of type Mutex. +class MutexBase { + public: + // Acquires this mutex. + void Lock() { + GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_lock(&mutex_)); + owner_ = pthread_self(); + } + + // Releases this mutex. + void Unlock() { + // We don't protect writing to owner_ here, as it's the caller's + // responsibility to ensure that the current thread holds the + // mutex when this is called. + owner_ = 0; + GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_unlock(&mutex_)); + } + + // Does nothing if the current thread holds the mutex. Otherwise, crashes + // with high probability. + void AssertHeld() const { + GTEST_CHECK_(owner_ == pthread_self()) + << "The current thread is not holding the mutex @" << this; + } + + // A static mutex may be used before main() is entered. It may even + // be used before the dynamic initialization stage. Therefore we + // must be able to initialize a static mutex object at link time. + // This means MutexBase has to be a POD and its member variables + // have to be public. + public: + pthread_mutex_t mutex_; // The underlying pthread mutex. + pthread_t owner_; // The thread holding the mutex; 0 means no one holds it. +}; + +// Forward-declares a static mutex. +# define GTEST_DECLARE_STATIC_MUTEX_(mutex) \ + extern ::testing::internal::MutexBase mutex + +// Defines and statically (i.e. at link time) initializes a static mutex. +# define GTEST_DEFINE_STATIC_MUTEX_(mutex) \ + ::testing::internal::MutexBase mutex = { PTHREAD_MUTEX_INITIALIZER, 0 } + +// The Mutex class can only be used for mutexes created at runtime. It +// shares its API with MutexBase otherwise. +class Mutex : public MutexBase { + public: + Mutex() { + GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_init(&mutex_, NULL)); + owner_ = 0; + } + ~Mutex() { + GTEST_CHECK_POSIX_SUCCESS_(pthread_mutex_destroy(&mutex_)); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(Mutex); +}; + +// We cannot name this class MutexLock as the ctor declaration would +// conflict with a macro named MutexLock, which is defined on some +// platforms. Hence the typedef trick below. +class GTestMutexLock { + public: + explicit GTestMutexLock(MutexBase* mutex) + : mutex_(mutex) { mutex_->Lock(); } + + ~GTestMutexLock() { mutex_->Unlock(); } + + private: + MutexBase* const mutex_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(GTestMutexLock); +}; + +typedef GTestMutexLock MutexLock; + +// Helpers for ThreadLocal. + +// pthread_key_create() requires DeleteThreadLocalValue() to have +// C-linkage. Therefore it cannot be templatized to access +// ThreadLocal. Hence the need for class +// ThreadLocalValueHolderBase. +class ThreadLocalValueHolderBase { + public: + virtual ~ThreadLocalValueHolderBase() {} +}; + +// Called by pthread to delete thread-local data stored by +// pthread_setspecific(). +extern "C" inline void DeleteThreadLocalValue(void* value_holder) { + delete static_cast(value_holder); +} + +// Implements thread-local storage on pthreads-based systems. +// +// // Thread 1 +// ThreadLocal tl(100); // 100 is the default value for each thread. +// +// // Thread 2 +// tl.set(150); // Changes the value for thread 2 only. +// EXPECT_EQ(150, tl.get()); +// +// // Thread 1 +// EXPECT_EQ(100, tl.get()); // In thread 1, tl has the original value. +// tl.set(200); +// EXPECT_EQ(200, tl.get()); +// +// The template type argument T must have a public copy constructor. +// In addition, the default ThreadLocal constructor requires T to have +// a public default constructor. +// +// An object managed for a thread by a ThreadLocal instance is deleted +// when the thread exits. Or, if the ThreadLocal instance dies in +// that thread, when the ThreadLocal dies. It's the user's +// responsibility to ensure that all other threads using a ThreadLocal +// have exited when it dies, or the per-thread objects for those +// threads will not be deleted. +// +// Google Test only uses global ThreadLocal objects. That means they +// will die after main() has returned. Therefore, no per-thread +// object managed by Google Test will be leaked as long as all threads +// using Google Test have exited when main() returns. +template +class ThreadLocal { + public: + ThreadLocal() : key_(CreateKey()), + default_() {} + explicit ThreadLocal(const T& value) : key_(CreateKey()), + default_(value) {} + + ~ThreadLocal() { + // Destroys the managed object for the current thread, if any. + DeleteThreadLocalValue(pthread_getspecific(key_)); + + // Releases resources associated with the key. This will *not* + // delete managed objects for other threads. + GTEST_CHECK_POSIX_SUCCESS_(pthread_key_delete(key_)); + } + + T* pointer() { return GetOrCreateValue(); } + const T* pointer() const { return GetOrCreateValue(); } + const T& get() const { return *pointer(); } + void set(const T& value) { *pointer() = value; } + + private: + // Holds a value of type T. + class ValueHolder : public ThreadLocalValueHolderBase { + public: + explicit ValueHolder(const T& value) : value_(value) {} + + T* pointer() { return &value_; } + + private: + T value_; + GTEST_DISALLOW_COPY_AND_ASSIGN_(ValueHolder); + }; + + static pthread_key_t CreateKey() { + pthread_key_t key; + // When a thread exits, DeleteThreadLocalValue() will be called on + // the object managed for that thread. + GTEST_CHECK_POSIX_SUCCESS_( + pthread_key_create(&key, &DeleteThreadLocalValue)); + return key; + } + + T* GetOrCreateValue() const { + ThreadLocalValueHolderBase* const holder = + static_cast(pthread_getspecific(key_)); + if (holder != NULL) { + return CheckedDowncastToActualType(holder)->pointer(); + } + + ValueHolder* const new_holder = new ValueHolder(default_); + ThreadLocalValueHolderBase* const holder_base = new_holder; + GTEST_CHECK_POSIX_SUCCESS_(pthread_setspecific(key_, holder_base)); + return new_holder->pointer(); + } + + // A key pthreads uses for looking up per-thread values. + const pthread_key_t key_; + const T default_; // The default value for each thread. + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ThreadLocal); +}; + +# define GTEST_IS_THREADSAFE 1 + +#else // GTEST_HAS_PTHREAD + +// A dummy implementation of synchronization primitives (mutex, lock, +// and thread-local variable). Necessary for compiling Google Test where +// mutex is not supported - using Google Test in multiple threads is not +// supported on such platforms. + +class Mutex { + public: + Mutex() {} + void AssertHeld() const {} +}; + +# define GTEST_DECLARE_STATIC_MUTEX_(mutex) \ + extern ::testing::internal::Mutex mutex + +# define GTEST_DEFINE_STATIC_MUTEX_(mutex) ::testing::internal::Mutex mutex + +class GTestMutexLock { + public: + explicit GTestMutexLock(Mutex*) {} // NOLINT +}; + +typedef GTestMutexLock MutexLock; + +template +class ThreadLocal { + public: + ThreadLocal() : value_() {} + explicit ThreadLocal(const T& value) : value_(value) {} + T* pointer() { return &value_; } + const T* pointer() const { return &value_; } + const T& get() const { return value_; } + void set(const T& value) { value_ = value; } + private: + T value_; +}; + +// The above synchronization primitives have dummy implementations. +// Therefore Google Test is not thread-safe. +# define GTEST_IS_THREADSAFE 0 + +#endif // GTEST_HAS_PTHREAD + +// Returns the number of threads running in the process, or 0 to indicate that +// we cannot detect it. +GTEST_API_ size_t GetThreadCount(); + +// Passing non-POD classes through ellipsis (...) crashes the ARM +// compiler and generates a warning in Sun Studio. The Nokia Symbian +// and the IBM XL C/C++ compiler try to instantiate a copy constructor +// for objects passed through ellipsis (...), failing for uncopyable +// objects. We define this to ensure that only POD is passed through +// ellipsis on these systems. +#if defined(__SYMBIAN32__) || defined(__IBMCPP__) || defined(__SUNPRO_CC) +// We lose support for NULL detection where the compiler doesn't like +// passing non-POD classes through ellipsis (...). +# define GTEST_ELLIPSIS_NEEDS_POD_ 1 +#else +# define GTEST_CAN_COMPARE_NULL 1 +#endif + +// The Nokia Symbian and IBM XL C/C++ compilers cannot decide between +// const T& and const T* in a function template. These compilers +// _can_ decide between class template specializations for T and T*, +// so a tr1::type_traits-like is_pointer works. +#if defined(__SYMBIAN32__) || defined(__IBMCPP__) +# define GTEST_NEEDS_IS_POINTER_ 1 +#endif + +template +struct bool_constant { + typedef bool_constant type; + static const bool value = bool_value; +}; +template const bool bool_constant::value; + +typedef bool_constant false_type; +typedef bool_constant true_type; + +template +struct is_pointer : public false_type {}; + +template +struct is_pointer : public true_type {}; + +template +struct IteratorTraits { + typedef typename Iterator::value_type value_type; +}; + +template +struct IteratorTraits { + typedef T value_type; +}; + +template +struct IteratorTraits { + typedef T value_type; +}; + +#if GTEST_OS_WINDOWS +# define GTEST_PATH_SEP_ "\\" +# define GTEST_HAS_ALT_PATH_SEP_ 1 +// The biggest signed integer type the compiler supports. +typedef __int64 BiggestInt; +#else +# define GTEST_PATH_SEP_ "/" +# define GTEST_HAS_ALT_PATH_SEP_ 0 +typedef long long BiggestInt; // NOLINT +#endif // GTEST_OS_WINDOWS + +// Utilities for char. + +// isspace(int ch) and friends accept an unsigned char or EOF. char +// may be signed, depending on the compiler (or compiler flags). +// Therefore we need to cast a char to unsigned char before calling +// isspace(), etc. + +inline bool IsAlpha(char ch) { + return isalpha(static_cast(ch)) != 0; +} +inline bool IsAlNum(char ch) { + return isalnum(static_cast(ch)) != 0; +} +inline bool IsDigit(char ch) { + return isdigit(static_cast(ch)) != 0; +} +inline bool IsLower(char ch) { + return islower(static_cast(ch)) != 0; +} +inline bool IsSpace(char ch) { + return isspace(static_cast(ch)) != 0; +} +inline bool IsUpper(char ch) { + return isupper(static_cast(ch)) != 0; +} +inline bool IsXDigit(char ch) { + return isxdigit(static_cast(ch)) != 0; +} + +inline char ToLower(char ch) { + return static_cast(tolower(static_cast(ch))); +} +inline char ToUpper(char ch) { + return static_cast(toupper(static_cast(ch))); +} + +// The testing::internal::posix namespace holds wrappers for common +// POSIX functions. These wrappers hide the differences between +// Windows/MSVC and POSIX systems. Since some compilers define these +// standard functions as macros, the wrapper cannot have the same name +// as the wrapped function. + +namespace posix { + +// Functions with a different name on Windows. + +#if GTEST_OS_WINDOWS + +typedef struct _stat StatStruct; + +# ifdef __BORLANDC__ +inline int IsATTY(int fd) { return isatty(fd); } +inline int StrCaseCmp(const char* s1, const char* s2) { + return stricmp(s1, s2); +} +inline char* StrDup(const char* src) { return strdup(src); } +# else // !__BORLANDC__ +# if GTEST_OS_WINDOWS_MOBILE +inline int IsATTY(int /* fd */) { return 0; } +# else +inline int IsATTY(int fd) { return _isatty(fd); } +# endif // GTEST_OS_WINDOWS_MOBILE +inline int StrCaseCmp(const char* s1, const char* s2) { + return _stricmp(s1, s2); +} +inline char* StrDup(const char* src) { return _strdup(src); } +# endif // __BORLANDC__ + +# if GTEST_OS_WINDOWS_MOBILE +inline int FileNo(FILE* file) { return reinterpret_cast(_fileno(file)); } +// Stat(), RmDir(), and IsDir() are not needed on Windows CE at this +// time and thus not defined there. +# else +inline int FileNo(FILE* file) { return _fileno(file); } +inline int Stat(const char* path, StatStruct* buf) { return _stat(path, buf); } +inline int RmDir(const char* dir) { return _rmdir(dir); } +inline bool IsDir(const StatStruct& st) { + return (_S_IFDIR & st.st_mode) != 0; +} +# endif // GTEST_OS_WINDOWS_MOBILE + +#else + +typedef struct stat StatStruct; + +inline int FileNo(FILE* file) { return fileno(file); } +inline int IsATTY(int fd) { return isatty(fd); } +inline int Stat(const char* path, StatStruct* buf) { return stat(path, buf); } +inline int StrCaseCmp(const char* s1, const char* s2) { + return strcasecmp(s1, s2); +} +inline char* StrDup(const char* src) { return strdup(src); } +inline int RmDir(const char* dir) { return rmdir(dir); } +inline bool IsDir(const StatStruct& st) { return S_ISDIR(st.st_mode); } + +#endif // GTEST_OS_WINDOWS + +// Functions deprecated by MSVC 8.0. + +#ifdef _MSC_VER +// Temporarily disable warning 4996 (deprecated function). +# pragma warning(push) +# pragma warning(disable:4996) +#endif + +inline const char* StrNCpy(char* dest, const char* src, size_t n) { + return strncpy(dest, src, n); +} + +// ChDir(), FReopen(), FDOpen(), Read(), Write(), Close(), and +// StrError() aren't needed on Windows CE at this time and thus not +// defined there. + +#if !GTEST_OS_WINDOWS_MOBILE +inline int ChDir(const char* dir) { return chdir(dir); } +#endif +inline FILE* FOpen(const char* path, const char* mode) { + return fopen(path, mode); +} +#if !GTEST_OS_WINDOWS_MOBILE +inline FILE *FReopen(const char* path, const char* mode, FILE* stream) { + return freopen(path, mode, stream); +} +inline FILE* FDOpen(int fd, const char* mode) { return fdopen(fd, mode); } +#endif +inline int FClose(FILE* fp) { return fclose(fp); } +#if !GTEST_OS_WINDOWS_MOBILE +inline int Read(int fd, void* buf, unsigned int count) { + return static_cast(read(fd, buf, count)); +} +inline int Write(int fd, const void* buf, unsigned int count) { + return static_cast(write(fd, buf, count)); +} +inline int Close(int fd) { return close(fd); } +inline const char* StrError(int errnum) { return strerror(errnum); } +#endif +inline const char* GetEnv(const char* name) { +#if GTEST_OS_WINDOWS_MOBILE + // We are on Windows CE, which has no environment variables. + return NULL; +#elif defined(__BORLANDC__) || defined(__SunOS_5_8) || defined(__SunOS_5_9) + // Environment variables which we programmatically clear will be set to the + // empty string rather than unset (NULL). Handle that case. + const char* const env = getenv(name); + return (env != NULL && env[0] != '\0') ? env : NULL; +#else + return getenv(name); +#endif +} + +#ifdef _MSC_VER +# pragma warning(pop) // Restores the warning state. +#endif + +#if GTEST_OS_WINDOWS_MOBILE +// Windows CE has no C library. The abort() function is used in +// several places in Google Test. This implementation provides a reasonable +// imitation of standard behaviour. +void Abort(); +#else +inline void Abort() { abort(); } +#endif // GTEST_OS_WINDOWS_MOBILE + +} // namespace posix + +// The maximum number a BiggestInt can represent. This definition +// works no matter BiggestInt is represented in one's complement or +// two's complement. +// +// We cannot rely on numeric_limits in STL, as __int64 and long long +// are not part of standard C++ and numeric_limits doesn't need to be +// defined for them. +const BiggestInt kMaxBiggestInt = + ~(static_cast(1) << (8*sizeof(BiggestInt) - 1)); + +// This template class serves as a compile-time function from size to +// type. It maps a size in bytes to a primitive type with that +// size. e.g. +// +// TypeWithSize<4>::UInt +// +// is typedef-ed to be unsigned int (unsigned integer made up of 4 +// bytes). +// +// Such functionality should belong to STL, but I cannot find it +// there. +// +// Google Test uses this class in the implementation of floating-point +// comparison. +// +// For now it only handles UInt (unsigned int) as that's all Google Test +// needs. Other types can be easily added in the future if need +// arises. +template +class TypeWithSize { + public: + // This prevents the user from using TypeWithSize with incorrect + // values of N. + typedef void UInt; +}; + +// The specialization for size 4. +template <> +class TypeWithSize<4> { + public: + // unsigned int has size 4 in both gcc and MSVC. + // + // As base/basictypes.h doesn't compile on Windows, we cannot use + // uint32, uint64, and etc here. + typedef int Int; + typedef unsigned int UInt; +}; + +// The specialization for size 8. +template <> +class TypeWithSize<8> { + public: + +#if GTEST_OS_WINDOWS + typedef __int64 Int; + typedef unsigned __int64 UInt; +#else + typedef long long Int; // NOLINT + typedef unsigned long long UInt; // NOLINT +#endif // GTEST_OS_WINDOWS +}; + +// Integer types of known sizes. +typedef TypeWithSize<4>::Int Int32; +typedef TypeWithSize<4>::UInt UInt32; +typedef TypeWithSize<8>::Int Int64; +typedef TypeWithSize<8>::UInt UInt64; +typedef TypeWithSize<8>::Int TimeInMillis; // Represents time in milliseconds. + +// Utilities for command line flags and environment variables. + +// Macro for referencing flags. +#define GTEST_FLAG(name) FLAGS_gtest_##name + +// Macros for declaring flags. +#define GTEST_DECLARE_bool_(name) GTEST_API_ extern bool GTEST_FLAG(name) +#define GTEST_DECLARE_int32_(name) \ + GTEST_API_ extern ::testing::internal::Int32 GTEST_FLAG(name) +#define GTEST_DECLARE_string_(name) \ + GTEST_API_ extern ::testing::internal::String GTEST_FLAG(name) + +// Macros for defining flags. +#define GTEST_DEFINE_bool_(name, default_val, doc) \ + GTEST_API_ bool GTEST_FLAG(name) = (default_val) +#define GTEST_DEFINE_int32_(name, default_val, doc) \ + GTEST_API_ ::testing::internal::Int32 GTEST_FLAG(name) = (default_val) +#define GTEST_DEFINE_string_(name, default_val, doc) \ + GTEST_API_ ::testing::internal::String GTEST_FLAG(name) = (default_val) + +// Parses 'str' for a 32-bit signed integer. If successful, writes the result +// to *value and returns true; otherwise leaves *value unchanged and returns +// false. +// TODO(chandlerc): Find a better way to refactor flag and environment parsing +// out of both gtest-port.cc and gtest.cc to avoid exporting this utility +// function. +bool ParseInt32(const Message& src_text, const char* str, Int32* value); + +// Parses a bool/Int32/string from the environment variable +// corresponding to the given Google Test flag. +bool BoolFromGTestEnv(const char* flag, bool default_val); +GTEST_API_ Int32 Int32FromGTestEnv(const char* flag, Int32 default_val); +const char* StringFromGTestEnv(const char* flag, const char* default_val); + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PORT_H_ + +#if GTEST_OS_LINUX +# include +# include +# include +# include +#endif // GTEST_OS_LINUX + +#include +#include +#include +#include +#include + +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Authors: wan@google.com (Zhanyong Wan), eefacm@gmail.com (Sean Mcafee) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file declares the String class and functions used internally by +// Google Test. They are subject to change without notice. They should not used +// by code external to Google Test. +// +// This header file is #included by . +// It should not be #included by other files. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_ + +#ifdef __BORLANDC__ +// string.h is not guaranteed to provide strcpy on C++ Builder. +# include +#endif + +#include + +#include + +namespace testing { +namespace internal { + +// String - a UTF-8 string class. +// +// For historic reasons, we don't use std::string. +// +// TODO(wan@google.com): replace this class with std::string or +// implement it in terms of the latter. +// +// Note that String can represent both NULL and the empty string, +// while std::string cannot represent NULL. +// +// NULL and the empty string are considered different. NULL is less +// than anything (including the empty string) except itself. +// +// This class only provides minimum functionality necessary for +// implementing Google Test. We do not intend to implement a full-fledged +// string class here. +// +// Since the purpose of this class is to provide a substitute for +// std::string on platforms where it cannot be used, we define a copy +// constructor and assignment operators such that we don't need +// conditional compilation in a lot of places. +// +// In order to make the representation efficient, the d'tor of String +// is not virtual. Therefore DO NOT INHERIT FROM String. +class GTEST_API_ String { + public: + // Static utility methods + + // Returns the input enclosed in double quotes if it's not NULL; + // otherwise returns "(null)". For example, "\"Hello\"" is returned + // for input "Hello". + // + // This is useful for printing a C string in the syntax of a literal. + // + // Known issue: escape sequences are not handled yet. + static String ShowCStringQuoted(const char* c_str); + + // Clones a 0-terminated C string, allocating memory using new. The + // caller is responsible for deleting the return value using + // delete[]. Returns the cloned string, or NULL if the input is + // NULL. + // + // This is different from strdup() in string.h, which allocates + // memory using malloc(). + static const char* CloneCString(const char* c_str); + +#if GTEST_OS_WINDOWS_MOBILE + // Windows CE does not have the 'ANSI' versions of Win32 APIs. To be + // able to pass strings to Win32 APIs on CE we need to convert them + // to 'Unicode', UTF-16. + + // Creates a UTF-16 wide string from the given ANSI string, allocating + // memory using new. The caller is responsible for deleting the return + // value using delete[]. Returns the wide string, or NULL if the + // input is NULL. + // + // The wide string is created using the ANSI codepage (CP_ACP) to + // match the behaviour of the ANSI versions of Win32 calls and the + // C runtime. + static LPCWSTR AnsiToUtf16(const char* c_str); + + // Creates an ANSI string from the given wide string, allocating + // memory using new. The caller is responsible for deleting the return + // value using delete[]. Returns the ANSI string, or NULL if the + // input is NULL. + // + // The returned string is created using the ANSI codepage (CP_ACP) to + // match the behaviour of the ANSI versions of Win32 calls and the + // C runtime. + static const char* Utf16ToAnsi(LPCWSTR utf16_str); +#endif + + // Compares two C strings. Returns true iff they have the same content. + // + // Unlike strcmp(), this function can handle NULL argument(s). A + // NULL C string is considered different to any non-NULL C string, + // including the empty string. + static bool CStringEquals(const char* lhs, const char* rhs); + + // Converts a wide C string to a String using the UTF-8 encoding. + // NULL will be converted to "(null)". If an error occurred during + // the conversion, "(failed to convert from wide string)" is + // returned. + static String ShowWideCString(const wchar_t* wide_c_str); + + // Similar to ShowWideCString(), except that this function encloses + // the converted string in double quotes. + static String ShowWideCStringQuoted(const wchar_t* wide_c_str); + + // Compares two wide C strings. Returns true iff they have the same + // content. + // + // Unlike wcscmp(), this function can handle NULL argument(s). A + // NULL C string is considered different to any non-NULL C string, + // including the empty string. + static bool WideCStringEquals(const wchar_t* lhs, const wchar_t* rhs); + + // Compares two C strings, ignoring case. Returns true iff they + // have the same content. + // + // Unlike strcasecmp(), this function can handle NULL argument(s). + // A NULL C string is considered different to any non-NULL C string, + // including the empty string. + static bool CaseInsensitiveCStringEquals(const char* lhs, + const char* rhs); + + // Compares two wide C strings, ignoring case. Returns true iff they + // have the same content. + // + // Unlike wcscasecmp(), this function can handle NULL argument(s). + // A NULL C string is considered different to any non-NULL wide C string, + // including the empty string. + // NB: The implementations on different platforms slightly differ. + // On windows, this method uses _wcsicmp which compares according to LC_CTYPE + // environment variable. On GNU platform this method uses wcscasecmp + // which compares according to LC_CTYPE category of the current locale. + // On MacOS X, it uses towlower, which also uses LC_CTYPE category of the + // current locale. + static bool CaseInsensitiveWideCStringEquals(const wchar_t* lhs, + const wchar_t* rhs); + + // Formats a list of arguments to a String, using the same format + // spec string as for printf. + // + // We do not use the StringPrintf class as it is not universally + // available. + // + // The result is limited to 4096 characters (including the tailing + // 0). If 4096 characters are not enough to format the input, + // "" is returned. + static String Format(const char* format, ...); + + // C'tors + + // The default c'tor constructs a NULL string. + String() : c_str_(NULL), length_(0) {} + + // Constructs a String by cloning a 0-terminated C string. + String(const char* a_c_str) { // NOLINT + if (a_c_str == NULL) { + c_str_ = NULL; + length_ = 0; + } else { + ConstructNonNull(a_c_str, strlen(a_c_str)); + } + } + + // Constructs a String by copying a given number of chars from a + // buffer. E.g. String("hello", 3) creates the string "hel", + // String("a\0bcd", 4) creates "a\0bc", String(NULL, 0) creates "", + // and String(NULL, 1) results in access violation. + String(const char* buffer, size_t a_length) { + ConstructNonNull(buffer, a_length); + } + + // The copy c'tor creates a new copy of the string. The two + // String objects do not share content. + String(const String& str) : c_str_(NULL), length_(0) { *this = str; } + + // D'tor. String is intended to be a final class, so the d'tor + // doesn't need to be virtual. + ~String() { delete[] c_str_; } + + // Allows a String to be implicitly converted to an ::std::string or + // ::string, and vice versa. Converting a String containing a NULL + // pointer to ::std::string or ::string is undefined behavior. + // Converting a ::std::string or ::string containing an embedded NUL + // character to a String will result in the prefix up to the first + // NUL character. + String(const ::std::string& str) { + ConstructNonNull(str.c_str(), str.length()); + } + + operator ::std::string() const { return ::std::string(c_str(), length()); } + +#if GTEST_HAS_GLOBAL_STRING + String(const ::string& str) { + ConstructNonNull(str.c_str(), str.length()); + } + + operator ::string() const { return ::string(c_str(), length()); } +#endif // GTEST_HAS_GLOBAL_STRING + + // Returns true iff this is an empty string (i.e. ""). + bool empty() const { return (c_str() != NULL) && (length() == 0); } + + // Compares this with another String. + // Returns < 0 if this is less than rhs, 0 if this is equal to rhs, or > 0 + // if this is greater than rhs. + int Compare(const String& rhs) const; + + // Returns true iff this String equals the given C string. A NULL + // string and a non-NULL string are considered not equal. + bool operator==(const char* a_c_str) const { return Compare(a_c_str) == 0; } + + // Returns true iff this String is less than the given String. A + // NULL string is considered less than "". + bool operator<(const String& rhs) const { return Compare(rhs) < 0; } + + // Returns true iff this String doesn't equal the given C string. A NULL + // string and a non-NULL string are considered not equal. + bool operator!=(const char* a_c_str) const { return !(*this == a_c_str); } + + // Returns true iff this String ends with the given suffix. *Any* + // String is considered to end with a NULL or empty suffix. + bool EndsWith(const char* suffix) const; + + // Returns true iff this String ends with the given suffix, not considering + // case. Any String is considered to end with a NULL or empty suffix. + bool EndsWithCaseInsensitive(const char* suffix) const; + + // Returns the length of the encapsulated string, or 0 if the + // string is NULL. + size_t length() const { return length_; } + + // Gets the 0-terminated C string this String object represents. + // The String object still owns the string. Therefore the caller + // should NOT delete the return value. + const char* c_str() const { return c_str_; } + + // Assigns a C string to this object. Self-assignment works. + const String& operator=(const char* a_c_str) { + return *this = String(a_c_str); + } + + // Assigns a String object to this object. Self-assignment works. + const String& operator=(const String& rhs) { + if (this != &rhs) { + delete[] c_str_; + if (rhs.c_str() == NULL) { + c_str_ = NULL; + length_ = 0; + } else { + ConstructNonNull(rhs.c_str(), rhs.length()); + } + } + + return *this; + } + + private: + // Constructs a non-NULL String from the given content. This + // function can only be called when c_str_ has not been allocated. + // ConstructNonNull(NULL, 0) results in an empty string (""). + // ConstructNonNull(NULL, non_zero) is undefined behavior. + void ConstructNonNull(const char* buffer, size_t a_length) { + char* const str = new char[a_length + 1]; + memcpy(str, buffer, a_length); + str[a_length] = '\0'; + c_str_ = str; + length_ = a_length; + } + + const char* c_str_; + size_t length_; +}; // class String + +// Streams a String to an ostream. Each '\0' character in the String +// is replaced with "\\0". +inline ::std::ostream& operator<<(::std::ostream& os, const String& str) { + if (str.c_str() == NULL) { + os << "(null)"; + } else { + const char* const c_str = str.c_str(); + for (size_t i = 0; i != str.length(); i++) { + if (c_str[i] == '\0') { + os << "\\0"; + } else { + os << c_str[i]; + } + } + } + return os; +} + +// Gets the content of the stringstream's buffer as a String. Each '\0' +// character in the buffer is replaced with "\\0". +GTEST_API_ String StringStreamToString(::std::stringstream* stream); + +// Converts a streamable value to a String. A NULL pointer is +// converted to "(null)". When the input value is a ::string, +// ::std::string, ::wstring, or ::std::wstring object, each NUL +// character in it is replaced with "\\0". + +// Declared here but defined in gtest.h, so that it has access +// to the definition of the Message class, required by the ARM +// compiler. +template +String StreamableToString(const T& streamable); + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_STRING_H_ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: keith.ray@gmail.com (Keith Ray) +// +// Google Test filepath utilities +// +// This header file declares classes and functions used internally by +// Google Test. They are subject to change without notice. +// +// This file is #included in . +// Do not include this header file separately! + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_ + + +namespace testing { +namespace internal { + +// FilePath - a class for file and directory pathname manipulation which +// handles platform-specific conventions (like the pathname separator). +// Used for helper functions for naming files in a directory for xml output. +// Except for Set methods, all methods are const or static, which provides an +// "immutable value object" -- useful for peace of mind. +// A FilePath with a value ending in a path separator ("like/this/") represents +// a directory, otherwise it is assumed to represent a file. In either case, +// it may or may not represent an actual file or directory in the file system. +// Names are NOT checked for syntax correctness -- no checking for illegal +// characters, malformed paths, etc. + +class GTEST_API_ FilePath { + public: + FilePath() : pathname_("") { } + FilePath(const FilePath& rhs) : pathname_(rhs.pathname_) { } + + explicit FilePath(const char* pathname) : pathname_(pathname) { + Normalize(); + } + + explicit FilePath(const String& pathname) : pathname_(pathname) { + Normalize(); + } + + FilePath& operator=(const FilePath& rhs) { + Set(rhs); + return *this; + } + + void Set(const FilePath& rhs) { + pathname_ = rhs.pathname_; + } + + String ToString() const { return pathname_; } + const char* c_str() const { return pathname_.c_str(); } + + // Returns the current working directory, or "" if unsuccessful. + static FilePath GetCurrentDir(); + + // Given directory = "dir", base_name = "test", number = 0, + // extension = "xml", returns "dir/test.xml". If number is greater + // than zero (e.g., 12), returns "dir/test_12.xml". + // On Windows platform, uses \ as the separator rather than /. + static FilePath MakeFileName(const FilePath& directory, + const FilePath& base_name, + int number, + const char* extension); + + // Given directory = "dir", relative_path = "test.xml", + // returns "dir/test.xml". + // On Windows, uses \ as the separator rather than /. + static FilePath ConcatPaths(const FilePath& directory, + const FilePath& relative_path); + + // Returns a pathname for a file that does not currently exist. The pathname + // will be directory/base_name.extension or + // directory/base_name_.extension if directory/base_name.extension + // already exists. The number will be incremented until a pathname is found + // that does not already exist. + // Examples: 'dir/foo_test.xml' or 'dir/foo_test_1.xml'. + // There could be a race condition if two or more processes are calling this + // function at the same time -- they could both pick the same filename. + static FilePath GenerateUniqueFileName(const FilePath& directory, + const FilePath& base_name, + const char* extension); + + // Returns true iff the path is NULL or "". + bool IsEmpty() const { return c_str() == NULL || *c_str() == '\0'; } + + // If input name has a trailing separator character, removes it and returns + // the name, otherwise return the name string unmodified. + // On Windows platform, uses \ as the separator, other platforms use /. + FilePath RemoveTrailingPathSeparator() const; + + // Returns a copy of the FilePath with the directory part removed. + // Example: FilePath("path/to/file").RemoveDirectoryName() returns + // FilePath("file"). If there is no directory part ("just_a_file"), it returns + // the FilePath unmodified. If there is no file part ("just_a_dir/") it + // returns an empty FilePath (""). + // On Windows platform, '\' is the path separator, otherwise it is '/'. + FilePath RemoveDirectoryName() const; + + // RemoveFileName returns the directory path with the filename removed. + // Example: FilePath("path/to/file").RemoveFileName() returns "path/to/". + // If the FilePath is "a_file" or "/a_file", RemoveFileName returns + // FilePath("./") or, on Windows, FilePath(".\\"). If the filepath does + // not have a file, like "just/a/dir/", it returns the FilePath unmodified. + // On Windows platform, '\' is the path separator, otherwise it is '/'. + FilePath RemoveFileName() const; + + // Returns a copy of the FilePath with the case-insensitive extension removed. + // Example: FilePath("dir/file.exe").RemoveExtension("EXE") returns + // FilePath("dir/file"). If a case-insensitive extension is not + // found, returns a copy of the original FilePath. + FilePath RemoveExtension(const char* extension) const; + + // Creates directories so that path exists. Returns true if successful or if + // the directories already exist; returns false if unable to create + // directories for any reason. Will also return false if the FilePath does + // not represent a directory (that is, it doesn't end with a path separator). + bool CreateDirectoriesRecursively() const; + + // Create the directory so that path exists. Returns true if successful or + // if the directory already exists; returns false if unable to create the + // directory for any reason, including if the parent directory does not + // exist. Not named "CreateDirectory" because that's a macro on Windows. + bool CreateFolder() const; + + // Returns true if FilePath describes something in the file-system, + // either a file, directory, or whatever, and that something exists. + bool FileOrDirectoryExists() const; + + // Returns true if pathname describes a directory in the file-system + // that exists. + bool DirectoryExists() const; + + // Returns true if FilePath ends with a path separator, which indicates that + // it is intended to represent a directory. Returns false otherwise. + // This does NOT check that a directory (or file) actually exists. + bool IsDirectory() const; + + // Returns true if pathname describes a root directory. (Windows has one + // root directory per disk drive.) + bool IsRootDirectory() const; + + // Returns true if pathname describes an absolute path. + bool IsAbsolutePath() const; + + private: + // Replaces multiple consecutive separators with a single separator. + // For example, "bar///foo" becomes "bar/foo". Does not eliminate other + // redundancies that might be in a pathname involving "." or "..". + // + // A pathname with multiple consecutive separators may occur either through + // user error or as a result of some scripts or APIs that generate a pathname + // with a trailing separator. On other platforms the same API or script + // may NOT generate a pathname with a trailing "/". Then elsewhere that + // pathname may have another "/" and pathname components added to it, + // without checking for the separator already being there. + // The script language and operating system may allow paths like "foo//bar" + // but some of the functions in FilePath will not handle that correctly. In + // particular, RemoveTrailingPathSeparator() only removes one separator, and + // it is called in CreateDirectoriesRecursively() assuming that it will change + // a pathname from directory syntax (trailing separator) to filename syntax. + // + // On Windows this method also replaces the alternate path separator '/' with + // the primary path separator '\\', so that for example "bar\\/\\foo" becomes + // "bar\\foo". + + void Normalize(); + + // Returns a pointer to the last occurence of a valid path separator in + // the FilePath. On Windows, for example, both '/' and '\' are valid path + // separators. Returns NULL if no path separator was found. + const char* FindLastPathSeparator() const; + + String pathname_; +}; // class FilePath + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_FILEPATH_H_ +// This file was GENERATED by command: +// pump.py gtest-type-util.h.pump +// DO NOT EDIT BY HAND!!! + +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Type utilities needed for implementing typed and type-parameterized +// tests. This file is generated by a SCRIPT. DO NOT EDIT BY HAND! +// +// Currently we support at most 50 types in a list, and at most 50 +// type-parameterized tests in one type-parameterized test case. +// Please contact googletestframework@googlegroups.com if you need +// more. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ + + +// #ifdef __GNUC__ is too general here. It is possible to use gcc without using +// libstdc++ (which is where cxxabi.h comes from). +# ifdef __GLIBCXX__ +# include +# elif defined(__HP_aCC) +# include +# endif // __GLIBCXX__ + +namespace testing { +namespace internal { + +// GetTypeName() returns a human-readable name of type T. +// NB: This function is also used in Google Mock, so don't move it inside of +// the typed-test-only section below. +template +String GetTypeName() { +# if GTEST_HAS_RTTI + + const char* const name = typeid(T).name(); +# if defined(__GLIBCXX__) || defined(__HP_aCC) + int status = 0; + // gcc's implementation of typeid(T).name() mangles the type name, + // so we have to demangle it. +# ifdef __GLIBCXX__ + using abi::__cxa_demangle; +# endif // __GLIBCXX__ + char* const readable_name = __cxa_demangle(name, 0, 0, &status); + const String name_str(status == 0 ? readable_name : name); + free(readable_name); + return name_str; +# else + return name; +# endif // __GLIBCXX__ || __HP_aCC + +# else + + return ""; + +# endif // GTEST_HAS_RTTI +} + +#if GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + +// AssertyTypeEq::type is defined iff T1 and T2 are the same +// type. This can be used as a compile-time assertion to ensure that +// two types are equal. + +template +struct AssertTypeEq; + +template +struct AssertTypeEq { + typedef bool type; +}; + +// A unique type used as the default value for the arguments of class +// template Types. This allows us to simulate variadic templates +// (e.g. Types, Type, and etc), which C++ doesn't +// support directly. +struct None {}; + +// The following family of struct and struct templates are used to +// represent type lists. In particular, TypesN +// represents a type list with N types (T1, T2, ..., and TN) in it. +// Except for Types0, every struct in the family has two member types: +// Head for the first type in the list, and Tail for the rest of the +// list. + +// The empty type list. +struct Types0 {}; + +// Type lists of length 1, 2, 3, and so on. + +template +struct Types1 { + typedef T1 Head; + typedef Types0 Tail; +}; +template +struct Types2 { + typedef T1 Head; + typedef Types1 Tail; +}; + +template +struct Types3 { + typedef T1 Head; + typedef Types2 Tail; +}; + +template +struct Types4 { + typedef T1 Head; + typedef Types3 Tail; +}; + +template +struct Types5 { + typedef T1 Head; + typedef Types4 Tail; +}; + +template +struct Types6 { + typedef T1 Head; + typedef Types5 Tail; +}; + +template +struct Types7 { + typedef T1 Head; + typedef Types6 Tail; +}; + +template +struct Types8 { + typedef T1 Head; + typedef Types7 Tail; +}; + +template +struct Types9 { + typedef T1 Head; + typedef Types8 Tail; +}; + +template +struct Types10 { + typedef T1 Head; + typedef Types9 Tail; +}; + +template +struct Types11 { + typedef T1 Head; + typedef Types10 Tail; +}; + +template +struct Types12 { + typedef T1 Head; + typedef Types11 Tail; +}; + +template +struct Types13 { + typedef T1 Head; + typedef Types12 Tail; +}; + +template +struct Types14 { + typedef T1 Head; + typedef Types13 Tail; +}; + +template +struct Types15 { + typedef T1 Head; + typedef Types14 Tail; +}; + +template +struct Types16 { + typedef T1 Head; + typedef Types15 Tail; +}; + +template +struct Types17 { + typedef T1 Head; + typedef Types16 Tail; +}; + +template +struct Types18 { + typedef T1 Head; + typedef Types17 Tail; +}; + +template +struct Types19 { + typedef T1 Head; + typedef Types18 Tail; +}; + +template +struct Types20 { + typedef T1 Head; + typedef Types19 Tail; +}; + +template +struct Types21 { + typedef T1 Head; + typedef Types20 Tail; +}; + +template +struct Types22 { + typedef T1 Head; + typedef Types21 Tail; +}; + +template +struct Types23 { + typedef T1 Head; + typedef Types22 Tail; +}; + +template +struct Types24 { + typedef T1 Head; + typedef Types23 Tail; +}; + +template +struct Types25 { + typedef T1 Head; + typedef Types24 Tail; +}; + +template +struct Types26 { + typedef T1 Head; + typedef Types25 Tail; +}; + +template +struct Types27 { + typedef T1 Head; + typedef Types26 Tail; +}; + +template +struct Types28 { + typedef T1 Head; + typedef Types27 Tail; +}; + +template +struct Types29 { + typedef T1 Head; + typedef Types28 Tail; +}; + +template +struct Types30 { + typedef T1 Head; + typedef Types29 Tail; +}; + +template +struct Types31 { + typedef T1 Head; + typedef Types30 Tail; +}; + +template +struct Types32 { + typedef T1 Head; + typedef Types31 Tail; +}; + +template +struct Types33 { + typedef T1 Head; + typedef Types32 Tail; +}; + +template +struct Types34 { + typedef T1 Head; + typedef Types33 Tail; +}; + +template +struct Types35 { + typedef T1 Head; + typedef Types34 Tail; +}; + +template +struct Types36 { + typedef T1 Head; + typedef Types35 Tail; +}; + +template +struct Types37 { + typedef T1 Head; + typedef Types36 Tail; +}; + +template +struct Types38 { + typedef T1 Head; + typedef Types37 Tail; +}; + +template +struct Types39 { + typedef T1 Head; + typedef Types38 Tail; +}; + +template +struct Types40 { + typedef T1 Head; + typedef Types39 Tail; +}; + +template +struct Types41 { + typedef T1 Head; + typedef Types40 Tail; +}; + +template +struct Types42 { + typedef T1 Head; + typedef Types41 Tail; +}; + +template +struct Types43 { + typedef T1 Head; + typedef Types42 Tail; +}; + +template +struct Types44 { + typedef T1 Head; + typedef Types43 Tail; +}; + +template +struct Types45 { + typedef T1 Head; + typedef Types44 Tail; +}; + +template +struct Types46 { + typedef T1 Head; + typedef Types45 Tail; +}; + +template +struct Types47 { + typedef T1 Head; + typedef Types46 Tail; +}; + +template +struct Types48 { + typedef T1 Head; + typedef Types47 Tail; +}; + +template +struct Types49 { + typedef T1 Head; + typedef Types48 Tail; +}; + +template +struct Types50 { + typedef T1 Head; + typedef Types49 Tail; +}; + + +} // namespace internal + +// We don't want to require the users to write TypesN<...> directly, +// as that would require them to count the length. Types<...> is much +// easier to write, but generates horrible messages when there is a +// compiler error, as gcc insists on printing out each template +// argument, even if it has the default value (this means Types +// will appear as Types in the compiler +// errors). +// +// Our solution is to combine the best part of the two approaches: a +// user would write Types, and Google Test will translate +// that to TypesN internally to make error messages +// readable. The translation is done by the 'type' member of the +// Types template. +template +struct Types { + typedef internal::Types50 type; +}; + +template <> +struct Types { + typedef internal::Types0 type; +}; +template +struct Types { + typedef internal::Types1 type; +}; +template +struct Types { + typedef internal::Types2 type; +}; +template +struct Types { + typedef internal::Types3 type; +}; +template +struct Types { + typedef internal::Types4 type; +}; +template +struct Types { + typedef internal::Types5 type; +}; +template +struct Types { + typedef internal::Types6 type; +}; +template +struct Types { + typedef internal::Types7 type; +}; +template +struct Types { + typedef internal::Types8 type; +}; +template +struct Types { + typedef internal::Types9 type; +}; +template +struct Types { + typedef internal::Types10 type; +}; +template +struct Types { + typedef internal::Types11 type; +}; +template +struct Types { + typedef internal::Types12 type; +}; +template +struct Types { + typedef internal::Types13 type; +}; +template +struct Types { + typedef internal::Types14 type; +}; +template +struct Types { + typedef internal::Types15 type; +}; +template +struct Types { + typedef internal::Types16 type; +}; +template +struct Types { + typedef internal::Types17 type; +}; +template +struct Types { + typedef internal::Types18 type; +}; +template +struct Types { + typedef internal::Types19 type; +}; +template +struct Types { + typedef internal::Types20 type; +}; +template +struct Types { + typedef internal::Types21 type; +}; +template +struct Types { + typedef internal::Types22 type; +}; +template +struct Types { + typedef internal::Types23 type; +}; +template +struct Types { + typedef internal::Types24 type; +}; +template +struct Types { + typedef internal::Types25 type; +}; +template +struct Types { + typedef internal::Types26 type; +}; +template +struct Types { + typedef internal::Types27 type; +}; +template +struct Types { + typedef internal::Types28 type; +}; +template +struct Types { + typedef internal::Types29 type; +}; +template +struct Types { + typedef internal::Types30 type; +}; +template +struct Types { + typedef internal::Types31 type; +}; +template +struct Types { + typedef internal::Types32 type; +}; +template +struct Types { + typedef internal::Types33 type; +}; +template +struct Types { + typedef internal::Types34 type; +}; +template +struct Types { + typedef internal::Types35 type; +}; +template +struct Types { + typedef internal::Types36 type; +}; +template +struct Types { + typedef internal::Types37 type; +}; +template +struct Types { + typedef internal::Types38 type; +}; +template +struct Types { + typedef internal::Types39 type; +}; +template +struct Types { + typedef internal::Types40 type; +}; +template +struct Types { + typedef internal::Types41 type; +}; +template +struct Types { + typedef internal::Types42 type; +}; +template +struct Types { + typedef internal::Types43 type; +}; +template +struct Types { + typedef internal::Types44 type; +}; +template +struct Types { + typedef internal::Types45 type; +}; +template +struct Types { + typedef internal::Types46 type; +}; +template +struct Types { + typedef internal::Types47 type; +}; +template +struct Types { + typedef internal::Types48 type; +}; +template +struct Types { + typedef internal::Types49 type; +}; + +namespace internal { + +# define GTEST_TEMPLATE_ template class + +// The template "selector" struct TemplateSel is used to +// represent Tmpl, which must be a class template with one type +// parameter, as a type. TemplateSel::Bind::type is defined +// as the type Tmpl. This allows us to actually instantiate the +// template "selected" by TemplateSel. +// +// This trick is necessary for simulating typedef for class templates, +// which C++ doesn't support directly. +template +struct TemplateSel { + template + struct Bind { + typedef Tmpl type; + }; +}; + +# define GTEST_BIND_(TmplSel, T) \ + TmplSel::template Bind::type + +// A unique struct template used as the default value for the +// arguments of class template Templates. This allows us to simulate +// variadic templates (e.g. Templates, Templates, +// and etc), which C++ doesn't support directly. +template +struct NoneT {}; + +// The following family of struct and struct templates are used to +// represent template lists. In particular, TemplatesN represents a list of N templates (T1, T2, ..., and TN). Except +// for Templates0, every struct in the family has two member types: +// Head for the selector of the first template in the list, and Tail +// for the rest of the list. + +// The empty template list. +struct Templates0 {}; + +// Template lists of length 1, 2, 3, and so on. + +template +struct Templates1 { + typedef TemplateSel Head; + typedef Templates0 Tail; +}; +template +struct Templates2 { + typedef TemplateSel Head; + typedef Templates1 Tail; +}; + +template +struct Templates3 { + typedef TemplateSel Head; + typedef Templates2 Tail; +}; + +template +struct Templates4 { + typedef TemplateSel Head; + typedef Templates3 Tail; +}; + +template +struct Templates5 { + typedef TemplateSel Head; + typedef Templates4 Tail; +}; + +template +struct Templates6 { + typedef TemplateSel Head; + typedef Templates5 Tail; +}; + +template +struct Templates7 { + typedef TemplateSel Head; + typedef Templates6 Tail; +}; + +template +struct Templates8 { + typedef TemplateSel Head; + typedef Templates7 Tail; +}; + +template +struct Templates9 { + typedef TemplateSel Head; + typedef Templates8 Tail; +}; + +template +struct Templates10 { + typedef TemplateSel Head; + typedef Templates9 Tail; +}; + +template +struct Templates11 { + typedef TemplateSel Head; + typedef Templates10 Tail; +}; + +template +struct Templates12 { + typedef TemplateSel Head; + typedef Templates11 Tail; +}; + +template +struct Templates13 { + typedef TemplateSel Head; + typedef Templates12 Tail; +}; + +template +struct Templates14 { + typedef TemplateSel Head; + typedef Templates13 Tail; +}; + +template +struct Templates15 { + typedef TemplateSel Head; + typedef Templates14 Tail; +}; + +template +struct Templates16 { + typedef TemplateSel Head; + typedef Templates15 Tail; +}; + +template +struct Templates17 { + typedef TemplateSel Head; + typedef Templates16 Tail; +}; + +template +struct Templates18 { + typedef TemplateSel Head; + typedef Templates17 Tail; +}; + +template +struct Templates19 { + typedef TemplateSel Head; + typedef Templates18 Tail; +}; + +template +struct Templates20 { + typedef TemplateSel Head; + typedef Templates19 Tail; +}; + +template +struct Templates21 { + typedef TemplateSel Head; + typedef Templates20 Tail; +}; + +template +struct Templates22 { + typedef TemplateSel Head; + typedef Templates21 Tail; +}; + +template +struct Templates23 { + typedef TemplateSel Head; + typedef Templates22 Tail; +}; + +template +struct Templates24 { + typedef TemplateSel Head; + typedef Templates23 Tail; +}; + +template +struct Templates25 { + typedef TemplateSel Head; + typedef Templates24 Tail; +}; + +template +struct Templates26 { + typedef TemplateSel Head; + typedef Templates25 Tail; +}; + +template +struct Templates27 { + typedef TemplateSel Head; + typedef Templates26 Tail; +}; + +template +struct Templates28 { + typedef TemplateSel Head; + typedef Templates27 Tail; +}; + +template +struct Templates29 { + typedef TemplateSel Head; + typedef Templates28 Tail; +}; + +template +struct Templates30 { + typedef TemplateSel Head; + typedef Templates29 Tail; +}; + +template +struct Templates31 { + typedef TemplateSel Head; + typedef Templates30 Tail; +}; + +template +struct Templates32 { + typedef TemplateSel Head; + typedef Templates31 Tail; +}; + +template +struct Templates33 { + typedef TemplateSel Head; + typedef Templates32 Tail; +}; + +template +struct Templates34 { + typedef TemplateSel Head; + typedef Templates33 Tail; +}; + +template +struct Templates35 { + typedef TemplateSel Head; + typedef Templates34 Tail; +}; + +template +struct Templates36 { + typedef TemplateSel Head; + typedef Templates35 Tail; +}; + +template +struct Templates37 { + typedef TemplateSel Head; + typedef Templates36 Tail; +}; + +template +struct Templates38 { + typedef TemplateSel Head; + typedef Templates37 Tail; +}; + +template +struct Templates39 { + typedef TemplateSel Head; + typedef Templates38 Tail; +}; + +template +struct Templates40 { + typedef TemplateSel Head; + typedef Templates39 Tail; +}; + +template +struct Templates41 { + typedef TemplateSel Head; + typedef Templates40 Tail; +}; + +template +struct Templates42 { + typedef TemplateSel Head; + typedef Templates41 Tail; +}; + +template +struct Templates43 { + typedef TemplateSel Head; + typedef Templates42 Tail; +}; + +template +struct Templates44 { + typedef TemplateSel Head; + typedef Templates43 Tail; +}; + +template +struct Templates45 { + typedef TemplateSel Head; + typedef Templates44 Tail; +}; + +template +struct Templates46 { + typedef TemplateSel Head; + typedef Templates45 Tail; +}; + +template +struct Templates47 { + typedef TemplateSel Head; + typedef Templates46 Tail; +}; + +template +struct Templates48 { + typedef TemplateSel Head; + typedef Templates47 Tail; +}; + +template +struct Templates49 { + typedef TemplateSel Head; + typedef Templates48 Tail; +}; + +template +struct Templates50 { + typedef TemplateSel Head; + typedef Templates49 Tail; +}; + + +// We don't want to require the users to write TemplatesN<...> directly, +// as that would require them to count the length. Templates<...> is much +// easier to write, but generates horrible messages when there is a +// compiler error, as gcc insists on printing out each template +// argument, even if it has the default value (this means Templates +// will appear as Templates in the compiler +// errors). +// +// Our solution is to combine the best part of the two approaches: a +// user would write Templates, and Google Test will translate +// that to TemplatesN internally to make error messages +// readable. The translation is done by the 'type' member of the +// Templates template. +template +struct Templates { + typedef Templates50 type; +}; + +template <> +struct Templates { + typedef Templates0 type; +}; +template +struct Templates { + typedef Templates1 type; +}; +template +struct Templates { + typedef Templates2 type; +}; +template +struct Templates { + typedef Templates3 type; +}; +template +struct Templates { + typedef Templates4 type; +}; +template +struct Templates { + typedef Templates5 type; +}; +template +struct Templates { + typedef Templates6 type; +}; +template +struct Templates { + typedef Templates7 type; +}; +template +struct Templates { + typedef Templates8 type; +}; +template +struct Templates { + typedef Templates9 type; +}; +template +struct Templates { + typedef Templates10 type; +}; +template +struct Templates { + typedef Templates11 type; +}; +template +struct Templates { + typedef Templates12 type; +}; +template +struct Templates { + typedef Templates13 type; +}; +template +struct Templates { + typedef Templates14 type; +}; +template +struct Templates { + typedef Templates15 type; +}; +template +struct Templates { + typedef Templates16 type; +}; +template +struct Templates { + typedef Templates17 type; +}; +template +struct Templates { + typedef Templates18 type; +}; +template +struct Templates { + typedef Templates19 type; +}; +template +struct Templates { + typedef Templates20 type; +}; +template +struct Templates { + typedef Templates21 type; +}; +template +struct Templates { + typedef Templates22 type; +}; +template +struct Templates { + typedef Templates23 type; +}; +template +struct Templates { + typedef Templates24 type; +}; +template +struct Templates { + typedef Templates25 type; +}; +template +struct Templates { + typedef Templates26 type; +}; +template +struct Templates { + typedef Templates27 type; +}; +template +struct Templates { + typedef Templates28 type; +}; +template +struct Templates { + typedef Templates29 type; +}; +template +struct Templates { + typedef Templates30 type; +}; +template +struct Templates { + typedef Templates31 type; +}; +template +struct Templates { + typedef Templates32 type; +}; +template +struct Templates { + typedef Templates33 type; +}; +template +struct Templates { + typedef Templates34 type; +}; +template +struct Templates { + typedef Templates35 type; +}; +template +struct Templates { + typedef Templates36 type; +}; +template +struct Templates { + typedef Templates37 type; +}; +template +struct Templates { + typedef Templates38 type; +}; +template +struct Templates { + typedef Templates39 type; +}; +template +struct Templates { + typedef Templates40 type; +}; +template +struct Templates { + typedef Templates41 type; +}; +template +struct Templates { + typedef Templates42 type; +}; +template +struct Templates { + typedef Templates43 type; +}; +template +struct Templates { + typedef Templates44 type; +}; +template +struct Templates { + typedef Templates45 type; +}; +template +struct Templates { + typedef Templates46 type; +}; +template +struct Templates { + typedef Templates47 type; +}; +template +struct Templates { + typedef Templates48 type; +}; +template +struct Templates { + typedef Templates49 type; +}; + +// The TypeList template makes it possible to use either a single type +// or a Types<...> list in TYPED_TEST_CASE() and +// INSTANTIATE_TYPED_TEST_CASE_P(). + +template +struct TypeList { typedef Types1 type; }; + +template +struct TypeList > { + typedef typename Types::type type; +}; + +#endif // GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_TYPE_UTIL_H_ + +// Due to C++ preprocessor weirdness, we need double indirection to +// concatenate two tokens when one of them is __LINE__. Writing +// +// foo ## __LINE__ +// +// will result in the token foo__LINE__, instead of foo followed by +// the current line number. For more details, see +// http://www.parashift.com/c++-faq-lite/misc-technical-issues.html#faq-39.6 +#define GTEST_CONCAT_TOKEN_(foo, bar) GTEST_CONCAT_TOKEN_IMPL_(foo, bar) +#define GTEST_CONCAT_TOKEN_IMPL_(foo, bar) foo ## bar + +// Google Test defines the testing::Message class to allow construction of +// test messages via the << operator. The idea is that anything +// streamable to std::ostream can be streamed to a testing::Message. +// This allows a user to use his own types in Google Test assertions by +// overloading the << operator. +// +// util/gtl/stl_logging-inl.h overloads << for STL containers. These +// overloads cannot be defined in the std namespace, as that will be +// undefined behavior. Therefore, they are defined in the global +// namespace instead. +// +// C++'s symbol lookup rule (i.e. Koenig lookup) says that these +// overloads are visible in either the std namespace or the global +// namespace, but not other namespaces, including the testing +// namespace which Google Test's Message class is in. +// +// To allow STL containers (and other types that has a << operator +// defined in the global namespace) to be used in Google Test assertions, +// testing::Message must access the custom << operator from the global +// namespace. Hence this helper function. +// +// Note: Jeffrey Yasskin suggested an alternative fix by "using +// ::operator<<;" in the definition of Message's operator<<. That fix +// doesn't require a helper function, but unfortunately doesn't +// compile with MSVC. +template +inline void GTestStreamToHelper(std::ostream* os, const T& val) { + *os << val; +} + +class ProtocolMessage; +namespace proto2 { class Message; } + +namespace testing { + +// Forward declarations. + +class AssertionResult; // Result of an assertion. +class Message; // Represents a failure message. +class Test; // Represents a test. +class TestInfo; // Information about a test. +class TestPartResult; // Result of a test part. +class UnitTest; // A collection of test cases. + +template +::std::string PrintToString(const T& value); + +namespace internal { + +struct TraceInfo; // Information about a trace point. +class ScopedTrace; // Implements scoped trace. +class TestInfoImpl; // Opaque implementation of TestInfo +class UnitTestImpl; // Opaque implementation of UnitTest + +// How many times InitGoogleTest() has been called. +extern int g_init_gtest_count; + +// The text used in failure messages to indicate the start of the +// stack trace. +GTEST_API_ extern const char kStackTraceMarker[]; + +// A secret type that Google Test users don't know about. It has no +// definition on purpose. Therefore it's impossible to create a +// Secret object, which is what we want. +class Secret; + +// Two overloaded helpers for checking at compile time whether an +// expression is a null pointer literal (i.e. NULL or any 0-valued +// compile-time integral constant). Their return values have +// different sizes, so we can use sizeof() to test which version is +// picked by the compiler. These helpers have no implementations, as +// we only need their signatures. +// +// Given IsNullLiteralHelper(x), the compiler will pick the first +// version if x can be implicitly converted to Secret*, and pick the +// second version otherwise. Since Secret is a secret and incomplete +// type, the only expression a user can write that has type Secret* is +// a null pointer literal. Therefore, we know that x is a null +// pointer literal if and only if the first version is picked by the +// compiler. +char IsNullLiteralHelper(Secret* p); +char (&IsNullLiteralHelper(...))[2]; // NOLINT + +// A compile-time bool constant that is true if and only if x is a +// null pointer literal (i.e. NULL or any 0-valued compile-time +// integral constant). +#ifdef GTEST_ELLIPSIS_NEEDS_POD_ +// We lose support for NULL detection where the compiler doesn't like +// passing non-POD classes through ellipsis (...). +# define GTEST_IS_NULL_LITERAL_(x) false +#else +# define GTEST_IS_NULL_LITERAL_(x) \ + (sizeof(::testing::internal::IsNullLiteralHelper(x)) == 1) +#endif // GTEST_ELLIPSIS_NEEDS_POD_ + +// Appends the user-supplied message to the Google-Test-generated message. +GTEST_API_ String AppendUserMessage(const String& gtest_msg, + const Message& user_msg); + +// A helper class for creating scoped traces in user programs. +class GTEST_API_ ScopedTrace { + public: + // The c'tor pushes the given source file location and message onto + // a trace stack maintained by Google Test. + ScopedTrace(const char* file, int line, const Message& message); + + // The d'tor pops the info pushed by the c'tor. + // + // Note that the d'tor is not virtual in order to be efficient. + // Don't inherit from ScopedTrace! + ~ScopedTrace(); + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(ScopedTrace); +} GTEST_ATTRIBUTE_UNUSED_; // A ScopedTrace object does its job in its + // c'tor and d'tor. Therefore it doesn't + // need to be used otherwise. + +// Converts a streamable value to a String. A NULL pointer is +// converted to "(null)". When the input value is a ::string, +// ::std::string, ::wstring, or ::std::wstring object, each NUL +// character in it is replaced with "\\0". +// Declared here but defined in gtest.h, so that it has access +// to the definition of the Message class, required by the ARM +// compiler. +template +String StreamableToString(const T& streamable); + +// The Symbian compiler has a bug that prevents it from selecting the +// correct overload of FormatForComparisonFailureMessage (see below) +// unless we pass the first argument by reference. If we do that, +// however, Visual Age C++ 10.1 generates a compiler error. Therefore +// we only apply the work-around for Symbian. +#if defined(__SYMBIAN32__) +# define GTEST_CREF_WORKAROUND_ const& +#else +# define GTEST_CREF_WORKAROUND_ +#endif + +// When this operand is a const char* or char*, if the other operand +// is a ::std::string or ::string, we print this operand as a C string +// rather than a pointer (we do the same for wide strings); otherwise +// we print it as a pointer to be safe. + +// This internal macro is used to avoid duplicated code. +#define GTEST_FORMAT_IMPL_(operand2_type, operand1_printer)\ +inline String FormatForComparisonFailureMessage(\ + operand2_type::value_type* GTEST_CREF_WORKAROUND_ str, \ + const operand2_type& /*operand2*/) {\ + return operand1_printer(str);\ +}\ +inline String FormatForComparisonFailureMessage(\ + const operand2_type::value_type* GTEST_CREF_WORKAROUND_ str, \ + const operand2_type& /*operand2*/) {\ + return operand1_printer(str);\ +} + +GTEST_FORMAT_IMPL_(::std::string, String::ShowCStringQuoted) +#if GTEST_HAS_STD_WSTRING +GTEST_FORMAT_IMPL_(::std::wstring, String::ShowWideCStringQuoted) +#endif // GTEST_HAS_STD_WSTRING + +#if GTEST_HAS_GLOBAL_STRING +GTEST_FORMAT_IMPL_(::string, String::ShowCStringQuoted) +#endif // GTEST_HAS_GLOBAL_STRING +#if GTEST_HAS_GLOBAL_WSTRING +GTEST_FORMAT_IMPL_(::wstring, String::ShowWideCStringQuoted) +#endif // GTEST_HAS_GLOBAL_WSTRING + +#undef GTEST_FORMAT_IMPL_ + +// The next four overloads handle the case where the operand being +// printed is a char/wchar_t pointer and the other operand is not a +// string/wstring object. In such cases, we just print the operand as +// a pointer to be safe. +#define GTEST_FORMAT_CHAR_PTR_IMPL_(CharType) \ + template \ + String FormatForComparisonFailureMessage(CharType* GTEST_CREF_WORKAROUND_ p, \ + const T&) { \ + return PrintToString(static_cast(p)); \ + } + +GTEST_FORMAT_CHAR_PTR_IMPL_(char) +GTEST_FORMAT_CHAR_PTR_IMPL_(const char) +GTEST_FORMAT_CHAR_PTR_IMPL_(wchar_t) +GTEST_FORMAT_CHAR_PTR_IMPL_(const wchar_t) + +#undef GTEST_FORMAT_CHAR_PTR_IMPL_ + +// Constructs and returns the message for an equality assertion +// (e.g. ASSERT_EQ, EXPECT_STREQ, etc) failure. +// +// The first four parameters are the expressions used in the assertion +// and their values, as strings. For example, for ASSERT_EQ(foo, bar) +// where foo is 5 and bar is 6, we have: +// +// expected_expression: "foo" +// actual_expression: "bar" +// expected_value: "5" +// actual_value: "6" +// +// The ignoring_case parameter is true iff the assertion is a +// *_STRCASEEQ*. When it's true, the string " (ignoring case)" will +// be inserted into the message. +GTEST_API_ AssertionResult EqFailure(const char* expected_expression, + const char* actual_expression, + const String& expected_value, + const String& actual_value, + bool ignoring_case); + +// Constructs a failure message for Boolean assertions such as EXPECT_TRUE. +GTEST_API_ String GetBoolAssertionFailureMessage( + const AssertionResult& assertion_result, + const char* expression_text, + const char* actual_predicate_value, + const char* expected_predicate_value); + +// This template class represents an IEEE floating-point number +// (either single-precision or double-precision, depending on the +// template parameters). +// +// The purpose of this class is to do more sophisticated number +// comparison. (Due to round-off error, etc, it's very unlikely that +// two floating-points will be equal exactly. Hence a naive +// comparison by the == operation often doesn't work.) +// +// Format of IEEE floating-point: +// +// The most-significant bit being the leftmost, an IEEE +// floating-point looks like +// +// sign_bit exponent_bits fraction_bits +// +// Here, sign_bit is a single bit that designates the sign of the +// number. +// +// For float, there are 8 exponent bits and 23 fraction bits. +// +// For double, there are 11 exponent bits and 52 fraction bits. +// +// More details can be found at +// http://en.wikipedia.org/wiki/IEEE_floating-point_standard. +// +// Template parameter: +// +// RawType: the raw floating-point type (either float or double) +template +class FloatingPoint { + public: + // Defines the unsigned integer type that has the same size as the + // floating point number. + typedef typename TypeWithSize::UInt Bits; + + // Constants. + + // # of bits in a number. + static const size_t kBitCount = 8*sizeof(RawType); + + // # of fraction bits in a number. + static const size_t kFractionBitCount = + std::numeric_limits::digits - 1; + + // # of exponent bits in a number. + static const size_t kExponentBitCount = kBitCount - 1 - kFractionBitCount; + + // The mask for the sign bit. + static const Bits kSignBitMask = static_cast(1) << (kBitCount - 1); + + // The mask for the fraction bits. + static const Bits kFractionBitMask = + ~static_cast(0) >> (kExponentBitCount + 1); + + // The mask for the exponent bits. + static const Bits kExponentBitMask = ~(kSignBitMask | kFractionBitMask); + + // How many ULP's (Units in the Last Place) we want to tolerate when + // comparing two numbers. The larger the value, the more error we + // allow. A 0 value means that two numbers must be exactly the same + // to be considered equal. + // + // The maximum error of a single floating-point operation is 0.5 + // units in the last place. On Intel CPU's, all floating-point + // calculations are done with 80-bit precision, while double has 64 + // bits. Therefore, 4 should be enough for ordinary use. + // + // See the following article for more details on ULP: + // http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm. + static const size_t kMaxUlps = 4; + + // Constructs a FloatingPoint from a raw floating-point number. + // + // On an Intel CPU, passing a non-normalized NAN (Not a Number) + // around may change its bits, although the new value is guaranteed + // to be also a NAN. Therefore, don't expect this constructor to + // preserve the bits in x when x is a NAN. + explicit FloatingPoint(const RawType& x) { u_.value_ = x; } + + // Static methods + + // Reinterprets a bit pattern as a floating-point number. + // + // This function is needed to test the AlmostEquals() method. + static RawType ReinterpretBits(const Bits bits) { + FloatingPoint fp(0); + fp.u_.bits_ = bits; + return fp.u_.value_; + } + + // Returns the floating-point number that represent positive infinity. + static RawType Infinity() { + return ReinterpretBits(kExponentBitMask); + } + + // Non-static methods + + // Returns the bits that represents this number. + const Bits &bits() const { return u_.bits_; } + + // Returns the exponent bits of this number. + Bits exponent_bits() const { return kExponentBitMask & u_.bits_; } + + // Returns the fraction bits of this number. + Bits fraction_bits() const { return kFractionBitMask & u_.bits_; } + + // Returns the sign bit of this number. + Bits sign_bit() const { return kSignBitMask & u_.bits_; } + + // Returns true iff this is NAN (not a number). + bool is_nan() const { + // It's a NAN if the exponent bits are all ones and the fraction + // bits are not entirely zeros. + return (exponent_bits() == kExponentBitMask) && (fraction_bits() != 0); + } + + // Returns true iff this number is at most kMaxUlps ULP's away from + // rhs. In particular, this function: + // + // - returns false if either number is (or both are) NAN. + // - treats really large numbers as almost equal to infinity. + // - thinks +0.0 and -0.0 are 0 DLP's apart. + bool AlmostEquals(const FloatingPoint& rhs) const { + // The IEEE standard says that any comparison operation involving + // a NAN must return false. + if (is_nan() || rhs.is_nan()) return false; + + return DistanceBetweenSignAndMagnitudeNumbers(u_.bits_, rhs.u_.bits_) + <= kMaxUlps; + } + + private: + // The data type used to store the actual floating-point number. + union FloatingPointUnion { + RawType value_; // The raw floating-point number. + Bits bits_; // The bits that represent the number. + }; + + // Converts an integer from the sign-and-magnitude representation to + // the biased representation. More precisely, let N be 2 to the + // power of (kBitCount - 1), an integer x is represented by the + // unsigned number x + N. + // + // For instance, + // + // -N + 1 (the most negative number representable using + // sign-and-magnitude) is represented by 1; + // 0 is represented by N; and + // N - 1 (the biggest number representable using + // sign-and-magnitude) is represented by 2N - 1. + // + // Read http://en.wikipedia.org/wiki/Signed_number_representations + // for more details on signed number representations. + static Bits SignAndMagnitudeToBiased(const Bits &sam) { + if (kSignBitMask & sam) { + // sam represents a negative number. + return ~sam + 1; + } else { + // sam represents a positive number. + return kSignBitMask | sam; + } + } + + // Given two numbers in the sign-and-magnitude representation, + // returns the distance between them as an unsigned number. + static Bits DistanceBetweenSignAndMagnitudeNumbers(const Bits &sam1, + const Bits &sam2) { + const Bits biased1 = SignAndMagnitudeToBiased(sam1); + const Bits biased2 = SignAndMagnitudeToBiased(sam2); + return (biased1 >= biased2) ? (biased1 - biased2) : (biased2 - biased1); + } + + FloatingPointUnion u_; +}; + +// Typedefs the instances of the FloatingPoint template class that we +// care to use. +typedef FloatingPoint Float; +typedef FloatingPoint Double; + +// In order to catch the mistake of putting tests that use different +// test fixture classes in the same test case, we need to assign +// unique IDs to fixture classes and compare them. The TypeId type is +// used to hold such IDs. The user should treat TypeId as an opaque +// type: the only operation allowed on TypeId values is to compare +// them for equality using the == operator. +typedef const void* TypeId; + +template +class TypeIdHelper { + public: + // dummy_ must not have a const type. Otherwise an overly eager + // compiler (e.g. MSVC 7.1 & 8.0) may try to merge + // TypeIdHelper::dummy_ for different Ts as an "optimization". + static bool dummy_; +}; + +template +bool TypeIdHelper::dummy_ = false; + +// GetTypeId() returns the ID of type T. Different values will be +// returned for different types. Calling the function twice with the +// same type argument is guaranteed to return the same ID. +template +TypeId GetTypeId() { + // The compiler is required to allocate a different + // TypeIdHelper::dummy_ variable for each T used to instantiate + // the template. Therefore, the address of dummy_ is guaranteed to + // be unique. + return &(TypeIdHelper::dummy_); +} + +// Returns the type ID of ::testing::Test. Always call this instead +// of GetTypeId< ::testing::Test>() to get the type ID of +// ::testing::Test, as the latter may give the wrong result due to a +// suspected linker bug when compiling Google Test as a Mac OS X +// framework. +GTEST_API_ TypeId GetTestTypeId(); + +// Defines the abstract factory interface that creates instances +// of a Test object. +class TestFactoryBase { + public: + virtual ~TestFactoryBase() {} + + // Creates a test instance to run. The instance is both created and destroyed + // within TestInfoImpl::Run() + virtual Test* CreateTest() = 0; + + protected: + TestFactoryBase() {} + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestFactoryBase); +}; + +// This class provides implementation of TeastFactoryBase interface. +// It is used in TEST and TEST_F macros. +template +class TestFactoryImpl : public TestFactoryBase { + public: + virtual Test* CreateTest() { return new TestClass; } +}; + +#if GTEST_OS_WINDOWS + +// Predicate-formatters for implementing the HRESULT checking macros +// {ASSERT|EXPECT}_HRESULT_{SUCCEEDED|FAILED} +// We pass a long instead of HRESULT to avoid causing an +// include dependency for the HRESULT type. +GTEST_API_ AssertionResult IsHRESULTSuccess(const char* expr, + long hr); // NOLINT +GTEST_API_ AssertionResult IsHRESULTFailure(const char* expr, + long hr); // NOLINT + +#endif // GTEST_OS_WINDOWS + +// Types of SetUpTestCase() and TearDownTestCase() functions. +typedef void (*SetUpTestCaseFunc)(); +typedef void (*TearDownTestCaseFunc)(); + +// Creates a new TestInfo object and registers it with Google Test; +// returns the created object. +// +// Arguments: +// +// test_case_name: name of the test case +// name: name of the test +// type_param the name of the test's type parameter, or NULL if +// this is not a typed or a type-parameterized test. +// value_param text representation of the test's value parameter, +// or NULL if this is not a type-parameterized test. +// fixture_class_id: ID of the test fixture class +// set_up_tc: pointer to the function that sets up the test case +// tear_down_tc: pointer to the function that tears down the test case +// factory: pointer to the factory that creates a test object. +// The newly created TestInfo instance will assume +// ownership of the factory object. +GTEST_API_ TestInfo* MakeAndRegisterTestInfo( + const char* test_case_name, const char* name, + const char* type_param, + const char* value_param, + TypeId fixture_class_id, + SetUpTestCaseFunc set_up_tc, + TearDownTestCaseFunc tear_down_tc, + TestFactoryBase* factory); + +// If *pstr starts with the given prefix, modifies *pstr to be right +// past the prefix and returns true; otherwise leaves *pstr unchanged +// and returns false. None of pstr, *pstr, and prefix can be NULL. +GTEST_API_ bool SkipPrefix(const char* prefix, const char** pstr); + +#if GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + +// State of the definition of a type-parameterized test case. +class GTEST_API_ TypedTestCasePState { + public: + TypedTestCasePState() : registered_(false) {} + + // Adds the given test name to defined_test_names_ and return true + // if the test case hasn't been registered; otherwise aborts the + // program. + bool AddTestName(const char* file, int line, const char* case_name, + const char* test_name) { + if (registered_) { + fprintf(stderr, "%s Test %s must be defined before " + "REGISTER_TYPED_TEST_CASE_P(%s, ...).\n", + FormatFileLocation(file, line).c_str(), test_name, case_name); + fflush(stderr); + posix::Abort(); + } + defined_test_names_.insert(test_name); + return true; + } + + // Verifies that registered_tests match the test names in + // defined_test_names_; returns registered_tests if successful, or + // aborts the program otherwise. + const char* VerifyRegisteredTestNames( + const char* file, int line, const char* registered_tests); + + private: + bool registered_; + ::std::set defined_test_names_; +}; + +// Skips to the first non-space char after the first comma in 'str'; +// returns NULL if no comma is found in 'str'. +inline const char* SkipComma(const char* str) { + const char* comma = strchr(str, ','); + if (comma == NULL) { + return NULL; + } + while (IsSpace(*(++comma))) {} + return comma; +} + +// Returns the prefix of 'str' before the first comma in it; returns +// the entire string if it contains no comma. +inline String GetPrefixUntilComma(const char* str) { + const char* comma = strchr(str, ','); + return comma == NULL ? String(str) : String(str, comma - str); +} + +// TypeParameterizedTest::Register() +// registers a list of type-parameterized tests with Google Test. The +// return value is insignificant - we just need to return something +// such that we can call this function in a namespace scope. +// +// Implementation note: The GTEST_TEMPLATE_ macro declares a template +// template parameter. It's defined in gtest-type-util.h. +template +class TypeParameterizedTest { + public: + // 'index' is the index of the test in the type list 'Types' + // specified in INSTANTIATE_TYPED_TEST_CASE_P(Prefix, TestCase, + // Types). Valid values for 'index' are [0, N - 1] where N is the + // length of Types. + static bool Register(const char* prefix, const char* case_name, + const char* test_names, int index) { + typedef typename Types::Head Type; + typedef Fixture FixtureClass; + typedef typename GTEST_BIND_(TestSel, Type) TestClass; + + // First, registers the first type-parameterized test in the type + // list. + MakeAndRegisterTestInfo( + String::Format("%s%s%s/%d", prefix, prefix[0] == '\0' ? "" : "/", + case_name, index).c_str(), + GetPrefixUntilComma(test_names).c_str(), + GetTypeName().c_str(), + NULL, // No value parameter. + GetTypeId(), + TestClass::SetUpTestCase, + TestClass::TearDownTestCase, + new TestFactoryImpl); + + // Next, recurses (at compile time) with the tail of the type list. + return TypeParameterizedTest + ::Register(prefix, case_name, test_names, index + 1); + } +}; + +// The base case for the compile time recursion. +template +class TypeParameterizedTest { + public: + static bool Register(const char* /*prefix*/, const char* /*case_name*/, + const char* /*test_names*/, int /*index*/) { + return true; + } +}; + +// TypeParameterizedTestCase::Register() +// registers *all combinations* of 'Tests' and 'Types' with Google +// Test. The return value is insignificant - we just need to return +// something such that we can call this function in a namespace scope. +template +class TypeParameterizedTestCase { + public: + static bool Register(const char* prefix, const char* case_name, + const char* test_names) { + typedef typename Tests::Head Head; + + // First, register the first test in 'Test' for each type in 'Types'. + TypeParameterizedTest::Register( + prefix, case_name, test_names, 0); + + // Next, recurses (at compile time) with the tail of the test list. + return TypeParameterizedTestCase + ::Register(prefix, case_name, SkipComma(test_names)); + } +}; + +// The base case for the compile time recursion. +template +class TypeParameterizedTestCase { + public: + static bool Register(const char* /*prefix*/, const char* /*case_name*/, + const char* /*test_names*/) { + return true; + } +}; + +#endif // GTEST_HAS_TYPED_TEST || GTEST_HAS_TYPED_TEST_P + +// Returns the current OS stack trace as a String. +// +// The maximum number of stack frames to be included is specified by +// the gtest_stack_trace_depth flag. The skip_count parameter +// specifies the number of top frames to be skipped, which doesn't +// count against the number of frames to be included. +// +// For example, if Foo() calls Bar(), which in turn calls +// GetCurrentOsStackTraceExceptTop(..., 1), Foo() will be included in +// the trace but Bar() and GetCurrentOsStackTraceExceptTop() won't. +GTEST_API_ String GetCurrentOsStackTraceExceptTop(UnitTest* unit_test, + int skip_count); + +// Helpers for suppressing warnings on unreachable code or constant +// condition. + +// Always returns true. +GTEST_API_ bool AlwaysTrue(); + +// Always returns false. +inline bool AlwaysFalse() { return !AlwaysTrue(); } + +// Helper for suppressing false warning from Clang on a const char* +// variable declared in a conditional expression always being NULL in +// the else branch. +struct GTEST_API_ ConstCharPtr { + ConstCharPtr(const char* str) : value(str) {} + operator bool() const { return true; } + const char* value; +}; + +// A simple Linear Congruential Generator for generating random +// numbers with a uniform distribution. Unlike rand() and srand(), it +// doesn't use global state (and therefore can't interfere with user +// code). Unlike rand_r(), it's portable. An LCG isn't very random, +// but it's good enough for our purposes. +class GTEST_API_ Random { + public: + static const UInt32 kMaxRange = 1u << 31; + + explicit Random(UInt32 seed) : state_(seed) {} + + void Reseed(UInt32 seed) { state_ = seed; } + + // Generates a random number from [0, range). Crashes if 'range' is + // 0 or greater than kMaxRange. + UInt32 Generate(UInt32 range); + + private: + UInt32 state_; + GTEST_DISALLOW_COPY_AND_ASSIGN_(Random); +}; + +// Defining a variable of type CompileAssertTypesEqual will cause a +// compiler error iff T1 and T2 are different types. +template +struct CompileAssertTypesEqual; + +template +struct CompileAssertTypesEqual { +}; + +// Removes the reference from a type if it is a reference type, +// otherwise leaves it unchanged. This is the same as +// tr1::remove_reference, which is not widely available yet. +template +struct RemoveReference { typedef T type; }; // NOLINT +template +struct RemoveReference { typedef T type; }; // NOLINT + +// A handy wrapper around RemoveReference that works when the argument +// T depends on template parameters. +#define GTEST_REMOVE_REFERENCE_(T) \ + typename ::testing::internal::RemoveReference::type + +// Removes const from a type if it is a const type, otherwise leaves +// it unchanged. This is the same as tr1::remove_const, which is not +// widely available yet. +template +struct RemoveConst { typedef T type; }; // NOLINT +template +struct RemoveConst { typedef T type; }; // NOLINT + +// MSVC 8.0, Sun C++, and IBM XL C++ have a bug which causes the above +// definition to fail to remove the const in 'const int[3]' and 'const +// char[3][4]'. The following specialization works around the bug. +// However, it causes trouble with GCC and thus needs to be +// conditionally compiled. +#if defined(_MSC_VER) || defined(__SUNPRO_CC) || defined(__IBMCPP__) +template +struct RemoveConst { + typedef typename RemoveConst::type type[N]; +}; +#endif + +// A handy wrapper around RemoveConst that works when the argument +// T depends on template parameters. +#define GTEST_REMOVE_CONST_(T) \ + typename ::testing::internal::RemoveConst::type + +// Turns const U&, U&, const U, and U all into U. +#define GTEST_REMOVE_REFERENCE_AND_CONST_(T) \ + GTEST_REMOVE_CONST_(GTEST_REMOVE_REFERENCE_(T)) + +// Adds reference to a type if it is not a reference type, +// otherwise leaves it unchanged. This is the same as +// tr1::add_reference, which is not widely available yet. +template +struct AddReference { typedef T& type; }; // NOLINT +template +struct AddReference { typedef T& type; }; // NOLINT + +// A handy wrapper around AddReference that works when the argument T +// depends on template parameters. +#define GTEST_ADD_REFERENCE_(T) \ + typename ::testing::internal::AddReference::type + +// Adds a reference to const on top of T as necessary. For example, +// it transforms +// +// char ==> const char& +// const char ==> const char& +// char& ==> const char& +// const char& ==> const char& +// +// The argument T must depend on some template parameters. +#define GTEST_REFERENCE_TO_CONST_(T) \ + GTEST_ADD_REFERENCE_(const GTEST_REMOVE_REFERENCE_(T)) + +// ImplicitlyConvertible::value is a compile-time bool +// constant that's true iff type From can be implicitly converted to +// type To. +template +class ImplicitlyConvertible { + private: + // We need the following helper functions only for their types. + // They have no implementations. + + // MakeFrom() is an expression whose type is From. We cannot simply + // use From(), as the type From may not have a public default + // constructor. + static From MakeFrom(); + + // These two functions are overloaded. Given an expression + // Helper(x), the compiler will pick the first version if x can be + // implicitly converted to type To; otherwise it will pick the + // second version. + // + // The first version returns a value of size 1, and the second + // version returns a value of size 2. Therefore, by checking the + // size of Helper(x), which can be done at compile time, we can tell + // which version of Helper() is used, and hence whether x can be + // implicitly converted to type To. + static char Helper(To); + static char (&Helper(...))[2]; // NOLINT + + // We have to put the 'public' section after the 'private' section, + // or MSVC refuses to compile the code. + public: + // MSVC warns about implicitly converting from double to int for + // possible loss of data, so we need to temporarily disable the + // warning. +#ifdef _MSC_VER +# pragma warning(push) // Saves the current warning state. +# pragma warning(disable:4244) // Temporarily disables warning 4244. + + static const bool value = + sizeof(Helper(ImplicitlyConvertible::MakeFrom())) == 1; +# pragma warning(pop) // Restores the warning state. +#elif defined(__BORLANDC__) + // C++Builder cannot use member overload resolution during template + // instantiation. The simplest workaround is to use its C++0x type traits + // functions (C++Builder 2009 and above only). + static const bool value = __is_convertible(From, To); +#else + static const bool value = + sizeof(Helper(ImplicitlyConvertible::MakeFrom())) == 1; +#endif // _MSV_VER +}; +template +const bool ImplicitlyConvertible::value; + +// IsAProtocolMessage::value is a compile-time bool constant that's +// true iff T is type ProtocolMessage, proto2::Message, or a subclass +// of those. +template +struct IsAProtocolMessage + : public bool_constant< + ImplicitlyConvertible::value || + ImplicitlyConvertible::value> { +}; + +// When the compiler sees expression IsContainerTest(0), if C is an +// STL-style container class, the first overload of IsContainerTest +// will be viable (since both C::iterator* and C::const_iterator* are +// valid types and NULL can be implicitly converted to them). It will +// be picked over the second overload as 'int' is a perfect match for +// the type of argument 0. If C::iterator or C::const_iterator is not +// a valid type, the first overload is not viable, and the second +// overload will be picked. Therefore, we can determine whether C is +// a container class by checking the type of IsContainerTest(0). +// The value of the expression is insignificant. +// +// Note that we look for both C::iterator and C::const_iterator. The +// reason is that C++ injects the name of a class as a member of the +// class itself (e.g. you can refer to class iterator as either +// 'iterator' or 'iterator::iterator'). If we look for C::iterator +// only, for example, we would mistakenly think that a class named +// iterator is an STL container. +// +// Also note that the simpler approach of overloading +// IsContainerTest(typename C::const_iterator*) and +// IsContainerTest(...) doesn't work with Visual Age C++ and Sun C++. +typedef int IsContainer; +template +IsContainer IsContainerTest(int /* dummy */, + typename C::iterator* /* it */ = NULL, + typename C::const_iterator* /* const_it */ = NULL) { + return 0; +} + +typedef char IsNotContainer; +template +IsNotContainer IsContainerTest(long /* dummy */) { return '\0'; } + +// EnableIf::type is void when 'Cond' is true, and +// undefined when 'Cond' is false. To use SFINAE to make a function +// overload only apply when a particular expression is true, add +// "typename EnableIf::type* = 0" as the last parameter. +template struct EnableIf; +template<> struct EnableIf { typedef void type; }; // NOLINT + +// Utilities for native arrays. + +// ArrayEq() compares two k-dimensional native arrays using the +// elements' operator==, where k can be any integer >= 0. When k is +// 0, ArrayEq() degenerates into comparing a single pair of values. + +template +bool ArrayEq(const T* lhs, size_t size, const U* rhs); + +// This generic version is used when k is 0. +template +inline bool ArrayEq(const T& lhs, const U& rhs) { return lhs == rhs; } + +// This overload is used when k >= 1. +template +inline bool ArrayEq(const T(&lhs)[N], const U(&rhs)[N]) { + return internal::ArrayEq(lhs, N, rhs); +} + +// This helper reduces code bloat. If we instead put its logic inside +// the previous ArrayEq() function, arrays with different sizes would +// lead to different copies of the template code. +template +bool ArrayEq(const T* lhs, size_t size, const U* rhs) { + for (size_t i = 0; i != size; i++) { + if (!internal::ArrayEq(lhs[i], rhs[i])) + return false; + } + return true; +} + +// Finds the first element in the iterator range [begin, end) that +// equals elem. Element may be a native array type itself. +template +Iter ArrayAwareFind(Iter begin, Iter end, const Element& elem) { + for (Iter it = begin; it != end; ++it) { + if (internal::ArrayEq(*it, elem)) + return it; + } + return end; +} + +// CopyArray() copies a k-dimensional native array using the elements' +// operator=, where k can be any integer >= 0. When k is 0, +// CopyArray() degenerates into copying a single value. + +template +void CopyArray(const T* from, size_t size, U* to); + +// This generic version is used when k is 0. +template +inline void CopyArray(const T& from, U* to) { *to = from; } + +// This overload is used when k >= 1. +template +inline void CopyArray(const T(&from)[N], U(*to)[N]) { + internal::CopyArray(from, N, *to); +} + +// This helper reduces code bloat. If we instead put its logic inside +// the previous CopyArray() function, arrays with different sizes +// would lead to different copies of the template code. +template +void CopyArray(const T* from, size_t size, U* to) { + for (size_t i = 0; i != size; i++) { + internal::CopyArray(from[i], to + i); + } +} + +// The relation between an NativeArray object (see below) and the +// native array it represents. +enum RelationToSource { + kReference, // The NativeArray references the native array. + kCopy // The NativeArray makes a copy of the native array and + // owns the copy. +}; + +// Adapts a native array to a read-only STL-style container. Instead +// of the complete STL container concept, this adaptor only implements +// members useful for Google Mock's container matchers. New members +// should be added as needed. To simplify the implementation, we only +// support Element being a raw type (i.e. having no top-level const or +// reference modifier). It's the client's responsibility to satisfy +// this requirement. Element can be an array type itself (hence +// multi-dimensional arrays are supported). +template +class NativeArray { + public: + // STL-style container typedefs. + typedef Element value_type; + typedef Element* iterator; + typedef const Element* const_iterator; + + // Constructs from a native array. + NativeArray(const Element* array, size_t count, RelationToSource relation) { + Init(array, count, relation); + } + + // Copy constructor. + NativeArray(const NativeArray& rhs) { + Init(rhs.array_, rhs.size_, rhs.relation_to_source_); + } + + ~NativeArray() { + // Ensures that the user doesn't instantiate NativeArray with a + // const or reference type. + static_cast(StaticAssertTypeEqHelper()); + if (relation_to_source_ == kCopy) + delete[] array_; + } + + // STL-style container methods. + size_t size() const { return size_; } + const_iterator begin() const { return array_; } + const_iterator end() const { return array_ + size_; } + bool operator==(const NativeArray& rhs) const { + return size() == rhs.size() && + ArrayEq(begin(), size(), rhs.begin()); + } + + private: + // Initializes this object; makes a copy of the input array if + // 'relation' is kCopy. + void Init(const Element* array, size_t a_size, RelationToSource relation) { + if (relation == kReference) { + array_ = array; + } else { + Element* const copy = new Element[a_size]; + CopyArray(array, a_size, copy); + array_ = copy; + } + size_ = a_size; + relation_to_source_ = relation; + } + + const Element* array_; + size_t size_; + RelationToSource relation_to_source_; + + GTEST_DISALLOW_ASSIGN_(NativeArray); +}; + +} // namespace internal +} // namespace testing + +#define GTEST_MESSAGE_AT_(file, line, message, result_type) \ + ::testing::internal::AssertHelper(result_type, file, line, message) \ + = ::testing::Message() + +#define GTEST_MESSAGE_(message, result_type) \ + GTEST_MESSAGE_AT_(__FILE__, __LINE__, message, result_type) + +#define GTEST_FATAL_FAILURE_(message) \ + return GTEST_MESSAGE_(message, ::testing::TestPartResult::kFatalFailure) + +#define GTEST_NONFATAL_FAILURE_(message) \ + GTEST_MESSAGE_(message, ::testing::TestPartResult::kNonFatalFailure) + +#define GTEST_SUCCESS_(message) \ + GTEST_MESSAGE_(message, ::testing::TestPartResult::kSuccess) + +// Suppresses MSVC warnings 4072 (unreachable code) for the code following +// statement if it returns or throws (or doesn't return or throw in some +// situations). +#define GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) \ + if (::testing::internal::AlwaysTrue()) { statement; } + +#define GTEST_TEST_THROW_(statement, expected_exception, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::ConstCharPtr gtest_msg = "") { \ + bool gtest_caught_expected = false; \ + try { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } \ + catch (expected_exception const&) { \ + gtest_caught_expected = true; \ + } \ + catch (...) { \ + gtest_msg.value = \ + "Expected: " #statement " throws an exception of type " \ + #expected_exception ".\n Actual: it throws a different type."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ + } \ + if (!gtest_caught_expected) { \ + gtest_msg.value = \ + "Expected: " #statement " throws an exception of type " \ + #expected_exception ".\n Actual: it throws nothing."; \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__); \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_testthrow_, __LINE__): \ + fail(gtest_msg.value) + +#define GTEST_TEST_NO_THROW_(statement, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::AlwaysTrue()) { \ + try { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } \ + catch (...) { \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__); \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_testnothrow_, __LINE__): \ + fail("Expected: " #statement " doesn't throw an exception.\n" \ + " Actual: it throws.") + +#define GTEST_TEST_ANY_THROW_(statement, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::AlwaysTrue()) { \ + bool gtest_caught_any = false; \ + try { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } \ + catch (...) { \ + gtest_caught_any = true; \ + } \ + if (!gtest_caught_any) { \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testanythrow_, __LINE__); \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_testanythrow_, __LINE__): \ + fail("Expected: " #statement " throws an exception.\n" \ + " Actual: it doesn't.") + + +// Implements Boolean test assertions such as EXPECT_TRUE. expression can be +// either a boolean expression or an AssertionResult. text is a textual +// represenation of expression as it was passed into the EXPECT_TRUE. +#define GTEST_TEST_BOOLEAN_(expression, text, actual, expected, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (const ::testing::AssertionResult gtest_ar_ = \ + ::testing::AssertionResult(expression)) \ + ; \ + else \ + fail(::testing::internal::GetBoolAssertionFailureMessage(\ + gtest_ar_, text, #actual, #expected).c_str()) + +#define GTEST_TEST_NO_FATAL_FAILURE_(statement, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::AlwaysTrue()) { \ + ::testing::internal::HasNewFatalFailureHelper gtest_fatal_failure_checker; \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + if (gtest_fatal_failure_checker.has_new_fatal_failure()) { \ + goto GTEST_CONCAT_TOKEN_(gtest_label_testnofatal_, __LINE__); \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_testnofatal_, __LINE__): \ + fail("Expected: " #statement " doesn't generate new fatal " \ + "failures in the current thread.\n" \ + " Actual: it does.") + +// Expands to the name of the class that implements the given test. +#define GTEST_TEST_CLASS_NAME_(test_case_name, test_name) \ + test_case_name##_##test_name##_Test + +// Helper macro for defining tests. +#define GTEST_TEST_(test_case_name, test_name, parent_class, parent_id)\ +class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) : public parent_class {\ + public:\ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {}\ + private:\ + virtual void TestBody();\ + static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;\ + GTEST_DISALLOW_COPY_AND_ASSIGN_(\ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name));\ +};\ +\ +::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_case_name, test_name)\ + ::test_info_ =\ + ::testing::internal::MakeAndRegisterTestInfo(\ + #test_case_name, #test_name, NULL, NULL, \ + (parent_id), \ + parent_class::SetUpTestCase, \ + parent_class::TearDownTestCase, \ + new ::testing::internal::TestFactoryImpl<\ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>);\ +void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_INTERNAL_H_ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file defines the public API for death tests. It is +// #included by gtest.h so a user doesn't need to include this +// directly. + +#ifndef GTEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_ +#define GTEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_ + +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Authors: wan@google.com (Zhanyong Wan), eefacm@gmail.com (Sean Mcafee) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file defines internal utilities needed for implementing +// death tests. They are subject to change without notice. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_ + + +#include + +namespace testing { +namespace internal { + +GTEST_DECLARE_string_(internal_run_death_test); + +// Names of the flags (needed for parsing Google Test flags). +const char kDeathTestStyleFlag[] = "death_test_style"; +const char kDeathTestUseFork[] = "death_test_use_fork"; +const char kInternalRunDeathTestFlag[] = "internal_run_death_test"; + +#if GTEST_HAS_DEATH_TEST + +// DeathTest is a class that hides much of the complexity of the +// GTEST_DEATH_TEST_ macro. It is abstract; its static Create method +// returns a concrete class that depends on the prevailing death test +// style, as defined by the --gtest_death_test_style and/or +// --gtest_internal_run_death_test flags. + +// In describing the results of death tests, these terms are used with +// the corresponding definitions: +// +// exit status: The integer exit information in the format specified +// by wait(2) +// exit code: The integer code passed to exit(3), _exit(2), or +// returned from main() +class GTEST_API_ DeathTest { + public: + // Create returns false if there was an error determining the + // appropriate action to take for the current death test; for example, + // if the gtest_death_test_style flag is set to an invalid value. + // The LastMessage method will return a more detailed message in that + // case. Otherwise, the DeathTest pointer pointed to by the "test" + // argument is set. If the death test should be skipped, the pointer + // is set to NULL; otherwise, it is set to the address of a new concrete + // DeathTest object that controls the execution of the current test. + static bool Create(const char* statement, const RE* regex, + const char* file, int line, DeathTest** test); + DeathTest(); + virtual ~DeathTest() { } + + // A helper class that aborts a death test when it's deleted. + class ReturnSentinel { + public: + explicit ReturnSentinel(DeathTest* test) : test_(test) { } + ~ReturnSentinel() { test_->Abort(TEST_ENCOUNTERED_RETURN_STATEMENT); } + private: + DeathTest* const test_; + GTEST_DISALLOW_COPY_AND_ASSIGN_(ReturnSentinel); + } GTEST_ATTRIBUTE_UNUSED_; + + // An enumeration of possible roles that may be taken when a death + // test is encountered. EXECUTE means that the death test logic should + // be executed immediately. OVERSEE means that the program should prepare + // the appropriate environment for a child process to execute the death + // test, then wait for it to complete. + enum TestRole { OVERSEE_TEST, EXECUTE_TEST }; + + // An enumeration of the three reasons that a test might be aborted. + enum AbortReason { + TEST_ENCOUNTERED_RETURN_STATEMENT, + TEST_THREW_EXCEPTION, + TEST_DID_NOT_DIE + }; + + // Assumes one of the above roles. + virtual TestRole AssumeRole() = 0; + + // Waits for the death test to finish and returns its status. + virtual int Wait() = 0; + + // Returns true if the death test passed; that is, the test process + // exited during the test, its exit status matches a user-supplied + // predicate, and its stderr output matches a user-supplied regular + // expression. + // The user-supplied predicate may be a macro expression rather + // than a function pointer or functor, or else Wait and Passed could + // be combined. + virtual bool Passed(bool exit_status_ok) = 0; + + // Signals that the death test did not die as expected. + virtual void Abort(AbortReason reason) = 0; + + // Returns a human-readable outcome message regarding the outcome of + // the last death test. + static const char* LastMessage(); + + static void set_last_death_test_message(const String& message); + + private: + // A string containing a description of the outcome of the last death test. + static String last_death_test_message_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(DeathTest); +}; + +// Factory interface for death tests. May be mocked out for testing. +class DeathTestFactory { + public: + virtual ~DeathTestFactory() { } + virtual bool Create(const char* statement, const RE* regex, + const char* file, int line, DeathTest** test) = 0; +}; + +// A concrete DeathTestFactory implementation for normal use. +class DefaultDeathTestFactory : public DeathTestFactory { + public: + virtual bool Create(const char* statement, const RE* regex, + const char* file, int line, DeathTest** test); +}; + +// Returns true if exit_status describes a process that was terminated +// by a signal, or exited normally with a nonzero exit code. +GTEST_API_ bool ExitedUnsuccessfully(int exit_status); + +// Traps C++ exceptions escaping statement and reports them as test +// failures. Note that trapping SEH exceptions is not implemented here. +# if GTEST_HAS_EXCEPTIONS +# define GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, death_test) \ + try { \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + } catch (const ::std::exception& gtest_exception) { \ + fprintf(\ + stderr, \ + "\n%s: Caught std::exception-derived exception escaping the " \ + "death test statement. Exception message: %s\n", \ + ::testing::internal::FormatFileLocation(__FILE__, __LINE__).c_str(), \ + gtest_exception.what()); \ + fflush(stderr); \ + death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \ + } catch (...) { \ + death_test->Abort(::testing::internal::DeathTest::TEST_THREW_EXCEPTION); \ + } + +# else +# define GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, death_test) \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement) + +# endif + +// This macro is for implementing ASSERT_DEATH*, EXPECT_DEATH*, +// ASSERT_EXIT*, and EXPECT_EXIT*. +# define GTEST_DEATH_TEST_(statement, predicate, regex, fail) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::AlwaysTrue()) { \ + const ::testing::internal::RE& gtest_regex = (regex); \ + ::testing::internal::DeathTest* gtest_dt; \ + if (!::testing::internal::DeathTest::Create(#statement, >est_regex, \ + __FILE__, __LINE__, >est_dt)) { \ + goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \ + } \ + if (gtest_dt != NULL) { \ + ::testing::internal::scoped_ptr< ::testing::internal::DeathTest> \ + gtest_dt_ptr(gtest_dt); \ + switch (gtest_dt->AssumeRole()) { \ + case ::testing::internal::DeathTest::OVERSEE_TEST: \ + if (!gtest_dt->Passed(predicate(gtest_dt->Wait()))) { \ + goto GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__); \ + } \ + break; \ + case ::testing::internal::DeathTest::EXECUTE_TEST: { \ + ::testing::internal::DeathTest::ReturnSentinel \ + gtest_sentinel(gtest_dt); \ + GTEST_EXECUTE_DEATH_TEST_STATEMENT_(statement, gtest_dt); \ + gtest_dt->Abort(::testing::internal::DeathTest::TEST_DID_NOT_DIE); \ + break; \ + } \ + default: \ + break; \ + } \ + } \ + } else \ + GTEST_CONCAT_TOKEN_(gtest_label_, __LINE__): \ + fail(::testing::internal::DeathTest::LastMessage()) +// The symbol "fail" here expands to something into which a message +// can be streamed. + +// A class representing the parsed contents of the +// --gtest_internal_run_death_test flag, as it existed when +// RUN_ALL_TESTS was called. +class InternalRunDeathTestFlag { + public: + InternalRunDeathTestFlag(const String& a_file, + int a_line, + int an_index, + int a_write_fd) + : file_(a_file), line_(a_line), index_(an_index), + write_fd_(a_write_fd) {} + + ~InternalRunDeathTestFlag() { + if (write_fd_ >= 0) + posix::Close(write_fd_); + } + + String file() const { return file_; } + int line() const { return line_; } + int index() const { return index_; } + int write_fd() const { return write_fd_; } + + private: + String file_; + int line_; + int index_; + int write_fd_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(InternalRunDeathTestFlag); +}; + +// Returns a newly created InternalRunDeathTestFlag object with fields +// initialized from the GTEST_FLAG(internal_run_death_test) flag if +// the flag is specified; otherwise returns NULL. +InternalRunDeathTestFlag* ParseInternalRunDeathTestFlag(); + +#else // GTEST_HAS_DEATH_TEST + +// This macro is used for implementing macros such as +// EXPECT_DEATH_IF_SUPPORTED and ASSERT_DEATH_IF_SUPPORTED on systems where +// death tests are not supported. Those macros must compile on such systems +// iff EXPECT_DEATH and ASSERT_DEATH compile with the same parameters on +// systems that support death tests. This allows one to write such a macro +// on a system that does not support death tests and be sure that it will +// compile on a death-test supporting system. +// +// Parameters: +// statement - A statement that a macro such as EXPECT_DEATH would test +// for program termination. This macro has to make sure this +// statement is compiled but not executed, to ensure that +// EXPECT_DEATH_IF_SUPPORTED compiles with a certain +// parameter iff EXPECT_DEATH compiles with it. +// regex - A regex that a macro such as EXPECT_DEATH would use to test +// the output of statement. This parameter has to be +// compiled but not evaluated by this macro, to ensure that +// this macro only accepts expressions that a macro such as +// EXPECT_DEATH would accept. +// terminator - Must be an empty statement for EXPECT_DEATH_IF_SUPPORTED +// and a return statement for ASSERT_DEATH_IF_SUPPORTED. +// This ensures that ASSERT_DEATH_IF_SUPPORTED will not +// compile inside functions where ASSERT_DEATH doesn't +// compile. +// +// The branch that has an always false condition is used to ensure that +// statement and regex are compiled (and thus syntactically correct) but +// never executed. The unreachable code macro protects the terminator +// statement from generating an 'unreachable code' warning in case +// statement unconditionally returns or throws. The Message constructor at +// the end allows the syntax of streaming additional messages into the +// macro, for compilational compatibility with EXPECT_DEATH/ASSERT_DEATH. +# define GTEST_UNSUPPORTED_DEATH_TEST_(statement, regex, terminator) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (::testing::internal::AlwaysTrue()) { \ + GTEST_LOG_(WARNING) \ + << "Death tests are not supported on this platform.\n" \ + << "Statement '" #statement "' cannot be verified."; \ + } else if (::testing::internal::AlwaysFalse()) { \ + ::testing::internal::RE::PartialMatch(".*", (regex)); \ + GTEST_SUPPRESS_UNREACHABLE_CODE_WARNING_BELOW_(statement); \ + terminator; \ + } else \ + ::testing::Message() + +#endif // GTEST_HAS_DEATH_TEST + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_DEATH_TEST_INTERNAL_H_ + +namespace testing { + +// This flag controls the style of death tests. Valid values are "threadsafe", +// meaning that the death test child process will re-execute the test binary +// from the start, running only a single death test, or "fast", +// meaning that the child process will execute the test logic immediately +// after forking. +GTEST_DECLARE_string_(death_test_style); + +#if GTEST_HAS_DEATH_TEST + +// The following macros are useful for writing death tests. + +// Here's what happens when an ASSERT_DEATH* or EXPECT_DEATH* is +// executed: +// +// 1. It generates a warning if there is more than one active +// thread. This is because it's safe to fork() or clone() only +// when there is a single thread. +// +// 2. The parent process clone()s a sub-process and runs the death +// test in it; the sub-process exits with code 0 at the end of the +// death test, if it hasn't exited already. +// +// 3. The parent process waits for the sub-process to terminate. +// +// 4. The parent process checks the exit code and error message of +// the sub-process. +// +// Examples: +// +// ASSERT_DEATH(server.SendMessage(56, "Hello"), "Invalid port number"); +// for (int i = 0; i < 5; i++) { +// EXPECT_DEATH(server.ProcessRequest(i), +// "Invalid request .* in ProcessRequest()") +// << "Failed to die on request " << i); +// } +// +// ASSERT_EXIT(server.ExitNow(), ::testing::ExitedWithCode(0), "Exiting"); +// +// bool KilledBySIGHUP(int exit_code) { +// return WIFSIGNALED(exit_code) && WTERMSIG(exit_code) == SIGHUP; +// } +// +// ASSERT_EXIT(client.HangUpServer(), KilledBySIGHUP, "Hanging up!"); +// +// On the regular expressions used in death tests: +// +// On POSIX-compliant systems (*nix), we use the library, +// which uses the POSIX extended regex syntax. +// +// On other platforms (e.g. Windows), we only support a simple regex +// syntax implemented as part of Google Test. This limited +// implementation should be enough most of the time when writing +// death tests; though it lacks many features you can find in PCRE +// or POSIX extended regex syntax. For example, we don't support +// union ("x|y"), grouping ("(xy)"), brackets ("[xy]"), and +// repetition count ("x{5,7}"), among others. +// +// Below is the syntax that we do support. We chose it to be a +// subset of both PCRE and POSIX extended regex, so it's easy to +// learn wherever you come from. In the following: 'A' denotes a +// literal character, period (.), or a single \\ escape sequence; +// 'x' and 'y' denote regular expressions; 'm' and 'n' are for +// natural numbers. +// +// c matches any literal character c +// \\d matches any decimal digit +// \\D matches any character that's not a decimal digit +// \\f matches \f +// \\n matches \n +// \\r matches \r +// \\s matches any ASCII whitespace, including \n +// \\S matches any character that's not a whitespace +// \\t matches \t +// \\v matches \v +// \\w matches any letter, _, or decimal digit +// \\W matches any character that \\w doesn't match +// \\c matches any literal character c, which must be a punctuation +// . matches any single character except \n +// A? matches 0 or 1 occurrences of A +// A* matches 0 or many occurrences of A +// A+ matches 1 or many occurrences of A +// ^ matches the beginning of a string (not that of each line) +// $ matches the end of a string (not that of each line) +// xy matches x followed by y +// +// If you accidentally use PCRE or POSIX extended regex features +// not implemented by us, you will get a run-time failure. In that +// case, please try to rewrite your regular expression within the +// above syntax. +// +// This implementation is *not* meant to be as highly tuned or robust +// as a compiled regex library, but should perform well enough for a +// death test, which already incurs significant overhead by launching +// a child process. +// +// Known caveats: +// +// A "threadsafe" style death test obtains the path to the test +// program from argv[0] and re-executes it in the sub-process. For +// simplicity, the current implementation doesn't search the PATH +// when launching the sub-process. This means that the user must +// invoke the test program via a path that contains at least one +// path separator (e.g. path/to/foo_test and +// /absolute/path/to/bar_test are fine, but foo_test is not). This +// is rarely a problem as people usually don't put the test binary +// directory in PATH. +// +// TODO(wan@google.com): make thread-safe death tests search the PATH. + +// Asserts that a given statement causes the program to exit, with an +// integer exit status that satisfies predicate, and emitting error output +// that matches regex. +# define ASSERT_EXIT(statement, predicate, regex) \ + GTEST_DEATH_TEST_(statement, predicate, regex, GTEST_FATAL_FAILURE_) + +// Like ASSERT_EXIT, but continues on to successive tests in the +// test case, if any: +# define EXPECT_EXIT(statement, predicate, regex) \ + GTEST_DEATH_TEST_(statement, predicate, regex, GTEST_NONFATAL_FAILURE_) + +// Asserts that a given statement causes the program to exit, either by +// explicitly exiting with a nonzero exit code or being killed by a +// signal, and emitting error output that matches regex. +# define ASSERT_DEATH(statement, regex) \ + ASSERT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, regex) + +// Like ASSERT_DEATH, but continues on to successive tests in the +// test case, if any: +# define EXPECT_DEATH(statement, regex) \ + EXPECT_EXIT(statement, ::testing::internal::ExitedUnsuccessfully, regex) + +// Two predicate classes that can be used in {ASSERT,EXPECT}_EXIT*: + +// Tests that an exit code describes a normal exit with a given exit code. +class GTEST_API_ ExitedWithCode { + public: + explicit ExitedWithCode(int exit_code); + bool operator()(int exit_status) const; + private: + // No implementation - assignment is unsupported. + void operator=(const ExitedWithCode& other); + + const int exit_code_; +}; + +# if !GTEST_OS_WINDOWS +// Tests that an exit code describes an exit due to termination by a +// given signal. +class GTEST_API_ KilledBySignal { + public: + explicit KilledBySignal(int signum); + bool operator()(int exit_status) const; + private: + const int signum_; +}; +# endif // !GTEST_OS_WINDOWS + +// EXPECT_DEBUG_DEATH asserts that the given statements die in debug mode. +// The death testing framework causes this to have interesting semantics, +// since the sideeffects of the call are only visible in opt mode, and not +// in debug mode. +// +// In practice, this can be used to test functions that utilize the +// LOG(DFATAL) macro using the following style: +// +// int DieInDebugOr12(int* sideeffect) { +// if (sideeffect) { +// *sideeffect = 12; +// } +// LOG(DFATAL) << "death"; +// return 12; +// } +// +// TEST(TestCase, TestDieOr12WorksInDgbAndOpt) { +// int sideeffect = 0; +// // Only asserts in dbg. +// EXPECT_DEBUG_DEATH(DieInDebugOr12(&sideeffect), "death"); +// +// #ifdef NDEBUG +// // opt-mode has sideeffect visible. +// EXPECT_EQ(12, sideeffect); +// #else +// // dbg-mode no visible sideeffect. +// EXPECT_EQ(0, sideeffect); +// #endif +// } +// +// This will assert that DieInDebugReturn12InOpt() crashes in debug +// mode, usually due to a DCHECK or LOG(DFATAL), but returns the +// appropriate fallback value (12 in this case) in opt mode. If you +// need to test that a function has appropriate side-effects in opt +// mode, include assertions against the side-effects. A general +// pattern for this is: +// +// EXPECT_DEBUG_DEATH({ +// // Side-effects here will have an effect after this statement in +// // opt mode, but none in debug mode. +// EXPECT_EQ(12, DieInDebugOr12(&sideeffect)); +// }, "death"); +// +# ifdef NDEBUG + +# define EXPECT_DEBUG_DEATH(statement, regex) \ + do { statement; } while (::testing::internal::AlwaysFalse()) + +# define ASSERT_DEBUG_DEATH(statement, regex) \ + do { statement; } while (::testing::internal::AlwaysFalse()) + +# else + +# define EXPECT_DEBUG_DEATH(statement, regex) \ + EXPECT_DEATH(statement, regex) + +# define ASSERT_DEBUG_DEATH(statement, regex) \ + ASSERT_DEATH(statement, regex) + +# endif // NDEBUG for EXPECT_DEBUG_DEATH +#endif // GTEST_HAS_DEATH_TEST + +// EXPECT_DEATH_IF_SUPPORTED(statement, regex) and +// ASSERT_DEATH_IF_SUPPORTED(statement, regex) expand to real death tests if +// death tests are supported; otherwise they just issue a warning. This is +// useful when you are combining death test assertions with normal test +// assertions in one test. +#if GTEST_HAS_DEATH_TEST +# define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \ + EXPECT_DEATH(statement, regex) +# define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \ + ASSERT_DEATH(statement, regex) +#else +# define EXPECT_DEATH_IF_SUPPORTED(statement, regex) \ + GTEST_UNSUPPORTED_DEATH_TEST_(statement, regex, ) +# define ASSERT_DEATH_IF_SUPPORTED(statement, regex) \ + GTEST_UNSUPPORTED_DEATH_TEST_(statement, regex, return) +#endif + +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_GTEST_DEATH_TEST_H_ +// Copyright 2005, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) +// +// The Google C++ Testing Framework (Google Test) +// +// This header file defines the Message class. +// +// IMPORTANT NOTE: Due to limitation of the C++ language, we have to +// leave some internal implementation details in this header file. +// They are clearly marked by comments like this: +// +// // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +// +// Such code is NOT meant to be used by a user directly, and is subject +// to CHANGE WITHOUT NOTICE. Therefore DO NOT DEPEND ON IT in a user +// program! + +#ifndef GTEST_INCLUDE_GTEST_GTEST_MESSAGE_H_ +#define GTEST_INCLUDE_GTEST_GTEST_MESSAGE_H_ + +#include + + +namespace testing { + +// The Message class works like an ostream repeater. +// +// Typical usage: +// +// 1. You stream a bunch of values to a Message object. +// It will remember the text in a stringstream. +// 2. Then you stream the Message object to an ostream. +// This causes the text in the Message to be streamed +// to the ostream. +// +// For example; +// +// testing::Message foo; +// foo << 1 << " != " << 2; +// std::cout << foo; +// +// will print "1 != 2". +// +// Message is not intended to be inherited from. In particular, its +// destructor is not virtual. +// +// Note that stringstream behaves differently in gcc and in MSVC. You +// can stream a NULL char pointer to it in the former, but not in the +// latter (it causes an access violation if you do). The Message +// class hides this difference by treating a NULL char pointer as +// "(null)". +class GTEST_API_ Message { + private: + // The type of basic IO manipulators (endl, ends, and flush) for + // narrow streams. + typedef std::ostream& (*BasicNarrowIoManip)(std::ostream&); + + public: + // Constructs an empty Message. + // We allocate the stringstream separately because otherwise each use of + // ASSERT/EXPECT in a procedure adds over 200 bytes to the procedure's + // stack frame leading to huge stack frames in some cases; gcc does not reuse + // the stack space. + Message() : ss_(new ::std::stringstream) { + // By default, we want there to be enough precision when printing + // a double to a Message. + *ss_ << std::setprecision(std::numeric_limits::digits10 + 2); + } + + // Copy constructor. + Message(const Message& msg) : ss_(new ::std::stringstream) { // NOLINT + *ss_ << msg.GetString(); + } + + // Constructs a Message from a C-string. + explicit Message(const char* str) : ss_(new ::std::stringstream) { + *ss_ << str; + } + +#if GTEST_OS_SYMBIAN + // Streams a value (either a pointer or not) to this object. + template + inline Message& operator <<(const T& value) { + StreamHelper(typename internal::is_pointer::type(), value); + return *this; + } +#else + // Streams a non-pointer value to this object. + template + inline Message& operator <<(const T& val) { + ::GTestStreamToHelper(ss_.get(), val); + return *this; + } + + // Streams a pointer value to this object. + // + // This function is an overload of the previous one. When you + // stream a pointer to a Message, this definition will be used as it + // is more specialized. (The C++ Standard, section + // [temp.func.order].) If you stream a non-pointer, then the + // previous definition will be used. + // + // The reason for this overload is that streaming a NULL pointer to + // ostream is undefined behavior. Depending on the compiler, you + // may get "0", "(nil)", "(null)", or an access violation. To + // ensure consistent result across compilers, we always treat NULL + // as "(null)". + template + inline Message& operator <<(T* const& pointer) { // NOLINT + if (pointer == NULL) { + *ss_ << "(null)"; + } else { + ::GTestStreamToHelper(ss_.get(), pointer); + } + return *this; + } +#endif // GTEST_OS_SYMBIAN + + // Since the basic IO manipulators are overloaded for both narrow + // and wide streams, we have to provide this specialized definition + // of operator <<, even though its body is the same as the + // templatized version above. Without this definition, streaming + // endl or other basic IO manipulators to Message will confuse the + // compiler. + Message& operator <<(BasicNarrowIoManip val) { + *ss_ << val; + return *this; + } + + // Instead of 1/0, we want to see true/false for bool values. + Message& operator <<(bool b) { + return *this << (b ? "true" : "false"); + } + + // These two overloads allow streaming a wide C string to a Message + // using the UTF-8 encoding. + Message& operator <<(const wchar_t* wide_c_str) { + return *this << internal::String::ShowWideCString(wide_c_str); + } + Message& operator <<(wchar_t* wide_c_str) { + return *this << internal::String::ShowWideCString(wide_c_str); + } + +#if GTEST_HAS_STD_WSTRING + // Converts the given wide string to a narrow string using the UTF-8 + // encoding, and streams the result to this Message object. + Message& operator <<(const ::std::wstring& wstr); +#endif // GTEST_HAS_STD_WSTRING + +#if GTEST_HAS_GLOBAL_WSTRING + // Converts the given wide string to a narrow string using the UTF-8 + // encoding, and streams the result to this Message object. + Message& operator <<(const ::wstring& wstr); +#endif // GTEST_HAS_GLOBAL_WSTRING + + // Gets the text streamed to this object so far as a String. + // Each '\0' character in the buffer is replaced with "\\0". + // + // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + internal::String GetString() const { + return internal::StringStreamToString(ss_.get()); + } + + private: + +#if GTEST_OS_SYMBIAN + // These are needed as the Nokia Symbian Compiler cannot decide between + // const T& and const T* in a function template. The Nokia compiler _can_ + // decide between class template specializations for T and T*, so a + // tr1::type_traits-like is_pointer works, and we can overload on that. + template + inline void StreamHelper(internal::true_type /*dummy*/, T* pointer) { + if (pointer == NULL) { + *ss_ << "(null)"; + } else { + ::GTestStreamToHelper(ss_.get(), pointer); + } + } + template + inline void StreamHelper(internal::false_type /*dummy*/, const T& value) { + ::GTestStreamToHelper(ss_.get(), value); + } +#endif // GTEST_OS_SYMBIAN + + // We'll hold the text streamed to this object here. + const internal::scoped_ptr< ::std::stringstream> ss_; + + // We declare (but don't implement) this to prevent the compiler + // from implementing the assignment operator. + void operator=(const Message&); +}; + +// Streams a Message to an ostream. +inline std::ostream& operator <<(std::ostream& os, const Message& sb) { + return os << sb.GetString(); +} + +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_GTEST_MESSAGE_H_ +// This file was GENERATED by command: +// pump.py gtest-param-test.h.pump +// DO NOT EDIT BY HAND!!! + +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Authors: vladl@google.com (Vlad Losev) +// +// Macros and functions for implementing parameterized tests +// in Google C++ Testing Framework (Google Test) +// +// This file is generated by a SCRIPT. DO NOT EDIT BY HAND! +// +#ifndef GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ +#define GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ + + +// Value-parameterized tests allow you to test your code with different +// parameters without writing multiple copies of the same test. +// +// Here is how you use value-parameterized tests: + +#if 0 + +// To write value-parameterized tests, first you should define a fixture +// class. It is usually derived from testing::TestWithParam (see below for +// another inheritance scheme that's sometimes useful in more complicated +// class hierarchies), where the type of your parameter values. +// TestWithParam is itself derived from testing::Test. T can be any +// copyable type. If it's a raw pointer, you are responsible for managing the +// lifespan of the pointed values. + +class FooTest : public ::testing::TestWithParam { + // You can implement all the usual class fixture members here. +}; + +// Then, use the TEST_P macro to define as many parameterized tests +// for this fixture as you want. The _P suffix is for "parameterized" +// or "pattern", whichever you prefer to think. + +TEST_P(FooTest, DoesBlah) { + // Inside a test, access the test parameter with the GetParam() method + // of the TestWithParam class: + EXPECT_TRUE(foo.Blah(GetParam())); + ... +} + +TEST_P(FooTest, HasBlahBlah) { + ... +} + +// Finally, you can use INSTANTIATE_TEST_CASE_P to instantiate the test +// case with any set of parameters you want. Google Test defines a number +// of functions for generating test parameters. They return what we call +// (surprise!) parameter generators. Here is a summary of them, which +// are all in the testing namespace: +// +// +// Range(begin, end [, step]) - Yields values {begin, begin+step, +// begin+step+step, ...}. The values do not +// include end. step defaults to 1. +// Values(v1, v2, ..., vN) - Yields values {v1, v2, ..., vN}. +// ValuesIn(container) - Yields values from a C-style array, an STL +// ValuesIn(begin,end) container, or an iterator range [begin, end). +// Bool() - Yields sequence {false, true}. +// Combine(g1, g2, ..., gN) - Yields all combinations (the Cartesian product +// for the math savvy) of the values generated +// by the N generators. +// +// For more details, see comments at the definitions of these functions below +// in this file. +// +// The following statement will instantiate tests from the FooTest test case +// each with parameter values "meeny", "miny", and "moe". + +INSTANTIATE_TEST_CASE_P(InstantiationName, + FooTest, + Values("meeny", "miny", "moe")); + +// To distinguish different instances of the pattern, (yes, you +// can instantiate it more then once) the first argument to the +// INSTANTIATE_TEST_CASE_P macro is a prefix that will be added to the +// actual test case name. Remember to pick unique prefixes for different +// instantiations. The tests from the instantiation above will have +// these names: +// +// * InstantiationName/FooTest.DoesBlah/0 for "meeny" +// * InstantiationName/FooTest.DoesBlah/1 for "miny" +// * InstantiationName/FooTest.DoesBlah/2 for "moe" +// * InstantiationName/FooTest.HasBlahBlah/0 for "meeny" +// * InstantiationName/FooTest.HasBlahBlah/1 for "miny" +// * InstantiationName/FooTest.HasBlahBlah/2 for "moe" +// +// You can use these names in --gtest_filter. +// +// This statement will instantiate all tests from FooTest again, each +// with parameter values "cat" and "dog": + +const char* pets[] = {"cat", "dog"}; +INSTANTIATE_TEST_CASE_P(AnotherInstantiationName, FooTest, ValuesIn(pets)); + +// The tests from the instantiation above will have these names: +// +// * AnotherInstantiationName/FooTest.DoesBlah/0 for "cat" +// * AnotherInstantiationName/FooTest.DoesBlah/1 for "dog" +// * AnotherInstantiationName/FooTest.HasBlahBlah/0 for "cat" +// * AnotherInstantiationName/FooTest.HasBlahBlah/1 for "dog" +// +// Please note that INSTANTIATE_TEST_CASE_P will instantiate all tests +// in the given test case, whether their definitions come before or +// AFTER the INSTANTIATE_TEST_CASE_P statement. +// +// Please also note that generator expressions (including parameters to the +// generators) are evaluated in InitGoogleTest(), after main() has started. +// This allows the user on one hand, to adjust generator parameters in order +// to dynamically determine a set of tests to run and on the other hand, +// give the user a chance to inspect the generated tests with Google Test +// reflection API before RUN_ALL_TESTS() is executed. +// +// You can see samples/sample7_unittest.cc and samples/sample8_unittest.cc +// for more examples. +// +// In the future, we plan to publish the API for defining new parameter +// generators. But for now this interface remains part of the internal +// implementation and is subject to change. +// +// +// A parameterized test fixture must be derived from testing::Test and from +// testing::WithParamInterface, where T is the type of the parameter +// values. Inheriting from TestWithParam satisfies that requirement because +// TestWithParam inherits from both Test and WithParamInterface. In more +// complicated hierarchies, however, it is occasionally useful to inherit +// separately from Test and WithParamInterface. For example: + +class BaseTest : public ::testing::Test { + // You can inherit all the usual members for a non-parameterized test + // fixture here. +}; + +class DerivedTest : public BaseTest, public ::testing::WithParamInterface { + // The usual test fixture members go here too. +}; + +TEST_F(BaseTest, HasFoo) { + // This is an ordinary non-parameterized test. +} + +TEST_P(DerivedTest, DoesBlah) { + // GetParam works just the same here as if you inherit from TestWithParam. + EXPECT_TRUE(foo.Blah(GetParam())); +} + +#endif // 0 + + +#if !GTEST_OS_SYMBIAN +# include +#endif + +// scripts/fuse_gtest.py depends on gtest's own header being #included +// *unconditionally*. Therefore these #includes cannot be moved +// inside #if GTEST_HAS_PARAM_TEST. +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: vladl@google.com (Vlad Losev) + +// Type and function utilities for implementing parameterized tests. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ + +#include +#include +#include + +// scripts/fuse_gtest.py depends on gtest's own header being #included +// *unconditionally*. Therefore these #includes cannot be moved +// inside #if GTEST_HAS_PARAM_TEST. +// Copyright 2003 Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Authors: Dan Egnor (egnor@google.com) +// +// A "smart" pointer type with reference tracking. Every pointer to a +// particular object is kept on a circular linked list. When the last pointer +// to an object is destroyed or reassigned, the object is deleted. +// +// Used properly, this deletes the object when the last reference goes away. +// There are several caveats: +// - Like all reference counting schemes, cycles lead to leaks. +// - Each smart pointer is actually two pointers (8 bytes instead of 4). +// - Every time a pointer is assigned, the entire list of pointers to that +// object is traversed. This class is therefore NOT SUITABLE when there +// will often be more than two or three pointers to a particular object. +// - References are only tracked as long as linked_ptr<> objects are copied. +// If a linked_ptr<> is converted to a raw pointer and back, BAD THINGS +// will happen (double deletion). +// +// A good use of this class is storing object references in STL containers. +// You can safely put linked_ptr<> in a vector<>. +// Other uses may not be as good. +// +// Note: If you use an incomplete type with linked_ptr<>, the class +// *containing* linked_ptr<> must have a constructor and destructor (even +// if they do nothing!). +// +// Bill Gibbons suggested we use something like this. +// +// Thread Safety: +// Unlike other linked_ptr implementations, in this implementation +// a linked_ptr object is thread-safe in the sense that: +// - it's safe to copy linked_ptr objects concurrently, +// - it's safe to copy *from* a linked_ptr and read its underlying +// raw pointer (e.g. via get()) concurrently, and +// - it's safe to write to two linked_ptrs that point to the same +// shared object concurrently. +// TODO(wan@google.com): rename this to safe_linked_ptr to avoid +// confusion with normal linked_ptr. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_LINKED_PTR_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_LINKED_PTR_H_ + +#include +#include + + +namespace testing { +namespace internal { + +// Protects copying of all linked_ptr objects. +GTEST_API_ GTEST_DECLARE_STATIC_MUTEX_(g_linked_ptr_mutex); + +// This is used internally by all instances of linked_ptr<>. It needs to be +// a non-template class because different types of linked_ptr<> can refer to +// the same object (linked_ptr(obj) vs linked_ptr(obj)). +// So, it needs to be possible for different types of linked_ptr to participate +// in the same circular linked list, so we need a single class type here. +// +// DO NOT USE THIS CLASS DIRECTLY YOURSELF. Use linked_ptr. +class linked_ptr_internal { + public: + // Create a new circle that includes only this instance. + void join_new() { + next_ = this; + } + + // Many linked_ptr operations may change p.link_ for some linked_ptr + // variable p in the same circle as this object. Therefore we need + // to prevent two such operations from occurring concurrently. + // + // Note that different types of linked_ptr objects can coexist in a + // circle (e.g. linked_ptr, linked_ptr, and + // linked_ptr). Therefore we must use a single mutex to + // protect all linked_ptr objects. This can create serious + // contention in production code, but is acceptable in a testing + // framework. + + // Join an existing circle. + // L < g_linked_ptr_mutex + void join(linked_ptr_internal const* ptr) { + MutexLock lock(&g_linked_ptr_mutex); + + linked_ptr_internal const* p = ptr; + while (p->next_ != ptr) p = p->next_; + p->next_ = this; + next_ = ptr; + } + + // Leave whatever circle we're part of. Returns true if we were the + // last member of the circle. Once this is done, you can join() another. + // L < g_linked_ptr_mutex + bool depart() { + MutexLock lock(&g_linked_ptr_mutex); + + if (next_ == this) return true; + linked_ptr_internal const* p = next_; + while (p->next_ != this) p = p->next_; + p->next_ = next_; + return false; + } + + private: + mutable linked_ptr_internal const* next_; +}; + +template +class linked_ptr { + public: + typedef T element_type; + + // Take over ownership of a raw pointer. This should happen as soon as + // possible after the object is created. + explicit linked_ptr(T* ptr = NULL) { capture(ptr); } + ~linked_ptr() { depart(); } + + // Copy an existing linked_ptr<>, adding ourselves to the list of references. + template linked_ptr(linked_ptr const& ptr) { copy(&ptr); } + linked_ptr(linked_ptr const& ptr) { // NOLINT + assert(&ptr != this); + copy(&ptr); + } + + // Assignment releases the old value and acquires the new. + template linked_ptr& operator=(linked_ptr const& ptr) { + depart(); + copy(&ptr); + return *this; + } + + linked_ptr& operator=(linked_ptr const& ptr) { + if (&ptr != this) { + depart(); + copy(&ptr); + } + return *this; + } + + // Smart pointer members. + void reset(T* ptr = NULL) { + depart(); + capture(ptr); + } + T* get() const { return value_; } + T* operator->() const { return value_; } + T& operator*() const { return *value_; } + + bool operator==(T* p) const { return value_ == p; } + bool operator!=(T* p) const { return value_ != p; } + template + bool operator==(linked_ptr const& ptr) const { + return value_ == ptr.get(); + } + template + bool operator!=(linked_ptr const& ptr) const { + return value_ != ptr.get(); + } + + private: + template + friend class linked_ptr; + + T* value_; + linked_ptr_internal link_; + + void depart() { + if (link_.depart()) delete value_; + } + + void capture(T* ptr) { + value_ = ptr; + link_.join_new(); + } + + template void copy(linked_ptr const* ptr) { + value_ = ptr->get(); + if (value_) + link_.join(&ptr->link_); + else + link_.join_new(); + } +}; + +template inline +bool operator==(T* ptr, const linked_ptr& x) { + return ptr == x.get(); +} + +template inline +bool operator!=(T* ptr, const linked_ptr& x) { + return ptr != x.get(); +} + +// A function to convert T* into linked_ptr +// Doing e.g. make_linked_ptr(new FooBarBaz(arg)) is a shorter notation +// for linked_ptr >(new FooBarBaz(arg)) +template +linked_ptr make_linked_ptr(T* ptr) { + return linked_ptr(ptr); +} + +} // namespace internal +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_LINKED_PTR_H_ +// Copyright 2007, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +// Google Test - The Google C++ Testing Framework +// +// This file implements a universal value printer that can print a +// value of any type T: +// +// void ::testing::internal::UniversalPrinter::Print(value, ostream_ptr); +// +// A user can teach this function how to print a class type T by +// defining either operator<<() or PrintTo() in the namespace that +// defines T. More specifically, the FIRST defined function in the +// following list will be used (assuming T is defined in namespace +// foo): +// +// 1. foo::PrintTo(const T&, ostream*) +// 2. operator<<(ostream&, const T&) defined in either foo or the +// global namespace. +// +// If none of the above is defined, it will print the debug string of +// the value if it is a protocol buffer, or print the raw bytes in the +// value otherwise. +// +// To aid debugging: when T is a reference type, the address of the +// value is also printed; when T is a (const) char pointer, both the +// pointer value and the NUL-terminated string it points to are +// printed. +// +// We also provide some convenient wrappers: +// +// // Prints a value to a string. For a (const or not) char +// // pointer, the NUL-terminated string (but not the pointer) is +// // printed. +// std::string ::testing::PrintToString(const T& value); +// +// // Prints a value tersely: for a reference type, the referenced +// // value (but not the address) is printed; for a (const or not) char +// // pointer, the NUL-terminated string (but not the pointer) is +// // printed. +// void ::testing::internal::UniversalTersePrint(const T& value, ostream*); +// +// // Prints value using the type inferred by the compiler. The difference +// // from UniversalTersePrint() is that this function prints both the +// // pointer and the NUL-terminated string for a (const or not) char pointer. +// void ::testing::internal::UniversalPrint(const T& value, ostream*); +// +// // Prints the fields of a tuple tersely to a string vector, one +// // element for each field. Tuple support must be enabled in +// // gtest-port.h. +// std::vector UniversalTersePrintTupleFieldsToStrings( +// const Tuple& value); +// +// Known limitation: +// +// The print primitives print the elements of an STL-style container +// using the compiler-inferred type of *iter where iter is a +// const_iterator of the container. When const_iterator is an input +// iterator but not a forward iterator, this inferred type may not +// match value_type, and the print output may be incorrect. In +// practice, this is rarely a problem as for most containers +// const_iterator is a forward iterator. We'll fix this if there's an +// actual need for it. Note that this fix cannot rely on value_type +// being defined as many user-defined container types don't have +// value_type. + +#ifndef GTEST_INCLUDE_GTEST_GTEST_PRINTERS_H_ +#define GTEST_INCLUDE_GTEST_GTEST_PRINTERS_H_ + +#include // NOLINT +#include +#include +#include +#include + +namespace testing { + +// Definitions in the 'internal' and 'internal2' name spaces are +// subject to change without notice. DO NOT USE THEM IN USER CODE! +namespace internal2 { + +// Prints the given number of bytes in the given object to the given +// ostream. +GTEST_API_ void PrintBytesInObjectTo(const unsigned char* obj_bytes, + size_t count, + ::std::ostream* os); + +// For selecting which printer to use when a given type has neither << +// nor PrintTo(). +enum TypeKind { + kProtobuf, // a protobuf type + kConvertibleToInteger, // a type implicitly convertible to BiggestInt + // (e.g. a named or unnamed enum type) + kOtherType // anything else +}; + +// TypeWithoutFormatter::PrintValue(value, os) is called +// by the universal printer to print a value of type T when neither +// operator<< nor PrintTo() is defined for T, where kTypeKind is the +// "kind" of T as defined by enum TypeKind. +template +class TypeWithoutFormatter { + public: + // This default version is called when kTypeKind is kOtherType. + static void PrintValue(const T& value, ::std::ostream* os) { + PrintBytesInObjectTo(reinterpret_cast(&value), + sizeof(value), os); + } +}; + +// We print a protobuf using its ShortDebugString() when the string +// doesn't exceed this many characters; otherwise we print it using +// DebugString() for better readability. +const size_t kProtobufOneLinerMaxLength = 50; + +template +class TypeWithoutFormatter { + public: + static void PrintValue(const T& value, ::std::ostream* os) { + const ::testing::internal::string short_str = value.ShortDebugString(); + const ::testing::internal::string pretty_str = + short_str.length() <= kProtobufOneLinerMaxLength ? + short_str : ("\n" + value.DebugString()); + *os << ("<" + pretty_str + ">"); + } +}; + +template +class TypeWithoutFormatter { + public: + // Since T has no << operator or PrintTo() but can be implicitly + // converted to BiggestInt, we print it as a BiggestInt. + // + // Most likely T is an enum type (either named or unnamed), in which + // case printing it as an integer is the desired behavior. In case + // T is not an enum, printing it as an integer is the best we can do + // given that it has no user-defined printer. + static void PrintValue(const T& value, ::std::ostream* os) { + const internal::BiggestInt kBigInt = value; + *os << kBigInt; + } +}; + +// Prints the given value to the given ostream. If the value is a +// protocol message, its debug string is printed; if it's an enum or +// of a type implicitly convertible to BiggestInt, it's printed as an +// integer; otherwise the bytes in the value are printed. This is +// what UniversalPrinter::Print() does when it knows nothing about +// type T and T has neither << operator nor PrintTo(). +// +// A user can override this behavior for a class type Foo by defining +// a << operator in the namespace where Foo is defined. +// +// We put this operator in namespace 'internal2' instead of 'internal' +// to simplify the implementation, as much code in 'internal' needs to +// use << in STL, which would conflict with our own << were it defined +// in 'internal'. +// +// Note that this operator<< takes a generic std::basic_ostream type instead of the more restricted std::ostream. If +// we define it to take an std::ostream instead, we'll get an +// "ambiguous overloads" compiler error when trying to print a type +// Foo that supports streaming to std::basic_ostream, as the compiler cannot tell whether +// operator<<(std::ostream&, const T&) or +// operator<<(std::basic_stream, const Foo&) is more +// specific. +template +::std::basic_ostream& operator<<( + ::std::basic_ostream& os, const T& x) { + TypeWithoutFormatter::value ? kProtobuf : + internal::ImplicitlyConvertible::value ? + kConvertibleToInteger : kOtherType)>::PrintValue(x, &os); + return os; +} + +} // namespace internal2 +} // namespace testing + +// This namespace MUST NOT BE NESTED IN ::testing, or the name look-up +// magic needed for implementing UniversalPrinter won't work. +namespace testing_internal { + +// Used to print a value that is not an STL-style container when the +// user doesn't define PrintTo() for it. +template +void DefaultPrintNonContainerTo(const T& value, ::std::ostream* os) { + // With the following statement, during unqualified name lookup, + // testing::internal2::operator<< appears as if it was declared in + // the nearest enclosing namespace that contains both + // ::testing_internal and ::testing::internal2, i.e. the global + // namespace. For more details, refer to the C++ Standard section + // 7.3.4-1 [namespace.udir]. This allows us to fall back onto + // testing::internal2::operator<< in case T doesn't come with a << + // operator. + // + // We cannot write 'using ::testing::internal2::operator<<;', which + // gcc 3.3 fails to compile due to a compiler bug. + using namespace ::testing::internal2; // NOLINT + + // Assuming T is defined in namespace foo, in the next statement, + // the compiler will consider all of: + // + // 1. foo::operator<< (thanks to Koenig look-up), + // 2. ::operator<< (as the current namespace is enclosed in ::), + // 3. testing::internal2::operator<< (thanks to the using statement above). + // + // The operator<< whose type matches T best will be picked. + // + // We deliberately allow #2 to be a candidate, as sometimes it's + // impossible to define #1 (e.g. when foo is ::std, defining + // anything in it is undefined behavior unless you are a compiler + // vendor.). + *os << value; +} + +} // namespace testing_internal + +namespace testing { +namespace internal { + +// UniversalPrinter::Print(value, ostream_ptr) prints the given +// value to the given ostream. The caller must ensure that +// 'ostream_ptr' is not NULL, or the behavior is undefined. +// +// We define UniversalPrinter as a class template (as opposed to a +// function template), as we need to partially specialize it for +// reference types, which cannot be done with function templates. +template +class UniversalPrinter; + +template +void UniversalPrint(const T& value, ::std::ostream* os); + +// Used to print an STL-style container when the user doesn't define +// a PrintTo() for it. +template +void DefaultPrintTo(IsContainer /* dummy */, + false_type /* is not a pointer */, + const C& container, ::std::ostream* os) { + const size_t kMaxCount = 32; // The maximum number of elements to print. + *os << '{'; + size_t count = 0; + for (typename C::const_iterator it = container.begin(); + it != container.end(); ++it, ++count) { + if (count > 0) { + *os << ','; + if (count == kMaxCount) { // Enough has been printed. + *os << " ..."; + break; + } + } + *os << ' '; + // We cannot call PrintTo(*it, os) here as PrintTo() doesn't + // handle *it being a native array. + internal::UniversalPrint(*it, os); + } + + if (count > 0) { + *os << ' '; + } + *os << '}'; +} + +// Used to print a pointer that is neither a char pointer nor a member +// pointer, when the user doesn't define PrintTo() for it. (A member +// variable pointer or member function pointer doesn't really point to +// a location in the address space. Their representation is +// implementation-defined. Therefore they will be printed as raw +// bytes.) +template +void DefaultPrintTo(IsNotContainer /* dummy */, + true_type /* is a pointer */, + T* p, ::std::ostream* os) { + if (p == NULL) { + *os << "NULL"; + } else { + // C++ doesn't allow casting from a function pointer to any object + // pointer. + // + // IsTrue() silences warnings: "Condition is always true", + // "unreachable code". + if (IsTrue(ImplicitlyConvertible::value)) { + // T is not a function type. We just call << to print p, + // relying on ADL to pick up user-defined << for their pointer + // types, if any. + *os << p; + } else { + // T is a function type, so '*os << p' doesn't do what we want + // (it just prints p as bool). We want to print p as a const + // void*. However, we cannot cast it to const void* directly, + // even using reinterpret_cast, as earlier versions of gcc + // (e.g. 3.4.5) cannot compile the cast when p is a function + // pointer. Casting to UInt64 first solves the problem. + *os << reinterpret_cast( + reinterpret_cast(p)); + } + } +} + +// Used to print a non-container, non-pointer value when the user +// doesn't define PrintTo() for it. +template +void DefaultPrintTo(IsNotContainer /* dummy */, + false_type /* is not a pointer */, + const T& value, ::std::ostream* os) { + ::testing_internal::DefaultPrintNonContainerTo(value, os); +} + +// Prints the given value using the << operator if it has one; +// otherwise prints the bytes in it. This is what +// UniversalPrinter::Print() does when PrintTo() is not specialized +// or overloaded for type T. +// +// A user can override this behavior for a class type Foo by defining +// an overload of PrintTo() in the namespace where Foo is defined. We +// give the user this option as sometimes defining a << operator for +// Foo is not desirable (e.g. the coding style may prevent doing it, +// or there is already a << operator but it doesn't do what the user +// wants). +template +void PrintTo(const T& value, ::std::ostream* os) { + // DefaultPrintTo() is overloaded. The type of its first two + // arguments determine which version will be picked. If T is an + // STL-style container, the version for container will be called; if + // T is a pointer, the pointer version will be called; otherwise the + // generic version will be called. + // + // Note that we check for container types here, prior to we check + // for protocol message types in our operator<<. The rationale is: + // + // For protocol messages, we want to give people a chance to + // override Google Mock's format by defining a PrintTo() or + // operator<<. For STL containers, other formats can be + // incompatible with Google Mock's format for the container + // elements; therefore we check for container types here to ensure + // that our format is used. + // + // The second argument of DefaultPrintTo() is needed to bypass a bug + // in Symbian's C++ compiler that prevents it from picking the right + // overload between: + // + // PrintTo(const T& x, ...); + // PrintTo(T* x, ...); + DefaultPrintTo(IsContainerTest(0), is_pointer(), value, os); +} + +// The following list of PrintTo() overloads tells +// UniversalPrinter::Print() how to print standard types (built-in +// types, strings, plain arrays, and pointers). + +// Overloads for various char types. +GTEST_API_ void PrintTo(unsigned char c, ::std::ostream* os); +GTEST_API_ void PrintTo(signed char c, ::std::ostream* os); +inline void PrintTo(char c, ::std::ostream* os) { + // When printing a plain char, we always treat it as unsigned. This + // way, the output won't be affected by whether the compiler thinks + // char is signed or not. + PrintTo(static_cast(c), os); +} + +// Overloads for other simple built-in types. +inline void PrintTo(bool x, ::std::ostream* os) { + *os << (x ? "true" : "false"); +} + +// Overload for wchar_t type. +// Prints a wchar_t as a symbol if it is printable or as its internal +// code otherwise and also as its decimal code (except for L'\0'). +// The L'\0' char is printed as "L'\\0'". The decimal code is printed +// as signed integer when wchar_t is implemented by the compiler +// as a signed type and is printed as an unsigned integer when wchar_t +// is implemented as an unsigned type. +GTEST_API_ void PrintTo(wchar_t wc, ::std::ostream* os); + +// Overloads for C strings. +GTEST_API_ void PrintTo(const char* s, ::std::ostream* os); +inline void PrintTo(char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} + +// signed/unsigned char is often used for representing binary data, so +// we print pointers to it as void* to be safe. +inline void PrintTo(const signed char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} +inline void PrintTo(signed char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} +inline void PrintTo(const unsigned char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} +inline void PrintTo(unsigned char* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} + +// MSVC can be configured to define wchar_t as a typedef of unsigned +// short. It defines _NATIVE_WCHAR_T_DEFINED when wchar_t is a native +// type. When wchar_t is a typedef, defining an overload for const +// wchar_t* would cause unsigned short* be printed as a wide string, +// possibly causing invalid memory accesses. +#if !defined(_MSC_VER) || defined(_NATIVE_WCHAR_T_DEFINED) +// Overloads for wide C strings +GTEST_API_ void PrintTo(const wchar_t* s, ::std::ostream* os); +inline void PrintTo(wchar_t* s, ::std::ostream* os) { + PrintTo(ImplicitCast_(s), os); +} +#endif + +// Overload for C arrays. Multi-dimensional arrays are printed +// properly. + +// Prints the given number of elements in an array, without printing +// the curly braces. +template +void PrintRawArrayTo(const T a[], size_t count, ::std::ostream* os) { + UniversalPrint(a[0], os); + for (size_t i = 1; i != count; i++) { + *os << ", "; + UniversalPrint(a[i], os); + } +} + +// Overloads for ::string and ::std::string. +#if GTEST_HAS_GLOBAL_STRING +GTEST_API_ void PrintStringTo(const ::string&s, ::std::ostream* os); +inline void PrintTo(const ::string& s, ::std::ostream* os) { + PrintStringTo(s, os); +} +#endif // GTEST_HAS_GLOBAL_STRING + +GTEST_API_ void PrintStringTo(const ::std::string&s, ::std::ostream* os); +inline void PrintTo(const ::std::string& s, ::std::ostream* os) { + PrintStringTo(s, os); +} + +// Overloads for ::wstring and ::std::wstring. +#if GTEST_HAS_GLOBAL_WSTRING +GTEST_API_ void PrintWideStringTo(const ::wstring&s, ::std::ostream* os); +inline void PrintTo(const ::wstring& s, ::std::ostream* os) { + PrintWideStringTo(s, os); +} +#endif // GTEST_HAS_GLOBAL_WSTRING + +#if GTEST_HAS_STD_WSTRING +GTEST_API_ void PrintWideStringTo(const ::std::wstring&s, ::std::ostream* os); +inline void PrintTo(const ::std::wstring& s, ::std::ostream* os) { + PrintWideStringTo(s, os); +} +#endif // GTEST_HAS_STD_WSTRING + +#if GTEST_HAS_TR1_TUPLE +// Overload for ::std::tr1::tuple. Needed for printing function arguments, +// which are packed as tuples. + +// Helper function for printing a tuple. T must be instantiated with +// a tuple type. +template +void PrintTupleTo(const T& t, ::std::ostream* os); + +// Overloaded PrintTo() for tuples of various arities. We support +// tuples of up-to 10 fields. The following implementation works +// regardless of whether tr1::tuple is implemented using the +// non-standard variadic template feature or not. + +inline void PrintTo(const ::std::tr1::tuple<>& t, ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo(const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} + +template +void PrintTo( + const ::std::tr1::tuple& t, + ::std::ostream* os) { + PrintTupleTo(t, os); +} +#endif // GTEST_HAS_TR1_TUPLE + +// Overload for std::pair. +template +void PrintTo(const ::std::pair& value, ::std::ostream* os) { + *os << '('; + // We cannot use UniversalPrint(value.first, os) here, as T1 may be + // a reference type. The same for printing value.second. + UniversalPrinter::Print(value.first, os); + *os << ", "; + UniversalPrinter::Print(value.second, os); + *os << ')'; +} + +// Implements printing a non-reference type T by letting the compiler +// pick the right overload of PrintTo() for T. +template +class UniversalPrinter { + public: + // MSVC warns about adding const to a function type, so we want to + // disable the warning. +#ifdef _MSC_VER +# pragma warning(push) // Saves the current warning state. +# pragma warning(disable:4180) // Temporarily disables warning 4180. +#endif // _MSC_VER + + // Note: we deliberately don't call this PrintTo(), as that name + // conflicts with ::testing::internal::PrintTo in the body of the + // function. + static void Print(const T& value, ::std::ostream* os) { + // By default, ::testing::internal::PrintTo() is used for printing + // the value. + // + // Thanks to Koenig look-up, if T is a class and has its own + // PrintTo() function defined in its namespace, that function will + // be visible here. Since it is more specific than the generic ones + // in ::testing::internal, it will be picked by the compiler in the + // following statement - exactly what we want. + PrintTo(value, os); + } + +#ifdef _MSC_VER +# pragma warning(pop) // Restores the warning state. +#endif // _MSC_VER +}; + +// UniversalPrintArray(begin, len, os) prints an array of 'len' +// elements, starting at address 'begin'. +template +void UniversalPrintArray(const T* begin, size_t len, ::std::ostream* os) { + if (len == 0) { + *os << "{}"; + } else { + *os << "{ "; + const size_t kThreshold = 18; + const size_t kChunkSize = 8; + // If the array has more than kThreshold elements, we'll have to + // omit some details by printing only the first and the last + // kChunkSize elements. + // TODO(wan@google.com): let the user control the threshold using a flag. + if (len <= kThreshold) { + PrintRawArrayTo(begin, len, os); + } else { + PrintRawArrayTo(begin, kChunkSize, os); + *os << ", ..., "; + PrintRawArrayTo(begin + len - kChunkSize, kChunkSize, os); + } + *os << " }"; + } +} +// This overload prints a (const) char array compactly. +GTEST_API_ void UniversalPrintArray(const char* begin, + size_t len, + ::std::ostream* os); + +// Implements printing an array type T[N]. +template +class UniversalPrinter { + public: + // Prints the given array, omitting some elements when there are too + // many. + static void Print(const T (&a)[N], ::std::ostream* os) { + UniversalPrintArray(a, N, os); + } +}; + +// Implements printing a reference type T&. +template +class UniversalPrinter { + public: + // MSVC warns about adding const to a function type, so we want to + // disable the warning. +#ifdef _MSC_VER +# pragma warning(push) // Saves the current warning state. +# pragma warning(disable:4180) // Temporarily disables warning 4180. +#endif // _MSC_VER + + static void Print(const T& value, ::std::ostream* os) { + // Prints the address of the value. We use reinterpret_cast here + // as static_cast doesn't compile when T is a function type. + *os << "@" << reinterpret_cast(&value) << " "; + + // Then prints the value itself. + UniversalPrint(value, os); + } + +#ifdef _MSC_VER +# pragma warning(pop) // Restores the warning state. +#endif // _MSC_VER +}; + +// Prints a value tersely: for a reference type, the referenced value +// (but not the address) is printed; for a (const) char pointer, the +// NUL-terminated string (but not the pointer) is printed. +template +void UniversalTersePrint(const T& value, ::std::ostream* os) { + UniversalPrint(value, os); +} +inline void UniversalTersePrint(const char* str, ::std::ostream* os) { + if (str == NULL) { + *os << "NULL"; + } else { + UniversalPrint(string(str), os); + } +} +inline void UniversalTersePrint(char* str, ::std::ostream* os) { + UniversalTersePrint(static_cast(str), os); +} + +// Prints a value using the type inferred by the compiler. The +// difference between this and UniversalTersePrint() is that for a +// (const) char pointer, this prints both the pointer and the +// NUL-terminated string. +template +void UniversalPrint(const T& value, ::std::ostream* os) { + UniversalPrinter::Print(value, os); +} + +#if GTEST_HAS_TR1_TUPLE +typedef ::std::vector Strings; + +// This helper template allows PrintTo() for tuples and +// UniversalTersePrintTupleFieldsToStrings() to be defined by +// induction on the number of tuple fields. The idea is that +// TuplePrefixPrinter::PrintPrefixTo(t, os) prints the first N +// fields in tuple t, and can be defined in terms of +// TuplePrefixPrinter. + +// The inductive case. +template +struct TuplePrefixPrinter { + // Prints the first N fields of a tuple. + template + static void PrintPrefixTo(const Tuple& t, ::std::ostream* os) { + TuplePrefixPrinter::PrintPrefixTo(t, os); + *os << ", "; + UniversalPrinter::type> + ::Print(::std::tr1::get(t), os); + } + + // Tersely prints the first N fields of a tuple to a string vector, + // one element for each field. + template + static void TersePrintPrefixToStrings(const Tuple& t, Strings* strings) { + TuplePrefixPrinter::TersePrintPrefixToStrings(t, strings); + ::std::stringstream ss; + UniversalTersePrint(::std::tr1::get(t), &ss); + strings->push_back(ss.str()); + } +}; + +// Base cases. +template <> +struct TuplePrefixPrinter<0> { + template + static void PrintPrefixTo(const Tuple&, ::std::ostream*) {} + + template + static void TersePrintPrefixToStrings(const Tuple&, Strings*) {} +}; +// We have to specialize the entire TuplePrefixPrinter<> class +// template here, even though the definition of +// TersePrintPrefixToStrings() is the same as the generic version, as +// Embarcadero (formerly CodeGear, formerly Borland) C++ doesn't +// support specializing a method template of a class template. +template <> +struct TuplePrefixPrinter<1> { + template + static void PrintPrefixTo(const Tuple& t, ::std::ostream* os) { + UniversalPrinter::type>:: + Print(::std::tr1::get<0>(t), os); + } + + template + static void TersePrintPrefixToStrings(const Tuple& t, Strings* strings) { + ::std::stringstream ss; + UniversalTersePrint(::std::tr1::get<0>(t), &ss); + strings->push_back(ss.str()); + } +}; + +// Helper function for printing a tuple. T must be instantiated with +// a tuple type. +template +void PrintTupleTo(const T& t, ::std::ostream* os) { + *os << "("; + TuplePrefixPrinter< ::std::tr1::tuple_size::value>:: + PrintPrefixTo(t, os); + *os << ")"; +} + +// Prints the fields of a tuple tersely to a string vector, one +// element for each field. See the comment before +// UniversalTersePrint() for how we define "tersely". +template +Strings UniversalTersePrintTupleFieldsToStrings(const Tuple& value) { + Strings result; + TuplePrefixPrinter< ::std::tr1::tuple_size::value>:: + TersePrintPrefixToStrings(value, &result); + return result; +} +#endif // GTEST_HAS_TR1_TUPLE + +} // namespace internal + +template +::std::string PrintToString(const T& value) { + ::std::stringstream ss; + internal::UniversalTersePrint(value, &ss); + return ss.str(); +} + +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_GTEST_PRINTERS_H_ + +#if GTEST_HAS_PARAM_TEST + +namespace testing { +namespace internal { + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Outputs a message explaining invalid registration of different +// fixture class for the same test case. This may happen when +// TEST_P macro is used to define two tests with the same name +// but in different namespaces. +GTEST_API_ void ReportInvalidTestCaseType(const char* test_case_name, + const char* file, int line); + +template class ParamGeneratorInterface; +template class ParamGenerator; + +// Interface for iterating over elements provided by an implementation +// of ParamGeneratorInterface. +template +class ParamIteratorInterface { + public: + virtual ~ParamIteratorInterface() {} + // A pointer to the base generator instance. + // Used only for the purposes of iterator comparison + // to make sure that two iterators belong to the same generator. + virtual const ParamGeneratorInterface* BaseGenerator() const = 0; + // Advances iterator to point to the next element + // provided by the generator. The caller is responsible + // for not calling Advance() on an iterator equal to + // BaseGenerator()->End(). + virtual void Advance() = 0; + // Clones the iterator object. Used for implementing copy semantics + // of ParamIterator. + virtual ParamIteratorInterface* Clone() const = 0; + // Dereferences the current iterator and provides (read-only) access + // to the pointed value. It is the caller's responsibility not to call + // Current() on an iterator equal to BaseGenerator()->End(). + // Used for implementing ParamGenerator::operator*(). + virtual const T* Current() const = 0; + // Determines whether the given iterator and other point to the same + // element in the sequence generated by the generator. + // Used for implementing ParamGenerator::operator==(). + virtual bool Equals(const ParamIteratorInterface& other) const = 0; +}; + +// Class iterating over elements provided by an implementation of +// ParamGeneratorInterface. It wraps ParamIteratorInterface +// and implements the const forward iterator concept. +template +class ParamIterator { + public: + typedef T value_type; + typedef const T& reference; + typedef ptrdiff_t difference_type; + + // ParamIterator assumes ownership of the impl_ pointer. + ParamIterator(const ParamIterator& other) : impl_(other.impl_->Clone()) {} + ParamIterator& operator=(const ParamIterator& other) { + if (this != &other) + impl_.reset(other.impl_->Clone()); + return *this; + } + + const T& operator*() const { return *impl_->Current(); } + const T* operator->() const { return impl_->Current(); } + // Prefix version of operator++. + ParamIterator& operator++() { + impl_->Advance(); + return *this; + } + // Postfix version of operator++. + ParamIterator operator++(int /*unused*/) { + ParamIteratorInterface* clone = impl_->Clone(); + impl_->Advance(); + return ParamIterator(clone); + } + bool operator==(const ParamIterator& other) const { + return impl_.get() == other.impl_.get() || impl_->Equals(*other.impl_); + } + bool operator!=(const ParamIterator& other) const { + return !(*this == other); + } + + private: + friend class ParamGenerator; + explicit ParamIterator(ParamIteratorInterface* impl) : impl_(impl) {} + scoped_ptr > impl_; +}; + +// ParamGeneratorInterface is the binary interface to access generators +// defined in other translation units. +template +class ParamGeneratorInterface { + public: + typedef T ParamType; + + virtual ~ParamGeneratorInterface() {} + + // Generator interface definition + virtual ParamIteratorInterface* Begin() const = 0; + virtual ParamIteratorInterface* End() const = 0; +}; + +// Wraps ParamGeneratorInterface and provides general generator syntax +// compatible with the STL Container concept. +// This class implements copy initialization semantics and the contained +// ParamGeneratorInterface instance is shared among all copies +// of the original object. This is possible because that instance is immutable. +template +class ParamGenerator { + public: + typedef ParamIterator iterator; + + explicit ParamGenerator(ParamGeneratorInterface* impl) : impl_(impl) {} + ParamGenerator(const ParamGenerator& other) : impl_(other.impl_) {} + + ParamGenerator& operator=(const ParamGenerator& other) { + impl_ = other.impl_; + return *this; + } + + iterator begin() const { return iterator(impl_->Begin()); } + iterator end() const { return iterator(impl_->End()); } + + private: + linked_ptr > impl_; +}; + +// Generates values from a range of two comparable values. Can be used to +// generate sequences of user-defined types that implement operator+() and +// operator<(). +// This class is used in the Range() function. +template +class RangeGenerator : public ParamGeneratorInterface { + public: + RangeGenerator(T begin, T end, IncrementT step) + : begin_(begin), end_(end), + step_(step), end_index_(CalculateEndIndex(begin, end, step)) {} + virtual ~RangeGenerator() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, begin_, 0, step_); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, end_, end_index_, step_); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, T value, int index, + IncrementT step) + : base_(base), value_(value), index_(index), step_(step) {} + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + virtual void Advance() { + value_ = value_ + step_; + index_++; + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const T* Current() const { return &value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const int other_index = + CheckedDowncastToActualType(&other)->index_; + return index_ == other_index; + } + + private: + Iterator(const Iterator& other) + : ParamIteratorInterface(), + base_(other.base_), value_(other.value_), index_(other.index_), + step_(other.step_) {} + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + T value_; + int index_; + const IncrementT step_; + }; // class RangeGenerator::Iterator + + static int CalculateEndIndex(const T& begin, + const T& end, + const IncrementT& step) { + int end_index = 0; + for (T i = begin; i < end; i = i + step) + end_index++; + return end_index; + } + + // No implementation - assignment is unsupported. + void operator=(const RangeGenerator& other); + + const T begin_; + const T end_; + const IncrementT step_; + // The index for the end() iterator. All the elements in the generated + // sequence are indexed (0-based) to aid iterator comparison. + const int end_index_; +}; // class RangeGenerator + + +// Generates values from a pair of STL-style iterators. Used in the +// ValuesIn() function. The elements are copied from the source range +// since the source can be located on the stack, and the generator +// is likely to persist beyond that stack frame. +template +class ValuesInIteratorRangeGenerator : public ParamGeneratorInterface { + public: + template + ValuesInIteratorRangeGenerator(ForwardIterator begin, ForwardIterator end) + : container_(begin, end) {} + virtual ~ValuesInIteratorRangeGenerator() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, container_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, container_.end()); + } + + private: + typedef typename ::std::vector ContainerType; + + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + typename ContainerType::const_iterator iterator) + : base_(base), iterator_(iterator) {} + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + virtual void Advance() { + ++iterator_; + value_.reset(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + // We need to use cached value referenced by iterator_ because *iterator_ + // can return a temporary object (and of type other then T), so just + // having "return &*iterator_;" doesn't work. + // value_ is updated here and not in Advance() because Advance() + // can advance iterator_ beyond the end of the range, and we cannot + // detect that fact. The client code, on the other hand, is + // responsible for not calling Current() on an out-of-range iterator. + virtual const T* Current() const { + if (value_.get() == NULL) + value_.reset(new T(*iterator_)); + return value_.get(); + } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + return iterator_ == + CheckedDowncastToActualType(&other)->iterator_; + } + + private: + Iterator(const Iterator& other) + // The explicit constructor call suppresses a false warning + // emitted by gcc when supplied with the -Wextra option. + : ParamIteratorInterface(), + base_(other.base_), + iterator_(other.iterator_) {} + + const ParamGeneratorInterface* const base_; + typename ContainerType::const_iterator iterator_; + // A cached value of *iterator_. We keep it here to allow access by + // pointer in the wrapping iterator's operator->(). + // value_ needs to be mutable to be accessed in Current(). + // Use of scoped_ptr helps manage cached value's lifetime, + // which is bound by the lifespan of the iterator itself. + mutable scoped_ptr value_; + }; // class ValuesInIteratorRangeGenerator::Iterator + + // No implementation - assignment is unsupported. + void operator=(const ValuesInIteratorRangeGenerator& other); + + const ContainerType container_; +}; // class ValuesInIteratorRangeGenerator + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Stores a parameter value and later creates tests parameterized with that +// value. +template +class ParameterizedTestFactory : public TestFactoryBase { + public: + typedef typename TestClass::ParamType ParamType; + explicit ParameterizedTestFactory(ParamType parameter) : + parameter_(parameter) {} + virtual Test* CreateTest() { + TestClass::SetParam(¶meter_); + return new TestClass(); + } + + private: + const ParamType parameter_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestFactory); +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// TestMetaFactoryBase is a base class for meta-factories that create +// test factories for passing into MakeAndRegisterTestInfo function. +template +class TestMetaFactoryBase { + public: + virtual ~TestMetaFactoryBase() {} + + virtual TestFactoryBase* CreateTestFactory(ParamType parameter) = 0; +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// TestMetaFactory creates test factories for passing into +// MakeAndRegisterTestInfo function. Since MakeAndRegisterTestInfo receives +// ownership of test factory pointer, same factory object cannot be passed +// into that method twice. But ParameterizedTestCaseInfo is going to call +// it for each Test/Parameter value combination. Thus it needs meta factory +// creator class. +template +class TestMetaFactory + : public TestMetaFactoryBase { + public: + typedef typename TestCase::ParamType ParamType; + + TestMetaFactory() {} + + virtual TestFactoryBase* CreateTestFactory(ParamType parameter) { + return new ParameterizedTestFactory(parameter); + } + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestMetaFactory); +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// ParameterizedTestCaseInfoBase is a generic interface +// to ParameterizedTestCaseInfo classes. ParameterizedTestCaseInfoBase +// accumulates test information provided by TEST_P macro invocations +// and generators provided by INSTANTIATE_TEST_CASE_P macro invocations +// and uses that information to register all resulting test instances +// in RegisterTests method. The ParameterizeTestCaseRegistry class holds +// a collection of pointers to the ParameterizedTestCaseInfo objects +// and calls RegisterTests() on each of them when asked. +class ParameterizedTestCaseInfoBase { + public: + virtual ~ParameterizedTestCaseInfoBase() {} + + // Base part of test case name for display purposes. + virtual const string& GetTestCaseName() const = 0; + // Test case id to verify identity. + virtual TypeId GetTestCaseTypeId() const = 0; + // UnitTest class invokes this method to register tests in this + // test case right before running them in RUN_ALL_TESTS macro. + // This method should not be called more then once on any single + // instance of a ParameterizedTestCaseInfoBase derived class. + virtual void RegisterTests() = 0; + + protected: + ParameterizedTestCaseInfoBase() {} + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestCaseInfoBase); +}; + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// ParameterizedTestCaseInfo accumulates tests obtained from TEST_P +// macro invocations for a particular test case and generators +// obtained from INSTANTIATE_TEST_CASE_P macro invocations for that +// test case. It registers tests with all values generated by all +// generators when asked. +template +class ParameterizedTestCaseInfo : public ParameterizedTestCaseInfoBase { + public: + // ParamType and GeneratorCreationFunc are private types but are required + // for declarations of public methods AddTestPattern() and + // AddTestCaseInstantiation(). + typedef typename TestCase::ParamType ParamType; + // A function that returns an instance of appropriate generator type. + typedef ParamGenerator(GeneratorCreationFunc)(); + + explicit ParameterizedTestCaseInfo(const char* name) + : test_case_name_(name) {} + + // Test case base name for display purposes. + virtual const string& GetTestCaseName() const { return test_case_name_; } + // Test case id to verify identity. + virtual TypeId GetTestCaseTypeId() const { return GetTypeId(); } + // TEST_P macro uses AddTestPattern() to record information + // about a single test in a LocalTestInfo structure. + // test_case_name is the base name of the test case (without invocation + // prefix). test_base_name is the name of an individual test without + // parameter index. For the test SequenceA/FooTest.DoBar/1 FooTest is + // test case base name and DoBar is test base name. + void AddTestPattern(const char* test_case_name, + const char* test_base_name, + TestMetaFactoryBase* meta_factory) { + tests_.push_back(linked_ptr(new TestInfo(test_case_name, + test_base_name, + meta_factory))); + } + // INSTANTIATE_TEST_CASE_P macro uses AddGenerator() to record information + // about a generator. + int AddTestCaseInstantiation(const string& instantiation_name, + GeneratorCreationFunc* func, + const char* /* file */, + int /* line */) { + instantiations_.push_back(::std::make_pair(instantiation_name, func)); + return 0; // Return value used only to run this method in namespace scope. + } + // UnitTest class invokes this method to register tests in this test case + // test cases right before running tests in RUN_ALL_TESTS macro. + // This method should not be called more then once on any single + // instance of a ParameterizedTestCaseInfoBase derived class. + // UnitTest has a guard to prevent from calling this method more then once. + virtual void RegisterTests() { + for (typename TestInfoContainer::iterator test_it = tests_.begin(); + test_it != tests_.end(); ++test_it) { + linked_ptr test_info = *test_it; + for (typename InstantiationContainer::iterator gen_it = + instantiations_.begin(); gen_it != instantiations_.end(); + ++gen_it) { + const string& instantiation_name = gen_it->first; + ParamGenerator generator((*gen_it->second)()); + + Message test_case_name_stream; + if ( !instantiation_name.empty() ) + test_case_name_stream << instantiation_name << "/"; + test_case_name_stream << test_info->test_case_base_name; + + int i = 0; + for (typename ParamGenerator::iterator param_it = + generator.begin(); + param_it != generator.end(); ++param_it, ++i) { + Message test_name_stream; + test_name_stream << test_info->test_base_name << "/" << i; + MakeAndRegisterTestInfo( + test_case_name_stream.GetString().c_str(), + test_name_stream.GetString().c_str(), + NULL, // No type parameter. + PrintToString(*param_it).c_str(), + GetTestCaseTypeId(), + TestCase::SetUpTestCase, + TestCase::TearDownTestCase, + test_info->test_meta_factory->CreateTestFactory(*param_it)); + } // for param_it + } // for gen_it + } // for test_it + } // RegisterTests + + private: + // LocalTestInfo structure keeps information about a single test registered + // with TEST_P macro. + struct TestInfo { + TestInfo(const char* a_test_case_base_name, + const char* a_test_base_name, + TestMetaFactoryBase* a_test_meta_factory) : + test_case_base_name(a_test_case_base_name), + test_base_name(a_test_base_name), + test_meta_factory(a_test_meta_factory) {} + + const string test_case_base_name; + const string test_base_name; + const scoped_ptr > test_meta_factory; + }; + typedef ::std::vector > TestInfoContainer; + // Keeps pairs of + // received from INSTANTIATE_TEST_CASE_P macros. + typedef ::std::vector > + InstantiationContainer; + + const string test_case_name_; + TestInfoContainer tests_; + InstantiationContainer instantiations_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestCaseInfo); +}; // class ParameterizedTestCaseInfo + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// ParameterizedTestCaseRegistry contains a map of ParameterizedTestCaseInfoBase +// classes accessed by test case names. TEST_P and INSTANTIATE_TEST_CASE_P +// macros use it to locate their corresponding ParameterizedTestCaseInfo +// descriptors. +class ParameterizedTestCaseRegistry { + public: + ParameterizedTestCaseRegistry() {} + ~ParameterizedTestCaseRegistry() { + for (TestCaseInfoContainer::iterator it = test_case_infos_.begin(); + it != test_case_infos_.end(); ++it) { + delete *it; + } + } + + // Looks up or creates and returns a structure containing information about + // tests and instantiations of a particular test case. + template + ParameterizedTestCaseInfo* GetTestCasePatternHolder( + const char* test_case_name, + const char* file, + int line) { + ParameterizedTestCaseInfo* typed_test_info = NULL; + for (TestCaseInfoContainer::iterator it = test_case_infos_.begin(); + it != test_case_infos_.end(); ++it) { + if ((*it)->GetTestCaseName() == test_case_name) { + if ((*it)->GetTestCaseTypeId() != GetTypeId()) { + // Complain about incorrect usage of Google Test facilities + // and terminate the program since we cannot guaranty correct + // test case setup and tear-down in this case. + ReportInvalidTestCaseType(test_case_name, file, line); + posix::Abort(); + } else { + // At this point we are sure that the object we found is of the same + // type we are looking for, so we downcast it to that type + // without further checks. + typed_test_info = CheckedDowncastToActualType< + ParameterizedTestCaseInfo >(*it); + } + break; + } + } + if (typed_test_info == NULL) { + typed_test_info = new ParameterizedTestCaseInfo(test_case_name); + test_case_infos_.push_back(typed_test_info); + } + return typed_test_info; + } + void RegisterTests() { + for (TestCaseInfoContainer::iterator it = test_case_infos_.begin(); + it != test_case_infos_.end(); ++it) { + (*it)->RegisterTests(); + } + } + + private: + typedef ::std::vector TestCaseInfoContainer; + + TestCaseInfoContainer test_case_infos_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(ParameterizedTestCaseRegistry); +}; + +} // namespace internal +} // namespace testing + +#endif // GTEST_HAS_PARAM_TEST + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_H_ +// This file was GENERATED by command: +// pump.py gtest-param-util-generated.h.pump +// DO NOT EDIT BY HAND!!! + +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: vladl@google.com (Vlad Losev) + +// Type and function utilities for implementing parameterized tests. +// This file is generated by a SCRIPT. DO NOT EDIT BY HAND! +// +// Currently Google Test supports at most 50 arguments in Values, +// and at most 10 arguments in Combine. Please contact +// googletestframework@googlegroups.com if you need more. +// Please note that the number of arguments to Combine is limited +// by the maximum arity of the implementation of tr1::tuple which is +// currently set at 10. + +#ifndef GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_ +#define GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_ + +// scripts/fuse_gtest.py depends on gtest's own header being #included +// *unconditionally*. Therefore these #includes cannot be moved +// inside #if GTEST_HAS_PARAM_TEST. + +#if GTEST_HAS_PARAM_TEST + +namespace testing { + +// Forward declarations of ValuesIn(), which is implemented in +// include/gtest/gtest-param-test.h. +template +internal::ParamGenerator< + typename ::testing::internal::IteratorTraits::value_type> +ValuesIn(ForwardIterator begin, ForwardIterator end); + +template +internal::ParamGenerator ValuesIn(const T (&array)[N]); + +template +internal::ParamGenerator ValuesIn( + const Container& container); + +namespace internal { + +// Used in the Values() function to provide polymorphic capabilities. +template +class ValueArray1 { + public: + explicit ValueArray1(T1 v1) : v1_(v1) {} + + template + operator ParamGenerator() const { return ValuesIn(&v1_, &v1_ + 1); } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray1& other); + + const T1 v1_; +}; + +template +class ValueArray2 { + public: + ValueArray2(T1 v1, T2 v2) : v1_(v1), v2_(v2) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray2& other); + + const T1 v1_; + const T2 v2_; +}; + +template +class ValueArray3 { + public: + ValueArray3(T1 v1, T2 v2, T3 v3) : v1_(v1), v2_(v2), v3_(v3) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray3& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; +}; + +template +class ValueArray4 { + public: + ValueArray4(T1 v1, T2 v2, T3 v3, T4 v4) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray4& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; +}; + +template +class ValueArray5 { + public: + ValueArray5(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray5& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; +}; + +template +class ValueArray6 { + public: + ValueArray6(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray6& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; +}; + +template +class ValueArray7 { + public: + ValueArray7(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray7& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; +}; + +template +class ValueArray8 { + public: + ValueArray8(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray8& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; +}; + +template +class ValueArray9 { + public: + ValueArray9(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, + T9 v9) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray9& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; +}; + +template +class ValueArray10 { + public: + ValueArray10(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray10& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; +}; + +template +class ValueArray11 { + public: + ValueArray11(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), + v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray11& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; +}; + +template +class ValueArray12 { + public: + ValueArray12(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), + v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray12& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; +}; + +template +class ValueArray13 { + public: + ValueArray13(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), + v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), + v12_(v12), v13_(v13) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray13& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; +}; + +template +class ValueArray14 { + public: + ValueArray14(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray14& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; +}; + +template +class ValueArray15 { + public: + ValueArray15(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray15& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; +}; + +template +class ValueArray16 { + public: + ValueArray16(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), + v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), + v16_(v16) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray16& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; +}; + +template +class ValueArray17 { + public: + ValueArray17(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, + T17 v17) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray17& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; +}; + +template +class ValueArray18 { + public: + ValueArray18(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray18& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; +}; + +template +class ValueArray19 { + public: + ValueArray19(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), + v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), + v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray19& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; +}; + +template +class ValueArray20 { + public: + ValueArray20(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), + v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), + v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), + v19_(v19), v20_(v20) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray20& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; +}; + +template +class ValueArray21 { + public: + ValueArray21(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), + v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), + v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), + v18_(v18), v19_(v19), v20_(v20), v21_(v21) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray21& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; +}; + +template +class ValueArray22 { + public: + ValueArray22(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray22& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; +}; + +template +class ValueArray23 { + public: + ValueArray23(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, + v23_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray23& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; +}; + +template +class ValueArray24 { + public: + ValueArray24(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), + v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), + v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), + v22_(v22), v23_(v23), v24_(v24) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray24& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; +}; + +template +class ValueArray25 { + public: + ValueArray25(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, + T25 v25) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray25& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; +}; + +template +class ValueArray26 { + public: + ValueArray26(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray26& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; +}; + +template +class ValueArray27 { + public: + ValueArray27(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), + v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), + v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), + v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), + v26_(v26), v27_(v27) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray27& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; +}; + +template +class ValueArray28 { + public: + ValueArray28(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), + v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), + v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), + v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), + v25_(v25), v26_(v26), v27_(v27), v28_(v28) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray28& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; +}; + +template +class ValueArray29 { + public: + ValueArray29(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), + v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), + v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), + v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), + v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray29& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; +}; + +template +class ValueArray30 { + public: + ValueArray30(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray30& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; +}; + +template +class ValueArray31 { + public: + ValueArray31(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30), v31_(v31) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray31& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; +}; + +template +class ValueArray32 { + public: + ValueArray32(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), + v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), + v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), + v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), + v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray32& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; +}; + +template +class ValueArray33 { + public: + ValueArray33(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, + T33 v33) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray33& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; +}; + +template +class ValueArray34 { + public: + ValueArray34(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33), v34_(v34) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray34& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; +}; + +template +class ValueArray35 { + public: + ValueArray35(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), + v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), + v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), + v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), + v26_(v26), v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), + v32_(v32), v33_(v33), v34_(v34), v35_(v35) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, + v35_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray35& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; +}; + +template +class ValueArray36 { + public: + ValueArray36(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), + v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), + v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), + v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), + v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29), v30_(v30), + v31_(v31), v32_(v32), v33_(v33), v34_(v34), v35_(v35), v36_(v36) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray36& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; +}; + +template +class ValueArray37 { + public: + ValueArray37(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), + v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), + v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), + v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), + v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29), + v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), v35_(v35), + v36_(v36), v37_(v37) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray37& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; +}; + +template +class ValueArray38 { + public: + ValueArray38(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), + v35_(v35), v36_(v36), v37_(v37), v38_(v38) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray38& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; +}; + +template +class ValueArray39 { + public: + ValueArray39(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), + v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray39& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; +}; + +template +class ValueArray40 { + public: + ValueArray40(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), + v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), + v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), + v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), + v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), + v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39), + v40_(v40) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray40& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; +}; + +template +class ValueArray41 { + public: + ValueArray41(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, + T41 v41) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), + v39_(v39), v40_(v40), v41_(v41) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray41& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; +}; + +template +class ValueArray42 { + public: + ValueArray42(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), + v39_(v39), v40_(v40), v41_(v41), v42_(v42) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray42& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; +}; + +template +class ValueArray43 { + public: + ValueArray43(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), + v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), + v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), + v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), + v26_(v26), v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), + v32_(v32), v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), + v38_(v38), v39_(v39), v40_(v40), v41_(v41), v42_(v42), v43_(v43) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray43& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; +}; + +template +class ValueArray44 { + public: + ValueArray44(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), + v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), + v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), v18_(v18), + v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), v24_(v24), + v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29), v30_(v30), + v31_(v31), v32_(v32), v33_(v33), v34_(v34), v35_(v35), v36_(v36), + v37_(v37), v38_(v38), v39_(v39), v40_(v40), v41_(v41), v42_(v42), + v43_(v43), v44_(v44) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_, v44_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray44& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; +}; + +template +class ValueArray45 { + public: + ValueArray45(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), + v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), v11_(v11), + v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), v17_(v17), + v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), v23_(v23), + v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), v29_(v29), + v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), v35_(v35), + v36_(v36), v37_(v37), v38_(v38), v39_(v39), v40_(v40), v41_(v41), + v42_(v42), v43_(v43), v44_(v44), v45_(v45) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_, v44_, v45_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray45& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; +}; + +template +class ValueArray46 { + public: + ValueArray46(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45, T46 v46) : v1_(v1), v2_(v2), v3_(v3), + v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), + v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39), v40_(v40), + v41_(v41), v42_(v42), v43_(v43), v44_(v44), v45_(v45), v46_(v46) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_, v44_, v45_, v46_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray46& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; + const T46 v46_; +}; + +template +class ValueArray47 { + public: + ValueArray47(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47) : v1_(v1), v2_(v2), + v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), v10_(v10), + v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), v16_(v16), + v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), v22_(v22), + v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), v28_(v28), + v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), v34_(v34), + v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39), v40_(v40), + v41_(v41), v42_(v42), v43_(v43), v44_(v44), v45_(v45), v46_(v46), + v47_(v47) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_, v44_, v45_, v46_, + v47_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray47& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; + const T46 v46_; + const T47 v47_; +}; + +template +class ValueArray48 { + public: + ValueArray48(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47, T48 v48) : v1_(v1), + v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), v8_(v8), v9_(v9), + v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), v15_(v15), + v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), v21_(v21), + v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), v27_(v27), + v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), v33_(v33), + v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), v39_(v39), + v40_(v40), v41_(v41), v42_(v42), v43_(v43), v44_(v44), v45_(v45), + v46_(v46), v47_(v47), v48_(v48) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_, v44_, v45_, v46_, v47_, + v48_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray48& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; + const T46 v46_; + const T47 v47_; + const T48 v48_; +}; + +template +class ValueArray49 { + public: + ValueArray49(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47, T48 v48, + T49 v49) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), + v39_(v39), v40_(v40), v41_(v41), v42_(v42), v43_(v43), v44_(v44), + v45_(v45), v46_(v46), v47_(v47), v48_(v48), v49_(v49) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_, v44_, v45_, v46_, v47_, + v48_, v49_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray49& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; + const T46 v46_; + const T47 v47_; + const T48 v48_; + const T49 v49_; +}; + +template +class ValueArray50 { + public: + ValueArray50(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47, T48 v48, T49 v49, + T50 v50) : v1_(v1), v2_(v2), v3_(v3), v4_(v4), v5_(v5), v6_(v6), v7_(v7), + v8_(v8), v9_(v9), v10_(v10), v11_(v11), v12_(v12), v13_(v13), v14_(v14), + v15_(v15), v16_(v16), v17_(v17), v18_(v18), v19_(v19), v20_(v20), + v21_(v21), v22_(v22), v23_(v23), v24_(v24), v25_(v25), v26_(v26), + v27_(v27), v28_(v28), v29_(v29), v30_(v30), v31_(v31), v32_(v32), + v33_(v33), v34_(v34), v35_(v35), v36_(v36), v37_(v37), v38_(v38), + v39_(v39), v40_(v40), v41_(v41), v42_(v42), v43_(v43), v44_(v44), + v45_(v45), v46_(v46), v47_(v47), v48_(v48), v49_(v49), v50_(v50) {} + + template + operator ParamGenerator() const { + const T array[] = {v1_, v2_, v3_, v4_, v5_, v6_, v7_, v8_, v9_, v10_, v11_, + v12_, v13_, v14_, v15_, v16_, v17_, v18_, v19_, v20_, v21_, v22_, v23_, + v24_, v25_, v26_, v27_, v28_, v29_, v30_, v31_, v32_, v33_, v34_, v35_, + v36_, v37_, v38_, v39_, v40_, v41_, v42_, v43_, v44_, v45_, v46_, v47_, + v48_, v49_, v50_}; + return ValuesIn(array); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const ValueArray50& other); + + const T1 v1_; + const T2 v2_; + const T3 v3_; + const T4 v4_; + const T5 v5_; + const T6 v6_; + const T7 v7_; + const T8 v8_; + const T9 v9_; + const T10 v10_; + const T11 v11_; + const T12 v12_; + const T13 v13_; + const T14 v14_; + const T15 v15_; + const T16 v16_; + const T17 v17_; + const T18 v18_; + const T19 v19_; + const T20 v20_; + const T21 v21_; + const T22 v22_; + const T23 v23_; + const T24 v24_; + const T25 v25_; + const T26 v26_; + const T27 v27_; + const T28 v28_; + const T29 v29_; + const T30 v30_; + const T31 v31_; + const T32 v32_; + const T33 v33_; + const T34 v34_; + const T35 v35_; + const T36 v36_; + const T37 v37_; + const T38 v38_; + const T39 v39_; + const T40 v40_; + const T41 v41_; + const T42 v42_; + const T43 v43_; + const T44 v44_; + const T45 v45_; + const T46 v46_; + const T47 v47_; + const T48 v48_; + const T49 v49_; + const T50 v50_; +}; + +# if GTEST_HAS_COMBINE +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Generates values from the Cartesian product of values produced +// by the argument generators. +// +template +class CartesianProductGenerator2 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator2(const ParamGenerator& g1, + const ParamGenerator& g2) + : g1_(g1), g2_(g2) {} + virtual ~CartesianProductGenerator2() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current2_; + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + ParamType current_value_; + }; // class CartesianProductGenerator2::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator2& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; +}; // class CartesianProductGenerator2 + + +template +class CartesianProductGenerator3 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator3(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3) + : g1_(g1), g2_(g2), g3_(g3) {} + virtual ~CartesianProductGenerator3() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current3_; + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + ParamType current_value_; + }; // class CartesianProductGenerator3::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator3& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; +}; // class CartesianProductGenerator3 + + +template +class CartesianProductGenerator4 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator4(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4) {} + virtual ~CartesianProductGenerator4() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current4_; + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + ParamType current_value_; + }; // class CartesianProductGenerator4::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator4& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; +}; // class CartesianProductGenerator4 + + +template +class CartesianProductGenerator5 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator5(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5) {} + virtual ~CartesianProductGenerator5() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current5_; + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + ParamType current_value_; + }; // class CartesianProductGenerator5::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator5& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; +}; // class CartesianProductGenerator5 + + +template +class CartesianProductGenerator6 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator6(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5, + const ParamGenerator& g6) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6) {} + virtual ~CartesianProductGenerator6() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5, + const ParamGenerator& g6, + const typename ParamGenerator::iterator& current6) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5), + begin6_(g6.begin()), end6_(g6.end()), current6_(current6) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current6_; + if (current6_ == end6_) { + current6_ = begin6_; + ++current5_; + } + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_ && + current6_ == typed_other->current6_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_), + begin6_(other.begin6_), + end6_(other.end6_), + current6_(other.current6_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_, *current6_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_ || + current6_ == end6_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + const typename ParamGenerator::iterator begin6_; + const typename ParamGenerator::iterator end6_; + typename ParamGenerator::iterator current6_; + ParamType current_value_; + }; // class CartesianProductGenerator6::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator6& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; + const ParamGenerator g6_; +}; // class CartesianProductGenerator6 + + +template +class CartesianProductGenerator7 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator7(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5, + const ParamGenerator& g6, const ParamGenerator& g7) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7) {} + virtual ~CartesianProductGenerator7() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin(), g7_, + g7_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end(), g7_, g7_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5, + const ParamGenerator& g6, + const typename ParamGenerator::iterator& current6, + const ParamGenerator& g7, + const typename ParamGenerator::iterator& current7) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5), + begin6_(g6.begin()), end6_(g6.end()), current6_(current6), + begin7_(g7.begin()), end7_(g7.end()), current7_(current7) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current7_; + if (current7_ == end7_) { + current7_ = begin7_; + ++current6_; + } + if (current6_ == end6_) { + current6_ = begin6_; + ++current5_; + } + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_ && + current6_ == typed_other->current6_ && + current7_ == typed_other->current7_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_), + begin6_(other.begin6_), + end6_(other.end6_), + current6_(other.current6_), + begin7_(other.begin7_), + end7_(other.end7_), + current7_(other.current7_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_, *current6_, *current7_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_ || + current6_ == end6_ || + current7_ == end7_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + const typename ParamGenerator::iterator begin6_; + const typename ParamGenerator::iterator end6_; + typename ParamGenerator::iterator current6_; + const typename ParamGenerator::iterator begin7_; + const typename ParamGenerator::iterator end7_; + typename ParamGenerator::iterator current7_; + ParamType current_value_; + }; // class CartesianProductGenerator7::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator7& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; + const ParamGenerator g6_; + const ParamGenerator g7_; +}; // class CartesianProductGenerator7 + + +template +class CartesianProductGenerator8 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator8(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5, + const ParamGenerator& g6, const ParamGenerator& g7, + const ParamGenerator& g8) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), + g8_(g8) {} + virtual ~CartesianProductGenerator8() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin(), g7_, + g7_.begin(), g8_, g8_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end(), g7_, g7_.end(), g8_, + g8_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5, + const ParamGenerator& g6, + const typename ParamGenerator::iterator& current6, + const ParamGenerator& g7, + const typename ParamGenerator::iterator& current7, + const ParamGenerator& g8, + const typename ParamGenerator::iterator& current8) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5), + begin6_(g6.begin()), end6_(g6.end()), current6_(current6), + begin7_(g7.begin()), end7_(g7.end()), current7_(current7), + begin8_(g8.begin()), end8_(g8.end()), current8_(current8) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current8_; + if (current8_ == end8_) { + current8_ = begin8_; + ++current7_; + } + if (current7_ == end7_) { + current7_ = begin7_; + ++current6_; + } + if (current6_ == end6_) { + current6_ = begin6_; + ++current5_; + } + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_ && + current6_ == typed_other->current6_ && + current7_ == typed_other->current7_ && + current8_ == typed_other->current8_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_), + begin6_(other.begin6_), + end6_(other.end6_), + current6_(other.current6_), + begin7_(other.begin7_), + end7_(other.end7_), + current7_(other.current7_), + begin8_(other.begin8_), + end8_(other.end8_), + current8_(other.current8_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_, *current6_, *current7_, *current8_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_ || + current6_ == end6_ || + current7_ == end7_ || + current8_ == end8_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + const typename ParamGenerator::iterator begin6_; + const typename ParamGenerator::iterator end6_; + typename ParamGenerator::iterator current6_; + const typename ParamGenerator::iterator begin7_; + const typename ParamGenerator::iterator end7_; + typename ParamGenerator::iterator current7_; + const typename ParamGenerator::iterator begin8_; + const typename ParamGenerator::iterator end8_; + typename ParamGenerator::iterator current8_; + ParamType current_value_; + }; // class CartesianProductGenerator8::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator8& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; + const ParamGenerator g6_; + const ParamGenerator g7_; + const ParamGenerator g8_; +}; // class CartesianProductGenerator8 + + +template +class CartesianProductGenerator9 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator9(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5, + const ParamGenerator& g6, const ParamGenerator& g7, + const ParamGenerator& g8, const ParamGenerator& g9) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), g8_(g8), + g9_(g9) {} + virtual ~CartesianProductGenerator9() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin(), g7_, + g7_.begin(), g8_, g8_.begin(), g9_, g9_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end(), g7_, g7_.end(), g8_, + g8_.end(), g9_, g9_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5, + const ParamGenerator& g6, + const typename ParamGenerator::iterator& current6, + const ParamGenerator& g7, + const typename ParamGenerator::iterator& current7, + const ParamGenerator& g8, + const typename ParamGenerator::iterator& current8, + const ParamGenerator& g9, + const typename ParamGenerator::iterator& current9) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5), + begin6_(g6.begin()), end6_(g6.end()), current6_(current6), + begin7_(g7.begin()), end7_(g7.end()), current7_(current7), + begin8_(g8.begin()), end8_(g8.end()), current8_(current8), + begin9_(g9.begin()), end9_(g9.end()), current9_(current9) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current9_; + if (current9_ == end9_) { + current9_ = begin9_; + ++current8_; + } + if (current8_ == end8_) { + current8_ = begin8_; + ++current7_; + } + if (current7_ == end7_) { + current7_ = begin7_; + ++current6_; + } + if (current6_ == end6_) { + current6_ = begin6_; + ++current5_; + } + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_ && + current6_ == typed_other->current6_ && + current7_ == typed_other->current7_ && + current8_ == typed_other->current8_ && + current9_ == typed_other->current9_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_), + begin6_(other.begin6_), + end6_(other.end6_), + current6_(other.current6_), + begin7_(other.begin7_), + end7_(other.end7_), + current7_(other.current7_), + begin8_(other.begin8_), + end8_(other.end8_), + current8_(other.current8_), + begin9_(other.begin9_), + end9_(other.end9_), + current9_(other.current9_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_, *current6_, *current7_, *current8_, + *current9_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_ || + current6_ == end6_ || + current7_ == end7_ || + current8_ == end8_ || + current9_ == end9_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + const typename ParamGenerator::iterator begin6_; + const typename ParamGenerator::iterator end6_; + typename ParamGenerator::iterator current6_; + const typename ParamGenerator::iterator begin7_; + const typename ParamGenerator::iterator end7_; + typename ParamGenerator::iterator current7_; + const typename ParamGenerator::iterator begin8_; + const typename ParamGenerator::iterator end8_; + typename ParamGenerator::iterator current8_; + const typename ParamGenerator::iterator begin9_; + const typename ParamGenerator::iterator end9_; + typename ParamGenerator::iterator current9_; + ParamType current_value_; + }; // class CartesianProductGenerator9::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator9& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; + const ParamGenerator g6_; + const ParamGenerator g7_; + const ParamGenerator g8_; + const ParamGenerator g9_; +}; // class CartesianProductGenerator9 + + +template +class CartesianProductGenerator10 + : public ParamGeneratorInterface< ::std::tr1::tuple > { + public: + typedef ::std::tr1::tuple ParamType; + + CartesianProductGenerator10(const ParamGenerator& g1, + const ParamGenerator& g2, const ParamGenerator& g3, + const ParamGenerator& g4, const ParamGenerator& g5, + const ParamGenerator& g6, const ParamGenerator& g7, + const ParamGenerator& g8, const ParamGenerator& g9, + const ParamGenerator& g10) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), g8_(g8), + g9_(g9), g10_(g10) {} + virtual ~CartesianProductGenerator10() {} + + virtual ParamIteratorInterface* Begin() const { + return new Iterator(this, g1_, g1_.begin(), g2_, g2_.begin(), g3_, + g3_.begin(), g4_, g4_.begin(), g5_, g5_.begin(), g6_, g6_.begin(), g7_, + g7_.begin(), g8_, g8_.begin(), g9_, g9_.begin(), g10_, g10_.begin()); + } + virtual ParamIteratorInterface* End() const { + return new Iterator(this, g1_, g1_.end(), g2_, g2_.end(), g3_, g3_.end(), + g4_, g4_.end(), g5_, g5_.end(), g6_, g6_.end(), g7_, g7_.end(), g8_, + g8_.end(), g9_, g9_.end(), g10_, g10_.end()); + } + + private: + class Iterator : public ParamIteratorInterface { + public: + Iterator(const ParamGeneratorInterface* base, + const ParamGenerator& g1, + const typename ParamGenerator::iterator& current1, + const ParamGenerator& g2, + const typename ParamGenerator::iterator& current2, + const ParamGenerator& g3, + const typename ParamGenerator::iterator& current3, + const ParamGenerator& g4, + const typename ParamGenerator::iterator& current4, + const ParamGenerator& g5, + const typename ParamGenerator::iterator& current5, + const ParamGenerator& g6, + const typename ParamGenerator::iterator& current6, + const ParamGenerator& g7, + const typename ParamGenerator::iterator& current7, + const ParamGenerator& g8, + const typename ParamGenerator::iterator& current8, + const ParamGenerator& g9, + const typename ParamGenerator::iterator& current9, + const ParamGenerator& g10, + const typename ParamGenerator::iterator& current10) + : base_(base), + begin1_(g1.begin()), end1_(g1.end()), current1_(current1), + begin2_(g2.begin()), end2_(g2.end()), current2_(current2), + begin3_(g3.begin()), end3_(g3.end()), current3_(current3), + begin4_(g4.begin()), end4_(g4.end()), current4_(current4), + begin5_(g5.begin()), end5_(g5.end()), current5_(current5), + begin6_(g6.begin()), end6_(g6.end()), current6_(current6), + begin7_(g7.begin()), end7_(g7.end()), current7_(current7), + begin8_(g8.begin()), end8_(g8.end()), current8_(current8), + begin9_(g9.begin()), end9_(g9.end()), current9_(current9), + begin10_(g10.begin()), end10_(g10.end()), current10_(current10) { + ComputeCurrentValue(); + } + virtual ~Iterator() {} + + virtual const ParamGeneratorInterface* BaseGenerator() const { + return base_; + } + // Advance should not be called on beyond-of-range iterators + // so no component iterators must be beyond end of range, either. + virtual void Advance() { + assert(!AtEnd()); + ++current10_; + if (current10_ == end10_) { + current10_ = begin10_; + ++current9_; + } + if (current9_ == end9_) { + current9_ = begin9_; + ++current8_; + } + if (current8_ == end8_) { + current8_ = begin8_; + ++current7_; + } + if (current7_ == end7_) { + current7_ = begin7_; + ++current6_; + } + if (current6_ == end6_) { + current6_ = begin6_; + ++current5_; + } + if (current5_ == end5_) { + current5_ = begin5_; + ++current4_; + } + if (current4_ == end4_) { + current4_ = begin4_; + ++current3_; + } + if (current3_ == end3_) { + current3_ = begin3_; + ++current2_; + } + if (current2_ == end2_) { + current2_ = begin2_; + ++current1_; + } + ComputeCurrentValue(); + } + virtual ParamIteratorInterface* Clone() const { + return new Iterator(*this); + } + virtual const ParamType* Current() const { return ¤t_value_; } + virtual bool Equals(const ParamIteratorInterface& other) const { + // Having the same base generator guarantees that the other + // iterator is of the same type and we can downcast. + GTEST_CHECK_(BaseGenerator() == other.BaseGenerator()) + << "The program attempted to compare iterators " + << "from different generators." << std::endl; + const Iterator* typed_other = + CheckedDowncastToActualType(&other); + // We must report iterators equal if they both point beyond their + // respective ranges. That can happen in a variety of fashions, + // so we have to consult AtEnd(). + return (AtEnd() && typed_other->AtEnd()) || + ( + current1_ == typed_other->current1_ && + current2_ == typed_other->current2_ && + current3_ == typed_other->current3_ && + current4_ == typed_other->current4_ && + current5_ == typed_other->current5_ && + current6_ == typed_other->current6_ && + current7_ == typed_other->current7_ && + current8_ == typed_other->current8_ && + current9_ == typed_other->current9_ && + current10_ == typed_other->current10_); + } + + private: + Iterator(const Iterator& other) + : base_(other.base_), + begin1_(other.begin1_), + end1_(other.end1_), + current1_(other.current1_), + begin2_(other.begin2_), + end2_(other.end2_), + current2_(other.current2_), + begin3_(other.begin3_), + end3_(other.end3_), + current3_(other.current3_), + begin4_(other.begin4_), + end4_(other.end4_), + current4_(other.current4_), + begin5_(other.begin5_), + end5_(other.end5_), + current5_(other.current5_), + begin6_(other.begin6_), + end6_(other.end6_), + current6_(other.current6_), + begin7_(other.begin7_), + end7_(other.end7_), + current7_(other.current7_), + begin8_(other.begin8_), + end8_(other.end8_), + current8_(other.current8_), + begin9_(other.begin9_), + end9_(other.end9_), + current9_(other.current9_), + begin10_(other.begin10_), + end10_(other.end10_), + current10_(other.current10_) { + ComputeCurrentValue(); + } + + void ComputeCurrentValue() { + if (!AtEnd()) + current_value_ = ParamType(*current1_, *current2_, *current3_, + *current4_, *current5_, *current6_, *current7_, *current8_, + *current9_, *current10_); + } + bool AtEnd() const { + // We must report iterator past the end of the range when either of the + // component iterators has reached the end of its range. + return + current1_ == end1_ || + current2_ == end2_ || + current3_ == end3_ || + current4_ == end4_ || + current5_ == end5_ || + current6_ == end6_ || + current7_ == end7_ || + current8_ == end8_ || + current9_ == end9_ || + current10_ == end10_; + } + + // No implementation - assignment is unsupported. + void operator=(const Iterator& other); + + const ParamGeneratorInterface* const base_; + // begin[i]_ and end[i]_ define the i-th range that Iterator traverses. + // current[i]_ is the actual traversing iterator. + const typename ParamGenerator::iterator begin1_; + const typename ParamGenerator::iterator end1_; + typename ParamGenerator::iterator current1_; + const typename ParamGenerator::iterator begin2_; + const typename ParamGenerator::iterator end2_; + typename ParamGenerator::iterator current2_; + const typename ParamGenerator::iterator begin3_; + const typename ParamGenerator::iterator end3_; + typename ParamGenerator::iterator current3_; + const typename ParamGenerator::iterator begin4_; + const typename ParamGenerator::iterator end4_; + typename ParamGenerator::iterator current4_; + const typename ParamGenerator::iterator begin5_; + const typename ParamGenerator::iterator end5_; + typename ParamGenerator::iterator current5_; + const typename ParamGenerator::iterator begin6_; + const typename ParamGenerator::iterator end6_; + typename ParamGenerator::iterator current6_; + const typename ParamGenerator::iterator begin7_; + const typename ParamGenerator::iterator end7_; + typename ParamGenerator::iterator current7_; + const typename ParamGenerator::iterator begin8_; + const typename ParamGenerator::iterator end8_; + typename ParamGenerator::iterator current8_; + const typename ParamGenerator::iterator begin9_; + const typename ParamGenerator::iterator end9_; + typename ParamGenerator::iterator current9_; + const typename ParamGenerator::iterator begin10_; + const typename ParamGenerator::iterator end10_; + typename ParamGenerator::iterator current10_; + ParamType current_value_; + }; // class CartesianProductGenerator10::Iterator + + // No implementation - assignment is unsupported. + void operator=(const CartesianProductGenerator10& other); + + const ParamGenerator g1_; + const ParamGenerator g2_; + const ParamGenerator g3_; + const ParamGenerator g4_; + const ParamGenerator g5_; + const ParamGenerator g6_; + const ParamGenerator g7_; + const ParamGenerator g8_; + const ParamGenerator g9_; + const ParamGenerator g10_; +}; // class CartesianProductGenerator10 + + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Helper classes providing Combine() with polymorphic features. They allow +// casting CartesianProductGeneratorN to ParamGenerator if T is +// convertible to U. +// +template +class CartesianProductHolder2 { + public: +CartesianProductHolder2(const Generator1& g1, const Generator2& g2) + : g1_(g1), g2_(g2) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator2( + static_cast >(g1_), + static_cast >(g2_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder2& other); + + const Generator1 g1_; + const Generator2 g2_; +}; // class CartesianProductHolder2 + +template +class CartesianProductHolder3 { + public: +CartesianProductHolder3(const Generator1& g1, const Generator2& g2, + const Generator3& g3) + : g1_(g1), g2_(g2), g3_(g3) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator3( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder3& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; +}; // class CartesianProductHolder3 + +template +class CartesianProductHolder4 { + public: +CartesianProductHolder4(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator4( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder4& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; +}; // class CartesianProductHolder4 + +template +class CartesianProductHolder5 { + public: +CartesianProductHolder5(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator5( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder5& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; +}; // class CartesianProductHolder5 + +template +class CartesianProductHolder6 { + public: +CartesianProductHolder6(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5, + const Generator6& g6) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator6( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_), + static_cast >(g6_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder6& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; + const Generator6 g6_; +}; // class CartesianProductHolder6 + +template +class CartesianProductHolder7 { + public: +CartesianProductHolder7(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5, + const Generator6& g6, const Generator7& g7) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator7( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_), + static_cast >(g6_), + static_cast >(g7_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder7& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; + const Generator6 g6_; + const Generator7 g7_; +}; // class CartesianProductHolder7 + +template +class CartesianProductHolder8 { + public: +CartesianProductHolder8(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5, + const Generator6& g6, const Generator7& g7, const Generator8& g8) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), + g8_(g8) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator8( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_), + static_cast >(g6_), + static_cast >(g7_), + static_cast >(g8_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder8& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; + const Generator6 g6_; + const Generator7 g7_; + const Generator8 g8_; +}; // class CartesianProductHolder8 + +template +class CartesianProductHolder9 { + public: +CartesianProductHolder9(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5, + const Generator6& g6, const Generator7& g7, const Generator8& g8, + const Generator9& g9) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), g8_(g8), + g9_(g9) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator9( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_), + static_cast >(g6_), + static_cast >(g7_), + static_cast >(g8_), + static_cast >(g9_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder9& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; + const Generator6 g6_; + const Generator7 g7_; + const Generator8 g8_; + const Generator9 g9_; +}; // class CartesianProductHolder9 + +template +class CartesianProductHolder10 { + public: +CartesianProductHolder10(const Generator1& g1, const Generator2& g2, + const Generator3& g3, const Generator4& g4, const Generator5& g5, + const Generator6& g6, const Generator7& g7, const Generator8& g8, + const Generator9& g9, const Generator10& g10) + : g1_(g1), g2_(g2), g3_(g3), g4_(g4), g5_(g5), g6_(g6), g7_(g7), g8_(g8), + g9_(g9), g10_(g10) {} + template + operator ParamGenerator< ::std::tr1::tuple >() const { + return ParamGenerator< ::std::tr1::tuple >( + new CartesianProductGenerator10( + static_cast >(g1_), + static_cast >(g2_), + static_cast >(g3_), + static_cast >(g4_), + static_cast >(g5_), + static_cast >(g6_), + static_cast >(g7_), + static_cast >(g8_), + static_cast >(g9_), + static_cast >(g10_))); + } + + private: + // No implementation - assignment is unsupported. + void operator=(const CartesianProductHolder10& other); + + const Generator1 g1_; + const Generator2 g2_; + const Generator3 g3_; + const Generator4 g4_; + const Generator5 g5_; + const Generator6 g6_; + const Generator7 g7_; + const Generator8 g8_; + const Generator9 g9_; + const Generator10 g10_; +}; // class CartesianProductHolder10 + +# endif // GTEST_HAS_COMBINE + +} // namespace internal +} // namespace testing + +#endif // GTEST_HAS_PARAM_TEST + +#endif // GTEST_INCLUDE_GTEST_INTERNAL_GTEST_PARAM_UTIL_GENERATED_H_ + +#if GTEST_HAS_PARAM_TEST + +namespace testing { + +// Functions producing parameter generators. +// +// Google Test uses these generators to produce parameters for value- +// parameterized tests. When a parameterized test case is instantiated +// with a particular generator, Google Test creates and runs tests +// for each element in the sequence produced by the generator. +// +// In the following sample, tests from test case FooTest are instantiated +// each three times with parameter values 3, 5, and 8: +// +// class FooTest : public TestWithParam { ... }; +// +// TEST_P(FooTest, TestThis) { +// } +// TEST_P(FooTest, TestThat) { +// } +// INSTANTIATE_TEST_CASE_P(TestSequence, FooTest, Values(3, 5, 8)); +// + +// Range() returns generators providing sequences of values in a range. +// +// Synopsis: +// Range(start, end) +// - returns a generator producing a sequence of values {start, start+1, +// start+2, ..., }. +// Range(start, end, step) +// - returns a generator producing a sequence of values {start, start+step, +// start+step+step, ..., }. +// Notes: +// * The generated sequences never include end. For example, Range(1, 5) +// returns a generator producing a sequence {1, 2, 3, 4}. Range(1, 9, 2) +// returns a generator producing {1, 3, 5, 7}. +// * start and end must have the same type. That type may be any integral or +// floating-point type or a user defined type satisfying these conditions: +// * It must be assignable (have operator=() defined). +// * It must have operator+() (operator+(int-compatible type) for +// two-operand version). +// * It must have operator<() defined. +// Elements in the resulting sequences will also have that type. +// * Condition start < end must be satisfied in order for resulting sequences +// to contain any elements. +// +template +internal::ParamGenerator Range(T start, T end, IncrementT step) { + return internal::ParamGenerator( + new internal::RangeGenerator(start, end, step)); +} + +template +internal::ParamGenerator Range(T start, T end) { + return Range(start, end, 1); +} + +// ValuesIn() function allows generation of tests with parameters coming from +// a container. +// +// Synopsis: +// ValuesIn(const T (&array)[N]) +// - returns a generator producing sequences with elements from +// a C-style array. +// ValuesIn(const Container& container) +// - returns a generator producing sequences with elements from +// an STL-style container. +// ValuesIn(Iterator begin, Iterator end) +// - returns a generator producing sequences with elements from +// a range [begin, end) defined by a pair of STL-style iterators. These +// iterators can also be plain C pointers. +// +// Please note that ValuesIn copies the values from the containers +// passed in and keeps them to generate tests in RUN_ALL_TESTS(). +// +// Examples: +// +// This instantiates tests from test case StringTest +// each with C-string values of "foo", "bar", and "baz": +// +// const char* strings[] = {"foo", "bar", "baz"}; +// INSTANTIATE_TEST_CASE_P(StringSequence, SrtingTest, ValuesIn(strings)); +// +// This instantiates tests from test case StlStringTest +// each with STL strings with values "a" and "b": +// +// ::std::vector< ::std::string> GetParameterStrings() { +// ::std::vector< ::std::string> v; +// v.push_back("a"); +// v.push_back("b"); +// return v; +// } +// +// INSTANTIATE_TEST_CASE_P(CharSequence, +// StlStringTest, +// ValuesIn(GetParameterStrings())); +// +// +// This will also instantiate tests from CharTest +// each with parameter values 'a' and 'b': +// +// ::std::list GetParameterChars() { +// ::std::list list; +// list.push_back('a'); +// list.push_back('b'); +// return list; +// } +// ::std::list l = GetParameterChars(); +// INSTANTIATE_TEST_CASE_P(CharSequence2, +// CharTest, +// ValuesIn(l.begin(), l.end())); +// +template +internal::ParamGenerator< + typename ::testing::internal::IteratorTraits::value_type> +ValuesIn(ForwardIterator begin, ForwardIterator end) { + typedef typename ::testing::internal::IteratorTraits + ::value_type ParamType; + return internal::ParamGenerator( + new internal::ValuesInIteratorRangeGenerator(begin, end)); +} + +template +internal::ParamGenerator ValuesIn(const T (&array)[N]) { + return ValuesIn(array, array + N); +} + +template +internal::ParamGenerator ValuesIn( + const Container& container) { + return ValuesIn(container.begin(), container.end()); +} + +// Values() allows generating tests from explicitly specified list of +// parameters. +// +// Synopsis: +// Values(T v1, T v2, ..., T vN) +// - returns a generator producing sequences with elements v1, v2, ..., vN. +// +// For example, this instantiates tests from test case BarTest each +// with values "one", "two", and "three": +// +// INSTANTIATE_TEST_CASE_P(NumSequence, BarTest, Values("one", "two", "three")); +// +// This instantiates tests from test case BazTest each with values 1, 2, 3.5. +// The exact type of values will depend on the type of parameter in BazTest. +// +// INSTANTIATE_TEST_CASE_P(FloatingNumbers, BazTest, Values(1, 2, 3.5)); +// +// Currently, Values() supports from 1 to 50 parameters. +// +template +internal::ValueArray1 Values(T1 v1) { + return internal::ValueArray1(v1); +} + +template +internal::ValueArray2 Values(T1 v1, T2 v2) { + return internal::ValueArray2(v1, v2); +} + +template +internal::ValueArray3 Values(T1 v1, T2 v2, T3 v3) { + return internal::ValueArray3(v1, v2, v3); +} + +template +internal::ValueArray4 Values(T1 v1, T2 v2, T3 v3, T4 v4) { + return internal::ValueArray4(v1, v2, v3, v4); +} + +template +internal::ValueArray5 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5) { + return internal::ValueArray5(v1, v2, v3, v4, v5); +} + +template +internal::ValueArray6 Values(T1 v1, T2 v2, T3 v3, + T4 v4, T5 v5, T6 v6) { + return internal::ValueArray6(v1, v2, v3, v4, v5, v6); +} + +template +internal::ValueArray7 Values(T1 v1, T2 v2, T3 v3, + T4 v4, T5 v5, T6 v6, T7 v7) { + return internal::ValueArray7(v1, v2, v3, v4, v5, + v6, v7); +} + +template +internal::ValueArray8 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8) { + return internal::ValueArray8(v1, v2, v3, v4, + v5, v6, v7, v8); +} + +template +internal::ValueArray9 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9) { + return internal::ValueArray9(v1, v2, v3, + v4, v5, v6, v7, v8, v9); +} + +template +internal::ValueArray10 Values(T1 v1, + T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10) { + return internal::ValueArray10(v1, + v2, v3, v4, v5, v6, v7, v8, v9, v10); +} + +template +internal::ValueArray11 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11) { + return internal::ValueArray11(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11); +} + +template +internal::ValueArray12 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12) { + return internal::ValueArray12(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12); +} + +template +internal::ValueArray13 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13) { + return internal::ValueArray13(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13); +} + +template +internal::ValueArray14 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14) { + return internal::ValueArray14(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, + v14); +} + +template +internal::ValueArray15 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, + T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15) { + return internal::ValueArray15(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, + v13, v14, v15); +} + +template +internal::ValueArray16 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16) { + return internal::ValueArray16(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14, v15, v16); +} + +template +internal::ValueArray17 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17) { + return internal::ValueArray17(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, + v11, v12, v13, v14, v15, v16, v17); +} + +template +internal::ValueArray18 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, + T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18) { + return internal::ValueArray18(v1, v2, v3, v4, v5, v6, v7, v8, v9, + v10, v11, v12, v13, v14, v15, v16, v17, v18); +} + +template +internal::ValueArray19 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, + T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, + T15 v15, T16 v16, T17 v17, T18 v18, T19 v19) { + return internal::ValueArray19(v1, v2, v3, v4, v5, v6, v7, v8, + v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19); +} + +template +internal::ValueArray20 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, + T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20) { + return internal::ValueArray20(v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20); +} + +template +internal::ValueArray21 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, + T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21) { + return internal::ValueArray21(v1, v2, v3, v4, v5, v6, + v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21); +} + +template +internal::ValueArray22 Values(T1 v1, T2 v2, T3 v3, + T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22) { + return internal::ValueArray22(v1, v2, v3, v4, + v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, + v20, v21, v22); +} + +template +internal::ValueArray23 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22, T23 v23) { + return internal::ValueArray23(v1, v2, v3, + v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, + v20, v21, v22, v23); +} + +template +internal::ValueArray24 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22, T23 v23, T24 v24) { + return internal::ValueArray24(v1, v2, + v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, + v19, v20, v21, v22, v23, v24); +} + +template +internal::ValueArray25 Values(T1 v1, + T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, + T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, + T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25) { + return internal::ValueArray25(v1, + v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, + v18, v19, v20, v21, v22, v23, v24, v25); +} + +template +internal::ValueArray26 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26) { + return internal::ValueArray26(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, + v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26); +} + +template +internal::ValueArray27 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27) { + return internal::ValueArray27(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, + v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27); +} + +template +internal::ValueArray28 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28) { + return internal::ValueArray28(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, + v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, + v28); +} + +template +internal::ValueArray29 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29) { + return internal::ValueArray29(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, + v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, + v27, v28, v29); +} + +template +internal::ValueArray30 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, + T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, + T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, + T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30) { + return internal::ValueArray30(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, + v26, v27, v28, v29, v30); +} + +template +internal::ValueArray31 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31) { + return internal::ValueArray31(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, + v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, + v25, v26, v27, v28, v29, v30, v31); +} + +template +internal::ValueArray32 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, + T32 v32) { + return internal::ValueArray32(v1, v2, v3, v4, v5, v6, v7, v8, v9, + v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, + v24, v25, v26, v27, v28, v29, v30, v31, v32); +} + +template +internal::ValueArray33 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, + T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, + T32 v32, T33 v33) { + return internal::ValueArray33(v1, v2, v3, v4, v5, v6, v7, v8, + v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, + v24, v25, v26, v27, v28, v29, v30, v31, v32, v33); +} + +template +internal::ValueArray34 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, + T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, + T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, + T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, + T31 v31, T32 v32, T33 v33, T34 v34) { + return internal::ValueArray34(v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, + v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34); +} + +template +internal::ValueArray35 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, + T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, + T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, + T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35) { + return internal::ValueArray35(v1, v2, v3, v4, v5, v6, + v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, + v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35); +} + +template +internal::ValueArray36 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, + T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, + T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, + T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36) { + return internal::ValueArray36(v1, v2, v3, v4, + v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, + v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, + v34, v35, v36); +} + +template +internal::ValueArray37 Values(T1 v1, T2 v2, T3 v3, + T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, + T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, + T37 v37) { + return internal::ValueArray37(v1, v2, v3, + v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, + v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, + v34, v35, v36, v37); +} + +template +internal::ValueArray38 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, + T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, + T37 v37, T38 v38) { + return internal::ValueArray38(v1, v2, + v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, + v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, + v33, v34, v35, v36, v37, v38); +} + +template +internal::ValueArray39 Values(T1 v1, T2 v2, + T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, + T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, + T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, + T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, + T37 v37, T38 v38, T39 v39) { + return internal::ValueArray39(v1, + v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, + v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, + v32, v33, v34, v35, v36, v37, v38, v39); +} + +template +internal::ValueArray40 Values(T1 v1, + T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, + T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, + T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, + T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, + T36 v36, T37 v37, T38 v38, T39 v39, T40 v40) { + return internal::ValueArray40(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, + v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, + v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40); +} + +template +internal::ValueArray41 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41) { + return internal::ValueArray41(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, + v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, v28, + v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41); +} + +template +internal::ValueArray42 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42) { + return internal::ValueArray42(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, + v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, v27, + v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, v41, + v42); +} + +template +internal::ValueArray43 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43) { + return internal::ValueArray43(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, + v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, v26, + v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, v40, + v41, v42, v43); +} + +template +internal::ValueArray44 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, + T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, T17 v17, + T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, T25 v25, + T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, T33 v33, + T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, T41 v41, + T42 v42, T43 v43, T44 v44) { + return internal::ValueArray44(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, + v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, v25, + v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, v39, + v40, v41, v42, v43, v44); +} + +template +internal::ValueArray45 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, T8 v8, + T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, T16 v16, + T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, T24 v24, + T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, T32 v32, + T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, T40 v40, + T41 v41, T42 v42, T43 v43, T44 v44, T45 v45) { + return internal::ValueArray45(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, + v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, v24, + v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, v38, + v39, v40, v41, v42, v43, v44, v45); +} + +template +internal::ValueArray46 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, + T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, + T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, T46 v46) { + return internal::ValueArray46(v1, v2, v3, v4, v5, v6, v7, v8, v9, + v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, + v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, + v38, v39, v40, v41, v42, v43, v44, v45, v46); +} + +template +internal::ValueArray47 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, T7 v7, + T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, + T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, + T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47) { + return internal::ValueArray47(v1, v2, v3, v4, v5, v6, v7, v8, + v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, v23, + v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, v37, + v38, v39, v40, v41, v42, v43, v44, v45, v46, v47); +} + +template +internal::ValueArray48 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, T6 v6, + T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, T15 v15, + T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, T23 v23, + T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, T31 v31, + T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, T39 v39, + T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, T47 v47, + T48 v48) { + return internal::ValueArray48(v1, v2, v3, v4, v5, v6, v7, + v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, v22, + v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, v36, + v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48); +} + +template +internal::ValueArray49 Values(T1 v1, T2 v2, T3 v3, T4 v4, T5 v5, + T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, T14 v14, + T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, T22 v22, + T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, T30 v30, + T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, T38 v38, + T39 v39, T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, T46 v46, + T47 v47, T48 v48, T49 v49) { + return internal::ValueArray49(v1, v2, v3, v4, v5, v6, + v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20, v21, + v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, v34, v35, + v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, v48, v49); +} + +template +internal::ValueArray50 Values(T1 v1, T2 v2, T3 v3, T4 v4, + T5 v5, T6 v6, T7 v7, T8 v8, T9 v9, T10 v10, T11 v11, T12 v12, T13 v13, + T14 v14, T15 v15, T16 v16, T17 v17, T18 v18, T19 v19, T20 v20, T21 v21, + T22 v22, T23 v23, T24 v24, T25 v25, T26 v26, T27 v27, T28 v28, T29 v29, + T30 v30, T31 v31, T32 v32, T33 v33, T34 v34, T35 v35, T36 v36, T37 v37, + T38 v38, T39 v39, T40 v40, T41 v41, T42 v42, T43 v43, T44 v44, T45 v45, + T46 v46, T47 v47, T48 v48, T49 v49, T50 v50) { + return internal::ValueArray50(v1, v2, v3, v4, + v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, + v20, v21, v22, v23, v24, v25, v26, v27, v28, v29, v30, v31, v32, v33, + v34, v35, v36, v37, v38, v39, v40, v41, v42, v43, v44, v45, v46, v47, + v48, v49, v50); +} + +// Bool() allows generating tests with parameters in a set of (false, true). +// +// Synopsis: +// Bool() +// - returns a generator producing sequences with elements {false, true}. +// +// It is useful when testing code that depends on Boolean flags. Combinations +// of multiple flags can be tested when several Bool()'s are combined using +// Combine() function. +// +// In the following example all tests in the test case FlagDependentTest +// will be instantiated twice with parameters false and true. +// +// class FlagDependentTest : public testing::TestWithParam { +// virtual void SetUp() { +// external_flag = GetParam(); +// } +// } +// INSTANTIATE_TEST_CASE_P(BoolSequence, FlagDependentTest, Bool()); +// +inline internal::ParamGenerator Bool() { + return Values(false, true); +} + +# if GTEST_HAS_COMBINE +// Combine() allows the user to combine two or more sequences to produce +// values of a Cartesian product of those sequences' elements. +// +// Synopsis: +// Combine(gen1, gen2, ..., genN) +// - returns a generator producing sequences with elements coming from +// the Cartesian product of elements from the sequences generated by +// gen1, gen2, ..., genN. The sequence elements will have a type of +// tuple where T1, T2, ..., TN are the types +// of elements from sequences produces by gen1, gen2, ..., genN. +// +// Combine can have up to 10 arguments. This number is currently limited +// by the maximum number of elements in the tuple implementation used by Google +// Test. +// +// Example: +// +// This will instantiate tests in test case AnimalTest each one with +// the parameter values tuple("cat", BLACK), tuple("cat", WHITE), +// tuple("dog", BLACK), and tuple("dog", WHITE): +// +// enum Color { BLACK, GRAY, WHITE }; +// class AnimalTest +// : public testing::TestWithParam > {...}; +// +// TEST_P(AnimalTest, AnimalLooksNice) {...} +// +// INSTANTIATE_TEST_CASE_P(AnimalVariations, AnimalTest, +// Combine(Values("cat", "dog"), +// Values(BLACK, WHITE))); +// +// This will instantiate tests in FlagDependentTest with all variations of two +// Boolean flags: +// +// class FlagDependentTest +// : public testing::TestWithParam > { +// virtual void SetUp() { +// // Assigns external_flag_1 and external_flag_2 values from the tuple. +// tie(external_flag_1, external_flag_2) = GetParam(); +// } +// }; +// +// TEST_P(FlagDependentTest, TestFeature1) { +// // Test your code using external_flag_1 and external_flag_2 here. +// } +// INSTANTIATE_TEST_CASE_P(TwoBoolSequence, FlagDependentTest, +// Combine(Bool(), Bool())); +// +template +internal::CartesianProductHolder2 Combine( + const Generator1& g1, const Generator2& g2) { + return internal::CartesianProductHolder2( + g1, g2); +} + +template +internal::CartesianProductHolder3 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3) { + return internal::CartesianProductHolder3( + g1, g2, g3); +} + +template +internal::CartesianProductHolder4 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4) { + return internal::CartesianProductHolder4( + g1, g2, g3, g4); +} + +template +internal::CartesianProductHolder5 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5) { + return internal::CartesianProductHolder5( + g1, g2, g3, g4, g5); +} + +template +internal::CartesianProductHolder6 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5, const Generator6& g6) { + return internal::CartesianProductHolder6( + g1, g2, g3, g4, g5, g6); +} + +template +internal::CartesianProductHolder7 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5, const Generator6& g6, + const Generator7& g7) { + return internal::CartesianProductHolder7( + g1, g2, g3, g4, g5, g6, g7); +} + +template +internal::CartesianProductHolder8 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5, const Generator6& g6, + const Generator7& g7, const Generator8& g8) { + return internal::CartesianProductHolder8( + g1, g2, g3, g4, g5, g6, g7, g8); +} + +template +internal::CartesianProductHolder9 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5, const Generator6& g6, + const Generator7& g7, const Generator8& g8, const Generator9& g9) { + return internal::CartesianProductHolder9( + g1, g2, g3, g4, g5, g6, g7, g8, g9); +} + +template +internal::CartesianProductHolder10 Combine( + const Generator1& g1, const Generator2& g2, const Generator3& g3, + const Generator4& g4, const Generator5& g5, const Generator6& g6, + const Generator7& g7, const Generator8& g8, const Generator9& g9, + const Generator10& g10) { + return internal::CartesianProductHolder10( + g1, g2, g3, g4, g5, g6, g7, g8, g9, g10); +} +# endif // GTEST_HAS_COMBINE + + + +# define TEST_P(test_case_name, test_name) \ + class GTEST_TEST_CLASS_NAME_(test_case_name, test_name) \ + : public test_case_name { \ + public: \ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)() {} \ + virtual void TestBody(); \ + private: \ + static int AddToRegistry() { \ + ::testing::UnitTest::GetInstance()->parameterized_test_registry(). \ + GetTestCasePatternHolder(\ + #test_case_name, __FILE__, __LINE__)->AddTestPattern(\ + #test_case_name, \ + #test_name, \ + new ::testing::internal::TestMetaFactory< \ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)>()); \ + return 0; \ + } \ + static int gtest_registering_dummy_; \ + GTEST_DISALLOW_COPY_AND_ASSIGN_(\ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)); \ + }; \ + int GTEST_TEST_CLASS_NAME_(test_case_name, \ + test_name)::gtest_registering_dummy_ = \ + GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::AddToRegistry(); \ + void GTEST_TEST_CLASS_NAME_(test_case_name, test_name)::TestBody() + +# define INSTANTIATE_TEST_CASE_P(prefix, test_case_name, generator) \ + ::testing::internal::ParamGenerator \ + gtest_##prefix##test_case_name##_EvalGenerator_() { return generator; } \ + int gtest_##prefix##test_case_name##_dummy_ = \ + ::testing::UnitTest::GetInstance()->parameterized_test_registry(). \ + GetTestCasePatternHolder(\ + #test_case_name, __FILE__, __LINE__)->AddTestCaseInstantiation(\ + #prefix, \ + >est_##prefix##test_case_name##_EvalGenerator_, \ + __FILE__, __LINE__) + +} // namespace testing + +#endif // GTEST_HAS_PARAM_TEST + +#endif // GTEST_INCLUDE_GTEST_GTEST_PARAM_TEST_H_ +// Copyright 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) +// +// Google C++ Testing Framework definitions useful in production code. + +#ifndef GTEST_INCLUDE_GTEST_GTEST_PROD_H_ +#define GTEST_INCLUDE_GTEST_GTEST_PROD_H_ + +// When you need to test the private or protected members of a class, +// use the FRIEND_TEST macro to declare your tests as friends of the +// class. For example: +// +// class MyClass { +// private: +// void MyMethod(); +// FRIEND_TEST(MyClassTest, MyMethod); +// }; +// +// class MyClassTest : public testing::Test { +// // ... +// }; +// +// TEST_F(MyClassTest, MyMethod) { +// // Can call MyClass::MyMethod() here. +// } + +#define FRIEND_TEST(test_case_name, test_name)\ +friend class test_case_name##_##test_name##_Test + +#endif // GTEST_INCLUDE_GTEST_GTEST_PROD_H_ +// Copyright 2008, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: mheule@google.com (Markus Heule) +// + +#ifndef GTEST_INCLUDE_GTEST_GTEST_TEST_PART_H_ +#define GTEST_INCLUDE_GTEST_GTEST_TEST_PART_H_ + +#include +#include + +namespace testing { + +// A copyable object representing the result of a test part (i.e. an +// assertion or an explicit FAIL(), ADD_FAILURE(), or SUCCESS()). +// +// Don't inherit from TestPartResult as its destructor is not virtual. +class GTEST_API_ TestPartResult { + public: + // The possible outcomes of a test part (i.e. an assertion or an + // explicit SUCCEED(), FAIL(), or ADD_FAILURE()). + enum Type { + kSuccess, // Succeeded. + kNonFatalFailure, // Failed but the test can continue. + kFatalFailure // Failed and the test should be terminated. + }; + + // C'tor. TestPartResult does NOT have a default constructor. + // Always use this constructor (with parameters) to create a + // TestPartResult object. + TestPartResult(Type a_type, + const char* a_file_name, + int a_line_number, + const char* a_message) + : type_(a_type), + file_name_(a_file_name), + line_number_(a_line_number), + summary_(ExtractSummary(a_message)), + message_(a_message) { + } + + // Gets the outcome of the test part. + Type type() const { return type_; } + + // Gets the name of the source file where the test part took place, or + // NULL if it's unknown. + const char* file_name() const { return file_name_.c_str(); } + + // Gets the line in the source file where the test part took place, + // or -1 if it's unknown. + int line_number() const { return line_number_; } + + // Gets the summary of the failure message. + const char* summary() const { return summary_.c_str(); } + + // Gets the message associated with the test part. + const char* message() const { return message_.c_str(); } + + // Returns true iff the test part passed. + bool passed() const { return type_ == kSuccess; } + + // Returns true iff the test part failed. + bool failed() const { return type_ != kSuccess; } + + // Returns true iff the test part non-fatally failed. + bool nonfatally_failed() const { return type_ == kNonFatalFailure; } + + // Returns true iff the test part fatally failed. + bool fatally_failed() const { return type_ == kFatalFailure; } + private: + Type type_; + + // Gets the summary of the failure message by omitting the stack + // trace in it. + static internal::String ExtractSummary(const char* message); + + // The name of the source file where the test part took place, or + // NULL if the source file is unknown. + internal::String file_name_; + // The line in the source file where the test part took place, or -1 + // if the line number is unknown. + int line_number_; + internal::String summary_; // The test failure summary. + internal::String message_; // The test failure message. +}; + +// Prints a TestPartResult object. +std::ostream& operator<<(std::ostream& os, const TestPartResult& result); + +// An array of TestPartResult objects. +// +// Don't inherit from TestPartResultArray as its destructor is not +// virtual. +class GTEST_API_ TestPartResultArray { + public: + TestPartResultArray() {} + + // Appends the given TestPartResult to the array. + void Append(const TestPartResult& result); + + // Returns the TestPartResult at the given index (0-based). + const TestPartResult& GetTestPartResult(int index) const; + + // Returns the number of TestPartResult objects in the array. + int size() const; + + private: + std::vector array_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestPartResultArray); +}; + +// This interface knows how to report a test part result. +class TestPartResultReporterInterface { + public: + virtual ~TestPartResultReporterInterface() {} + + virtual void ReportTestPartResult(const TestPartResult& result) = 0; +}; + +namespace internal { + +// This helper class is used by {ASSERT|EXPECT}_NO_FATAL_FAILURE to check if a +// statement generates new fatal failures. To do so it registers itself as the +// current test part result reporter. Besides checking if fatal failures were +// reported, it only delegates the reporting to the former result reporter. +// The original result reporter is restored in the destructor. +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +class GTEST_API_ HasNewFatalFailureHelper + : public TestPartResultReporterInterface { + public: + HasNewFatalFailureHelper(); + virtual ~HasNewFatalFailureHelper(); + virtual void ReportTestPartResult(const TestPartResult& result); + bool has_new_fatal_failure() const { return has_new_fatal_failure_; } + private: + bool has_new_fatal_failure_; + TestPartResultReporterInterface* original_reporter_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(HasNewFatalFailureHelper); +}; + +} // namespace internal + +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_GTEST_TEST_PART_H_ +// Copyright 2008 Google Inc. +// All Rights Reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// Author: wan@google.com (Zhanyong Wan) + +#ifndef GTEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_ +#define GTEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_ + +// This header implements typed tests and type-parameterized tests. + +// Typed (aka type-driven) tests repeat the same test for types in a +// list. You must know which types you want to test with when writing +// typed tests. Here's how you do it: + +#if 0 + +// First, define a fixture class template. It should be parameterized +// by a type. Remember to derive it from testing::Test. +template +class FooTest : public testing::Test { + public: + ... + typedef std::list List; + static T shared_; + T value_; +}; + +// Next, associate a list of types with the test case, which will be +// repeated for each type in the list. The typedef is necessary for +// the macro to parse correctly. +typedef testing::Types MyTypes; +TYPED_TEST_CASE(FooTest, MyTypes); + +// If the type list contains only one type, you can write that type +// directly without Types<...>: +// TYPED_TEST_CASE(FooTest, int); + +// Then, use TYPED_TEST() instead of TEST_F() to define as many typed +// tests for this test case as you want. +TYPED_TEST(FooTest, DoesBlah) { + // Inside a test, refer to TypeParam to get the type parameter. + // Since we are inside a derived class template, C++ requires use to + // visit the members of FooTest via 'this'. + TypeParam n = this->value_; + + // To visit static members of the fixture, add the TestFixture:: + // prefix. + n += TestFixture::shared_; + + // To refer to typedefs in the fixture, add the "typename + // TestFixture::" prefix. + typename TestFixture::List values; + values.push_back(n); + ... +} + +TYPED_TEST(FooTest, HasPropertyA) { ... } + +#endif // 0 + +// Type-parameterized tests are abstract test patterns parameterized +// by a type. Compared with typed tests, type-parameterized tests +// allow you to define the test pattern without knowing what the type +// parameters are. The defined pattern can be instantiated with +// different types any number of times, in any number of translation +// units. +// +// If you are designing an interface or concept, you can define a +// suite of type-parameterized tests to verify properties that any +// valid implementation of the interface/concept should have. Then, +// each implementation can easily instantiate the test suite to verify +// that it conforms to the requirements, without having to write +// similar tests repeatedly. Here's an example: + +#if 0 + +// First, define a fixture class template. It should be parameterized +// by a type. Remember to derive it from testing::Test. +template +class FooTest : public testing::Test { + ... +}; + +// Next, declare that you will define a type-parameterized test case +// (the _P suffix is for "parameterized" or "pattern", whichever you +// prefer): +TYPED_TEST_CASE_P(FooTest); + +// Then, use TYPED_TEST_P() to define as many type-parameterized tests +// for this type-parameterized test case as you want. +TYPED_TEST_P(FooTest, DoesBlah) { + // Inside a test, refer to TypeParam to get the type parameter. + TypeParam n = 0; + ... +} + +TYPED_TEST_P(FooTest, HasPropertyA) { ... } + +// Now the tricky part: you need to register all test patterns before +// you can instantiate them. The first argument of the macro is the +// test case name; the rest are the names of the tests in this test +// case. +REGISTER_TYPED_TEST_CASE_P(FooTest, + DoesBlah, HasPropertyA); + +// Finally, you are free to instantiate the pattern with the types you +// want. If you put the above code in a header file, you can #include +// it in multiple C++ source files and instantiate it multiple times. +// +// To distinguish different instances of the pattern, the first +// argument to the INSTANTIATE_* macro is a prefix that will be added +// to the actual test case name. Remember to pick unique prefixes for +// different instances. +typedef testing::Types MyTypes; +INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, MyTypes); + +// If the type list contains only one type, you can write that type +// directly without Types<...>: +// INSTANTIATE_TYPED_TEST_CASE_P(My, FooTest, int); + +#endif // 0 + + +// Implements typed tests. + +#if GTEST_HAS_TYPED_TEST + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Expands to the name of the typedef for the type parameters of the +// given test case. +# define GTEST_TYPE_PARAMS_(TestCaseName) gtest_type_params_##TestCaseName##_ + +// The 'Types' template argument below must have spaces around it +// since some compilers may choke on '>>' when passing a template +// instance (e.g. Types) +# define TYPED_TEST_CASE(CaseName, Types) \ + typedef ::testing::internal::TypeList< Types >::type \ + GTEST_TYPE_PARAMS_(CaseName) + +# define TYPED_TEST(CaseName, TestName) \ + template \ + class GTEST_TEST_CLASS_NAME_(CaseName, TestName) \ + : public CaseName { \ + private: \ + typedef CaseName TestFixture; \ + typedef gtest_TypeParam_ TypeParam; \ + virtual void TestBody(); \ + }; \ + bool gtest_##CaseName##_##TestName##_registered_ GTEST_ATTRIBUTE_UNUSED_ = \ + ::testing::internal::TypeParameterizedTest< \ + CaseName, \ + ::testing::internal::TemplateSel< \ + GTEST_TEST_CLASS_NAME_(CaseName, TestName)>, \ + GTEST_TYPE_PARAMS_(CaseName)>::Register(\ + "", #CaseName, #TestName, 0); \ + template \ + void GTEST_TEST_CLASS_NAME_(CaseName, TestName)::TestBody() + +#endif // GTEST_HAS_TYPED_TEST + +// Implements type-parameterized tests. + +#if GTEST_HAS_TYPED_TEST_P + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Expands to the namespace name that the type-parameterized tests for +// the given type-parameterized test case are defined in. The exact +// name of the namespace is subject to change without notice. +# define GTEST_CASE_NAMESPACE_(TestCaseName) \ + gtest_case_##TestCaseName##_ + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// +// Expands to the name of the variable used to remember the names of +// the defined tests in the given test case. +# define GTEST_TYPED_TEST_CASE_P_STATE_(TestCaseName) \ + gtest_typed_test_case_p_state_##TestCaseName##_ + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE DIRECTLY. +// +// Expands to the name of the variable used to remember the names of +// the registered tests in the given test case. +# define GTEST_REGISTERED_TEST_NAMES_(TestCaseName) \ + gtest_registered_test_names_##TestCaseName##_ + +// The variables defined in the type-parameterized test macros are +// static as typically these macros are used in a .h file that can be +// #included in multiple translation units linked together. +# define TYPED_TEST_CASE_P(CaseName) \ + static ::testing::internal::TypedTestCasePState \ + GTEST_TYPED_TEST_CASE_P_STATE_(CaseName) + +# define TYPED_TEST_P(CaseName, TestName) \ + namespace GTEST_CASE_NAMESPACE_(CaseName) { \ + template \ + class TestName : public CaseName { \ + private: \ + typedef CaseName TestFixture; \ + typedef gtest_TypeParam_ TypeParam; \ + virtual void TestBody(); \ + }; \ + static bool gtest_##TestName##_defined_ GTEST_ATTRIBUTE_UNUSED_ = \ + GTEST_TYPED_TEST_CASE_P_STATE_(CaseName).AddTestName(\ + __FILE__, __LINE__, #CaseName, #TestName); \ + } \ + template \ + void GTEST_CASE_NAMESPACE_(CaseName)::TestName::TestBody() + +# define REGISTER_TYPED_TEST_CASE_P(CaseName, ...) \ + namespace GTEST_CASE_NAMESPACE_(CaseName) { \ + typedef ::testing::internal::Templates<__VA_ARGS__>::type gtest_AllTests_; \ + } \ + static const char* const GTEST_REGISTERED_TEST_NAMES_(CaseName) = \ + GTEST_TYPED_TEST_CASE_P_STATE_(CaseName).VerifyRegisteredTestNames(\ + __FILE__, __LINE__, #__VA_ARGS__) + +// The 'Types' template argument below must have spaces around it +// since some compilers may choke on '>>' when passing a template +// instance (e.g. Types) +# define INSTANTIATE_TYPED_TEST_CASE_P(Prefix, CaseName, Types) \ + bool gtest_##Prefix##_##CaseName GTEST_ATTRIBUTE_UNUSED_ = \ + ::testing::internal::TypeParameterizedTestCase::type>::Register(\ + #Prefix, #CaseName, GTEST_REGISTERED_TEST_NAMES_(CaseName)) + +#endif // GTEST_HAS_TYPED_TEST_P + +#endif // GTEST_INCLUDE_GTEST_GTEST_TYPED_TEST_H_ + +// Depending on the platform, different string classes are available. +// On Linux, in addition to ::std::string, Google also makes use of +// class ::string, which has the same interface as ::std::string, but +// has a different implementation. +// +// The user can define GTEST_HAS_GLOBAL_STRING to 1 to indicate that +// ::string is available AND is a distinct type to ::std::string, or +// define it to 0 to indicate otherwise. +// +// If the user's ::std::string and ::string are the same class due to +// aliasing, he should define GTEST_HAS_GLOBAL_STRING to 0. +// +// If the user doesn't define GTEST_HAS_GLOBAL_STRING, it is defined +// heuristically. + +namespace testing { + +// Declares the flags. + +// This flag temporary enables the disabled tests. +GTEST_DECLARE_bool_(also_run_disabled_tests); + +// This flag brings the debugger on an assertion failure. +GTEST_DECLARE_bool_(break_on_failure); + +// This flag controls whether Google Test catches all test-thrown exceptions +// and logs them as failures. +GTEST_DECLARE_bool_(catch_exceptions); + +// This flag enables using colors in terminal output. Available values are +// "yes" to enable colors, "no" (disable colors), or "auto" (the default) +// to let Google Test decide. +GTEST_DECLARE_string_(color); + +// This flag sets up the filter to select by name using a glob pattern +// the tests to run. If the filter is not given all tests are executed. +GTEST_DECLARE_string_(filter); + +// This flag causes the Google Test to list tests. None of the tests listed +// are actually run if the flag is provided. +GTEST_DECLARE_bool_(list_tests); + +// This flag controls whether Google Test emits a detailed XML report to a file +// in addition to its normal textual output. +GTEST_DECLARE_string_(output); + +// This flags control whether Google Test prints the elapsed time for each +// test. +GTEST_DECLARE_bool_(print_time); + +// This flag specifies the random number seed. +GTEST_DECLARE_int32_(random_seed); + +// This flag sets how many times the tests are repeated. The default value +// is 1. If the value is -1 the tests are repeating forever. +GTEST_DECLARE_int32_(repeat); + +// This flag controls whether Google Test includes Google Test internal +// stack frames in failure stack traces. +GTEST_DECLARE_bool_(show_internal_stack_frames); + +// When this flag is specified, tests' order is randomized on every iteration. +GTEST_DECLARE_bool_(shuffle); + +// This flag specifies the maximum number of stack frames to be +// printed in a failure message. +GTEST_DECLARE_int32_(stack_trace_depth); + +// When this flag is specified, a failed assertion will throw an +// exception if exceptions are enabled, or exit the program with a +// non-zero code otherwise. +GTEST_DECLARE_bool_(throw_on_failure); + +// When this flag is set with a "host:port" string, on supported +// platforms test results are streamed to the specified port on +// the specified host machine. +GTEST_DECLARE_string_(stream_result_to); + +// The upper limit for valid stack trace depths. +const int kMaxStackTraceDepth = 100; + +namespace internal { + +class AssertHelper; +class DefaultGlobalTestPartResultReporter; +class ExecDeathTest; +class NoExecDeathTest; +class FinalSuccessChecker; +class GTestFlagSaver; +class TestResultAccessor; +class TestEventListenersAccessor; +class TestEventRepeater; +class WindowsDeathTest; +class UnitTestImpl* GetUnitTestImpl(); +void ReportFailureInUnknownLocation(TestPartResult::Type result_type, + const String& message); + +// Converts a streamable value to a String. A NULL pointer is +// converted to "(null)". When the input value is a ::string, +// ::std::string, ::wstring, or ::std::wstring object, each NUL +// character in it is replaced with "\\0". +// Declared in gtest-internal.h but defined here, so that it has access +// to the definition of the Message class, required by the ARM +// compiler. +template +String StreamableToString(const T& streamable) { + return (Message() << streamable).GetString(); +} + +} // namespace internal + +// The friend relationship of some of these classes is cyclic. +// If we don't forward declare them the compiler might confuse the classes +// in friendship clauses with same named classes on the scope. +class Test; +class TestCase; +class TestInfo; +class UnitTest; + +// A class for indicating whether an assertion was successful. When +// the assertion wasn't successful, the AssertionResult object +// remembers a non-empty message that describes how it failed. +// +// To create an instance of this class, use one of the factory functions +// (AssertionSuccess() and AssertionFailure()). +// +// This class is useful for two purposes: +// 1. Defining predicate functions to be used with Boolean test assertions +// EXPECT_TRUE/EXPECT_FALSE and their ASSERT_ counterparts +// 2. Defining predicate-format functions to be +// used with predicate assertions (ASSERT_PRED_FORMAT*, etc). +// +// For example, if you define IsEven predicate: +// +// testing::AssertionResult IsEven(int n) { +// if ((n % 2) == 0) +// return testing::AssertionSuccess(); +// else +// return testing::AssertionFailure() << n << " is odd"; +// } +// +// Then the failed expectation EXPECT_TRUE(IsEven(Fib(5))) +// will print the message +// +// Value of: IsEven(Fib(5)) +// Actual: false (5 is odd) +// Expected: true +// +// instead of a more opaque +// +// Value of: IsEven(Fib(5)) +// Actual: false +// Expected: true +// +// in case IsEven is a simple Boolean predicate. +// +// If you expect your predicate to be reused and want to support informative +// messages in EXPECT_FALSE and ASSERT_FALSE (negative assertions show up +// about half as often as positive ones in our tests), supply messages for +// both success and failure cases: +// +// testing::AssertionResult IsEven(int n) { +// if ((n % 2) == 0) +// return testing::AssertionSuccess() << n << " is even"; +// else +// return testing::AssertionFailure() << n << " is odd"; +// } +// +// Then a statement EXPECT_FALSE(IsEven(Fib(6))) will print +// +// Value of: IsEven(Fib(6)) +// Actual: true (8 is even) +// Expected: false +// +// NB: Predicates that support negative Boolean assertions have reduced +// performance in positive ones so be careful not to use them in tests +// that have lots (tens of thousands) of positive Boolean assertions. +// +// To use this class with EXPECT_PRED_FORMAT assertions such as: +// +// // Verifies that Foo() returns an even number. +// EXPECT_PRED_FORMAT1(IsEven, Foo()); +// +// you need to define: +// +// testing::AssertionResult IsEven(const char* expr, int n) { +// if ((n % 2) == 0) +// return testing::AssertionSuccess(); +// else +// return testing::AssertionFailure() +// << "Expected: " << expr << " is even\n Actual: it's " << n; +// } +// +// If Foo() returns 5, you will see the following message: +// +// Expected: Foo() is even +// Actual: it's 5 +// +class GTEST_API_ AssertionResult { + public: + // Copy constructor. + // Used in EXPECT_TRUE/FALSE(assertion_result). + AssertionResult(const AssertionResult& other); + // Used in the EXPECT_TRUE/FALSE(bool_expression). + explicit AssertionResult(bool success) : success_(success) {} + + // Returns true iff the assertion succeeded. + operator bool() const { return success_; } // NOLINT + + // Returns the assertion's negation. Used with EXPECT/ASSERT_FALSE. + AssertionResult operator!() const; + + // Returns the text streamed into this AssertionResult. Test assertions + // use it when they fail (i.e., the predicate's outcome doesn't match the + // assertion's expectation). When nothing has been streamed into the + // object, returns an empty string. + const char* message() const { + return message_.get() != NULL ? message_->c_str() : ""; + } + // TODO(vladl@google.com): Remove this after making sure no clients use it. + // Deprecated; please use message() instead. + const char* failure_message() const { return message(); } + + // Streams a custom failure message into this object. + template AssertionResult& operator<<(const T& value) { + AppendMessage(Message() << value); + return *this; + } + + // Allows streaming basic output manipulators such as endl or flush into + // this object. + AssertionResult& operator<<( + ::std::ostream& (*basic_manipulator)(::std::ostream& stream)) { + AppendMessage(Message() << basic_manipulator); + return *this; + } + + private: + // Appends the contents of message to message_. + void AppendMessage(const Message& a_message) { + if (message_.get() == NULL) + message_.reset(new ::std::string); + message_->append(a_message.GetString().c_str()); + } + + // Stores result of the assertion predicate. + bool success_; + // Stores the message describing the condition in case the expectation + // construct is not satisfied with the predicate's outcome. + // Referenced via a pointer to avoid taking too much stack frame space + // with test assertions. + internal::scoped_ptr< ::std::string> message_; + + GTEST_DISALLOW_ASSIGN_(AssertionResult); +}; + +// Makes a successful assertion result. +GTEST_API_ AssertionResult AssertionSuccess(); + +// Makes a failed assertion result. +GTEST_API_ AssertionResult AssertionFailure(); + +// Makes a failed assertion result with the given failure message. +// Deprecated; use AssertionFailure() << msg. +GTEST_API_ AssertionResult AssertionFailure(const Message& msg); + +// The abstract class that all tests inherit from. +// +// In Google Test, a unit test program contains one or many TestCases, and +// each TestCase contains one or many Tests. +// +// When you define a test using the TEST macro, you don't need to +// explicitly derive from Test - the TEST macro automatically does +// this for you. +// +// The only time you derive from Test is when defining a test fixture +// to be used a TEST_F. For example: +// +// class FooTest : public testing::Test { +// protected: +// virtual void SetUp() { ... } +// virtual void TearDown() { ... } +// ... +// }; +// +// TEST_F(FooTest, Bar) { ... } +// TEST_F(FooTest, Baz) { ... } +// +// Test is not copyable. +class GTEST_API_ Test { + public: + friend class TestInfo; + + // Defines types for pointers to functions that set up and tear down + // a test case. + typedef internal::SetUpTestCaseFunc SetUpTestCaseFunc; + typedef internal::TearDownTestCaseFunc TearDownTestCaseFunc; + + // The d'tor is virtual as we intend to inherit from Test. + virtual ~Test(); + + // Sets up the stuff shared by all tests in this test case. + // + // Google Test will call Foo::SetUpTestCase() before running the first + // test in test case Foo. Hence a sub-class can define its own + // SetUpTestCase() method to shadow the one defined in the super + // class. + static void SetUpTestCase() {} + + // Tears down the stuff shared by all tests in this test case. + // + // Google Test will call Foo::TearDownTestCase() after running the last + // test in test case Foo. Hence a sub-class can define its own + // TearDownTestCase() method to shadow the one defined in the super + // class. + static void TearDownTestCase() {} + + // Returns true iff the current test has a fatal failure. + static bool HasFatalFailure(); + + // Returns true iff the current test has a non-fatal failure. + static bool HasNonfatalFailure(); + + // Returns true iff the current test has a (either fatal or + // non-fatal) failure. + static bool HasFailure() { return HasFatalFailure() || HasNonfatalFailure(); } + + // Logs a property for the current test. Only the last value for a given + // key is remembered. + // These are public static so they can be called from utility functions + // that are not members of the test fixture. + // The arguments are const char* instead strings, as Google Test is used + // on platforms where string doesn't compile. + // + // Note that a driving consideration for these RecordProperty methods + // was to produce xml output suited to the Greenspan charting utility, + // which at present will only chart values that fit in a 32-bit int. It + // is the user's responsibility to restrict their values to 32-bit ints + // if they intend them to be used with Greenspan. + static void RecordProperty(const char* key, const char* value); + static void RecordProperty(const char* key, int value); + + protected: + // Creates a Test object. + Test(); + + // Sets up the test fixture. + virtual void SetUp(); + + // Tears down the test fixture. + virtual void TearDown(); + + private: + // Returns true iff the current test has the same fixture class as + // the first test in the current test case. + static bool HasSameFixtureClass(); + + // Runs the test after the test fixture has been set up. + // + // A sub-class must implement this to define the test logic. + // + // DO NOT OVERRIDE THIS FUNCTION DIRECTLY IN A USER PROGRAM. + // Instead, use the TEST or TEST_F macro. + virtual void TestBody() = 0; + + // Sets up, executes, and tears down the test. + void Run(); + + // Deletes self. We deliberately pick an unusual name for this + // internal method to avoid clashing with names used in user TESTs. + void DeleteSelf_() { delete this; } + + // Uses a GTestFlagSaver to save and restore all Google Test flags. + const internal::GTestFlagSaver* const gtest_flag_saver_; + + // Often a user mis-spells SetUp() as Setup() and spends a long time + // wondering why it is never called by Google Test. The declaration of + // the following method is solely for catching such an error at + // compile time: + // + // - The return type is deliberately chosen to be not void, so it + // will be a conflict if a user declares void Setup() in his test + // fixture. + // + // - This method is private, so it will be another compiler error + // if a user calls it from his test fixture. + // + // DO NOT OVERRIDE THIS FUNCTION. + // + // If you see an error about overriding the following function or + // about it being private, you have mis-spelled SetUp() as Setup(). + struct Setup_should_be_spelled_SetUp {}; + virtual Setup_should_be_spelled_SetUp* Setup() { return NULL; } + + // We disallow copying Tests. + GTEST_DISALLOW_COPY_AND_ASSIGN_(Test); +}; + +typedef internal::TimeInMillis TimeInMillis; + +// A copyable object representing a user specified test property which can be +// output as a key/value string pair. +// +// Don't inherit from TestProperty as its destructor is not virtual. +class TestProperty { + public: + // C'tor. TestProperty does NOT have a default constructor. + // Always use this constructor (with parameters) to create a + // TestProperty object. + TestProperty(const char* a_key, const char* a_value) : + key_(a_key), value_(a_value) { + } + + // Gets the user supplied key. + const char* key() const { + return key_.c_str(); + } + + // Gets the user supplied value. + const char* value() const { + return value_.c_str(); + } + + // Sets a new value, overriding the one supplied in the constructor. + void SetValue(const char* new_value) { + value_ = new_value; + } + + private: + // The key supplied by the user. + internal::String key_; + // The value supplied by the user. + internal::String value_; +}; + +// The result of a single Test. This includes a list of +// TestPartResults, a list of TestProperties, a count of how many +// death tests there are in the Test, and how much time it took to run +// the Test. +// +// TestResult is not copyable. +class GTEST_API_ TestResult { + public: + // Creates an empty TestResult. + TestResult(); + + // D'tor. Do not inherit from TestResult. + ~TestResult(); + + // Gets the number of all test parts. This is the sum of the number + // of successful test parts and the number of failed test parts. + int total_part_count() const; + + // Returns the number of the test properties. + int test_property_count() const; + + // Returns true iff the test passed (i.e. no test part failed). + bool Passed() const { return !Failed(); } + + // Returns true iff the test failed. + bool Failed() const; + + // Returns true iff the test fatally failed. + bool HasFatalFailure() const; + + // Returns true iff the test has a non-fatal failure. + bool HasNonfatalFailure() const; + + // Returns the elapsed time, in milliseconds. + TimeInMillis elapsed_time() const { return elapsed_time_; } + + // Returns the i-th test part result among all the results. i can range + // from 0 to test_property_count() - 1. If i is not in that range, aborts + // the program. + const TestPartResult& GetTestPartResult(int i) const; + + // Returns the i-th test property. i can range from 0 to + // test_property_count() - 1. If i is not in that range, aborts the + // program. + const TestProperty& GetTestProperty(int i) const; + + private: + friend class TestInfo; + friend class UnitTest; + friend class internal::DefaultGlobalTestPartResultReporter; + friend class internal::ExecDeathTest; + friend class internal::TestResultAccessor; + friend class internal::UnitTestImpl; + friend class internal::WindowsDeathTest; + + // Gets the vector of TestPartResults. + const std::vector& test_part_results() const { + return test_part_results_; + } + + // Gets the vector of TestProperties. + const std::vector& test_properties() const { + return test_properties_; + } + + // Sets the elapsed time. + void set_elapsed_time(TimeInMillis elapsed) { elapsed_time_ = elapsed; } + + // Adds a test property to the list. The property is validated and may add + // a non-fatal failure if invalid (e.g., if it conflicts with reserved + // key names). If a property is already recorded for the same key, the + // value will be updated, rather than storing multiple values for the same + // key. + void RecordProperty(const TestProperty& test_property); + + // Adds a failure if the key is a reserved attribute of Google Test + // testcase tags. Returns true if the property is valid. + // TODO(russr): Validate attribute names are legal and human readable. + static bool ValidateTestProperty(const TestProperty& test_property); + + // Adds a test part result to the list. + void AddTestPartResult(const TestPartResult& test_part_result); + + // Returns the death test count. + int death_test_count() const { return death_test_count_; } + + // Increments the death test count, returning the new count. + int increment_death_test_count() { return ++death_test_count_; } + + // Clears the test part results. + void ClearTestPartResults(); + + // Clears the object. + void Clear(); + + // Protects mutable state of the property vector and of owned + // properties, whose values may be updated. + internal::Mutex test_properites_mutex_; + + // The vector of TestPartResults + std::vector test_part_results_; + // The vector of TestProperties + std::vector test_properties_; + // Running count of death tests. + int death_test_count_; + // The elapsed time, in milliseconds. + TimeInMillis elapsed_time_; + + // We disallow copying TestResult. + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestResult); +}; // class TestResult + +// A TestInfo object stores the following information about a test: +// +// Test case name +// Test name +// Whether the test should be run +// A function pointer that creates the test object when invoked +// Test result +// +// The constructor of TestInfo registers itself with the UnitTest +// singleton such that the RUN_ALL_TESTS() macro knows which tests to +// run. +class GTEST_API_ TestInfo { + public: + // Destructs a TestInfo object. This function is not virtual, so + // don't inherit from TestInfo. + ~TestInfo(); + + // Returns the test case name. + const char* test_case_name() const { return test_case_name_.c_str(); } + + // Returns the test name. + const char* name() const { return name_.c_str(); } + + // Returns the name of the parameter type, or NULL if this is not a typed + // or a type-parameterized test. + const char* type_param() const { + if (type_param_.get() != NULL) + return type_param_->c_str(); + return NULL; + } + + // Returns the text representation of the value parameter, or NULL if this + // is not a value-parameterized test. + const char* value_param() const { + if (value_param_.get() != NULL) + return value_param_->c_str(); + return NULL; + } + + // Returns true if this test should run, that is if the test is not disabled + // (or it is disabled but the also_run_disabled_tests flag has been specified) + // and its full name matches the user-specified filter. + // + // Google Test allows the user to filter the tests by their full names. + // The full name of a test Bar in test case Foo is defined as + // "Foo.Bar". Only the tests that match the filter will run. + // + // A filter is a colon-separated list of glob (not regex) patterns, + // optionally followed by a '-' and a colon-separated list of + // negative patterns (tests to exclude). A test is run if it + // matches one of the positive patterns and does not match any of + // the negative patterns. + // + // For example, *A*:Foo.* is a filter that matches any string that + // contains the character 'A' or starts with "Foo.". + bool should_run() const { return should_run_; } + + // Returns the result of the test. + const TestResult* result() const { return &result_; } + + private: + +#if GTEST_HAS_DEATH_TEST + friend class internal::DefaultDeathTestFactory; +#endif // GTEST_HAS_DEATH_TEST + friend class Test; + friend class TestCase; + friend class internal::UnitTestImpl; + friend TestInfo* internal::MakeAndRegisterTestInfo( + const char* test_case_name, const char* name, + const char* type_param, + const char* value_param, + internal::TypeId fixture_class_id, + Test::SetUpTestCaseFunc set_up_tc, + Test::TearDownTestCaseFunc tear_down_tc, + internal::TestFactoryBase* factory); + + // Constructs a TestInfo object. The newly constructed instance assumes + // ownership of the factory object. + TestInfo(const char* test_case_name, const char* name, + const char* a_type_param, + const char* a_value_param, + internal::TypeId fixture_class_id, + internal::TestFactoryBase* factory); + + // Increments the number of death tests encountered in this test so + // far. + int increment_death_test_count() { + return result_.increment_death_test_count(); + } + + // Creates the test object, runs it, records its result, and then + // deletes it. + void Run(); + + static void ClearTestResult(TestInfo* test_info) { + test_info->result_.Clear(); + } + + // These fields are immutable properties of the test. + const std::string test_case_name_; // Test case name + const std::string name_; // Test name + // Name of the parameter type, or NULL if this is not a typed or a + // type-parameterized test. + const internal::scoped_ptr type_param_; + // Text representation of the value parameter, or NULL if this is not a + // value-parameterized test. + const internal::scoped_ptr value_param_; + const internal::TypeId fixture_class_id_; // ID of the test fixture class + bool should_run_; // True iff this test should run + bool is_disabled_; // True iff this test is disabled + bool matches_filter_; // True if this test matches the + // user-specified filter. + internal::TestFactoryBase* const factory_; // The factory that creates + // the test object + + // This field is mutable and needs to be reset before running the + // test for the second time. + TestResult result_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestInfo); +}; + +// A test case, which consists of a vector of TestInfos. +// +// TestCase is not copyable. +class GTEST_API_ TestCase { + public: + // Creates a TestCase with the given name. + // + // TestCase does NOT have a default constructor. Always use this + // constructor to create a TestCase object. + // + // Arguments: + // + // name: name of the test case + // a_type_param: the name of the test's type parameter, or NULL if + // this is not a type-parameterized test. + // set_up_tc: pointer to the function that sets up the test case + // tear_down_tc: pointer to the function that tears down the test case + TestCase(const char* name, const char* a_type_param, + Test::SetUpTestCaseFunc set_up_tc, + Test::TearDownTestCaseFunc tear_down_tc); + + // Destructor of TestCase. + virtual ~TestCase(); + + // Gets the name of the TestCase. + const char* name() const { return name_.c_str(); } + + // Returns the name of the parameter type, or NULL if this is not a + // type-parameterized test case. + const char* type_param() const { + if (type_param_.get() != NULL) + return type_param_->c_str(); + return NULL; + } + + // Returns true if any test in this test case should run. + bool should_run() const { return should_run_; } + + // Gets the number of successful tests in this test case. + int successful_test_count() const; + + // Gets the number of failed tests in this test case. + int failed_test_count() const; + + // Gets the number of disabled tests in this test case. + int disabled_test_count() const; + + // Get the number of tests in this test case that should run. + int test_to_run_count() const; + + // Gets the number of all tests in this test case. + int total_test_count() const; + + // Returns true iff the test case passed. + bool Passed() const { return !Failed(); } + + // Returns true iff the test case failed. + bool Failed() const { return failed_test_count() > 0; } + + // Returns the elapsed time, in milliseconds. + TimeInMillis elapsed_time() const { return elapsed_time_; } + + // Returns the i-th test among all the tests. i can range from 0 to + // total_test_count() - 1. If i is not in that range, returns NULL. + const TestInfo* GetTestInfo(int i) const; + + private: + friend class Test; + friend class internal::UnitTestImpl; + + // Gets the (mutable) vector of TestInfos in this TestCase. + std::vector& test_info_list() { return test_info_list_; } + + // Gets the (immutable) vector of TestInfos in this TestCase. + const std::vector& test_info_list() const { + return test_info_list_; + } + + // Returns the i-th test among all the tests. i can range from 0 to + // total_test_count() - 1. If i is not in that range, returns NULL. + TestInfo* GetMutableTestInfo(int i); + + // Sets the should_run member. + void set_should_run(bool should) { should_run_ = should; } + + // Adds a TestInfo to this test case. Will delete the TestInfo upon + // destruction of the TestCase object. + void AddTestInfo(TestInfo * test_info); + + // Clears the results of all tests in this test case. + void ClearResult(); + + // Clears the results of all tests in the given test case. + static void ClearTestCaseResult(TestCase* test_case) { + test_case->ClearResult(); + } + + // Runs every test in this TestCase. + void Run(); + + // Runs SetUpTestCase() for this TestCase. This wrapper is needed + // for catching exceptions thrown from SetUpTestCase(). + void RunSetUpTestCase() { (*set_up_tc_)(); } + + // Runs TearDownTestCase() for this TestCase. This wrapper is + // needed for catching exceptions thrown from TearDownTestCase(). + void RunTearDownTestCase() { (*tear_down_tc_)(); } + + // Returns true iff test passed. + static bool TestPassed(const TestInfo* test_info) { + return test_info->should_run() && test_info->result()->Passed(); + } + + // Returns true iff test failed. + static bool TestFailed(const TestInfo* test_info) { + return test_info->should_run() && test_info->result()->Failed(); + } + + // Returns true iff test is disabled. + static bool TestDisabled(const TestInfo* test_info) { + return test_info->is_disabled_; + } + + // Returns true if the given test should run. + static bool ShouldRunTest(const TestInfo* test_info) { + return test_info->should_run(); + } + + // Shuffles the tests in this test case. + void ShuffleTests(internal::Random* random); + + // Restores the test order to before the first shuffle. + void UnshuffleTests(); + + // Name of the test case. + internal::String name_; + // Name of the parameter type, or NULL if this is not a typed or a + // type-parameterized test. + const internal::scoped_ptr type_param_; + // The vector of TestInfos in their original order. It owns the + // elements in the vector. + std::vector test_info_list_; + // Provides a level of indirection for the test list to allow easy + // shuffling and restoring the test order. The i-th element in this + // vector is the index of the i-th test in the shuffled test list. + std::vector test_indices_; + // Pointer to the function that sets up the test case. + Test::SetUpTestCaseFunc set_up_tc_; + // Pointer to the function that tears down the test case. + Test::TearDownTestCaseFunc tear_down_tc_; + // True iff any test in this test case should run. + bool should_run_; + // Elapsed time, in milliseconds. + TimeInMillis elapsed_time_; + + // We disallow copying TestCases. + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestCase); +}; + +// An Environment object is capable of setting up and tearing down an +// environment. The user should subclass this to define his own +// environment(s). +// +// An Environment object does the set-up and tear-down in virtual +// methods SetUp() and TearDown() instead of the constructor and the +// destructor, as: +// +// 1. You cannot safely throw from a destructor. This is a problem +// as in some cases Google Test is used where exceptions are enabled, and +// we may want to implement ASSERT_* using exceptions where they are +// available. +// 2. You cannot use ASSERT_* directly in a constructor or +// destructor. +class Environment { + public: + // The d'tor is virtual as we need to subclass Environment. + virtual ~Environment() {} + + // Override this to define how to set up the environment. + virtual void SetUp() {} + + // Override this to define how to tear down the environment. + virtual void TearDown() {} + private: + // If you see an error about overriding the following function or + // about it being private, you have mis-spelled SetUp() as Setup(). + struct Setup_should_be_spelled_SetUp {}; + virtual Setup_should_be_spelled_SetUp* Setup() { return NULL; } +}; + +// The interface for tracing execution of tests. The methods are organized in +// the order the corresponding events are fired. +class TestEventListener { + public: + virtual ~TestEventListener() {} + + // Fired before any test activity starts. + virtual void OnTestProgramStart(const UnitTest& unit_test) = 0; + + // Fired before each iteration of tests starts. There may be more than + // one iteration if GTEST_FLAG(repeat) is set. iteration is the iteration + // index, starting from 0. + virtual void OnTestIterationStart(const UnitTest& unit_test, + int iteration) = 0; + + // Fired before environment set-up for each iteration of tests starts. + virtual void OnEnvironmentsSetUpStart(const UnitTest& unit_test) = 0; + + // Fired after environment set-up for each iteration of tests ends. + virtual void OnEnvironmentsSetUpEnd(const UnitTest& unit_test) = 0; + + // Fired before the test case starts. + virtual void OnTestCaseStart(const TestCase& test_case) = 0; + + // Fired before the test starts. + virtual void OnTestStart(const TestInfo& test_info) = 0; + + // Fired after a failed assertion or a SUCCEED() invocation. + virtual void OnTestPartResult(const TestPartResult& test_part_result) = 0; + + // Fired after the test ends. + virtual void OnTestEnd(const TestInfo& test_info) = 0; + + // Fired after the test case ends. + virtual void OnTestCaseEnd(const TestCase& test_case) = 0; + + // Fired before environment tear-down for each iteration of tests starts. + virtual void OnEnvironmentsTearDownStart(const UnitTest& unit_test) = 0; + + // Fired after environment tear-down for each iteration of tests ends. + virtual void OnEnvironmentsTearDownEnd(const UnitTest& unit_test) = 0; + + // Fired after each iteration of tests finishes. + virtual void OnTestIterationEnd(const UnitTest& unit_test, + int iteration) = 0; + + // Fired after all test activities have ended. + virtual void OnTestProgramEnd(const UnitTest& unit_test) = 0; +}; + +// The convenience class for users who need to override just one or two +// methods and are not concerned that a possible change to a signature of +// the methods they override will not be caught during the build. For +// comments about each method please see the definition of TestEventListener +// above. +class EmptyTestEventListener : public TestEventListener { + public: + virtual void OnTestProgramStart(const UnitTest& /*unit_test*/) {} + virtual void OnTestIterationStart(const UnitTest& /*unit_test*/, + int /*iteration*/) {} + virtual void OnEnvironmentsSetUpStart(const UnitTest& /*unit_test*/) {} + virtual void OnEnvironmentsSetUpEnd(const UnitTest& /*unit_test*/) {} + virtual void OnTestCaseStart(const TestCase& /*test_case*/) {} + virtual void OnTestStart(const TestInfo& /*test_info*/) {} + virtual void OnTestPartResult(const TestPartResult& /*test_part_result*/) {} + virtual void OnTestEnd(const TestInfo& /*test_info*/) {} + virtual void OnTestCaseEnd(const TestCase& /*test_case*/) {} + virtual void OnEnvironmentsTearDownStart(const UnitTest& /*unit_test*/) {} + virtual void OnEnvironmentsTearDownEnd(const UnitTest& /*unit_test*/) {} + virtual void OnTestIterationEnd(const UnitTest& /*unit_test*/, + int /*iteration*/) {} + virtual void OnTestProgramEnd(const UnitTest& /*unit_test*/) {} +}; + +// TestEventListeners lets users add listeners to track events in Google Test. +class GTEST_API_ TestEventListeners { + public: + TestEventListeners(); + ~TestEventListeners(); + + // Appends an event listener to the end of the list. Google Test assumes + // the ownership of the listener (i.e. it will delete the listener when + // the test program finishes). + void Append(TestEventListener* listener); + + // Removes the given event listener from the list and returns it. It then + // becomes the caller's responsibility to delete the listener. Returns + // NULL if the listener is not found in the list. + TestEventListener* Release(TestEventListener* listener); + + // Returns the standard listener responsible for the default console + // output. Can be removed from the listeners list to shut down default + // console output. Note that removing this object from the listener list + // with Release transfers its ownership to the caller and makes this + // function return NULL the next time. + TestEventListener* default_result_printer() const { + return default_result_printer_; + } + + // Returns the standard listener responsible for the default XML output + // controlled by the --gtest_output=xml flag. Can be removed from the + // listeners list by users who want to shut down the default XML output + // controlled by this flag and substitute it with custom one. Note that + // removing this object from the listener list with Release transfers its + // ownership to the caller and makes this function return NULL the next + // time. + TestEventListener* default_xml_generator() const { + return default_xml_generator_; + } + + private: + friend class TestCase; + friend class TestInfo; + friend class internal::DefaultGlobalTestPartResultReporter; + friend class internal::NoExecDeathTest; + friend class internal::TestEventListenersAccessor; + friend class internal::UnitTestImpl; + + // Returns repeater that broadcasts the TestEventListener events to all + // subscribers. + TestEventListener* repeater(); + + // Sets the default_result_printer attribute to the provided listener. + // The listener is also added to the listener list and previous + // default_result_printer is removed from it and deleted. The listener can + // also be NULL in which case it will not be added to the list. Does + // nothing if the previous and the current listener objects are the same. + void SetDefaultResultPrinter(TestEventListener* listener); + + // Sets the default_xml_generator attribute to the provided listener. The + // listener is also added to the listener list and previous + // default_xml_generator is removed from it and deleted. The listener can + // also be NULL in which case it will not be added to the list. Does + // nothing if the previous and the current listener objects are the same. + void SetDefaultXmlGenerator(TestEventListener* listener); + + // Controls whether events will be forwarded by the repeater to the + // listeners in the list. + bool EventForwardingEnabled() const; + void SuppressEventForwarding(); + + // The actual list of listeners. + internal::TestEventRepeater* repeater_; + // Listener responsible for the standard result output. + TestEventListener* default_result_printer_; + // Listener responsible for the creation of the XML output file. + TestEventListener* default_xml_generator_; + + // We disallow copying TestEventListeners. + GTEST_DISALLOW_COPY_AND_ASSIGN_(TestEventListeners); +}; + +// A UnitTest consists of a vector of TestCases. +// +// This is a singleton class. The only instance of UnitTest is +// created when UnitTest::GetInstance() is first called. This +// instance is never deleted. +// +// UnitTest is not copyable. +// +// This class is thread-safe as long as the methods are called +// according to their specification. +class GTEST_API_ UnitTest { + public: + // Gets the singleton UnitTest object. The first time this method + // is called, a UnitTest object is constructed and returned. + // Consecutive calls will return the same object. + static UnitTest* GetInstance(); + + // Runs all tests in this UnitTest object and prints the result. + // Returns 0 if successful, or 1 otherwise. + // + // This method can only be called from the main thread. + // + // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + int Run() GTEST_MUST_USE_RESULT_; + + // Returns the working directory when the first TEST() or TEST_F() + // was executed. The UnitTest object owns the string. + const char* original_working_dir() const; + + // Returns the TestCase object for the test that's currently running, + // or NULL if no test is running. + const TestCase* current_test_case() const; + + // Returns the TestInfo object for the test that's currently running, + // or NULL if no test is running. + const TestInfo* current_test_info() const; + + // Returns the random seed used at the start of the current test run. + int random_seed() const; + +#if GTEST_HAS_PARAM_TEST + // Returns the ParameterizedTestCaseRegistry object used to keep track of + // value-parameterized tests and instantiate and register them. + // + // INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + internal::ParameterizedTestCaseRegistry& parameterized_test_registry(); +#endif // GTEST_HAS_PARAM_TEST + + // Gets the number of successful test cases. + int successful_test_case_count() const; + + // Gets the number of failed test cases. + int failed_test_case_count() const; + + // Gets the number of all test cases. + int total_test_case_count() const; + + // Gets the number of all test cases that contain at least one test + // that should run. + int test_case_to_run_count() const; + + // Gets the number of successful tests. + int successful_test_count() const; + + // Gets the number of failed tests. + int failed_test_count() const; + + // Gets the number of disabled tests. + int disabled_test_count() const; + + // Gets the number of all tests. + int total_test_count() const; + + // Gets the number of tests that should run. + int test_to_run_count() const; + + // Gets the elapsed time, in milliseconds. + TimeInMillis elapsed_time() const; + + // Returns true iff the unit test passed (i.e. all test cases passed). + bool Passed() const; + + // Returns true iff the unit test failed (i.e. some test case failed + // or something outside of all tests failed). + bool Failed() const; + + // Gets the i-th test case among all the test cases. i can range from 0 to + // total_test_case_count() - 1. If i is not in that range, returns NULL. + const TestCase* GetTestCase(int i) const; + + // Returns the list of event listeners that can be used to track events + // inside Google Test. + TestEventListeners& listeners(); + + private: + // Registers and returns a global test environment. When a test + // program is run, all global test environments will be set-up in + // the order they were registered. After all tests in the program + // have finished, all global test environments will be torn-down in + // the *reverse* order they were registered. + // + // The UnitTest object takes ownership of the given environment. + // + // This method can only be called from the main thread. + Environment* AddEnvironment(Environment* env); + + // Adds a TestPartResult to the current TestResult object. All + // Google Test assertion macros (e.g. ASSERT_TRUE, EXPECT_EQ, etc) + // eventually call this to report their results. The user code + // should use the assertion macros instead of calling this directly. + void AddTestPartResult(TestPartResult::Type result_type, + const char* file_name, + int line_number, + const internal::String& message, + const internal::String& os_stack_trace); + + // Adds a TestProperty to the current TestResult object. If the result already + // contains a property with the same key, the value will be updated. + void RecordPropertyForCurrentTest(const char* key, const char* value); + + // Gets the i-th test case among all the test cases. i can range from 0 to + // total_test_case_count() - 1. If i is not in that range, returns NULL. + TestCase* GetMutableTestCase(int i); + + // Accessors for the implementation object. + internal::UnitTestImpl* impl() { return impl_; } + const internal::UnitTestImpl* impl() const { return impl_; } + + // These classes and funcions are friends as they need to access private + // members of UnitTest. + friend class Test; + friend class internal::AssertHelper; + friend class internal::ScopedTrace; + friend Environment* AddGlobalTestEnvironment(Environment* env); + friend internal::UnitTestImpl* internal::GetUnitTestImpl(); + friend void internal::ReportFailureInUnknownLocation( + TestPartResult::Type result_type, + const internal::String& message); + + // Creates an empty UnitTest. + UnitTest(); + + // D'tor + virtual ~UnitTest(); + + // Pushes a trace defined by SCOPED_TRACE() on to the per-thread + // Google Test trace stack. + void PushGTestTrace(const internal::TraceInfo& trace); + + // Pops a trace from the per-thread Google Test trace stack. + void PopGTestTrace(); + + // Protects mutable state in *impl_. This is mutable as some const + // methods need to lock it too. + mutable internal::Mutex mutex_; + + // Opaque implementation object. This field is never changed once + // the object is constructed. We don't mark it as const here, as + // doing so will cause a warning in the constructor of UnitTest. + // Mutable state in *impl_ is protected by mutex_. + internal::UnitTestImpl* impl_; + + // We disallow copying UnitTest. + GTEST_DISALLOW_COPY_AND_ASSIGN_(UnitTest); +}; + +// A convenient wrapper for adding an environment for the test +// program. +// +// You should call this before RUN_ALL_TESTS() is called, probably in +// main(). If you use gtest_main, you need to call this before main() +// starts for it to take effect. For example, you can define a global +// variable like this: +// +// testing::Environment* const foo_env = +// testing::AddGlobalTestEnvironment(new FooEnvironment); +// +// However, we strongly recommend you to write your own main() and +// call AddGlobalTestEnvironment() there, as relying on initialization +// of global variables makes the code harder to read and may cause +// problems when you register multiple environments from different +// translation units and the environments have dependencies among them +// (remember that the compiler doesn't guarantee the order in which +// global variables from different translation units are initialized). +inline Environment* AddGlobalTestEnvironment(Environment* env) { + return UnitTest::GetInstance()->AddEnvironment(env); +} + +// Initializes Google Test. This must be called before calling +// RUN_ALL_TESTS(). In particular, it parses a command line for the +// flags that Google Test recognizes. Whenever a Google Test flag is +// seen, it is removed from argv, and *argc is decremented. +// +// No value is returned. Instead, the Google Test flag variables are +// updated. +// +// Calling the function for the second time has no user-visible effect. +GTEST_API_ void InitGoogleTest(int* argc, char** argv); + +// This overloaded version can be used in Windows programs compiled in +// UNICODE mode. +GTEST_API_ void InitGoogleTest(int* argc, wchar_t** argv); + +namespace internal { + +// Formats a comparison assertion (e.g. ASSERT_EQ, EXPECT_LT, and etc) +// operand to be used in a failure message. The type (but not value) +// of the other operand may affect the format. This allows us to +// print a char* as a raw pointer when it is compared against another +// char*, and print it as a C string when it is compared against an +// std::string object, for example. +// +// The default implementation ignores the type of the other operand. +// Some specialized versions are used to handle formatting wide or +// narrow C strings. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +template +String FormatForComparisonFailureMessage(const T1& value, + const T2& /* other_operand */) { + // C++Builder compiles this incorrectly if the namespace isn't explicitly + // given. + return ::testing::PrintToString(value); +} + +// The helper function for {ASSERT|EXPECT}_EQ. +template +AssertionResult CmpHelperEQ(const char* expected_expression, + const char* actual_expression, + const T1& expected, + const T2& actual) { +#ifdef _MSC_VER +# pragma warning(push) // Saves the current warning state. +# pragma warning(disable:4389) // Temporarily disables warning on + // signed/unsigned mismatch. +#endif + + if (expected == actual) { + return AssertionSuccess(); + } + +#ifdef _MSC_VER +# pragma warning(pop) // Restores the warning state. +#endif + + return EqFailure(expected_expression, + actual_expression, + FormatForComparisonFailureMessage(expected, actual), + FormatForComparisonFailureMessage(actual, expected), + false); +} + +// With this overloaded version, we allow anonymous enums to be used +// in {ASSERT|EXPECT}_EQ when compiled with gcc 4, as anonymous enums +// can be implicitly cast to BiggestInt. +GTEST_API_ AssertionResult CmpHelperEQ(const char* expected_expression, + const char* actual_expression, + BiggestInt expected, + BiggestInt actual); + +// The helper class for {ASSERT|EXPECT}_EQ. The template argument +// lhs_is_null_literal is true iff the first argument to ASSERT_EQ() +// is a null pointer literal. The following default implementation is +// for lhs_is_null_literal being false. +template +class EqHelper { + public: + // This templatized version is for the general case. + template + static AssertionResult Compare(const char* expected_expression, + const char* actual_expression, + const T1& expected, + const T2& actual) { + return CmpHelperEQ(expected_expression, actual_expression, expected, + actual); + } + + // With this overloaded version, we allow anonymous enums to be used + // in {ASSERT|EXPECT}_EQ when compiled with gcc 4, as anonymous + // enums can be implicitly cast to BiggestInt. + // + // Even though its body looks the same as the above version, we + // cannot merge the two, as it will make anonymous enums unhappy. + static AssertionResult Compare(const char* expected_expression, + const char* actual_expression, + BiggestInt expected, + BiggestInt actual) { + return CmpHelperEQ(expected_expression, actual_expression, expected, + actual); + } +}; + +// This specialization is used when the first argument to ASSERT_EQ() +// is a null pointer literal, like NULL, false, or 0. +template <> +class EqHelper { + public: + // We define two overloaded versions of Compare(). The first + // version will be picked when the second argument to ASSERT_EQ() is + // NOT a pointer, e.g. ASSERT_EQ(0, AnIntFunction()) or + // EXPECT_EQ(false, a_bool). + template + static AssertionResult Compare( + const char* expected_expression, + const char* actual_expression, + const T1& expected, + const T2& actual, + // The following line prevents this overload from being considered if T2 + // is not a pointer type. We need this because ASSERT_EQ(NULL, my_ptr) + // expands to Compare("", "", NULL, my_ptr), which requires a conversion + // to match the Secret* in the other overload, which would otherwise make + // this template match better. + typename EnableIf::value>::type* = 0) { + return CmpHelperEQ(expected_expression, actual_expression, expected, + actual); + } + + // This version will be picked when the second argument to ASSERT_EQ() is a + // pointer, e.g. ASSERT_EQ(NULL, a_pointer). + template + static AssertionResult Compare( + const char* expected_expression, + const char* actual_expression, + // We used to have a second template parameter instead of Secret*. That + // template parameter would deduce to 'long', making this a better match + // than the first overload even without the first overload's EnableIf. + // Unfortunately, gcc with -Wconversion-null warns when "passing NULL to + // non-pointer argument" (even a deduced integral argument), so the old + // implementation caused warnings in user code. + Secret* /* expected (NULL) */, + T* actual) { + // We already know that 'expected' is a null pointer. + return CmpHelperEQ(expected_expression, actual_expression, + static_cast(NULL), actual); + } +}; + +// A macro for implementing the helper functions needed to implement +// ASSERT_?? and EXPECT_??. It is here just to avoid copy-and-paste +// of similar code. +// +// For each templatized helper function, we also define an overloaded +// version for BiggestInt in order to reduce code bloat and allow +// anonymous enums to be used with {ASSERT|EXPECT}_?? when compiled +// with gcc 4. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +#define GTEST_IMPL_CMP_HELPER_(op_name, op)\ +template \ +AssertionResult CmpHelper##op_name(const char* expr1, const char* expr2, \ + const T1& val1, const T2& val2) {\ + if (val1 op val2) {\ + return AssertionSuccess();\ + } else {\ + return AssertionFailure() \ + << "Expected: (" << expr1 << ") " #op " (" << expr2\ + << "), actual: " << FormatForComparisonFailureMessage(val1, val2)\ + << " vs " << FormatForComparisonFailureMessage(val2, val1);\ + }\ +}\ +GTEST_API_ AssertionResult CmpHelper##op_name(\ + const char* expr1, const char* expr2, BiggestInt val1, BiggestInt val2) + +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. + +// Implements the helper function for {ASSERT|EXPECT}_NE +GTEST_IMPL_CMP_HELPER_(NE, !=); +// Implements the helper function for {ASSERT|EXPECT}_LE +GTEST_IMPL_CMP_HELPER_(LE, <=); +// Implements the helper function for {ASSERT|EXPECT}_LT +GTEST_IMPL_CMP_HELPER_(LT, < ); +// Implements the helper function for {ASSERT|EXPECT}_GE +GTEST_IMPL_CMP_HELPER_(GE, >=); +// Implements the helper function for {ASSERT|EXPECT}_GT +GTEST_IMPL_CMP_HELPER_(GT, > ); + +#undef GTEST_IMPL_CMP_HELPER_ + +// The helper function for {ASSERT|EXPECT}_STREQ. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTREQ(const char* expected_expression, + const char* actual_expression, + const char* expected, + const char* actual); + +// The helper function for {ASSERT|EXPECT}_STRCASEEQ. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTRCASEEQ(const char* expected_expression, + const char* actual_expression, + const char* expected, + const char* actual); + +// The helper function for {ASSERT|EXPECT}_STRNE. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTRNE(const char* s1_expression, + const char* s2_expression, + const char* s1, + const char* s2); + +// The helper function for {ASSERT|EXPECT}_STRCASENE. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTRCASENE(const char* s1_expression, + const char* s2_expression, + const char* s1, + const char* s2); + + +// Helper function for *_STREQ on wide strings. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTREQ(const char* expected_expression, + const char* actual_expression, + const wchar_t* expected, + const wchar_t* actual); + +// Helper function for *_STRNE on wide strings. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult CmpHelperSTRNE(const char* s1_expression, + const char* s2_expression, + const wchar_t* s1, + const wchar_t* s2); + +} // namespace internal + +// IsSubstring() and IsNotSubstring() are intended to be used as the +// first argument to {EXPECT,ASSERT}_PRED_FORMAT2(), not by +// themselves. They check whether needle is a substring of haystack +// (NULL is considered a substring of itself only), and return an +// appropriate error message when they fail. +// +// The {needle,haystack}_expr arguments are the stringified +// expressions that generated the two real arguments. +GTEST_API_ AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const char* needle, const char* haystack); +GTEST_API_ AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const wchar_t* needle, const wchar_t* haystack); +GTEST_API_ AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const char* needle, const char* haystack); +GTEST_API_ AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const wchar_t* needle, const wchar_t* haystack); +GTEST_API_ AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::string& needle, const ::std::string& haystack); +GTEST_API_ AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::string& needle, const ::std::string& haystack); + +#if GTEST_HAS_STD_WSTRING +GTEST_API_ AssertionResult IsSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::wstring& needle, const ::std::wstring& haystack); +GTEST_API_ AssertionResult IsNotSubstring( + const char* needle_expr, const char* haystack_expr, + const ::std::wstring& needle, const ::std::wstring& haystack); +#endif // GTEST_HAS_STD_WSTRING + +namespace internal { + +// Helper template function for comparing floating-points. +// +// Template parameter: +// +// RawType: the raw floating-point type (either float or double) +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +template +AssertionResult CmpHelperFloatingPointEQ(const char* expected_expression, + const char* actual_expression, + RawType expected, + RawType actual) { + const FloatingPoint lhs(expected), rhs(actual); + + if (lhs.AlmostEquals(rhs)) { + return AssertionSuccess(); + } + + ::std::stringstream expected_ss; + expected_ss << std::setprecision(std::numeric_limits::digits10 + 2) + << expected; + + ::std::stringstream actual_ss; + actual_ss << std::setprecision(std::numeric_limits::digits10 + 2) + << actual; + + return EqFailure(expected_expression, + actual_expression, + StringStreamToString(&expected_ss), + StringStreamToString(&actual_ss), + false); +} + +// Helper function for implementing ASSERT_NEAR. +// +// INTERNAL IMPLEMENTATION - DO NOT USE IN A USER PROGRAM. +GTEST_API_ AssertionResult DoubleNearPredFormat(const char* expr1, + const char* expr2, + const char* abs_error_expr, + double val1, + double val2, + double abs_error); + +// INTERNAL IMPLEMENTATION - DO NOT USE IN USER CODE. +// A class that enables one to stream messages to assertion macros +class GTEST_API_ AssertHelper { + public: + // Constructor. + AssertHelper(TestPartResult::Type type, + const char* file, + int line, + const char* message); + ~AssertHelper(); + + // Message assignment is a semantic trick to enable assertion + // streaming; see the GTEST_MESSAGE_ macro below. + void operator=(const Message& message) const; + + private: + // We put our data in a struct so that the size of the AssertHelper class can + // be as small as possible. This is important because gcc is incapable of + // re-using stack space even for temporary variables, so every EXPECT_EQ + // reserves stack space for another AssertHelper. + struct AssertHelperData { + AssertHelperData(TestPartResult::Type t, + const char* srcfile, + int line_num, + const char* msg) + : type(t), file(srcfile), line(line_num), message(msg) { } + + TestPartResult::Type const type; + const char* const file; + int const line; + String const message; + + private: + GTEST_DISALLOW_COPY_AND_ASSIGN_(AssertHelperData); + }; + + AssertHelperData* const data_; + + GTEST_DISALLOW_COPY_AND_ASSIGN_(AssertHelper); +}; + +} // namespace internal + +#if GTEST_HAS_PARAM_TEST +// The pure interface class that all value-parameterized tests inherit from. +// A value-parameterized class must inherit from both ::testing::Test and +// ::testing::WithParamInterface. In most cases that just means inheriting +// from ::testing::TestWithParam, but more complicated test hierarchies +// may need to inherit from Test and WithParamInterface at different levels. +// +// This interface has support for accessing the test parameter value via +// the GetParam() method. +// +// Use it with one of the parameter generator defining functions, like Range(), +// Values(), ValuesIn(), Bool(), and Combine(). +// +// class FooTest : public ::testing::TestWithParam { +// protected: +// FooTest() { +// // Can use GetParam() here. +// } +// virtual ~FooTest() { +// // Can use GetParam() here. +// } +// virtual void SetUp() { +// // Can use GetParam() here. +// } +// virtual void TearDown { +// // Can use GetParam() here. +// } +// }; +// TEST_P(FooTest, DoesBar) { +// // Can use GetParam() method here. +// Foo foo; +// ASSERT_TRUE(foo.DoesBar(GetParam())); +// } +// INSTANTIATE_TEST_CASE_P(OneToTenRange, FooTest, ::testing::Range(1, 10)); + +template +class WithParamInterface { + public: + typedef T ParamType; + virtual ~WithParamInterface() {} + + // The current parameter value. Is also available in the test fixture's + // constructor. This member function is non-static, even though it only + // references static data, to reduce the opportunity for incorrect uses + // like writing 'WithParamInterface::GetParam()' for a test that + // uses a fixture whose parameter type is int. + const ParamType& GetParam() const { return *parameter_; } + + private: + // Sets parameter value. The caller is responsible for making sure the value + // remains alive and unchanged throughout the current test. + static void SetParam(const ParamType* parameter) { + parameter_ = parameter; + } + + // Static value used for accessing parameter during a test lifetime. + static const ParamType* parameter_; + + // TestClass must be a subclass of WithParamInterface and Test. + template friend class internal::ParameterizedTestFactory; +}; + +template +const T* WithParamInterface::parameter_ = NULL; + +// Most value-parameterized classes can ignore the existence of +// WithParamInterface, and can just inherit from ::testing::TestWithParam. + +template +class TestWithParam : public Test, public WithParamInterface { +}; + +#endif // GTEST_HAS_PARAM_TEST + +// Macros for indicating success/failure in test code. + +// ADD_FAILURE unconditionally adds a failure to the current test. +// SUCCEED generates a success - it doesn't automatically make the +// current test successful, as a test is only successful when it has +// no failure. +// +// EXPECT_* verifies that a certain condition is satisfied. If not, +// it behaves like ADD_FAILURE. In particular: +// +// EXPECT_TRUE verifies that a Boolean condition is true. +// EXPECT_FALSE verifies that a Boolean condition is false. +// +// FAIL and ASSERT_* are similar to ADD_FAILURE and EXPECT_*, except +// that they will also abort the current function on failure. People +// usually want the fail-fast behavior of FAIL and ASSERT_*, but those +// writing data-driven tests often find themselves using ADD_FAILURE +// and EXPECT_* more. +// +// Examples: +// +// EXPECT_TRUE(server.StatusIsOK()); +// ASSERT_FALSE(server.HasPendingRequest(port)) +// << "There are still pending requests " << "on port " << port; + +// Generates a nonfatal failure with a generic message. +#define ADD_FAILURE() GTEST_NONFATAL_FAILURE_("Failed") + +// Generates a nonfatal failure at the given source file location with +// a generic message. +#define ADD_FAILURE_AT(file, line) \ + GTEST_MESSAGE_AT_(file, line, "Failed", \ + ::testing::TestPartResult::kNonFatalFailure) + +// Generates a fatal failure with a generic message. +#define GTEST_FAIL() GTEST_FATAL_FAILURE_("Failed") + +// Define this macro to 1 to omit the definition of FAIL(), which is a +// generic name and clashes with some other libraries. +#if !GTEST_DONT_DEFINE_FAIL +# define FAIL() GTEST_FAIL() +#endif + +// Generates a success with a generic message. +#define GTEST_SUCCEED() GTEST_SUCCESS_("Succeeded") + +// Define this macro to 1 to omit the definition of SUCCEED(), which +// is a generic name and clashes with some other libraries. +#if !GTEST_DONT_DEFINE_SUCCEED +# define SUCCEED() GTEST_SUCCEED() +#endif + +// Macros for testing exceptions. +// +// * {ASSERT|EXPECT}_THROW(statement, expected_exception): +// Tests that the statement throws the expected exception. +// * {ASSERT|EXPECT}_NO_THROW(statement): +// Tests that the statement doesn't throw any exception. +// * {ASSERT|EXPECT}_ANY_THROW(statement): +// Tests that the statement throws an exception. + +#define EXPECT_THROW(statement, expected_exception) \ + GTEST_TEST_THROW_(statement, expected_exception, GTEST_NONFATAL_FAILURE_) +#define EXPECT_NO_THROW(statement) \ + GTEST_TEST_NO_THROW_(statement, GTEST_NONFATAL_FAILURE_) +#define EXPECT_ANY_THROW(statement) \ + GTEST_TEST_ANY_THROW_(statement, GTEST_NONFATAL_FAILURE_) +#define ASSERT_THROW(statement, expected_exception) \ + GTEST_TEST_THROW_(statement, expected_exception, GTEST_FATAL_FAILURE_) +#define ASSERT_NO_THROW(statement) \ + GTEST_TEST_NO_THROW_(statement, GTEST_FATAL_FAILURE_) +#define ASSERT_ANY_THROW(statement) \ + GTEST_TEST_ANY_THROW_(statement, GTEST_FATAL_FAILURE_) + +// Boolean assertions. Condition can be either a Boolean expression or an +// AssertionResult. For more information on how to use AssertionResult with +// these macros see comments on that class. +#define EXPECT_TRUE(condition) \ + GTEST_TEST_BOOLEAN_(condition, #condition, false, true, \ + GTEST_NONFATAL_FAILURE_) +#define EXPECT_FALSE(condition) \ + GTEST_TEST_BOOLEAN_(!(condition), #condition, true, false, \ + GTEST_NONFATAL_FAILURE_) +#define ASSERT_TRUE(condition) \ + GTEST_TEST_BOOLEAN_(condition, #condition, false, true, \ + GTEST_FATAL_FAILURE_) +#define ASSERT_FALSE(condition) \ + GTEST_TEST_BOOLEAN_(!(condition), #condition, true, false, \ + GTEST_FATAL_FAILURE_) + +// Includes the auto-generated header that implements a family of +// generic predicate assertion macros. +// Copyright 2006, Google Inc. +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +// This file is AUTOMATICALLY GENERATED on 09/24/2010 by command +// 'gen_gtest_pred_impl.py 5'. DO NOT EDIT BY HAND! +// +// Implements a family of generic predicate assertion macros. + +#ifndef GTEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_ +#define GTEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_ + +// Makes sure this header is not included before gtest.h. +#ifndef GTEST_INCLUDE_GTEST_GTEST_H_ +# error Do not include gtest_pred_impl.h directly. Include gtest.h instead. +#endif // GTEST_INCLUDE_GTEST_GTEST_H_ + +// This header implements a family of generic predicate assertion +// macros: +// +// ASSERT_PRED_FORMAT1(pred_format, v1) +// ASSERT_PRED_FORMAT2(pred_format, v1, v2) +// ... +// +// where pred_format is a function or functor that takes n (in the +// case of ASSERT_PRED_FORMATn) values and their source expression +// text, and returns a testing::AssertionResult. See the definition +// of ASSERT_EQ in gtest.h for an example. +// +// If you don't care about formatting, you can use the more +// restrictive version: +// +// ASSERT_PRED1(pred, v1) +// ASSERT_PRED2(pred, v1, v2) +// ... +// +// where pred is an n-ary function or functor that returns bool, +// and the values v1, v2, ..., must support the << operator for +// streaming to std::ostream. +// +// We also define the EXPECT_* variations. +// +// For now we only support predicates whose arity is at most 5. +// Please email googletestframework@googlegroups.com if you need +// support for higher arities. + +// GTEST_ASSERT_ is the basic statement to which all of the assertions +// in this file reduce. Don't use this in your code. + +#define GTEST_ASSERT_(expression, on_failure) \ + GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ + if (const ::testing::AssertionResult gtest_ar = (expression)) \ + ; \ + else \ + on_failure(gtest_ar.failure_message()) + + +// Helper function for implementing {EXPECT|ASSERT}_PRED1. Don't use +// this in your code. +template +AssertionResult AssertPred1Helper(const char* pred_text, + const char* e1, + Pred pred, + const T1& v1) { + if (pred(v1)) return AssertionSuccess(); + + return AssertionFailure() << pred_text << "(" + << e1 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1; +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT1. +// Don't use this in your code. +#define GTEST_PRED_FORMAT1_(pred_format, v1, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, v1),\ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED1. Don't use +// this in your code. +#define GTEST_PRED1_(pred, v1, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred1Helper(#pred, \ + #v1, \ + pred, \ + v1), on_failure) + +// Unary predicate assertion macros. +#define EXPECT_PRED_FORMAT1(pred_format, v1) \ + GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED1(pred, v1) \ + GTEST_PRED1_(pred, v1, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT1(pred_format, v1) \ + GTEST_PRED_FORMAT1_(pred_format, v1, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED1(pred, v1) \ + GTEST_PRED1_(pred, v1, GTEST_FATAL_FAILURE_) + + + +// Helper function for implementing {EXPECT|ASSERT}_PRED2. Don't use +// this in your code. +template +AssertionResult AssertPred2Helper(const char* pred_text, + const char* e1, + const char* e2, + Pred pred, + const T1& v1, + const T2& v2) { + if (pred(v1, v2)) return AssertionSuccess(); + + return AssertionFailure() << pred_text << "(" + << e1 << ", " + << e2 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1 + << "\n" << e2 << " evaluates to " << v2; +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT2. +// Don't use this in your code. +#define GTEST_PRED_FORMAT2_(pred_format, v1, v2, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, #v2, v1, v2),\ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED2. Don't use +// this in your code. +#define GTEST_PRED2_(pred, v1, v2, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred2Helper(#pred, \ + #v1, \ + #v2, \ + pred, \ + v1, \ + v2), on_failure) + +// Binary predicate assertion macros. +#define EXPECT_PRED_FORMAT2(pred_format, v1, v2) \ + GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED2(pred, v1, v2) \ + GTEST_PRED2_(pred, v1, v2, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT2(pred_format, v1, v2) \ + GTEST_PRED_FORMAT2_(pred_format, v1, v2, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED2(pred, v1, v2) \ + GTEST_PRED2_(pred, v1, v2, GTEST_FATAL_FAILURE_) + + + +// Helper function for implementing {EXPECT|ASSERT}_PRED3. Don't use +// this in your code. +template +AssertionResult AssertPred3Helper(const char* pred_text, + const char* e1, + const char* e2, + const char* e3, + Pred pred, + const T1& v1, + const T2& v2, + const T3& v3) { + if (pred(v1, v2, v3)) return AssertionSuccess(); + + return AssertionFailure() << pred_text << "(" + << e1 << ", " + << e2 << ", " + << e3 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1 + << "\n" << e2 << " evaluates to " << v2 + << "\n" << e3 << " evaluates to " << v3; +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT3. +// Don't use this in your code. +#define GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, #v2, #v3, v1, v2, v3),\ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED3. Don't use +// this in your code. +#define GTEST_PRED3_(pred, v1, v2, v3, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred3Helper(#pred, \ + #v1, \ + #v2, \ + #v3, \ + pred, \ + v1, \ + v2, \ + v3), on_failure) + +// Ternary predicate assertion macros. +#define EXPECT_PRED_FORMAT3(pred_format, v1, v2, v3) \ + GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED3(pred, v1, v2, v3) \ + GTEST_PRED3_(pred, v1, v2, v3, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT3(pred_format, v1, v2, v3) \ + GTEST_PRED_FORMAT3_(pred_format, v1, v2, v3, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED3(pred, v1, v2, v3) \ + GTEST_PRED3_(pred, v1, v2, v3, GTEST_FATAL_FAILURE_) + + + +// Helper function for implementing {EXPECT|ASSERT}_PRED4. Don't use +// this in your code. +template +AssertionResult AssertPred4Helper(const char* pred_text, + const char* e1, + const char* e2, + const char* e3, + const char* e4, + Pred pred, + const T1& v1, + const T2& v2, + const T3& v3, + const T4& v4) { + if (pred(v1, v2, v3, v4)) return AssertionSuccess(); + + return AssertionFailure() << pred_text << "(" + << e1 << ", " + << e2 << ", " + << e3 << ", " + << e4 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1 + << "\n" << e2 << " evaluates to " << v2 + << "\n" << e3 << " evaluates to " << v3 + << "\n" << e4 << " evaluates to " << v4; +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT4. +// Don't use this in your code. +#define GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, #v2, #v3, #v4, v1, v2, v3, v4),\ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED4. Don't use +// this in your code. +#define GTEST_PRED4_(pred, v1, v2, v3, v4, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred4Helper(#pred, \ + #v1, \ + #v2, \ + #v3, \ + #v4, \ + pred, \ + v1, \ + v2, \ + v3, \ + v4), on_failure) + +// 4-ary predicate assertion macros. +#define EXPECT_PRED_FORMAT4(pred_format, v1, v2, v3, v4) \ + GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED4(pred, v1, v2, v3, v4) \ + GTEST_PRED4_(pred, v1, v2, v3, v4, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT4(pred_format, v1, v2, v3, v4) \ + GTEST_PRED_FORMAT4_(pred_format, v1, v2, v3, v4, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED4(pred, v1, v2, v3, v4) \ + GTEST_PRED4_(pred, v1, v2, v3, v4, GTEST_FATAL_FAILURE_) + + + +// Helper function for implementing {EXPECT|ASSERT}_PRED5. Don't use +// this in your code. +template +AssertionResult AssertPred5Helper(const char* pred_text, + const char* e1, + const char* e2, + const char* e3, + const char* e4, + const char* e5, + Pred pred, + const T1& v1, + const T2& v2, + const T3& v3, + const T4& v4, + const T5& v5) { + if (pred(v1, v2, v3, v4, v5)) return AssertionSuccess(); + + return AssertionFailure() << pred_text << "(" + << e1 << ", " + << e2 << ", " + << e3 << ", " + << e4 << ", " + << e5 << ") evaluates to false, where" + << "\n" << e1 << " evaluates to " << v1 + << "\n" << e2 << " evaluates to " << v2 + << "\n" << e3 << " evaluates to " << v3 + << "\n" << e4 << " evaluates to " << v4 + << "\n" << e5 << " evaluates to " << v5; +} + +// Internal macro for implementing {EXPECT|ASSERT}_PRED_FORMAT5. +// Don't use this in your code. +#define GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, on_failure)\ + GTEST_ASSERT_(pred_format(#v1, #v2, #v3, #v4, #v5, v1, v2, v3, v4, v5),\ + on_failure) + +// Internal macro for implementing {EXPECT|ASSERT}_PRED5. Don't use +// this in your code. +#define GTEST_PRED5_(pred, v1, v2, v3, v4, v5, on_failure)\ + GTEST_ASSERT_(::testing::AssertPred5Helper(#pred, \ + #v1, \ + #v2, \ + #v3, \ + #v4, \ + #v5, \ + pred, \ + v1, \ + v2, \ + v3, \ + v4, \ + v5), on_failure) + +// 5-ary predicate assertion macros. +#define EXPECT_PRED_FORMAT5(pred_format, v1, v2, v3, v4, v5) \ + GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, GTEST_NONFATAL_FAILURE_) +#define EXPECT_PRED5(pred, v1, v2, v3, v4, v5) \ + GTEST_PRED5_(pred, v1, v2, v3, v4, v5, GTEST_NONFATAL_FAILURE_) +#define ASSERT_PRED_FORMAT5(pred_format, v1, v2, v3, v4, v5) \ + GTEST_PRED_FORMAT5_(pred_format, v1, v2, v3, v4, v5, GTEST_FATAL_FAILURE_) +#define ASSERT_PRED5(pred, v1, v2, v3, v4, v5) \ + GTEST_PRED5_(pred, v1, v2, v3, v4, v5, GTEST_FATAL_FAILURE_) + + + +#endif // GTEST_INCLUDE_GTEST_GTEST_PRED_IMPL_H_ + +// Macros for testing equalities and inequalities. +// +// * {ASSERT|EXPECT}_EQ(expected, actual): Tests that expected == actual +// * {ASSERT|EXPECT}_NE(v1, v2): Tests that v1 != v2 +// * {ASSERT|EXPECT}_LT(v1, v2): Tests that v1 < v2 +// * {ASSERT|EXPECT}_LE(v1, v2): Tests that v1 <= v2 +// * {ASSERT|EXPECT}_GT(v1, v2): Tests that v1 > v2 +// * {ASSERT|EXPECT}_GE(v1, v2): Tests that v1 >= v2 +// +// When they are not, Google Test prints both the tested expressions and +// their actual values. The values must be compatible built-in types, +// or you will get a compiler error. By "compatible" we mean that the +// values can be compared by the respective operator. +// +// Note: +// +// 1. It is possible to make a user-defined type work with +// {ASSERT|EXPECT}_??(), but that requires overloading the +// comparison operators and is thus discouraged by the Google C++ +// Usage Guide. Therefore, you are advised to use the +// {ASSERT|EXPECT}_TRUE() macro to assert that two objects are +// equal. +// +// 2. The {ASSERT|EXPECT}_??() macros do pointer comparisons on +// pointers (in particular, C strings). Therefore, if you use it +// with two C strings, you are testing how their locations in memory +// are related, not how their content is related. To compare two C +// strings by content, use {ASSERT|EXPECT}_STR*(). +// +// 3. {ASSERT|EXPECT}_EQ(expected, actual) is preferred to +// {ASSERT|EXPECT}_TRUE(expected == actual), as the former tells you +// what the actual value is when it fails, and similarly for the +// other comparisons. +// +// 4. Do not depend on the order in which {ASSERT|EXPECT}_??() +// evaluate their arguments, which is undefined. +// +// 5. These macros evaluate their arguments exactly once. +// +// Examples: +// +// EXPECT_NE(5, Foo()); +// EXPECT_EQ(NULL, a_pointer); +// ASSERT_LT(i, array_size); +// ASSERT_GT(records.size(), 0) << "There is no record left."; + +#define EXPECT_EQ(expected, actual) \ + EXPECT_PRED_FORMAT2(::testing::internal:: \ + EqHelper::Compare, \ + expected, actual) +#define EXPECT_NE(expected, actual) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperNE, expected, actual) +#define EXPECT_LE(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2) +#define EXPECT_LT(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2) +#define EXPECT_GE(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperGE, val1, val2) +#define EXPECT_GT(val1, val2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2) + +#define GTEST_ASSERT_EQ(expected, actual) \ + ASSERT_PRED_FORMAT2(::testing::internal:: \ + EqHelper::Compare, \ + expected, actual) +#define GTEST_ASSERT_NE(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperNE, val1, val2) +#define GTEST_ASSERT_LE(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperLE, val1, val2) +#define GTEST_ASSERT_LT(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperLT, val1, val2) +#define GTEST_ASSERT_GE(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperGE, val1, val2) +#define GTEST_ASSERT_GT(val1, val2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperGT, val1, val2) + +// Define macro GTEST_DONT_DEFINE_ASSERT_XY to 1 to omit the definition of +// ASSERT_XY(), which clashes with some users' own code. + +#if !GTEST_DONT_DEFINE_ASSERT_EQ +# define ASSERT_EQ(val1, val2) GTEST_ASSERT_EQ(val1, val2) +#endif + +#if !GTEST_DONT_DEFINE_ASSERT_NE +# define ASSERT_NE(val1, val2) GTEST_ASSERT_NE(val1, val2) +#endif + +#if !GTEST_DONT_DEFINE_ASSERT_LE +# define ASSERT_LE(val1, val2) GTEST_ASSERT_LE(val1, val2) +#endif + +#if !GTEST_DONT_DEFINE_ASSERT_LT +# define ASSERT_LT(val1, val2) GTEST_ASSERT_LT(val1, val2) +#endif + +#if !GTEST_DONT_DEFINE_ASSERT_GE +# define ASSERT_GE(val1, val2) GTEST_ASSERT_GE(val1, val2) +#endif + +#if !GTEST_DONT_DEFINE_ASSERT_GT +# define ASSERT_GT(val1, val2) GTEST_ASSERT_GT(val1, val2) +#endif + +// C String Comparisons. All tests treat NULL and any non-NULL string +// as different. Two NULLs are equal. +// +// * {ASSERT|EXPECT}_STREQ(s1, s2): Tests that s1 == s2 +// * {ASSERT|EXPECT}_STRNE(s1, s2): Tests that s1 != s2 +// * {ASSERT|EXPECT}_STRCASEEQ(s1, s2): Tests that s1 == s2, ignoring case +// * {ASSERT|EXPECT}_STRCASENE(s1, s2): Tests that s1 != s2, ignoring case +// +// For wide or narrow string objects, you can use the +// {ASSERT|EXPECT}_??() macros. +// +// Don't depend on the order in which the arguments are evaluated, +// which is undefined. +// +// These macros evaluate their arguments exactly once. + +#define EXPECT_STREQ(expected, actual) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTREQ, expected, actual) +#define EXPECT_STRNE(s1, s2) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRNE, s1, s2) +#define EXPECT_STRCASEEQ(expected, actual) \ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASEEQ, expected, actual) +#define EXPECT_STRCASENE(s1, s2)\ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASENE, s1, s2) + +#define ASSERT_STREQ(expected, actual) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTREQ, expected, actual) +#define ASSERT_STRNE(s1, s2) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRNE, s1, s2) +#define ASSERT_STRCASEEQ(expected, actual) \ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASEEQ, expected, actual) +#define ASSERT_STRCASENE(s1, s2)\ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperSTRCASENE, s1, s2) + +// Macros for comparing floating-point numbers. +// +// * {ASSERT|EXPECT}_FLOAT_EQ(expected, actual): +// Tests that two float values are almost equal. +// * {ASSERT|EXPECT}_DOUBLE_EQ(expected, actual): +// Tests that two double values are almost equal. +// * {ASSERT|EXPECT}_NEAR(v1, v2, abs_error): +// Tests that v1 and v2 are within the given distance to each other. +// +// Google Test uses ULP-based comparison to automatically pick a default +// error bound that is appropriate for the operands. See the +// FloatingPoint template class in gtest-internal.h if you are +// interested in the implementation details. + +#define EXPECT_FLOAT_EQ(expected, actual)\ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ + expected, actual) + +#define EXPECT_DOUBLE_EQ(expected, actual)\ + EXPECT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ + expected, actual) + +#define ASSERT_FLOAT_EQ(expected, actual)\ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ + expected, actual) + +#define ASSERT_DOUBLE_EQ(expected, actual)\ + ASSERT_PRED_FORMAT2(::testing::internal::CmpHelperFloatingPointEQ, \ + expected, actual) + +#define EXPECT_NEAR(val1, val2, abs_error)\ + EXPECT_PRED_FORMAT3(::testing::internal::DoubleNearPredFormat, \ + val1, val2, abs_error) + +#define ASSERT_NEAR(val1, val2, abs_error)\ + ASSERT_PRED_FORMAT3(::testing::internal::DoubleNearPredFormat, \ + val1, val2, abs_error) + +// These predicate format functions work on floating-point values, and +// can be used in {ASSERT|EXPECT}_PRED_FORMAT2*(), e.g. +// +// EXPECT_PRED_FORMAT2(testing::DoubleLE, Foo(), 5.0); + +// Asserts that val1 is less than, or almost equal to, val2. Fails +// otherwise. In particular, it fails if either val1 or val2 is NaN. +GTEST_API_ AssertionResult FloatLE(const char* expr1, const char* expr2, + float val1, float val2); +GTEST_API_ AssertionResult DoubleLE(const char* expr1, const char* expr2, + double val1, double val2); + + +#if GTEST_OS_WINDOWS + +// Macros that test for HRESULT failure and success, these are only useful +// on Windows, and rely on Windows SDK macros and APIs to compile. +// +// * {ASSERT|EXPECT}_HRESULT_{SUCCEEDED|FAILED}(expr) +// +// When expr unexpectedly fails or succeeds, Google Test prints the +// expected result and the actual result with both a human-readable +// string representation of the error, if available, as well as the +// hex result code. +# define EXPECT_HRESULT_SUCCEEDED(expr) \ + EXPECT_PRED_FORMAT1(::testing::internal::IsHRESULTSuccess, (expr)) + +# define ASSERT_HRESULT_SUCCEEDED(expr) \ + ASSERT_PRED_FORMAT1(::testing::internal::IsHRESULTSuccess, (expr)) + +# define EXPECT_HRESULT_FAILED(expr) \ + EXPECT_PRED_FORMAT1(::testing::internal::IsHRESULTFailure, (expr)) + +# define ASSERT_HRESULT_FAILED(expr) \ + ASSERT_PRED_FORMAT1(::testing::internal::IsHRESULTFailure, (expr)) + +#endif // GTEST_OS_WINDOWS + +// Macros that execute statement and check that it doesn't generate new fatal +// failures in the current thread. +// +// * {ASSERT|EXPECT}_NO_FATAL_FAILURE(statement); +// +// Examples: +// +// EXPECT_NO_FATAL_FAILURE(Process()); +// ASSERT_NO_FATAL_FAILURE(Process()) << "Process() failed"; +// +#define ASSERT_NO_FATAL_FAILURE(statement) \ + GTEST_TEST_NO_FATAL_FAILURE_(statement, GTEST_FATAL_FAILURE_) +#define EXPECT_NO_FATAL_FAILURE(statement) \ + GTEST_TEST_NO_FATAL_FAILURE_(statement, GTEST_NONFATAL_FAILURE_) + +// Causes a trace (including the source file path, the current line +// number, and the given message) to be included in every test failure +// message generated by code in the current scope. The effect is +// undone when the control leaves the current scope. +// +// The message argument can be anything streamable to std::ostream. +// +// In the implementation, we include the current line number as part +// of the dummy variable name, thus allowing multiple SCOPED_TRACE()s +// to appear in the same block - as long as they are on different +// lines. +#define SCOPED_TRACE(message) \ + ::testing::internal::ScopedTrace GTEST_CONCAT_TOKEN_(gtest_trace_, __LINE__)(\ + __FILE__, __LINE__, ::testing::Message() << (message)) + +// Compile-time assertion for type equality. +// StaticAssertTypeEq() compiles iff type1 and type2 are +// the same type. The value it returns is not interesting. +// +// Instead of making StaticAssertTypeEq a class template, we make it a +// function template that invokes a helper class template. This +// prevents a user from misusing StaticAssertTypeEq by +// defining objects of that type. +// +// CAVEAT: +// +// When used inside a method of a class template, +// StaticAssertTypeEq() is effective ONLY IF the method is +// instantiated. For example, given: +// +// template class Foo { +// public: +// void Bar() { testing::StaticAssertTypeEq(); } +// }; +// +// the code: +// +// void Test1() { Foo foo; } +// +// will NOT generate a compiler error, as Foo::Bar() is never +// actually instantiated. Instead, you need: +// +// void Test2() { Foo foo; foo.Bar(); } +// +// to cause a compiler error. +template +bool StaticAssertTypeEq() { + (void)internal::StaticAssertTypeEqHelper(); + return true; +} + +// Defines a test. +// +// The first parameter is the name of the test case, and the second +// parameter is the name of the test within the test case. +// +// The convention is to end the test case name with "Test". For +// example, a test case for the Foo class can be named FooTest. +// +// The user should put his test code between braces after using this +// macro. Example: +// +// TEST(FooTest, InitializesCorrectly) { +// Foo foo; +// EXPECT_TRUE(foo.StatusIsOK()); +// } + +// Note that we call GetTestTypeId() instead of GetTypeId< +// ::testing::Test>() here to get the type ID of testing::Test. This +// is to work around a suspected linker bug when using Google Test as +// a framework on Mac OS X. The bug causes GetTypeId< +// ::testing::Test>() to return different values depending on whether +// the call is from the Google Test framework itself or from user test +// code. GetTestTypeId() is guaranteed to always return the same +// value, as it always calls GetTypeId<>() from the Google Test +// framework. +#define GTEST_TEST(test_case_name, test_name)\ + GTEST_TEST_(test_case_name, test_name, \ + ::testing::Test, ::testing::internal::GetTestTypeId()) + +// Define this macro to 1 to omit the definition of TEST(), which +// is a generic name and clashes with some other libraries. +#if !GTEST_DONT_DEFINE_TEST +# define TEST(test_case_name, test_name) GTEST_TEST(test_case_name, test_name) +#endif + +// Defines a test that uses a test fixture. +// +// The first parameter is the name of the test fixture class, which +// also doubles as the test case name. The second parameter is the +// name of the test within the test case. +// +// A test fixture class must be declared earlier. The user should put +// his test code between braces after using this macro. Example: +// +// class FooTest : public testing::Test { +// protected: +// virtual void SetUp() { b_.AddElement(3); } +// +// Foo a_; +// Foo b_; +// }; +// +// TEST_F(FooTest, InitializesCorrectly) { +// EXPECT_TRUE(a_.StatusIsOK()); +// } +// +// TEST_F(FooTest, ReturnsElementCountCorrectly) { +// EXPECT_EQ(0, a_.size()); +// EXPECT_EQ(1, b_.size()); +// } + +#define TEST_F(test_fixture, test_name)\ + GTEST_TEST_(test_fixture, test_name, test_fixture, \ + ::testing::internal::GetTypeId()) + +// Use this macro in main() to run all tests. It returns 0 if all +// tests are successful, or 1 otherwise. +// +// RUN_ALL_TESTS() should be invoked after the command line has been +// parsed by InitGoogleTest(). + +#define RUN_ALL_TESTS()\ + (::testing::UnitTest::GetInstance()->Run()) + +} // namespace testing + +#endif // GTEST_INCLUDE_GTEST_GTEST_H_ diff --git a/plugins/fff/vendor/fff/test/Makefile b/plugins/fff/vendor/fff/test/Makefile new file mode 100644 index 000000000..5c154e20b --- /dev/null +++ b/plugins/fff/vendor/fff/test/Makefile @@ -0,0 +1,81 @@ + +BUILD_DIR = ../build + +FFF_TEST_CPP_OBJS += \ +$(BUILD_DIR)/fff_test_cpp.o \ +$(BUILD_DIR)/gtest-all.o \ +$(BUILD_DIR)/gtest-main.o + +FFF_TEST_GLOBAL_CPP_OBJS += \ +$(BUILD_DIR)/fff_test_global_cpp.o \ +$(BUILD_DIR)/global_fakes.o \ +$(BUILD_DIR)/gtest-all.o \ +$(BUILD_DIR)/gtest-main.o + +FFF_TEST_C_OBJS = $(BUILD_DIR)/fff_test_c.o + +FFF_TEST_GLOBAL_C_OBJS += \ +$(BUILD_DIR)/global_fakes.o \ +$(BUILD_DIR)/fff_test_global_c.o + +FFF_TEST_CPP_TARGET = $(BUILD_DIR)/fff_test_cpp +FFF_TEST_C_TARGET = $(BUILD_DIR)/fff_test_c +FFF_TEST_GLOBAL_C_TARGET = $(BUILD_DIR)/fff_test_glob_c +FFF_TEST_GLOBAL_CPP_TARGET = $(BUILD_DIR)/fff_test_glob_cpp + +LIBS := -lpthread +# All Target +all: $(FFF_TEST_CPP_TARGET) $(FFF_TEST_C_TARGET) $(FFF_TEST_GLOBAL_C_TARGET) $(FFF_TEST_GLOBAL_CPP_TARGET) + + +# Each subdirectory must supply rules for building sources it contributes +$(BUILD_DIR)/%.o: %.cpp + @echo 'Building file: $<' + @echo 'Invoking: GCC C++ Compiler' + g++ -I../ -O0 -g3 -Wall -DGTEST_USE_OWN_TR1_TUPLE=1 -c -o "$@" "$<" + @echo 'Finished building: $<' + @echo ' ' + +$(BUILD_DIR)/%.o: %.c + @echo 'Building file: $<' + @echo 'Invoking: GCC C Compiler' + gcc -I../ -O0 -g3 -Wall -std=c99 -c -o "$@" "$<" + @echo 'Finished building: $<' + @echo ' ' + + +# Link targets +$(FFF_TEST_CPP_TARGET): $(FFF_TEST_CPP_OBJS) + @echo 'Building target: $@' + @echo 'Invoking: GCC C++ Linker' + g++ -o "$(FFF_TEST_CPP_TARGET)" $(FFF_TEST_CPP_OBJS) $(LIBS) + @echo 'Finished building target: $@' + @echo ' ' + +$(FFF_TEST_C_TARGET): $(FFF_TEST_C_OBJS) + @echo 'Building target: $@' + @echo 'Invoking: GCC C Linker' + gcc -o "$(FFF_TEST_C_TARGET)" $(FFF_TEST_C_OBJS) $(LIBS) + @echo 'Finished building target: $@' + @echo ' ' + +$(FFF_TEST_GLOBAL_C_TARGET): $(FFF_TEST_GLOBAL_C_OBJS) + @echo 'Building target: $@' + @echo 'Invoking: GCC C++ Linker' + g++ -o "$(FFF_TEST_GLOBAL_C_TARGET)" $(FFF_TEST_GLOBAL_C_OBJS) $(LIBS) + @echo 'Finished building target: $@' + @echo ' ' + +$(FFF_TEST_GLOBAL_CPP_TARGET): $(FFF_TEST_GLOBAL_CPP_OBJS) + @echo 'Building target: $@' + @echo 'Invoking: GCC C++ Linker' + g++ -o "$(FFF_TEST_GLOBAL_CPP_TARGET)" $(FFF_TEST_GLOBAL_CPP_OBJS) $(LIBS) + @echo 'Finished building target: $@' + @echo ' ' + +# Other Targets +clean: + -$(RM) $(FFF_TEST_CPP_OBJS) $(FFF_TEST_GLOBAL_C_OBJS) $(FFF_TEST_C_OBJS) \ + $(FFF_TEST_CPP_TARGET) $(FFF_TEST_C_TARGET) $(FFF_TEST_GLOBAL_CPP_TARGET) $(FFF_TEST_GLOBAL_C_TARGET) + -@echo ' ' + diff --git a/plugins/fff/vendor/fff/test/c_test_framework.h b/plugins/fff/vendor/fff/test/c_test_framework.h new file mode 100644 index 000000000..eb561f46c --- /dev/null +++ b/plugins/fff/vendor/fff/test/c_test_framework.h @@ -0,0 +1,25 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + + + + +#ifndef C_TEST_FRAMEWORK_H_ +#define C_TEST_FRAMEWORK_H_ + +#include +#include +#include + +/* Test Framework :-) */ +void setup(); +#define TEST_F(SUITE, NAME) void NAME() +#define RUN_TEST(SUITE, TESTNAME) printf(" Running %s.%s: \n", #SUITE, #TESTNAME); setup(); TESTNAME(); printf(" SUCCESS\n"); +#define ASSERT_EQ(A, B) assert((A) == (B)) +#define ASSERT_TRUE(A) assert((A)) + +#endif /* C_TEST_FRAMEWORK_H_ */ diff --git a/plugins/fff/vendor/fff/test/fff_test_c.c b/plugins/fff/vendor/fff/test/fff_test_c.c new file mode 100644 index 000000000..0d7085bfd --- /dev/null +++ b/plugins/fff/vendor/fff/test/fff_test_c.c @@ -0,0 +1,116 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + + + +// Want to keep the argument history for 13 calls +#define OVERRIDE_ARG_HIST_LEN 13u +#define FFF_ARG_HISTORY_LEN OVERRIDE_ARG_HIST_LEN +// Want to keep the call sequence history for 17 function calls +#define OVERRIDE_CALL_HIST_LEN 17u +#define FFF_CALL_HISTORY_LEN OVERRIDE_CALL_HIST_LEN + +#include "../fff.h" +#include "c_test_framework.h" + +#include +#include +#include + + + +enum MYBOOL { FALSE = 899, TRUE }; +struct MyStruct { + int x; + int y; +}; + + +FAKE_VOID_FUNC(voidfunc1, int); +FAKE_VOID_FUNC(voidfunc2, char, char); +FAKE_VALUE_FUNC(long, longfunc0); +FAKE_VALUE_FUNC(enum MYBOOL, enumfunc0); +FAKE_VALUE_FUNC(struct MyStruct, structfunc0); +FAKE_VOID_FUNC3_VARARG(voidfunc3var, char *, int, ...); +FAKE_VALUE_FUNC(int, strlcpy3, char* const, const char* const, const size_t); +FAKE_VOID_FUNC(voidfunc20, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int); + +void setup() +{ + RESET_FAKE(voidfunc1); + RESET_FAKE(voidfunc2); + RESET_FAKE(longfunc0); + RESET_FAKE(enumfunc0); + RESET_FAKE(structfunc0); + RESET_FAKE(voidfunc3var); + RESET_FAKE(strlcpy3); + FFF_RESET_HISTORY(); +} + + +#include "test_cases.include" + +TEST_F(FFFTestSuite, default_constants_can_be_overridden) +{ + unsigned sizeCallHistory = (sizeof fff.call_history) / (sizeof fff.call_history[0]); + ASSERT_EQ(OVERRIDE_CALL_HIST_LEN, sizeCallHistory); + ASSERT_EQ(OVERRIDE_ARG_HIST_LEN, voidfunc2_fake.arg_history_len); +} + +DEFINE_FFF_GLOBALS; +int main() +{ + setbuf(stdout, NULL); + fprintf(stdout, "-------------\n"); + fprintf(stdout, "Running Tests\n"); + fprintf(stdout, "-------------\n\n"); + fflush(0); + + /* Run tests */ + RUN_TEST(FFFTestSuite, when_void_func_never_called_then_callcount_is_zero); + RUN_TEST(FFFTestSuite, when_void_func_called_once_then_callcount_is_one); + RUN_TEST(FFFTestSuite, when_void_func_called_once_and_reset_then_callcount_is_zero); + RUN_TEST(FFFTestSuite, when_void_func_with_1_integer_arg_called_then_last_arg_captured); + RUN_TEST(FFFTestSuite, when_void_func_with_1_integer_arg_called_twice_then_last_arg_captured); + RUN_TEST(FFFTestSuite, when_void_func_with_1_integer_arg_called_and_reset_then_captured_arg_is_zero); + RUN_TEST(FFFTestSuite, when_void_func_with_2_char_args_called_then_last_args_captured); + RUN_TEST(FFFTestSuite, when_void_func_with_2_char_args_called_twice_then_last_args_captured); + RUN_TEST(FFFTestSuite, when_void_func_with_2_char_args_called_and_reset_then_captured_arg_is_zero); + RUN_TEST(FFFTestSuite, when_fake_func_called_then_const_arguments_captured); + + RUN_TEST(FFFTestSuite, when_fake_func_created_default_history_is_fifty_calls); + RUN_TEST(FFFTestSuite, when_fake_func_called_then_arguments_captured_in_history); + RUN_TEST(FFFTestSuite, argument_history_is_reset_when_RESET_FAKE_called); + RUN_TEST(FFFTestSuite, when_fake_func_called_max_times_then_no_argument_histories_dropped); + RUN_TEST(FFFTestSuite, when_fake_func_called_max_times_plus_one_then_one_argument_history_dropped); + + RUN_TEST(FFFTestSuite, value_func_will_return_zero_by_default); + RUN_TEST(FFFTestSuite, value_func_will_return_value_given); + RUN_TEST(FFFTestSuite, value_func_will_return_zero_after_reset); + RUN_TEST(FFFTestSuite, register_call_macro_registers_one_call); + RUN_TEST(FFFTestSuite, register_call_macro_registers_two_calls); + RUN_TEST(FFFTestSuite, reset_call_history_resets_call_history); + RUN_TEST(FFFTestSuite, call_history_will_not_write_past_array_bounds); + RUN_TEST(FFFTestSuite, calling_fake_registers_one_call); + + RUN_TEST(FFFTestSuite, return_value_sequences_not_exhausted); + RUN_TEST(FFFTestSuite, return_value_sequences_exhausted); + RUN_TEST(FFFTestSuite, default_constants_can_be_overridden); + + RUN_TEST(FFFTestSuite, can_register_custom_fake); + RUN_TEST(FFFTestSuite, when_value_custom_fake_called_THEN_it_returns_custom_return_value); + + RUN_TEST(FFFTestSuite, use_vararg_fake_with_different_number_of_arguments); + + RUN_TEST(FFFTestSuite, can_capture_upto_20_arguments_correctly); + + printf("\n-------------\n"); + printf("Complete\n"); + printf("-------------\n\n"); + + return 0; +} diff --git a/plugins/fff/vendor/fff/test/fff_test_cpp.cpp b/plugins/fff/vendor/fff/test/fff_test_cpp.cpp new file mode 100644 index 000000000..1fe633687 --- /dev/null +++ b/plugins/fff/vendor/fff/test/fff_test_cpp.cpp @@ -0,0 +1,53 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + + +/* + * fff_test.cpp + * + * Created on: Dec 20, 2010 + * Author: mlong + */ + +// Want to keep the argument history for 13 calls +#define OVERRIDE_ARG_HIST_LEN 13u +#define FFF_ARG_HISTORY_LEN OVERRIDE_ARG_HIST_LEN +// Want to keep the call sequence history for 17 function calls +#define OVERRIDE_CALL_HIST_LEN 17u +#define FFF_CALL_HISTORY_LEN OVERRIDE_CALL_HIST_LEN + +#include "../fff.h" +#include + +DEFINE_FFF_GLOBALS + +FAKE_VOID_FUNC(voidfunc1, int); +FAKE_VOID_FUNC(voidfunc2, char, char); +FAKE_VALUE_FUNC(long, longfunc0); +FAKE_VOID_FUNC(voidfunc20, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int); + +class FFFTestSuite: public testing::Test +{ +public: + void SetUp() + { + RESET_FAKE(voidfunc1); + RESET_FAKE(voidfunc2); + RESET_FAKE(longfunc0); + FFF_RESET_HISTORY(); + } +}; + +#include "test_cases.include" + +TEST_F(FFFTestSuite, default_constants_can_be_overridden) +{ + unsigned sizeCallHistory = (sizeof fff.call_history) / (sizeof fff.call_history[0]); + ASSERT_EQ(OVERRIDE_CALL_HIST_LEN, sizeCallHistory); + ASSERT_EQ(OVERRIDE_ARG_HIST_LEN, voidfunc2_fake.arg_history_len); +} + diff --git a/plugins/fff/vendor/fff/test/fff_test_global_c.c b/plugins/fff/vendor/fff/test/fff_test_global_c.c new file mode 100644 index 000000000..5be8870b9 --- /dev/null +++ b/plugins/fff/vendor/fff/test/fff_test_global_c.c @@ -0,0 +1,84 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + + + +#include "global_fakes.h" +#include "c_test_framework.h" + + + +DEFINE_FFF_GLOBALS; + +void setup() +{ + RESET_FAKE(voidfunc1); + RESET_FAKE(voidfunc2); + RESET_FAKE(longfunc0); + RESET_FAKE(enumfunc0); + RESET_FAKE(structfunc0); + RESET_FAKE(voidfunc3var); + RESET_FAKE(strlcpy3); + + FFF_RESET_HISTORY(); +} + + +#include "test_cases.include" + + +int main() +{ + setbuf(stdout, NULL); + fprintf(stdout, "-------------\n"); + fprintf(stdout, "Running Tests\n"); + fprintf(stdout, "-------------\n\n"); + fflush(0); + + /* Run tests */ + RUN_TEST(FFFTestSuite, when_void_func_never_called_then_callcount_is_zero); + RUN_TEST(FFFTestSuite, when_void_func_called_once_then_callcount_is_one); + RUN_TEST(FFFTestSuite, when_void_func_called_once_and_reset_then_callcount_is_zero); + RUN_TEST(FFFTestSuite, when_void_func_with_1_integer_arg_called_then_last_arg_captured); + RUN_TEST(FFFTestSuite, when_void_func_with_1_integer_arg_called_twice_then_last_arg_captured); + RUN_TEST(FFFTestSuite, when_void_func_with_1_integer_arg_called_and_reset_then_captured_arg_is_zero); + RUN_TEST(FFFTestSuite, when_void_func_with_2_char_args_called_then_last_args_captured); + RUN_TEST(FFFTestSuite, when_void_func_with_2_char_args_called_twice_then_last_args_captured); + RUN_TEST(FFFTestSuite, when_void_func_with_2_char_args_called_and_reset_then_captured_arg_is_zero); + RUN_TEST(FFFTestSuite, when_fake_func_called_then_const_arguments_captured); + + RUN_TEST(FFFTestSuite, when_fake_func_created_default_history_is_fifty_calls); + RUN_TEST(FFFTestSuite, when_fake_func_called_then_arguments_captured_in_history); + RUN_TEST(FFFTestSuite, argument_history_is_reset_when_RESET_FAKE_called); + RUN_TEST(FFFTestSuite, when_fake_func_called_max_times_then_no_argument_histories_dropped); + RUN_TEST(FFFTestSuite, when_fake_func_called_max_times_plus_one_then_one_argument_history_dropped); + + RUN_TEST(FFFTestSuite, value_func_will_return_zero_by_default); + RUN_TEST(FFFTestSuite, value_func_will_return_value_given); + RUN_TEST(FFFTestSuite, value_func_will_return_zero_after_reset); + RUN_TEST(FFFTestSuite, register_call_macro_registers_one_call); + RUN_TEST(FFFTestSuite, register_call_macro_registers_two_calls); + RUN_TEST(FFFTestSuite, reset_call_history_resets_call_history); + RUN_TEST(FFFTestSuite, call_history_will_not_write_past_array_bounds); + RUN_TEST(FFFTestSuite, calling_fake_registers_one_call); + + RUN_TEST(FFFTestSuite, return_value_sequences_not_exhausted); + RUN_TEST(FFFTestSuite, return_value_sequences_exhausted); + + RUN_TEST(FFFTestSuite, can_register_custom_fake); + RUN_TEST(FFFTestSuite, when_value_custom_fake_called_THEN_it_returns_custom_return_value); + + RUN_TEST(FFFTestSuite, use_vararg_fake_with_different_number_of_arguments); + + RUN_TEST(FFFTestSuite, can_capture_upto_20_arguments_correctly); + + printf("\n-------------\n"); + printf("Complete\n"); + printf("-------------\n\n"); + + return 0; +} diff --git a/plugins/fff/vendor/fff/test/fff_test_global_cpp.cpp b/plugins/fff/vendor/fff/test/fff_test_global_cpp.cpp new file mode 100644 index 000000000..3ffa55f5b --- /dev/null +++ b/plugins/fff/vendor/fff/test/fff_test_global_cpp.cpp @@ -0,0 +1,31 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + + + +extern "C"{ + #include "global_fakes.h" +} +#include + +DEFINE_FFF_GLOBALS; + +class FFFTestSuite: public testing::Test +{ +public: + void SetUp() + { + RESET_FAKE(voidfunc1); + RESET_FAKE(voidfunc2); + RESET_FAKE(longfunc0); + FFF_RESET_HISTORY(); + } +}; + +#include "test_cases.include" + + diff --git a/plugins/fff/vendor/fff/test/global_fakes.c b/plugins/fff/vendor/fff/test/global_fakes.c new file mode 100644 index 000000000..90dc763b7 --- /dev/null +++ b/plugins/fff/vendor/fff/test/global_fakes.c @@ -0,0 +1,21 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + + +#include "global_fakes.h" +#include // for memcpy + +DEFINE_FAKE_VOID_FUNC1(voidfunc1, int); +DEFINE_FAKE_VOID_FUNC2(voidfunc2, char, char); +DEFINE_FAKE_VALUE_FUNC0(long, longfunc0); +DEFINE_FAKE_VALUE_FUNC0(enum MYBOOL, enumfunc0); +DEFINE_FAKE_VALUE_FUNC0(struct MyStruct, structfunc0); +DEFINE_FAKE_VOID_FUNC3_VARARG(voidfunc3var, const char *, int, ...); +#ifndef __cplusplus +DEFINE_FAKE_VALUE_FUNC3(int, strlcpy3, char* const, const char* const, const size_t); +#endif /* __cplusplus */ +DEFINE_FAKE_VOID_FUNC20(voidfunc20, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int); diff --git a/plugins/fff/vendor/fff/test/global_fakes.h b/plugins/fff/vendor/fff/test/global_fakes.h new file mode 100644 index 000000000..f7a3e3292 --- /dev/null +++ b/plugins/fff/vendor/fff/test/global_fakes.h @@ -0,0 +1,47 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + + + + + +#ifndef GLOBAL_FAKES_H_ +#define GLOBAL_FAKES_H_ + +#include "../fff.h" +#include "string.h" + + +//// Imaginary production code header file /// +void voidfunc1(int); +void voidfunc2(char, char); +long longfunc0(); +void voidfunc3var(const char *fmt, int argc, ...); +void voidfunc20(int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int); + +enum MYBOOL { FALSE = 899, TRUE }; +struct MyStruct { + int x; + int y; +}; +enum MYBOOL enumfunc(); +struct MyStruct structfunc(); +//// End Imaginary production code header file /// + +DECLARE_FAKE_VOID_FUNC1(voidfunc1, int); +DECLARE_FAKE_VOID_FUNC2(voidfunc2, char, char); +DECLARE_FAKE_VALUE_FUNC0(long, longfunc0); +DECLARE_FAKE_VALUE_FUNC0(enum MYBOOL, enumfunc0); +DECLARE_FAKE_VALUE_FUNC0(struct MyStruct, structfunc0); +DECLARE_FAKE_VOID_FUNC3_VARARG(voidfunc3var, const char *, int, ...); +DECLARE_FAKE_VOID_FUNC20(voidfunc20, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int); + +#ifndef __cplusplus +int strlcpy3(char* const, const char* const, const size_t); +DECLARE_FAKE_VALUE_FUNC3(int, strlcpy3, char* const, const char* const, const size_t); +#endif /* __cplusplus */ +#endif /* GLOBAL_FAKES_H_ */ diff --git a/plugins/fff/vendor/fff/test/test_cases.include b/plugins/fff/vendor/fff/test/test_cases.include new file mode 100644 index 000000000..b5ba7931d --- /dev/null +++ b/plugins/fff/vendor/fff/test/test_cases.include @@ -0,0 +1,276 @@ + + +TEST_F(FFFTestSuite, when_void_func_never_called_then_callcount_is_zero) +{ + ASSERT_EQ(voidfunc1_fake.call_count, 0u); +} + +TEST_F(FFFTestSuite, when_void_func_called_once_then_callcount_is_one) +{ + voidfunc1(66); + ASSERT_EQ(voidfunc1_fake.call_count, 1u); +} + +TEST_F(FFFTestSuite, when_void_func_called_once_and_reset_then_callcount_is_zero) +{ + voidfunc1(66); + RESET_FAKE(voidfunc1); + ASSERT_EQ(voidfunc1_fake.call_count, 0u); +} + +// Single Argument +TEST_F(FFFTestSuite, when_void_func_with_1_integer_arg_called_then_last_arg_captured) +{ + voidfunc1(77); + ASSERT_EQ(voidfunc1_fake.arg0_val, 77); +} + +TEST_F(FFFTestSuite, when_void_func_with_1_integer_arg_called_twice_then_last_arg_captured) +{ + voidfunc1(77); + voidfunc1(12); + ASSERT_EQ(voidfunc1_fake.arg0_val, 12); +} + +TEST_F(FFFTestSuite, when_void_func_with_1_integer_arg_called_and_reset_then_captured_arg_is_zero) +{ + voidfunc1(11); + RESET_FAKE(voidfunc1); + ASSERT_EQ(voidfunc1_fake.arg0_val, 0); +} + +// Two Arguments +TEST_F(FFFTestSuite, when_void_func_with_2_char_args_called_then_last_args_captured) +{ + voidfunc2('a', 'b'); + ASSERT_EQ(voidfunc2_fake.arg0_val, 'a'); + ASSERT_EQ(voidfunc2_fake.arg1_val, 'b'); +} + +TEST_F(FFFTestSuite, when_void_func_with_2_char_args_called_twice_then_last_args_captured) +{ + voidfunc2('a', 'b'); + voidfunc2('c', 'd'); + ASSERT_EQ(voidfunc2_fake.arg0_val, 'c'); + ASSERT_EQ(voidfunc2_fake.arg1_val, 'd'); +} + +TEST_F(FFFTestSuite, when_void_func_with_2_char_args_called_and_reset_then_captured_arg_is_zero) +{ + voidfunc2('e', 'f'); + RESET_FAKE(voidfunc2); + ASSERT_EQ(voidfunc2_fake.arg0_val, 0); + ASSERT_EQ(voidfunc2_fake.arg1_val, 0); +} + +#ifndef __cplusplus +TEST_F(FFFTestSuite, when_fake_func_called_then_const_arguments_captured) +{ + char dst[80]; + strlcpy3(dst, __FUNCTION__, sizeof(__FUNCTION__)); +} +#endif /* __cplusplus */ + +// Argument history +TEST_F(FFFTestSuite, when_fake_func_created_default_history_is_fifty_calls) +{ + ASSERT_EQ(FFF_ARG_HISTORY_LEN, (sizeof voidfunc2_fake.arg0_history) / (sizeof voidfunc2_fake.arg0_history[0])); + ASSERT_EQ(FFF_ARG_HISTORY_LEN, (sizeof voidfunc2_fake.arg1_history) / (sizeof voidfunc2_fake.arg1_history[0])); +} + +TEST_F(FFFTestSuite, when_fake_func_called_then_arguments_captured_in_history) +{ + voidfunc2('g', 'h'); + ASSERT_EQ('g', voidfunc2_fake.arg0_history[0]); + ASSERT_EQ('h', voidfunc2_fake.arg1_history[0]); +} + +TEST_F(FFFTestSuite, argument_history_is_reset_when_RESET_FAKE_called) +{ + //given + voidfunc2('g', 'h'); + ASSERT_EQ('g', voidfunc2_fake.arg0_history[0]); + ASSERT_EQ('h', voidfunc2_fake.arg1_history[0]); + //when + RESET_FAKE(voidfunc2); + //then + ASSERT_EQ('\0', voidfunc2_fake.arg0_history[0]); + ASSERT_EQ('\0', voidfunc2_fake.arg1_history[0]); +} + +TEST_F(FFFTestSuite, when_fake_func_called_max_times_then_no_argument_histories_dropped) +{ + unsigned int i; + for (i = 0; i < FFF_ARG_HISTORY_LEN; i++) + { + voidfunc2('1' + i, '2' + i); + } + ASSERT_EQ(0u, voidfunc2_fake.arg_histories_dropped); +} + +TEST_F(FFFTestSuite, when_fake_func_called_max_times_plus_one_then_one_argument_history_dropped) +{ + unsigned int i; + for (i = 0; i < FFF_ARG_HISTORY_LEN; i++) + { + voidfunc2('1' + i, '2' + i); + } + voidfunc2('1', '2'); + ASSERT_EQ(1u, voidfunc2_fake.arg_histories_dropped); + // or in other words.. + ASSERT_TRUE(voidfunc2_fake.call_count > voidfunc2_fake.arg_history_len); +} + +// Return values +TEST_F(FFFTestSuite, value_func_will_return_zero_by_default) +{ + ASSERT_EQ(0l, longfunc0()); +} + +TEST_F(FFFTestSuite, value_func_will_return_value_given) +{ + longfunc0_fake.return_val = 99l; + ASSERT_EQ(99l, longfunc0()); +} + +TEST_F(FFFTestSuite, value_func_will_return_zero_after_reset) +{ + longfunc0_fake.return_val = 99l; + RESET_FAKE(longfunc0); + ASSERT_EQ(0l, longfunc0()); +} + +TEST_F(FFFTestSuite, register_call_macro_registers_one_call) +{ + REGISTER_CALL(longfunc0); + ASSERT_EQ(fff.call_history[0], (void *)longfunc0); +} + +TEST_F(FFFTestSuite, register_call_macro_registers_two_calls) +{ + REGISTER_CALL(longfunc0); + REGISTER_CALL(voidfunc2); + + ASSERT_EQ(fff.call_history[0], (void *)longfunc0); + ASSERT_EQ(fff.call_history[1], (void *)voidfunc2); +} + +TEST_F(FFFTestSuite, reset_call_history_resets_call_history) +{ + REGISTER_CALL(longfunc0); + FFF_RESET_HISTORY(); + REGISTER_CALL(voidfunc2); + + ASSERT_EQ(1u, fff.call_history_idx); + ASSERT_EQ(fff.call_history[0], (void *)voidfunc2); +} + +TEST_F(FFFTestSuite, call_history_will_not_write_past_array_bounds) +{ + for (unsigned int i = 0; i < FFF_CALL_HISTORY_LEN + 1; i++) + { + REGISTER_CALL(longfunc0); + } + ASSERT_EQ(FFF_CALL_HISTORY_LEN, fff.call_history_idx); +} + +TEST_F(FFFTestSuite, calling_fake_registers_one_call) +{ + longfunc0(); + ASSERT_EQ(fff.call_history_idx, 1u); + ASSERT_EQ(fff.call_history[0], (void *)longfunc0); +} + +TEST_F(FFFTestSuite, return_value_sequences_not_exhausted) +{ + long myReturnVals[3] = { 3, 7, 9 }; + SET_RETURN_SEQ(longfunc0, myReturnVals, 3); + ASSERT_EQ(myReturnVals[0], longfunc0()); + ASSERT_EQ(myReturnVals[1], longfunc0()); + ASSERT_EQ(myReturnVals[2], longfunc0()); +} + + +TEST_F(FFFTestSuite, return_value_sequences_exhausted) +{ + long myReturnVals[3] = { 3, 7, 9 }; + SET_RETURN_SEQ(longfunc0, myReturnVals, 3); + ASSERT_EQ(myReturnVals[0], longfunc0()); + ASSERT_EQ(myReturnVals[1], longfunc0()); + ASSERT_EQ(myReturnVals[2], longfunc0()); + ASSERT_EQ(myReturnVals[2], longfunc0()); + ASSERT_EQ(myReturnVals[2], longfunc0()); +} + +TEST_F(FFFTestSuite, return_value_sequences_reset) +{ + long myReturnVals[3] = { 3, 7, 9 }; + SET_RETURN_SEQ(longfunc0, myReturnVals, 3); + ASSERT_EQ(myReturnVals[0], longfunc0()); + ASSERT_EQ(myReturnVals[1], longfunc0()); + RESET_FAKE(longfunc0); + ASSERT_EQ(0, longfunc0()); +} + +static int my_custom_fake_called = 0; +void my_custom_fake(char a, char b) +{ + my_custom_fake_called++; +} + +TEST_F(FFFTestSuite, can_register_custom_fake) +{ + voidfunc2_fake.custom_fake = my_custom_fake; + voidfunc2('a', 'b'); + ASSERT_EQ(1, my_custom_fake_called); +} + +//DECLARE_FAKE_VALUE_FUNC0(long, longfunc0); +#define MEANING_OF_LIFE 42 +long my_custom_value_fake(void) +{ + return MEANING_OF_LIFE; +} +TEST_F(FFFTestSuite, when_value_custom_fake_called_THEN_it_returns_custom_return_value) +{ + longfunc0_fake.custom_fake = my_custom_value_fake; + long retval = longfunc0(); + ASSERT_EQ(MEANING_OF_LIFE, retval); +} + +#ifndef __cplusplus +TEST_F(FFFTestSuite, use_vararg_fake_with_different_number_of_arguments) +{ + voidfunc3var("0 parameters", 0); + voidfunc3var("1 parameter", 1, 10); + voidfunc3var("2 parameters", 2, 10, 20); + voidfunc3var("3 parameters", 3, 10, 20, 30); +} +#endif /* __cplusplus */ + +TEST_F(FFFTestSuite, can_capture_upto_20_arguments_correctly) +{ + voidfunc20(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19); + ASSERT_EQ(0, voidfunc20_fake.arg0_val); + ASSERT_EQ(1, voidfunc20_fake.arg1_val); + ASSERT_EQ(2, voidfunc20_fake.arg2_val); + ASSERT_EQ(3, voidfunc20_fake.arg3_val); + ASSERT_EQ(4, voidfunc20_fake.arg4_val); + ASSERT_EQ(5, voidfunc20_fake.arg5_val); + ASSERT_EQ(6, voidfunc20_fake.arg6_val); + ASSERT_EQ(7, voidfunc20_fake.arg7_val); + ASSERT_EQ(8, voidfunc20_fake.arg8_val); + ASSERT_EQ(9, voidfunc20_fake.arg9_val); + ASSERT_EQ(10, voidfunc20_fake.arg10_val); + ASSERT_EQ(11, voidfunc20_fake.arg11_val); + ASSERT_EQ(12, voidfunc20_fake.arg12_val); + ASSERT_EQ(13, voidfunc20_fake.arg13_val); + ASSERT_EQ(14, voidfunc20_fake.arg14_val); + ASSERT_EQ(15, voidfunc20_fake.arg15_val); + ASSERT_EQ(16, voidfunc20_fake.arg16_val); + ASSERT_EQ(17, voidfunc20_fake.arg17_val); + ASSERT_EQ(18, voidfunc20_fake.arg18_val); + ASSERT_EQ(19, voidfunc20_fake.arg19_val); +} + + diff --git a/plugins/gcov/README.md b/plugins/gcov/README.md index 99c944d02..654b7b1cf 100644 --- a/plugins/gcov/README.md +++ b/plugins/gcov/README.md @@ -1,122 +1,515 @@ -ceedling-gcov -============= +# Ceedling Plugin: Gcov + +This plugin integrates the code coverage abilities of the GNU compiler +collection with test builds. It provides simple coverage metrics by default and +can optionally produce sophisticated coverage reports. # Plugin Overview -Plugin for integrating GNU GCov code coverage tool into Ceedling projects. -Currently only designed for the gcov command (like LCOV for example). In the -future we could configure this to work with other code coverage tools. +When enabled, this plugin creates a new set of `gcov:` tasks that mirror +Ceedling's existing `test:` tasks. A `gcov:` task executes one or more tests +with coverage enabled for the source files exercised by those tests. + +This plugin also provides an extensive set of options for generating various +coverage reports for your project. The simplest is text-based coverage +summaries printed to the console after a `gcov:` test task is executed. + +This document details configuration, reporting options, and provides basic +[troubleshooting help][troubleshooting]. + +[troubleshooting]: #advanced-configuration--troubleshooting + +# Simple Coverage Summaries + +In its simplest usage, this plugin outputs coverage statistics to the console +for each source file exercised by a test. These console-based coverage +summaries are provided after the standard Ceedling test results summary. Other +than enabling the plugin and ensuring `gcov` is installed, no further set up +is necessary to produce these summaries. + +_Note_: Automatic summaries may be disabled (see configuration options below). + +When the Gcov plugin is active it enables Ceedling tasks like this: + +```shell + > ceedling gcov:Model +``` + +
 that then generate output like this: + +``` +-------------------------- +GCOV: OVERALL TEST SUMMARY +-------------------------- +TESTED: 1 +PASSED: 1 +FAILED: 0 +IGNORED: 0 + +--------------------------- +GCOV: CODE COVERAGE SUMMARY +--------------------------- + +TestModel +--------- +Model.c | Lines executed:100.00% of 4 +Model.c | No branches +Model.c | No calls +TimerModel.c | Lines executed:0.00% of 3 +TimerModel.c | No branches +TimerModel.c | No calls +``` + +# Advanced Coverage Reports + +For more advanced visualizations and reporting, this plugin also supports a +variety of report generation options. + +Advanced report generation uses [gcovr] and / or [ReportGenerator] to generate +HTML, XML, JSON, or text-based reports from coverage-instrumented test runs. +See the tools' respective sites for examples of the reports they can generate. + +In the default configuration, if reports are enabled, this plugin automatically +generates reports in the build's `artifacts/` directory after each execution of +a `gcov:` task. + +An optional setting documented below disables automatic report generation, +providing a separate Ceedling task instead. Reports can then be generated +on demand after test suite runs. + +[gcovr]: https://www.gcovr.com/ +[ReportGenerator]: https://reportgenerator.io + +# Important Notes on Coverage Summaries vs. Coverage Reports + +Coverage summaries and coverage reports provide different levels of fidelity +and usability. Summaries are relatively unsophisticated while reports are +sophisticated. As such, both provide different capabilities and levels of +usability. + +## Coverage summaries + +Optional coverage summaries are intentionally simple. They require no +configuration and, to oversimplify, are largely filtered output from the `gcov` +tool. + +Coverage summaries are reported to the console for each source file exercised by +the tests executed by `gcov:` tasks. That is, coverage summaries correspond to +the tests executed, and in turn, the source code that your tests call. This +could be all tests (and thus all source code) or a subset of tests (and some +subset of source code). The `gcov` tool is run multiple times after test suite +execution in direct relation to the set of tests you ran with `gcov:` testing +tasks. In short, the scope of coverage summaries is guaranteed to match the +test suite you run. + +Coverage summaries do not include any sort of grand total, final tallies. This +is the domain of full coverage reports. + +Note that Ceedling can exercise the same source code under multiple scenarios +using multiple test files. Practically, this means that the same source file +may be listed in the coverage summaries more than once. That said, its coverage +statistics will be the same each time — the aggregate result of all tests that +exercised it. + +## Coverage reports + +Coverage reports provide both much more detail and better overviews of coverage +than the console-based coverage summaries. However, with this comes the need +for more sophisticated configuration and certain caveats on what is reported. + +Later sections detail how to configure the reports this plugin can generate. + +Of note is a consequence of how reports are generated and the limits of the +tools that do so. Reports are generated using coverage results on disk. The +report generation tools slurp up the coverage results they find in the `gcov/` +build output directory. This means that previous test suite runs can “pollute” +coverage reports. The solution is simple if blunt — run the `clobber` task +before running a coverage-instrumented test suite. This will yield a coverage +report with scope that matches that of the test suite you run. + +Both the `gcovr` and `reportgeneator` reporting utilities include powerful +filters that can limit the scope of reports. Hypothetically, it's possible for +coverage reports to have the same clear scope as coverage summaries. However, +in large projects, these filters would cause impractically long command lines. +Both tools provide configuration file options that would solve the command line +problem. However, this feature is “experimental” for `gcovr` and considerable +work to implement for both reporting utilities. At present, running +`ceedling clobber` before generating reports is the best option to ensure +accurate reports. + +# Plugin Set Up & Configuration + +## Supported tool versions [May 10, 2024] + +At the time of the last major updates to the Gcov plugin, the following notes +on version compatibility were known to be accurate. + +Keep in mind that for proper functioning, you do not necessarily need to +install all the tooks the Gcov plugin works with. Depending on configuration +options documented in later sections, any of the following tool combinations +may be sufficient for your needs: + +1. `gcov` +1. `gcov` + `gcovr` +1. `gcov` + `reportgenerator` +1. `gcov` + `gcovr` + `reportgenerator` + +### `gcov` + +The Gcov plugin is known to work with `gcov` packaged with GNU Compiler +Collection 12.2 and should work with versions through at least 14. + +The maintainers of `gcov` introduced significant behavioral changes for version +12. Previous versions of `gcov` had a simple exit code scheme with only a +single non-zero exit code upon fatal errors. Since version 12 `gcov` emits a +variety of exit codes even if the noted issue is a non-fatal error. The Gcov +plugin’s logic assumes version 12 behavior and processes failure messages and +exit codes appropriately, taking into account plugin configuration options. + +The Gcov plugin should be compatible with versions of `gcov` before version 12. +That is, its improved `gcov` exit handling should not be broken by the prior +simpler behavior. The Gcov plugin dependes on the `gcov` command line and has +been compatible with it as far back as `gcov` version 7. + +Because long file paths are quite common in software development scenarios, by +default, the Gcov plugin depends on the `gcov` `-x` flag. This flag hashes long +file paths to ensure they are not a problem for certain platforms' file +systems. This flag became available with `gcov` version 7. At the time of this +README section’s last update, the GNU Compiler Collection was at version 14. We +do not recommend using `gcov` version 6 and earlier. And, in fact, because of +the Gcov plugin’s dependence on the `gcov` `-x` flag, attempting to use it will +fail. + +### `gcovr` + +The Gcov plugin is known to work with `gcovr` 5.2 through `gcovr` 6.x. The +Gcov plugin supports `gcovr` command line conventions since version 4.2 and +attempts to support `gcovr` command lines before version 4.2. We recommend +using `gcovr` 5 and later. + +### `reportgenerator` + +The Gcov plugin is known to work with `reportgenerator` 5.2.4. The command line +for executing `reportgenerator` that the Gcov plugin relies on has largely been +stable since version 4. We recommend using `reportgenerator` 5.0 and later. + +## Toolchain dependencies + +### GNU Compiler Collection + +This plugin relies on the GNU compiler collection. Coverage instrumentation +is enabled through `gcc` compiler flags. Coverage-insrumented executables +(i.e. test suites) output coverage result files to disk when run. `gcov`, +`gcovr`, and `reportgenerator` (the tools managed by this plugin) all produce +their coverage tallies from these files. `gcov` is part of the GNU compiler +collection. The other tools — detailed below — require separate installation. + +Ceedling's default toolchain is the same as needed by this plugin. If you +are already running Ceedling test suites with the GNU compiler toolchain, +you are good to go. If you are using another toolchain for test suite and/or +release builds you will need to install the GNU compiler collection to use +this plugin. Depending on your needs you may also need to install the reporting +utilities, `gcovr` and/or `reportgenerator`. -This plugin currently uses [gcovr](https://www.gcovr.com/) and / or -[ReportGenerator](https://danielpalme.github.io/ReportGenerator/) -as utilities to generate HTML, XML, JSON, or Text reports. The normal gcov -plugin _must_ be run first for these reports to generate. +### `gcovr` and `reportgenerator`’s dependence on `gcov` -## Installation +Both the `gcovr` and `reportgenerator` tools depend on the `gcov` tool. This +dependency plays out in two different ways. In both cases, the report +generation utilities ingest `gcov`'s output to produce their artifacts. As +such, `gcov` must be available in your environment if using report generation. -gcovr can be installed via pip like so: +1. `gcovr` calls `gcov` directly. + + Because it calls `gcov` directly, you are limited as to the + advanced Ceedling features you can employ to modify `gcov`'s execution. + However, with a configuration option (see below) you can instruct `gcovr` + to call something other than `gcov` (e.g. a script that intercepts and + modifies how `gcovr` calls out to `gcov`). -```sh -pip install gcovr + `gcovr` instructs `gcov` to generate `.gcov` files that it processes and + discards. A `gcovr` option documented below will retain the `.gcov` files. + +2. `reportgenerator` expects the existence of `.gcov` files to do its work. + This Ceedling plugin calls `gcov` appropriately to generate the `.gcov` + files `reportgenerator` needs before then calling the report utility. + + You can use Ceedling's features to modify how `gcov` is run before + `reportgenerator`. + +## Enable this plugin + +To use this plugin it must be enabled in your Ceedling project file: + +```yaml +:plugins: + :enabled: + - gcov +``` + +This simple configuration will create new `gcov:` tasks to run tests with +source coverage and output simple coverage summaries to the console as above. + +## Disabling automatic coverage summaries + +To disable the coverage summaries generated immediately following `gcov:` tasks, +simply add the following to a top-level `:gcov:` section in your project +configuration file. + +```yaml +:plugins: + :enabled: + - gcov + +:gcov: + :summaries: FALSE ``` -ReportGenerator can be installed via .NET Core like so: +## Report generation + +To generate reports: + +1. GCovr and / or ReportGenerator must installed or otherwise ready to run in + Ceedling's environment. +1. Reporting options must be configured in your project file beneath a `:gcov:` + entry. + +The next sections explain each of these steps. + +### Installation of report generation utilities + +[gcovr] is available on any platform supported by Python. + +`gcovr` can be installed via pip like this: + +```shell + > pip install gcovr +``` + +[ReportGenerator] is available on any platform supported by .Net. + +`ReportGenerator` can be installed via .NET Core like so: + +```shell + > dotnet tool install -g dotnet-reportgenerator-globaltool +``` + +Either or both of `gcovr` or `ReportGenerator` may be used. Only one must +be installed for advanced report generation. + +## Enabling report generation utilities + +If reports are configured (see next sections) but no `:utilities:` subsection +exists, this plugin defaults to using `gcovr` for report generation. + +Otherwise, enable Gcovr and / or ReportGenerator to create coverage reports. + +```yaml +:gcov: + :utilities: + - gcovr # Use `gcovr` to create reports (default if no :utilities set). + - ReportGenerator # Use `ReportGenerator` to create reports. +``` + +## Automatic and manual report generation + +By default, if reports are specified, this plugin automatically generates +reports after any `gcov:` task is executed. To disable this behavior, add +`:report_task: TRUE` to your project file's `:gcov:` configuration. + +With this setting enabled, an additional Ceedling task `report:gcov` is enabled. +It may be executed after `gcov:` tasks to generate the configured reports. + +For small projects, the default behavior is likely preferred. This alernative +setting allows large or complex projects to execute potentially time intensive +report generation only when desired. -```sh -dotnet tool install -g dotnet-reportgenerator-globaltool +Enabling the manual report generation task looks like this: + +```yaml +:gcov: + :report_task: TRUE ``` -It is not required to install both `gcovr` and `ReportGenerator`. Either utility -may be installed to create reports. +# Example Usage + +_Note_: Unless disabled, basic coverage summaries are always printed to the +console regardless of report generation options. + +## Automatic report generation (default) + +If coverage report generation is configured, the plugin defaults to running +reports after any `gcov:` task. + +```yaml +:plugins: + :enabled: + - gcov + +:gcov: + :utilities: + - gcovr # Enabled by default -- shown for completeness + :report_task: FALSE # Disabled by default -- shown for completeness + :reports: # See later section for report configuration + - HtmlBasic + + ... # Further configuration for reporting (not shown) -## Configuration +``` -The gcov plugin supports configuration options via your `project.yml` provided -by Ceedling. +```shell + > ceedling gcov:all +``` -### Utilities +## Report generation configured as manual task -Gcovr and / or ReportGenerator may be enabled to create coverage reports. +If the `:report_task:` configuration option is enabled, reports are not +automatically generaed after test suite coverage builds. Instead, report +generation is triggered by the `report:gcov` task. ```yaml +:plugins: + :enabled: + - gcov + :gcov: :utilities: - - gcovr # Use gcovr to create the specified reports (default). - - ReportGenerator # Use ReportGenerator to create the specified reports. + - gcovr # Enabled by default -- shown for completeness + :report_task: TRUE + :reports: # See later section for report configuration + - HtmlBasic # Enabled by default -- shown for completeness + + ... # Further configuration for reporting (not shown) + ``` -### Reports +With the separate reporting task enabled, it can be used like any other Ceedling task. + +```shell + > ceedling gcov:all report:gcov +``` + +or + +```shell + > ceedling gcov:all + + > ceedling report:gcov +``` + +### Full report generation configuration example + +```yaml +:plugins: + :enabled: + - gcov + +:gcov: + :summaries: FALSE # Simple coverage summaries to console disabled + :reports: # `gcovr` tool enabled by default + - HtmlDetailed + - Text + - Cobertura + :gcovr: # `gcovr` common and report-specific options + :report_root: "../../" # Atypical layout -- project.yml is inside a subdirectoy below + :sort_percentage: TRUE + :sort_uncovered: FALSE + :html_medium_threshold: 60 + :html_high_threshold: 85 + :print_summary: TRUE + :threads: 4 + :keep: FALSE +``` + +# Report Generation Configuration + +Various reports are available. Each must be enabled in `:gcov` ↳ `:reports`. + +If no report types are specified, report generation (but not coverage summaries) +is disabled regardless of any other setting. + +Most report types can only be generated by `gcovr` or `ReportGenerator`. Some +can be generated by both. This means that your selection of report is impacted by +which generation utility is enabled. In fact, in some cases, the same report type +could be generated by each utility (to different artifact build output folders). + +Reports are configured with: + +1. General or common options for each report generation utility +1. Specific options for types of report per each report generation utility -Various reports are available and may be enabled with the following -configuration item. See the specific report sections in this README -for additional options and information. All generated reports will be found in `build/artifacts/gcov`. +These are detailed in the sections that follow. See the +[GCovr User Guide][gcovr-user-guide] and the +[ReportGenerator Wiki][report-generator-wiki] for full details. + +[gcovr-user-guide]: https://www.gcovr.com/en/stable/guide.html +[report-generator-wiki]: https://github.com/danielpalme/ReportGenerator/wiki ```yaml :gcov: # Specify one or more reports to generate. # Defaults to HtmlBasic. :reports: - # Make an HTML summary report. + # Generate an HTML summary report. # Supported utilities: gcovr, ReportGenerator - HtmlBasic - # Make an HTML report with line by line coverage of each source file. + # Generate an HTML report with line by line coverage of each source file. # Supported utilities: gcovr, ReportGenerator - HtmlDetailed - # Make a Text report, which may be output to the console with gcovr or a file in both gcovr and ReportGenerator. + # Generate a Text report, which may be output to the console with gcovr or a file in both gcovr and ReportGenerator. # Supported utilities: gcovr, ReportGenerator - Text - # Make a Cobertura XML report. + # Generate a Cobertura XML report. # Supported utilities: gcovr, ReportGenerator - Cobertura - # Make a SonarQube XML report. + # Generate a SonarQube XML report. # Supported utilities: gcovr, ReportGenerator - SonarQube - # Make a JSON report. + # Generate a JSON report. # Supported utilities: gcovr - JSON - # Make a detailed HTML report with CSS and JavaScript included in every HTML page. Useful for build servers. + # Generate a detailed HTML report with CSS and JavaScript included in every HTML page. Useful for build servers. # Supported utilities: ReportGenerator - HtmlInline - # Make a detailed HTML report with a light theme and CSS and JavaScript included in every HTML page for Azure DevOps. + # Generate a detailed HTML report with a light theme and CSS and JavaScript included in every HTML page for Azure DevOps. # Supported utilities: ReportGenerator - HtmlInlineAzure - # Make a detailed HTML report with a dark theme and CSS and JavaScript included in every HTML page for Azure DevOps. + # Generate a detailed HTML report with a dark theme and CSS and JavaScript included in every HTML page for Azure DevOps. # Supported utilities: ReportGenerator - HtmlInlineAzureDark - # Make a single HTML file containing a chart with historic coverage information. + # Generate a single HTML file containing a chart with historic coverage information. # Supported utilities: ReportGenerator - HtmlChart - # Make a detailed HTML report in a single file. + # Generate a detailed HTML report in a single file. # Supported utilities: ReportGenerator - MHtml - # Make SVG and PNG files that show line and / or branch coverage information. + # Generate SVG and PNG files that show line and / or branch coverage information. # Supported utilities: ReportGenerator - Badges - # Make a single CSV file containing coverage information per file. + # Generate a single CSV file containing coverage information per file. # Supported utilities: ReportGenerator - CsvSummary - # Make a single TEX file containing a summary for all files and detailed reports for each files. + # Generate a single TEX file containing a summary for all files and detailed reports for each files. # Supported utilities: ReportGenerator - Latex - # Make a single TEX file containing a summary for all files. + # Generate a single TEX file containing a summary for all files. # Supported utilities: ReportGenerator - LatexSummary - # Make a single PNG file containing a chart with historic coverage information. + # Generate a single PNG file containing a chart with historic coverage information. # Supported utilities: ReportGenerator - PngChart @@ -124,41 +517,32 @@ for additional options and information. All generated reports will be found in ` # Supported utilities: ReportGenerator - TeamCitySummary - # Make a text file in lcov format. + # Generate a text file in lcov format. # Supported utilities: ReportGenerator - lcov - # Make a XML file containing a summary for all classes and detailed reports for each class. + # Generate a XML file containing a summary for all classes and detailed reports for each class. # Supported utilities: ReportGenerator - Xml - # Make a single XML file containing a summary for all files. + # Generate a single XML file containing a summary for all files. # Supported utilities: ReportGenerator - XmlSummary ``` -### Gcovr HTML Reports +## Gcovr report output -Generation of Gcovr HTML reports may be modified with the following configuration items. +All reports generated by `gcovr` are found in `/artifacts/gcov/gcovr/`. -```yaml -:gcov: - # Set to 'true' to enable HTML reports or set to 'false' to disable. - # Defaults to enabled. (gcovr --html) - # Deprecated - See the :reports: configuration option. - :html_report: [true|false] - - # Gcovr supports generating two types of HTML reports. Use 'basic' to create - # an HTML report with only the overall file information. Use 'detailed' to create - # an HTML report with line by line coverage of each source file. - # Defaults to 'basic'. Set to 'detailed' for (gcovr --html-details). - # Deprecated - See the :reports: configuration option. - :html_report_type: [basic|detailed] +## Gcovr HTML reports +Generation of HTML reports may be modified with the following configuration items. +```yaml +:gcov: :gcovr: # HTML report filename. - :html_artifact_filename: + :html_artifact_filename: # Use 'title' as title for the HTML report. # Default is 'Head'. (gcovr --html-title) @@ -178,47 +562,39 @@ Generation of Gcovr HTML reports may be modified with the following configuratio # Set to 'true' to use absolute paths to link the 'detailed' reports. # Defaults to relative links. (gcovr --html-absolute-paths) - :html_absolute_paths: [true|false] + :html_absolute_paths: # Override the declared HTML report encoding. Defaults to UTF-8. (gcovr --html-encoding) :html_encoding: ``` -### Cobertura XML Reports +## Gcovr Cobertura XML reports Generation of Cobertura XML reports may be modified with the following configuration items. ```yaml :gcov: - # Set to 'true' to enable Cobertura XML reports or set to 'false' to disable. - # Defaults to disabled. (gcovr --xml) - # Deprecated - See the :reports: configuration option. - :xml_report: [true|false] - - :gcovr: # Set to 'true' to pretty-print the Cobertura XML report, otherwise set to 'false'. # Defaults to disabled. (gcovr --xml-pretty) - :xml_pretty: [true|false] - :cobertura_pretty: [true|false] + :cobertura_pretty: - # Cobertura XML report filename. - :xml_artifact_filename: - :cobertura_artifact_filename: + # Override default Cobertura XML report filename. + :cobertura_artifact_filename: ``` -### SonarQube XML Reports +## Gcovr SonarQube XML reports Generation of SonarQube XML reports may be modified with the following configuration items. ```yaml :gcov: :gcovr: - # SonarQube XML report filename. - :sonarqube_artifact_filename: + # Override default SonarQube XML report filename. + :sonarqube_artifact_filename: ``` -### JSON Reports +## Gcovr JSON reports Generation of JSON reports may be modified with the following configuration items. @@ -227,13 +603,13 @@ Generation of JSON reports may be modified with the following configuration item :gcovr: # Set to 'true' to pretty-print the JSON report, otherwise set 'false'. # Defaults to disabled. (gcovr --json-pretty) - :json_pretty: [true|false] + :json_pretty: - # JSON report filename. - :json_artifact_filename: + # Override default JSON report filename. + :json_artifact_filename: ``` -### Text Reports +## Gcovr text reports Generation of text reports may be modified with the following configuration items. Text reports may be printed to the console or output to a file. @@ -241,210 +617,277 @@ Text reports may be printed to the console or output to a file. ```yaml :gcov: :gcovr: - # Text report filename. - # The text report is printed to the console when no filename is provided. - :text_artifact_filename: + # Override default text report filename. + :text_artifact_filename: ``` -### Common Report Options +## Common gcovr options -There are a number of options to control which files are considered part of -the coverage report. Most often, we only care about coverage on our source code, and not -on tests or automatically generated mocks, runners, etc. However, there are times -where this isn't true... or there are times where we've moved ceedling's directory -structure so that the project file isn't at the root of the project anymore. In these -cases, you may need to tweak `report_include`, `report_exclude`, and `exclude_directories`. +A number of options exist to control which files are considered part of a +coverage report. This Ceedling gcov plugin itself handles the most important +aspect — only source files under test are compiled with coverage. Tests, mocks, +and test runners, are not compiled with coverage. -One important note about `report_root`: gcovr will take only a single root folder, unlike -Ceedling's ability to take as many as you like. So you will need to choose a folder which is -a superset of ALL the folders you want, and then use the include or exclude options to set up -patterns of files to pay attention to or ignore. It's not ideal, but it works. - -Finally, there are a number of settings which can be specified to adjust the -default behaviors of gcovr: +**Note:** `gcovr` will only accept a single path for `:report_root`. In typical +usage, this is of no concern as it is handled automatically. In unusual project +layouts, you may need to specify a folder that encompasses _all_ build folders +containing coverage result files and optionally, selectively exclude patterns +of paths or files. For instance, if your Ceedling project file is not at the +root of your project, you may need set `:report_root` as well as +`:report_exclude` and `:exclude_directories`. ```yaml :gcov: :gcovr: # The root directory of your source files. Defaults to ".", the current directory. # File names are reported relative to this root. The report_root is the default report_include. - :report_root: "." + # Default if unspecified: "." + :report_root: # Load the specified configuration file. # Defaults to gcovr.cfg in the report_root directory. (gcovr --config) :config_file: - # Exit with a status of 2 if the total line coverage is less than MIN. - # Can be ORed with exit status of 'fail_under_branch' option. (gcovr --fail-under-line) - :fail_under_line: 30 + # Exit with a status of 2 if the total line coverage is less than MIN percentage. + # Can be ORed with exit status of other fail options. (gcovr --fail-under-line) + :fail_under_line: <1-100> + + # Exit with a status of 4 if the total branch coverage is less than MIN percentage. + # Can be ORed with exit status of other fail options. (gcovr --fail-under-branch) + :fail_under_branch: <1-100> - # Exit with a status of 4 if the total branch coverage is less than MIN. - # Can be ORed with exit status of 'fail_under_line' option. (gcovr --fail-under-branch) - :fail_under_branch: 30 + # Exit with a status of 8 if the total decision coverage is less than MIN percentage. + # Can be ORed with exit status of other fail options. (gcovr --fail-under-decision) + :fail_under_decision: <1-100> + + # Exit with a status of 16 if the total function coverage is less than MIN percentage. + # Can be ORed with exit status of other fail options. (gcovr --fail-under-function) + :fail_under_function: <1-100> + + # If the fail options above are set, specify whether those conditions should break a build. + # The default option is false and simply logs a warning without breaking the build. + :exception_on_fail: # Select the source file encoding. # Defaults to the system default encoding (UTF-8). (gcovr --source-encoding) - :source_encoding: + :source_encoding: # Report the branch coverage instead of the line coverage. For text report only. (gcovr --branches). - :branches: [true|false] + :branches: # Sort entries by increasing number of uncovered lines. # For text and HTML report. (gcovr --sort-uncovered) - :sort_uncovered: [true|false] + :sort_uncovered: # Sort entries by increasing percentage of uncovered lines. # For text and HTML report. (gcovr --sort-percentage) - :sort_percentage: [true|false] + :sort_percentage: # Print a small report to stdout with line & branch percentage coverage. # This is in addition to other reports. (gcovr --print-summary). - :print_summary: [true|false] + :print_summary: # Keep only source files that match this filter. (gcovr --filter). - :report_include: "^src" + # Filters are regular expressions (ex: "^src") + :report_include: # Exclude source files that match this filter. (gcovr --exclude). - :report_exclude: "^vendor.*|^build.*|^test.*|^lib.*" + # Filters are regular expressions (ex: "^vendor.*|^build.*|^test.*|^lib.*") + :report_exclude: # Keep only gcov data files that match this filter. (gcovr --gcov-filter). - :gcov_filter: + # Filters are regular expressions + :gcov_filter: # Exclude gcov data files that match this filter. (gcovr --gcov-exclude). - :gcov_exclude: + # Filters are regular expressions + :gcov_exclude: - # Exclude directories that match this regex while searching + # Exclude directories that match this filter while searching # raw coverage files. (gcovr --exclude-directories). - :exclude_directories: + # Filters are regular expressions + :exclude_directories: # Use a particular gcov executable. (gcovr --gcov-executable). - :gcov_executable: + # (This may be appropriate and necessary in special circumstances. + # Please review Ceedling's options for modifying tools first.) + :gcov_executable: # Exclude branch coverage from lines without useful # source code. (gcovr --exclude-unreachable-branches). - :exclude_unreachable_branches: [true|false] + :exclude_unreachable_branches: # For branch coverage, exclude branches that the compiler # generates for exception handling. (gcovr --exclude-throw-branches). - :exclude_throw_branches: [true|false] + :exclude_throw_branches: + + # For Gcovr 6.0+, multiple instances of the same function in coverage results can + # cause a fatal error. Since Ceedling can test multiple build variations of the + # same source function, this is bad. + # Default value for Gcov plugin is 'merge-use-line-max'. See Gcovr docs for more. + # https://gcovr.com/en/stable/guide/merging.html + :merge_mode_function: <...> # Use existing gcov files for analysis. Default: False. (gcovr --use-gcov-files) - :use_gcov_files: [true|false] + :use_gcov_files: # Skip lines with parse errors in GCOV files instead of # exiting with an error. (gcovr --gcov-ignore-parse-errors). - :gcov_ignore_parse_errors: [true|false] + :gcov_ignore_parse_errors: # Override normal working directory detection. (gcovr --object-directory) - :object_directory: + :object_directory: # Keep gcov files after processing. (gcovr --keep). - :keep: [true|false] + :keep: # Delete gcda files after processing. (gcovr --delete). - :delete: [true|false] + :delete: # Set the number of threads to use in parallel. (gcovr -j). - :num_parallel_threads: - - # When scanning the code coverage, if any files are found that do not have - # associated coverage data, the command will abort with an error message. - :abort_on_uncovered: true - - # When using the ``abort_on_uncovered`` option, the files in this list will not - # trigger a failure. - # Ceedling globs described in the Ceedling packet ``Path`` section can be used - # when directories are placed on the list. Globs are limited to matching directories - # and not files. - :uncovered_ignore_list: [] + :threads: ``` -### ReportGenerator Configuration +## ReportGenerator configuration -The ReportGenerator utility may be configured with the following configuration items. -All generated reports may be found in `build/artifacts/gcov/ReportGenerator`. +The `ReportGenerator` utility may be configured with the following configuration items. + +All generated reports are found in `/artifacts/gcov/ReportGenerator/`. ```yaml :gcov: :report_generator: # Optional directory for storing persistent coverage information. # Can be used in future reports to show coverage evolution. - :history_directory: + :history_directory: # Optional plugin files for custom reports or custom history storage (separated by semicolon). - :plugins: CustomReports.dll + :plugins: ;<*.dll> - # Optional list of assemblies that should be included or excluded in the report (separated by semicolon).. + # Optional list of assemblies that should be included or excluded in the report (separated by semicolon). # Exclusion filters take precedence over inclusion filters. # Wildcards are allowed, but not regular expressions. - :assembly_filters: "+Included;-Excluded" + :assembly_filters: +;- - # Optional list of classes that should be included or excluded in the report (separated by semicolon).. + # Optional list of classes that should be included or excluded in the report (separated by semicolon). # Exclusion filters take precedence over inclusion filters. # Wildcards are allowed, but not regular expressions. - :class_filters: "+Included;-Excluded" + :class_filters: +;- - # Optional list of files that should be included or excluded in the report (separated by semicolon).. + # Optional list of files that should be included or excluded in the report (separated by semicolon). # Exclusion filters take precedence over inclusion filters. # Wildcards are allowed, but not regular expressions. - :file_filters: "-./vendor/*;-./build/*;-./test/*;-./lib/*;+./src/*" + # Example: "-./vendor/*;-./build/*;-./test/*;-./lib/*;+./src/*" + :file_filters: +;- # The verbosity level of the log messages. - # Values: Verbose, Info, Warning, Error, Off - :verbosity: Warning + # Values: Verbose, Info, Warning, Error, Off (defaults to Warning) + :verbosity: # Optional tag or build version. :tag: # Optional list of one or more regular expressions to exclude gcov notes files that match these filters. :gcov_exclude: - - - - - - # Optionally use a particular gcov executable. Defaults to gcov. - :gcov_executable: + - + - ... # Optionally set the number of threads to use in parallel. Defaults to 1. - :num_parallel_threads: + :threads: # Optional list of one or more command line arguments to pass to Report Generator. # Useful for configuring Risk Hotspots and Other Settings. # https://github.com/danielpalme/ReportGenerator/wiki/Settings + # Note: This can be accomplished with Ceedling's tool configuration options outside of plugin + # configuration but is supported here to collect configuration options in one place. :custom_args: - - - - + - + - ... ``` -## Example Usage +# Advanced Configuration & Troubleshooting + +See the _Ceedling Cookbook_ for options on how to use Ceedling's advanced +features to modify how this plugin is configured, especially tool +configurations. + +Details of interest for this plugin to be modified or made use of using +Ceedling's advanced features are primarily contained in +[defaults_gcov.rb](conig/defaults_gcov.rb) and [defaults.yml](config/defaults.yml). + +## “gcovr not found” + +`gcovr` is a Python-based application. Depending on the particulars of its +installation and your platform, you may encounter a “gcovr not found” error. +This is usually related to complications of running a Python script as an +executable. + +### Check your `PATH` -```sh -ceedling gcov:all utils:gcov +The problem may be as simple to solve as ensuring your user or system path +include the path to `python` and/or the `gcovr` script. `gcovr` may be +successfully installed and findable by Python; this does not necessarily +mean that shell commands Ceedling spawns can find these tools. + +Options: + +1. Modify your user or system path to include your Python installation, `gcovr` + location, or both. +1. Use Ceedling's `:environment` project configuration with its special + handling of `PATH` to modify the search path Ceedling accesses when it + executes shell commands. xample below. + +```yaml +:environment: + - :path: # Concatenates the following with OS-specific path separator + - # Add Python and/or `gcovr` path + - "#{ENV['PATH']}" # Fetch existing path entries ``` -## Known issues -### Empty Gcovr report with Gcovr 4.2+ -- If you are facing an empty gcovr report with version 4.2+ try to specify the folder you want to get a coverage. -```bash -├── Includes -├── Sources -└── UnitTestFramework - └──project.yml + +### Redefine `gcovr` to call Python directly + +Another solution is simple in concept. Instead of calling `gcovr` directly, call +`python` with the `gcovr` script as a command line argument (followed by all of +the configured `gcovr` arguments). + +To implement the solution, we make use of two features: + +* `gcovr`'s tool `:executable` definition that looks up an environment variable. +* Ceedling's `:environment` settings to redefine `gcovr`. + +Gcovr's tool defintion, like many of Ceedling's tool defintions, defaults to an +environment variable (`GCOVR`) if it is defined. If we set that environment +variable to call Python with the path to the `gcovr` script, Ceedling will call +that instead of only `gcovr`. Ceedling enables you to set environment variables +that only exist while it runs. + +In your project file: + +```yaml +:environment: + # Fill in / omit paths on your system as appropritate to your circumstances + - :gcovr: /python /gcovr ``` +Alternatively, a slightly more elegant approach may work in some cases: + ```yaml -:gcov: - :gcovr: - # Keep only source files that match this filter. (gcovr --filter). - :report_include: "^../Sources.*" - ``` +:environment: + - ":gcovr: python #{`which gcovr`}" # Shell out to look up the path to gcovr +``` +A variation of this concept relies on Python's knowledge of its runtime +environment and packages: -## To-Do list +```yaml +:environment: + - :gcovr: python -m gcovr # Call the gcovr module +``` -- Generate overall report (combined statistics from all files with coverage) +# References -## Citations +Much of the text describing report generations options in this document was +taken from the [Gcovr User Guide][gcovr-user-guide] and the +[ReportGenerator Wiki][report-generator-wiki]. -Most of the comment text which describes the options was taken from the -[Gcovr User Guide](https://www.gcovr.com/en/stable/guide.html) and the -[ReportGenerator Wiki](https://github.com/danielpalme/ReportGenerator/wiki). -The text is repeated here to provide the most accurate option functionality. +The text is repeated here to provide as useful documenation as possible. diff --git a/plugins/gcov/assets/template.erb b/plugins/gcov/assets/template.erb deleted file mode 100644 index 5e5a1742b..000000000 --- a/plugins/gcov/assets/template.erb +++ /dev/null @@ -1,15 +0,0 @@ -% function_string = hash[:coverage][:functions].to_s -% branch_string = hash[:coverage][:branches].to_s -% format_string = "%#{[function_string.length, branch_string.length].max}i" -<%=@ceedling[:plugin_reportinator].generate_banner("#{GCOV_ROOT_NAME.upcase}: CODE COVERAGE SUMMARY")%> -% if (!hash[:coverage][:functions].nil?) -FUNCTIONS: <%=sprintf(format_string, hash[:coverage][:functions])%>% -% else -FUNCTIONS: none -% end -% if (!hash[:coverage][:branches].nil?) -BRANCHES: <%=sprintf(format_string, hash[:coverage][:branches])%>% -% else -BRANCHES: none -% end - diff --git a/plugins/gcov/config/defaults.yml b/plugins/gcov/config/defaults.yml new file mode 100644 index 000000000..b99147c37 --- /dev/null +++ b/plugins/gcov/config/defaults.yml @@ -0,0 +1,30 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +--- +:gcov: + :summaries: TRUE # Enable simple coverage summaries to console after tests + :report_task: FALSE # Disabled dedicated report generation task (this enables automatic report generation) + + :utilities: + - gcovr # Defaults to `gcovr` as report generation utility + + :reports: [] # User must specify a report to enable report generation + + :gcovr: + :report_root: "." # Gcovr defaults to scanning for results starting in working directory + + # For v6.0+ merge coverage results for same function tested multiple times + # The default behavior is 'strict' which will cause a gcovr exception for many users + :merge_mode_function: merge-use-line-max + + :report_generator: + :verbosity: Warning # Default verbosity + :collection_paths_source: [] # Explicitly defined as default empty array to simplify option validation code + :custom_args: [] # Explicitly defined as default empty array to simplify option validation code + :gcov_exclude: [] # Explicitly defined as default empty array to simplify option validation code +... diff --git a/plugins/gcov/config/defaults_gcov.rb b/plugins/gcov/config/defaults_gcov.rb index e3ce340dd..5b5d8ab0c 100644 --- a/plugins/gcov/config/defaults_gcov.rb +++ b/plugins/gcov/config/defaults_gcov.rb @@ -1,118 +1,109 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= DEFAULT_GCOV_COMPILER_TOOL = { - :executable => ENV['CC'].nil? ? FilePathUtils.os_executable_ext('gcc').freeze : ENV['CC'].split[0], - :name => 'default_gcov_compiler'.freeze, - :stderr_redirect => StdErrRedirect::NONE.freeze, - :background_exec => BackgroundExec::NONE.freeze, - :optional => false.freeze, - :arguments => [ - "-g".freeze, - "-fprofile-arcs".freeze, - "-ftest-coverage".freeze, - ENV['CC'].nil? ? "" : ENV['CC'].split[1..-1], - ENV['CPPFLAGS'].nil? ? "" : ENV['CPPFLAGS'].split, - {"-I\"$\"" => 'COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR'}.freeze, - {"-I\"$\"" => 'COLLECTION_PATHS_TEST_TOOLCHAIN_INCLUDE'}.freeze, - {"-D$" => 'COLLECTION_DEFINES_TEST_AND_VENDOR'}.freeze, - "-DGCOV_COMPILER".freeze, - "-DCODE_COVERAGE".freeze, - ENV['CFLAGS'].nil? ? "" : ENV['CFLAGS'].split, - "-c \"${1}\"".freeze, - "-o \"${2}\"".freeze - ].freeze - } - + :executable => FilePathUtils.os_executable_ext('gcc').freeze, + :name => 'default_gcov_compiler'.freeze, + :optional => false.freeze, + :arguments => [ + "-g".freeze, + "-fprofile-arcs".freeze, + "-ftest-coverage".freeze, + "-I\"${5}\"".freeze, # Per-test executable search paths + "-D\"${6}\"".freeze, # Per-test executable defines + "-DGCOV_COMPILER".freeze, + "-DCODE_COVERAGE".freeze, + "-c \"${1}\"".freeze, + "-o \"${2}\"".freeze, + # gcc's list file output options are complex; no use of ${3} parameter in default config + "-MMD".freeze, + "-MF \"${4}\"".freeze, + ].freeze + } DEFAULT_GCOV_LINKER_TOOL = { - :executable => ENV['CCLD'].nil? ? FilePathUtils.os_executable_ext('gcc').freeze : ENV['CCLD'].split[0], - :name => 'default_gcov_linker'.freeze, - :stderr_redirect => StdErrRedirect::NONE.freeze, - :background_exec => BackgroundExec::NONE.freeze, - :optional => false.freeze, - :arguments => [ - "-g".freeze, - "-fprofile-arcs".freeze, - "-ftest-coverage".freeze, - ENV['CCLD'].nil? ? "" : ENV['CCLD'].split[1..-1], - ENV['CFLAGS'].nil? ? "" : ENV['CFLAGS'].split, - ENV['LDFLAGS'].nil? ? "" : ENV['LDFLAGS'].split, - "\"${1}\"".freeze, - "-o \"${2}\"".freeze, - "${4}".freeze, - "${5}".freeze, - ENV['LDLIBS'].nil? ? "" : ENV['LDLIBS'].split - ].freeze - } + :executable => FilePathUtils.os_executable_ext('gcc').freeze, + :name => 'default_gcov_linker'.freeze, + :optional => false.freeze, + :arguments => [ + "-g".freeze, + "-fprofile-arcs".freeze, + "-ftest-coverage".freeze, + "${1}".freeze, + "${5}".freeze, + "-o \"${2}\"".freeze, + "${4}".freeze, + ].freeze + } DEFAULT_GCOV_FIXTURE_TOOL = { - :executable => '${1}'.freeze, - :name => 'default_gcov_fixture'.freeze, - :stderr_redirect => StdErrRedirect::NONE.freeze, - :background_exec => BackgroundExec::NONE.freeze, - :optional => false.freeze, - :arguments => [].freeze - } + :executable => '${1}'.freeze, + :name => 'default_gcov_fixture'.freeze, + :optional => false.freeze, + :arguments => [].freeze + } -DEFAULT_GCOV_REPORT_TOOL = { - :executable => ENV['GCOV'].nil? ? FilePathUtils.os_executable_ext('gcov').freeze : ENV['GCOV'].split[0], - :name => 'default_gcov_report'.freeze, - :stderr_redirect => StdErrRedirect::NONE.freeze, - :background_exec => BackgroundExec::NONE.freeze, - :optional => false.freeze, - :arguments => [ - "-n".freeze, - "-p".freeze, - "-b".freeze, - {"-o \"$\"" => 'GCOV_BUILD_OUTPUT_PATH'}.freeze, - "\"${1}\"".freeze - ].freeze - } +# Produce summaries printed to console +DEFAULT_GCOV_SUMMARY_TOOL = { + :executable => FilePathUtils.os_executable_ext('gcov').freeze, + :name => 'default_gcov_summary'.freeze, + :optional => true.freeze, + :arguments => [ + "-n".freeze, + "-p".freeze, + "-b".freeze, + "-o \"${2}\"".freeze, + "\"${1}\"".freeze + ].freeze + } -DEFAULT_GCOV_GCOV_POST_REPORT_TOOL = { - :executable => ENV['GCOV'].nil? ? FilePathUtils.os_executable_ext('gcov').freeze : ENV['GCOV'].split[0], - :name => 'default_gcov_gcov_post_report'.freeze, - :stderr_redirect => StdErrRedirect::NONE.freeze, - :background_exec => BackgroundExec::NONE.freeze, - :optional => true.freeze, - :arguments => [ - "-b".freeze, - "-c".freeze, - "-r".freeze, - "-x".freeze, - "${1}".freeze - ].freeze - } +# Produce .gcov files (used in conjunction with ReportGenerator) +DEFAULT_GCOV_REPORT_TOOL = { + :executable => FilePathUtils.os_executable_ext('gcov').freeze, + :name => 'default_gcov_report'.freeze, + :optional => true.freeze, + :arguments => [ + "-b".freeze, + "-c".freeze, + "-r".freeze, + "-x".freeze, + "${1}".freeze + ].freeze + } -DEFAULT_GCOV_GCOVR_POST_REPORT_TOOL = { - :executable => 'gcovr'.freeze, - :name => 'default_gcov_gcovr_post_report'.freeze, - :stderr_redirect => StdErrRedirect::NONE.freeze, - :background_exec => BackgroundExec::NONE.freeze, - :optional => true.freeze, - :arguments => [ - "${1}".freeze - ].freeze - } +# Produce reports with `gcovr` +DEFAULT_GCOV_GCOVR_REPORT_TOOL = { + # No extension handling -- `gcovr` is generally an extensionless Python script + :executable => 'gcovr'.freeze, + :name => 'default_gcov_gcovr_report'.freeze, + :optional => true.freeze, + :arguments => [ + "${1}".freeze + ].freeze + } -DEFAULT_GCOV_REPORTGENERATOR_POST_REPORT = { - :executable => 'reportgenerator'.freeze, - :name => 'default_gcov_reportgenerator_post_report'.freeze, - :stderr_redirect => StdErrRedirect::NONE.freeze, - :background_exec => BackgroundExec::NONE.freeze, - :optional => true.freeze, - :arguments => [ - "${1}".freeze - ].freeze - } +# Produce reports with `reportgenerator` +DEFAULT_GCOV_REPORTGENERATOR_REPORT_TOOL = { + :executable => FilePathUtils.os_executable_ext('reportgenerator').freeze, + :name => 'default_gcov_reportgenerator_report'.freeze, + :optional => true.freeze, + :arguments => [ + "${1}".freeze + ].freeze + } def get_default_config - return :tools => { - :gcov_compiler => DEFAULT_GCOV_COMPILER_TOOL, - :gcov_linker => DEFAULT_GCOV_LINKER_TOOL, - :gcov_fixture => DEFAULT_GCOV_FIXTURE_TOOL, - :gcov_report => DEFAULT_GCOV_REPORT_TOOL, - :gcov_gcov_post_report => DEFAULT_GCOV_GCOV_POST_REPORT_TOOL, - :gcov_gcovr_post_report => DEFAULT_GCOV_GCOVR_POST_REPORT_TOOL, - :gcov_reportgenerator_post_report => DEFAULT_GCOV_REPORTGENERATOR_POST_REPORT - } + return :tools => { + :gcov_compiler => DEFAULT_GCOV_COMPILER_TOOL, + :gcov_linker => DEFAULT_GCOV_LINKER_TOOL, + :gcov_fixture => DEFAULT_GCOV_FIXTURE_TOOL, + :gcov_summary => DEFAULT_GCOV_SUMMARY_TOOL, + :gcov_report => DEFAULT_GCOV_REPORT_TOOL, + :gcov_gcovr_report => DEFAULT_GCOV_GCOVR_REPORT_TOOL, + :gcov_reportgenerator_report => DEFAULT_GCOV_REPORTGENERATOR_REPORT_TOOL + } end diff --git a/plugins/gcov/gcov.rake b/plugins/gcov/gcov.rake index ab2f8acb5..4874b7880 100755 --- a/plugins/gcov/gcov.rake +++ b/plugins/gcov/gcov.rake @@ -1,3 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'reportgenerator_reportinator' require 'gcovr_reportinator' @@ -8,90 +15,31 @@ directory(GCOV_DEPENDENCIES_PATH) CLEAN.include(File.join(GCOV_BUILD_OUTPUT_PATH, '*')) CLEAN.include(File.join(GCOV_RESULTS_PATH, '*')) -CLEAN.include(File.join(GCOV_ARTIFACTS_PATH, '*')) +CLEAN.include(File.join(GCOV_ARTIFACTS_PATH, '**/*')) CLEAN.include(File.join(GCOV_DEPENDENCIES_PATH, '*')) CLOBBER.include(File.join(GCOV_BUILD_PATH, '**/*')) -rule(/#{GCOV_BUILD_OUTPUT_PATH}\/#{'.+\\' + EXTENSION_OBJECT}$/ => [ - proc do |task_name| - @ceedling[:file_finder].find_compilation_input_file(task_name) - end - ]) do |object| - - if File.basename(object.source) =~ /^(#{PROJECT_TEST_FILE_PREFIX}|#{CMOCK_MOCK_PREFIX})|(#{VENDORS_FILES.map{|source| '\b' + source + '\b'}.join('|')})/ - @ceedling[:generator].generate_object_file( - TOOLS_GCOV_COMPILER, - OPERATION_COMPILE_SYM, - GCOV_SYM, - object.source, - object.name, - @ceedling[:file_path_utils].form_test_build_list_filepath(object.name) - ) - else - @ceedling[GCOV_SYM].generate_coverage_object_file(object.source, object.name) - end -end - -rule(/#{GCOV_BUILD_OUTPUT_PATH}\/#{'.+\\' + EXTENSION_EXECUTABLE}$/) do |bin_file| - lib_args = @ceedling[:test_invoker].convert_libraries_to_arguments() - lib_paths = @ceedling[:test_invoker].get_library_paths_to_arguments() - @ceedling[:generator].generate_executable_file( - TOOLS_GCOV_LINKER, - GCOV_SYM, - bin_file.prerequisites, - bin_file.name, - @ceedling[:file_path_utils].form_test_build_map_filepath(bin_file.name), - lib_args, - lib_paths - ) -end - -rule(/#{GCOV_RESULTS_PATH}\/#{'.+\\' + EXTENSION_TESTPASS}$/ => [ - proc do |task_name| - @ceedling[:file_path_utils].form_test_executable_filepath(task_name) - end - ]) do |test_result| - @ceedling[:generator].generate_test_results(TOOLS_GCOV_FIXTURE, GCOV_SYM, test_result.source, test_result.name) -end - -rule(/#{GCOV_DEPENDENCIES_PATH}\/#{'.+\\' + EXTENSION_DEPENDENCIES}$/ => [ - proc do |task_name| - @ceedling[:file_finder].find_compilation_input_file(task_name) - end - ]) do |dep| - @ceedling[:generator].generate_dependencies_file( - TOOLS_TEST_DEPENDENCIES_GENERATOR, - GCOV_SYM, - dep.source, - File.join(GCOV_BUILD_OUTPUT_PATH, File.basename(dep.source).ext(EXTENSION_OBJECT)), - dep.name - ) -end - task directories: [GCOV_BUILD_OUTPUT_PATH, GCOV_RESULTS_PATH, GCOV_DEPENDENCIES_PATH, GCOV_ARTIFACTS_PATH] namespace GCOV_SYM do - task source_coverage: COLLECTION_ALL_SOURCE.pathmap("#{GCOV_BUILD_OUTPUT_PATH}/%n#{@ceedling[:configurator].extension_object}") desc 'Run code coverage for all tests' - task all: [:test_deps] do - @ceedling[:configurator].replace_flattened_config(@ceedling[GCOV_SYM].config) - @ceedling[:test_invoker].setup_and_invoke(COLLECTION_ALL_TESTS, GCOV_SYM) - @ceedling[:configurator].restore_config + task all: [:prepare] do + @ceedling[:test_invoker].setup_and_invoke( tests:COLLECTION_ALL_TESTS, context:GCOV_SYM, options:TOOL_COLLECTION_GCOV_TASKS ) end - desc 'Run single test w/ coverage ([*] real test or source file name, no path).' + desc 'Run single test w/ coverage ([*] test or source file name, no path).' task :* do - message = "\nOops! '#{GCOV_ROOT_NAME}:*' isn't a real task. " \ + message = "Oops! '#{GCOV_ROOT_NAME}:*' isn't a real task. " \ "Use a real test or source file name (no path) in place of the wildcard.\n" \ - "Example: rake #{GCOV_ROOT_NAME}:foo.c\n\n" + "Example: `ceedling #{GCOV_ROOT_NAME}:foo.c`" - @ceedling[:streaminator].stdout_puts(message) + @ceedling[:loginator].log( message, Verbosity::ERRORS ) end desc 'Run tests by matching regular expression pattern.' - task :pattern, [:regex] => [:test_deps] do |_t, args| + task :pattern, [:regex] => [:prepare] do |_t, args| matches = [] COLLECTION_ALL_TESTS.each do |test| @@ -99,16 +47,14 @@ namespace GCOV_SYM do end if !matches.empty? - @ceedling[:configurator].replace_flattened_config(@ceedling[GCOV_SYM].config) - @ceedling[:test_invoker].setup_and_invoke(matches, GCOV_SYM, force_run: false) - @ceedling[:configurator].restore_config + @ceedling[:test_invoker].setup_and_invoke( tests:matches, context:GCOV_SYM, options:{ force_run: false }.merge(TOOL_COLLECTION_GCOV_TASKS) ) else - @ceedling[:streaminator].stdout_puts("\nFound no tests matching pattern /#{args.regex}/.") + @ceedling[:loginator].log("\nFound no tests matching pattern /#{args.regex}/.") end end desc 'Run tests whose test path contains [dir] or [dir] substring.' - task :path, [:dir] => [:test_deps] do |_t, args| + task :path, [:dir] => [:prepare] do |_t, args| matches = [] COLLECTION_ALL_TESTS.each do |test| @@ -116,94 +62,39 @@ namespace GCOV_SYM do end if !matches.empty? - @ceedling[:configurator].replace_flattened_config(@ceedling[GCOV_SYM].config) - @ceedling[:test_invoker].setup_and_invoke(matches, GCOV_SYM, force_run: false) - @ceedling[:configurator].restore_config + @ceedling[:test_invoker].setup_and_invoke( tests:matches, context:GCOV_SYM, options:{ force_run: false }.merge(TOOL_COLLECTION_GCOV_TASKS) ) else - @ceedling[:streaminator].stdout_puts("\nFound no tests including the given path or path component.") + @ceedling[:loginator].log( 'Found no tests including the given path or path component', Verbosity::ERRORS ) end end - desc 'Run code coverage for changed files' - task delta: [:test_deps] do - @ceedling[:configurator].replace_flattened_config(@ceedling[GCOV_SYM].config) - @ceedling[:test_invoker].setup_and_invoke(COLLECTION_ALL_TESTS, GCOV_SYM, force_run: false) - @ceedling[:configurator].restore_config - end - - # use a rule to increase efficiency for large projects - # gcov test tasks by regex - rule(/^#{GCOV_TASK_ROOT}\S+$/ => [ - proc do |task_name| - test = task_name.sub(/#{GCOV_TASK_ROOT}/, '') - test = "#{PROJECT_TEST_FILE_PREFIX}#{test}" unless test.start_with?(PROJECT_TEST_FILE_PREFIX) - @ceedling[:file_finder].find_test_from_file_path(test) - end - ]) do |test| - @ceedling[:rake_wrapper][:test_deps].invoke - @ceedling[:configurator].replace_flattened_config(@ceedling[GCOV_SYM].config) - @ceedling[:test_invoker].setup_and_invoke([test.source], GCOV_SYM) - @ceedling[:configurator].restore_config + # Use a rule to increase efficiency for large projects + rule(/^#{GCOV_TASK_ROOT}\S+$/ => [ # gcov test tasks by regex + proc do |task_name| + # Yield clean test name => Strip the task string, remove Rake test task prefix, and remove any code file extension + test = task_name.strip().sub(/^#{GCOV_TASK_ROOT}/, '').chomp( EXTENSION_SOURCE ) + + # Ensure the test name begins with a test name prefix + test = PROJECT_TEST_FILE_PREFIX + test if not (test.start_with?( PROJECT_TEST_FILE_PREFIX )) + + # Provide the filepath for the target test task back to the Rake task + @ceedling[:file_finder].find_test_file_from_name( test ) + end + ]) do |test| + @ceedling[:rake_wrapper][:prepare].invoke + @ceedling[:test_invoker].setup_and_invoke( tests:[test.source], context:GCOV_SYM, options:TOOL_COLLECTION_GCOV_TASKS ) end end -if PROJECT_USE_DEEP_DEPENDENCIES - namespace REFRESH_SYM do - task GCOV_SYM do - @ceedling[:configurator].replace_flattened_config(@ceedling[GCOV_SYM].config) - @ceedling[:test_invoker].refresh_deep_dependencies - @ceedling[:configurator].restore_config - end - end -end - -# Report Creation Utilities -UTILITY_NAME_GCOVR = "gcovr" -UTILITY_NAME_REPORT_GENERATOR = "ReportGenerator" -UTILITY_NAMES = [UTILITY_NAME_GCOVR, UTILITY_NAME_REPORT_GENERATOR] - -namespace UTILS_SYM do - - # Returns true is the given utility is enabled, otherwise returns false. - def is_utility_enabled(opts, utility_name) - return !(opts.nil?) && !(opts[:gcov_utilities].nil?) && (opts[:gcov_utilities].map(&:upcase).include? utility_name.upcase) - end +# If gcov config enables dedicated report generation task, create the task +if not @ceedling[GCOV_SYM].automatic_reporting_enabled? +namespace GCOV_REPORT_NAMESPACE_SYM do - desc "Create gcov code coverage html/xml/json/text report(s). (Note: Must run 'ceedling gcov' first)." + desc "Generate reports from coverage results (Note: a #{GCOV_SYM}: task must be executed first)" task GCOV_SYM do - # Get the gcov options from project.yml. - opts = @ceedling[:configurator].project_config_hash - - # Create the artifacts output directory. - if !File.directory? GCOV_ARTIFACTS_PATH - FileUtils.mkdir_p GCOV_ARTIFACTS_PATH - end - - # Remove unsupported reporting utilities. - if !(opts[:gcov_utilities].nil?) - opts[:gcov_utilities].reject! { |item| !(UTILITY_NAMES.map(&:upcase).include? item.upcase) } - end - - # Default to gcovr when no reporting utilities are specified. - if opts[:gcov_utilities].nil? || opts[:gcov_utilities].empty? - opts[:gcov_utilities] = [UTILITY_NAME_GCOVR] - end - - if opts[:gcov_reports].nil? - opts[:gcov_reports] = [] - end - - gcovr_reportinator = GcovrReportinator.new(@ceedling) - gcovr_reportinator.support_deprecated_options(opts) - - if is_utility_enabled(opts, UTILITY_NAME_GCOVR) - gcovr_reportinator.make_reports(opts) - end - - if is_utility_enabled(opts, UTILITY_NAME_REPORT_GENERATOR) - reportgenerator_reportinator = ReportGeneratorReportinator.new(@ceedling) - reportgenerator_reportinator.make_reports(opts) - end - + @ceedling[:gcov].generate_coverage_reports() end + end +end + diff --git a/plugins/gcov/lib/gcov.rb b/plugins/gcov/lib/gcov.rb index 24a021d3f..55de600a5 100755 --- a/plugins/gcov/lib/gcov.rb +++ b/plugins/gcov/lib/gcov.rb @@ -1,136 +1,282 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'ceedling/plugin' require 'ceedling/constants' +require 'ceedling/exceptions' require 'gcov_constants' +require 'gcovr_reportinator' +require 'reportgenerator_reportinator' class Gcov < Plugin - attr_reader :config - + + # `Plugin` setup() def setup @result_list = [] - @config = { - project_test_build_output_path: GCOV_BUILD_OUTPUT_PATH, - project_test_build_output_c_path: GCOV_BUILD_OUTPUT_PATH, - project_test_results_path: GCOV_RESULTS_PATH, - project_test_dependencies_path: GCOV_DEPENDENCIES_PATH, - defines_test: DEFINES_TEST + ['CODE_COVERAGE'], - gcov_html_report_filter: GCOV_FILTER_EXCLUDE - } + @project_config = @ceedling[:configurator].project_config_hash - @plugin_root = File.expand_path(File.join(File.dirname(__FILE__), '..')) - @coverage_template_all = @ceedling[:file_wrapper].read(File.join(@plugin_root, 'assets/template.erb')) - end + # Are any reports enabled? + @reports_enabled = reports_enabled?( @project_config[:gcov_reports] ) + + # Was a gcov: task on the command line? + @cli_gcov_task = @ceedling[:system_wrapper].get_cmdline().any?{|item| item.include?( GCOV_TASK_ROOT )} - def generate_coverage_object_file(source, object) - lib_args = @ceedling[:test_invoker].convert_libraries_to_arguments() - compile_command = - @ceedling[:tool_executor].build_command_line( - TOOLS_GCOV_COMPILER, - @ceedling[:flaginator].flag_down(OPERATION_COMPILE_SYM, GCOV_SYM, source), - source, - object, - @ceedling[:file_path_utils].form_test_build_list_filepath(object), - lib_args + # Validate the gcov tools if coverage summaries are enabled (summaries rely on the `gcov` tool) + # Note: This gcov tool is a different configuration than the gcov tool used by ReportGenerator + if summaries_enabled?( @project_config ) + @ceedling[:tool_validator].validate( + tool: TOOLS_GCOV_SUMMARY, + boom: true ) - @ceedling[:streaminator].stdout_puts("Compiling #{File.basename(source)} with coverage...") - @ceedling[:tool_executor].exec(compile_command[:line], compile_command[:options]) + end + + # Validate configuration and tools while building Reportinators + @reportinators = build_reportinators( + @project_config[:gcov_utilities], + @reports_enabled, + @cli_gcov_task + ) + + # Convenient instance variable references + @configurator = @ceedling[:configurator] + @loginator = @ceedling[:loginator] + @test_invoker = @ceedling[:test_invoker] + @plugin_reportinator = @ceedling[:plugin_reportinator] + @file_path_utils = @ceedling[:file_path_utils] + @file_wrapper = @ceedling[:file_wrapper] + @tool_executor = @ceedling[:tool_executor] + + @mutex = Mutex.new() + end + + # Called within class and also externally by plugin Rakefile + # No parameters enables the opportunity for latter mechanism + def automatic_reporting_enabled? + return (@project_config[:gcov_report_task] == false) + end + + def pre_compile_execute(arg_hash) + if arg_hash[:context] == GCOV_SYM + source = arg_hash[:source] + + # If a source file (not unity, mocks, etc.) is to be compiled use code coverage compiler + if (File.extname(source) != EXTENSION_ASSEMBLY) && @configurator.collection_all_source.include?(source) + arg_hash[:tool] = TOOLS_GCOV_COMPILER + arg_hash[:msg] = "Compiling #{File.basename(source)} with coverage..." + end + end + end + + def pre_link_execute(arg_hash) + if arg_hash[:context] == GCOV_SYM + arg_hash[:tool] = TOOLS_GCOV_LINKER + end end + # `Plugin` build step hook def post_test_fixture_execute(arg_hash) result_file = arg_hash[:result_file] - if (result_file =~ /#{GCOV_RESULTS_PATH}/) && !@result_list.include?(result_file) - @result_list << arg_hash[:result_file] + @mutex.synchronize do + if (result_file =~ /#{GCOV_RESULTS_PATH}/) && !@result_list.include?(result_file) + @result_list << arg_hash[:result_file] + end end end + # `Plugin` build step hook def post_build - return unless @ceedling[:task_invoker].invoked?(/^#{GCOV_TASK_ROOT}/) + # Do nothing unless a gcov: task was on the command line + return unless @cli_gcov_task - # test results - results = @ceedling[:plugin_reportinator].assemble_test_results(@result_list) - hash = { - header: GCOV_ROOT_NAME.upcase, - results: results - } + # Only present plugin-based test results if raw test results disabled by a reporting plugin + if !@configurator.plugins_display_raw_test_results + results = {} + + # Assemble test results + @mutex.synchronize do + results = @plugin_reportinator.assemble_test_results( @result_list ) + end - @ceedling[:plugin_reportinator].run_test_results_report(hash) do - message = '' - message = 'Unit test failures.' if results[:counts][:failed] > 0 - message + hash = { + header: GCOV_ROOT_NAME.upcase, + results: results + } + + # Print unit test suite results + @plugin_reportinator.run_test_results_report( hash ) do + message = '' + message = 'Unit test failures.' if results[:counts][:failed] > 0 + message + end end - report_per_file_coverage_results(@ceedling[:test_invoker].sources) + # Print summary of coverage to console for each source file exercised by a test + console_coverage_summaries() if summaries_enabled?( @project_config ) + + # Run full coverage report generation + generate_coverage_reports() if automatic_reporting_enabled? end + # `Plugin` build step hook def summary - result_list = @ceedling[:file_path_utils].form_pass_results_filelist(GCOV_RESULTS_PATH, COLLECTION_ALL_TESTS) + # Only present plugin-based test results if raw test results disabled by a reporting plugin + return if @configurator.plugins_display_raw_test_results + + # Build up the list of passing results from all tests + result_list = @file_path_utils.form_pass_results_filelist( + GCOV_RESULTS_PATH, + COLLECTION_ALL_TESTS + ) - # test results - # get test results for only those tests in our configuration and of those only tests with results on disk hash = { header: GCOV_ROOT_NAME.upcase, - results: @ceedling[:plugin_reportinator].assemble_test_results(result_list, boom: false) + # Collect all existing test results (success or failing) in the filesystem, + # limited to our test collection + :results => @plugin_reportinator.assemble_test_results( + result_list, + {:boom => false} + ) } - @ceedling[:plugin_reportinator].run_test_results_report(hash) + @plugin_reportinator.run_test_results_report(hash) end - private ################################### - - def report_per_file_coverage_results(sources) - banner = @ceedling[:plugin_reportinator].generate_banner "#{GCOV_ROOT_NAME.upcase}: CODE COVERAGE SUMMARY" - @ceedling[:streaminator].stdout_puts "\n" + banner + # Called within class and also externally by conditionally regnerated Rake task + # No parameters enables the opportunity for latter mechanism + def generate_coverage_reports() + return if not @reports_enabled - coverage_sources = @ceedling[:project_config_manager].filter_internal_sources(sources) - coverage_sources.each do |source| - basename = File.basename(source) - command = @ceedling[:tool_executor].build_command_line(TOOLS_GCOV_REPORT, [], [basename]) - shell_results = @ceedling[:tool_executor].exec(command[:line], command[:options]) - coverage_results = shell_results[:output] + @reportinators.each do |reportinator| + # Create the artifacts output directory. + @file_wrapper.mkdir( reportinator.artifacts_path ) - if coverage_results.strip =~ /(File\s+'#{Regexp.escape(source)}'.+$)/m - report = Regexp.last_match(1).lines.to_a[1..-1].map { |line| basename + ' ' + line }.join('') - @ceedling[:streaminator].stdout_puts(report + "\n\n") - end + # Generate reports + reportinator.generate_reports( @configurator.project_config_hash ) end + end - ignore_path_list = @ceedling[:file_system_utils].collect_paths(@ceedling[:configurator].project_config_hash[:gcov_uncovered_ignore_list] || []) - ignore_uncovered_list = @ceedling[:file_wrapper].instantiate_file_list - ignore_path_list.each do |path| - if File.exist?(path) and not File.directory?(path) - ignore_uncovered_list.include(path) - else - ignore_uncovered_list.include(File.join(path, "*#{EXTENSION_SOURCE}")) - end - end + ### Private ### + + private + + def reports_enabled?(cfg_reports) + return !cfg_reports.empty? + end + + def summaries_enabled?(config) + return config[:gcov_summaries] + end + + def utility_enabled?(opts, utility_name) + return opts.map(&:upcase).include?( utility_name.upcase ) + end + + def console_coverage_summaries() + banner = @plugin_reportinator.generate_banner( "#{GCOV_ROOT_NAME.upcase}: CODE COVERAGE SUMMARY" ) + @loginator.log "\n" + banner + + # Iterate over each test run and its list of source files + @test_invoker.each_test_with_sources do |test, sources| + heading = @plugin_reportinator.generate_heading( test ) + @loginator.log(heading) + + sources.each do |source| + filename = File.basename(source) + name = filename.ext('') + command = @tool_executor.build_command_line( + TOOLS_GCOV_SUMMARY, + [], # No additional arguments + filename, # .c source file that should have been compiled with coverage + File.join(GCOV_BUILD_OUTPUT_PATH, test) # /gcov/out/ for coverage data files + ) + + # Do not raise an exception if `gcov` terminates with a non-zero exit code, just note it and move on. + # Recent releases of `gcov` have become more strict and vocal about errors and exit codes. + command[:options][:boom] = false + + # Run the gcov tool and collect the raw coverage report + shell_results = @tool_executor.exec( command ) + results = shell_results[:output].strip + + # Handle errors instead of raising a shell exception + if shell_results[:exit_code] != 0 + debug = "gcov error (#{shell_results[:exit_code]}) while processing #{filename}... #{results}" + @loginator.log( debug, Verbosity::DEBUG, LogLabels::ERROR ) + @loginator.log( "gcov was unable to process coverage for #{filename}", Verbosity::COMPLAIN ) + next # Skip to next loop iteration + end - found_uncovered = false - COLLECTION_ALL_SOURCE.each do |source| - unless coverage_sources.include?(source) - v = Verbosity::DEBUG - msg = "Could not find coverage results for " + source - if ignore_uncovered_list.include?(source) - msg += " [IGNORED]" + # A source component may have been compiled with coverage but none of its code actually called in a test. + # In this case, versions of gcov may not produce an error, only blank results. + if results.empty? + msg = "No functions called or code paths exercised by test for #{filename}" + @loginator.log( msg, Verbosity::COMPLAIN, LogLabels::NOTICE ) + next # Skip to next loop iteration + end + + # Source filepath to be extracted from gcov coverage results via regex + _source = '' + + # Extract (relative) filepath from results and expand to absolute path + matches = results.match(/File\s+'(.+)'/) + if matches.nil? or matches.length() != 2 + msg = "Could not extract filepath via regex from gcov results for #{test}::#{File.basename(source)}" + @loginator.log( msg, Verbosity::DEBUG, LogLabels::ERROR ) + else + # Expand to full path from likely partial path to ensure correct matches on source component within gcov results + _source = File.expand_path(matches[1]) + end + + # If gcov results include intended source (comparing absolute paths), report coverage details summaries + if _source == File.expand_path(source) + # Reformat from first line as filename banner to each line of statistics labeled with the filename + # Only extract the first four lines of the console report (to avoid spidering coverage reports through libs, etc.) + report = results.lines.to_a[1..4].map { |line| filename + ' | ' + line }.join('') + @loginator.log(report + "\n") + + # Otherwise, found no coverage results else - found_uncovered = true - v = Verbosity::NORMAL + msg = "Found no coverage results for #{test}::#{File.basename(source)}" + @loginator.log( msg, Verbosity::COMPLAIN ) end - msg += "\n" - @ceedling[:streaminator].stdout_puts(msg, v) end end - if found_uncovered - if @ceedling[:configurator].project_config_hash[:gcov_abort_on_uncovered] - @ceedling[:streaminator].stderr_puts("There were files with no coverage results: aborting.\n") - exit(-1) + end + + def build_reportinators(config, enabled, gcov_task) + reportinators = [] + + # Do not instantiate reportinators (and tool validation) unless reports enabled and a gcov: task present in command line + return reportinators if ((!enabled) or (!gcov_task)) + + config.each do |reportinator| + if not GCOV_UTILITY_NAMES.map(&:upcase).include?( reportinator.upcase ) + options = GCOV_UTILITY_NAMES.map{ |utility| "'#{utility}'" }.join(', ') + msg = "Plugin configuration :gcov ↳ :utilities => `#{reportinator}` is not a recognized option {#{options}}." + raise CeedlingException.new(msg) end end + + # Run reports using gcovr + if utility_enabled?( config, GCOV_UTILITY_NAME_GCOVR ) + reportinator = GcovrReportinator.new( @ceedling ) + reportinators << reportinator + end + + # Run reports using ReportGenerator + if utility_enabled?( config, GCOV_UTILITY_NAME_REPORT_GENERATOR ) + reportinator = ReportGeneratorReportinator.new( @ceedling ) + reportinators << reportinator + end + + return reportinators end + end -# end blocks always executed following rake run -END { - # cache our input configurations to use in comparison upon next execution - @ceedling[:cacheinator].cache_test_config(@ceedling[:setupinator].config_hash) if @ceedling[:task_invoker].invoked?(/^#{GCOV_TASK_ROOT}/) -} diff --git a/plugins/gcov/lib/gcov_constants.rb b/plugins/gcov/lib/gcov_constants.rb index 74c9bbda8..56414495a 100644 --- a/plugins/gcov/lib/gcov_constants.rb +++ b/plugins/gcov/lib/gcov_constants.rb @@ -1,27 +1,42 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= GCOV_ROOT_NAME = 'gcov'.freeze GCOV_TASK_ROOT = GCOV_ROOT_NAME + ':' GCOV_SYM = GCOV_ROOT_NAME.to_sym +GCOV_REPORT_NAMESPACE = 'report'.freeze +GCOV_REPORT_NAMESPACE_SYM = GCOV_REPORT_NAMESPACE.to_sym + GCOV_BUILD_PATH = File.join(PROJECT_BUILD_ROOT, GCOV_ROOT_NAME) GCOV_BUILD_OUTPUT_PATH = File.join(GCOV_BUILD_PATH, "out") GCOV_RESULTS_PATH = File.join(GCOV_BUILD_PATH, "results") GCOV_DEPENDENCIES_PATH = File.join(GCOV_BUILD_PATH, "dependencies") GCOV_ARTIFACTS_PATH = File.join(PROJECT_BUILD_ARTIFACTS_ROOT, GCOV_ROOT_NAME) -GCOV_REPORT_GENERATOR_PATH = File.join(GCOV_ARTIFACTS_PATH, "ReportGenerator") -GCOV_ARTIFACTS_FILE_HTML = File.join(GCOV_ARTIFACTS_PATH, "GcovCoverageResults.html") -GCOV_ARTIFACTS_FILE_COBERTURA = File.join(GCOV_ARTIFACTS_PATH, "GcovCoverageCobertura.xml") -GCOV_ARTIFACTS_FILE_SONARQUBE = File.join(GCOV_ARTIFACTS_PATH, "GcovCoverageSonarQube.xml") -GCOV_ARTIFACTS_FILE_JSON = File.join(GCOV_ARTIFACTS_PATH, "GcovCoverage.json") +GCOV_REPORT_GENERATOR_ARTIFACTS_PATH = File.join(GCOV_ARTIFACTS_PATH, "ReportGenerator") +GCOV_GCOVR_ARTIFACTS_PATH = File.join(GCOV_ARTIFACTS_PATH, "gcovr") -GCOV_FILTER_EXCLUDE_PATHS = ['vendor', 'build', 'test', 'lib'] +GCOV_GCOVR_ARTIFACTS_FILE_HTML = File.join(GCOV_GCOVR_ARTIFACTS_PATH, "GcovCoverageResults.html") +GCOV_GCOVR_ARTIFACTS_FILE_COBERTURA = File.join(GCOV_GCOVR_ARTIFACTS_PATH, "GcovCoverageCobertura.xml") +GCOV_GCOVR_ARTIFACTS_FILE_SONARQUBE = File.join(GCOV_GCOVR_ARTIFACTS_PATH, "GcovCoverageSonarQube.xml") +GCOV_GCOVR_ARTIFACTS_FILE_JSON = File.join(GCOV_GCOVR_ARTIFACTS_PATH, "GcovCoverage.json") -# gcovr supports regular expressions. -GCOV_FILTER_EXCLUDE = GCOV_FILTER_EXCLUDE_PATHS.map{|path| '^'.concat(*path).concat('.*')}.join('|') +TOOL_COLLECTION_GCOV_TASKS = { + :test_compiler => TOOLS_GCOV_COMPILER, + :test_assembler => TOOLS_TEST_ASSEMBLER, + :test_linker => TOOLS_GCOV_LINKER, + :test_fixture => TOOLS_GCOV_FIXTURE +} -# ReportGenerator supports text with wildcard characters. -GCOV_REPORT_GENERATOR_FILE_FILTERS = GCOV_FILTER_EXCLUDE_PATHS.map{|path| File.join('-.', *path, '*')}.join(';') +# Report Creation Utilities +GCOV_UTILITY_NAME_GCOVR = "gcovr" +GCOV_UTILITY_NAME_REPORT_GENERATOR = "ReportGenerator" +GCOV_UTILITY_NAMES = [GCOV_UTILITY_NAME_GCOVR, GCOV_UTILITY_NAME_REPORT_GENERATOR] # Report Types class ReportTypes diff --git a/plugins/gcov/lib/gcovr_reportinator.rb b/plugins/gcov/lib/gcovr_reportinator.rb index 677af1c0c..fbcfc8ac3 100644 --- a/plugins/gcov/lib/gcovr_reportinator.rb +++ b/plugins/gcov/lib/gcovr_reportinator.rb @@ -1,32 +1,75 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'reportinator_helper' +require 'ceedling/exceptions' +require 'ceedling/constants' class GcovrReportinator + attr_reader :artifacts_path + def initialize(system_objects) + @artifacts_path = GCOV_GCOVR_ARTIFACTS_PATH @ceedling = system_objects - @reportinator_helper = ReportinatorHelper.new + @reportinator_helper = ReportinatorHelper.new(system_objects) + + # Validate the gcovr tool since it's used to generate reports + @ceedling[:tool_validator].validate( + tool: TOOLS_GCOV_GCOVR_REPORT, + boom: true + ) + + # Convenient instance variable references + @loginator = @ceedling[:loginator] + @reportinator = @ceedling[:reportinator] + @tool_executor = @ceedling[:tool_executor] end - # Generate the gcovr report(s) specified in the options. - def make_reports(opts) + def generate_reports(opts) # Get the gcovr version number. - gcovr_version_info = get_gcovr_version() + gcovr_version = get_gcovr_version() + + # Get gcovr options from project configuration options + gcovr_opts = get_gcovr_opts(opts) + + # Extract exception_on_fail setting + exception_on_fail = !!gcovr_opts[:exception_on_fail] # Build the common gcovr arguments. - args_common = args_builder_common(opts) + args_common = args_builder_common( gcovr_opts, gcovr_version ) + + msg = @reportinator.generate_heading( "Running Gcovr Coverage Reports" ) + @loginator.log( msg ) + + # gcovr version 4.2 and later supports generating multiple reports with a single call. + if min_version?( gcovr_version, 4, 2 ) + reports = [] - if ((gcovr_version_info[0] == 4) && (gcovr_version_info[1] >= 2)) || (gcovr_version_info[0] > 4) - # gcovr version 4.2 and later supports generating multiple reports with a single call. args = args_common - args += args_builder_cobertura(opts, false) - args += args_builder_sonarqube(opts, false) - args += args_builder_json(opts, true) - # As of gcovr version 4.2, the --html argument must appear last. - args += args_builder_html(opts, false) - print "Creating gcov results report(s) in '#{GCOV_ARTIFACTS_PATH}'... " - STDOUT.flush + args += (_args = args_builder_cobertura(opts, false)) + reports << "Cobertura XML" if not _args.empty? + + args += (_args = args_builder_sonarqube(opts, false)) + reports << "SonarQube" if not _args.empty? + + args += (_args = args_builder_json(opts, true)) + reports << "JSON" if not _args.empty? + + # As of gcovr version 4.2, the --html argument must appear last. + args += (_args = args_builder_html(opts, false)) + reports << "HTML" if not _args.empty? + + reports.each do |report| + msg = @reportinator.generate_progress("Generating #{report} coverage report in '#{GCOV_GCOVR_ARTIFACTS_PATH}'") + @loginator.log( msg ) + end # Generate the report(s). # only if one of the previous done checks for: @@ -36,98 +79,63 @@ def make_reports(opts) # - args_builder_json # - args_builder_html # - # updated the args variable. In other case, no need to run GCOVR - # for current setup. + # updated the args variable. In other case, no need to run GCOVR for current setup. if !(args == args_common) - run(args) + run( gcovr_opts, args, exception_on_fail ) end + + # gcovr version 4.1 and earlier supports HTML and Cobertura XML reports. + # It does not support SonarQube and JSON reports. + # Reports must also be generated separately. else - # gcovr version 4.1 and earlier supports HTML and Cobertura XML reports. - # It does not support SonarQube and JSON reports. - # Reports must also be generated separately. args_cobertura = args_builder_cobertura(opts, true) args_html = args_builder_html(opts, true) if args_html.length > 0 - print "Creating a gcov HTML report in '#{GCOV_ARTIFACTS_PATH}'... " - STDOUT.flush + msg = @reportinator.generate_progress("Generating an HTML coverage report in '#{GCOV_GCOVR_ARTIFACTS_PATH}'") + @loginator.log( msg ) # Generate the HTML report. - run(args_common + args_html) + run( gcovr_opts, (args_common + args_html), exception_on_fail ) end if args_cobertura.length > 0 - print "Creating a gcov XML report in '#{GCOV_ARTIFACTS_PATH}'... " - STDOUT.flush + msg = @reportinator.generate_progress("Generating an Cobertura XML coverage report in '#{GCOV_GCOVR_ARTIFACTS_PATH}'") + @loginator.log( msg ) # Generate the Cobertura XML report. - run(args_common + args_cobertura) + run( gcovr_opts, (args_common + args_cobertura), exception_on_fail ) end end # Determine if the gcovr text report is enabled. Defaults to disabled. - if is_report_enabled(opts, ReportTypes::TEXT) - make_text_report(opts, args_common) + if report_enabled?(opts, ReportTypes::TEXT) + generate_text_report( opts, args_common, exception_on_fail ) end - end - - def support_deprecated_options(opts) - # Support deprecated :html_report: and ":html_report_type: basic" options. - if !is_report_enabled(opts, ReportTypes::HTML_BASIC) && (opts[:gcov_html_report] || (opts[:gcov_html_report_type].is_a? String) && (opts[:gcov_html_report_type].casecmp("basic") == 0)) - opts[:gcov_reports].push(ReportTypes::HTML_BASIC) - end - - # Support deprecated ":html_report_type: detailed" option. - if !is_report_enabled(opts, ReportTypes::HTML_DETAILED) && (opts[:gcov_html_report_type].is_a? String) && (opts[:gcov_html_report_type].casecmp("detailed") == 0) - opts[:gcov_reports].push(ReportTypes::HTML_DETAILED) - end - - # Support deprecated :xml_report: option. - if opts[:gcov_xml_report] - opts[:gcov_reports].push(ReportTypes::COBERTURA) - end - - # Default to HTML basic report when no report types are defined. - if opts[:gcov_reports].empty? && opts[:gcov_html_report_type].nil? && opts[:gcov_xml_report].nil? - opts[:gcov_reports] = [ReportTypes::HTML_BASIC] - - puts "In your project.yml, define one or more of the" - puts "following to specify which reports to generate." - puts "For now, creating only an #{ReportTypes::HTML_BASIC} report." - puts "" - puts ":gcov:" - puts " :reports:" - puts " - #{ReportTypes::HTML_BASIC}" - puts " - #{ReportTypes::HTML_DETAILED}" - puts " - #{ReportTypes::TEXT}" - puts " - #{ReportTypes::COBERTURA}" - puts " - #{ReportTypes::SONARQUBE}" - puts " - #{ReportTypes::JSON}" - puts "" - end + # White space log line + @loginator.log( '' ) end + ### Private ### private GCOVR_SETTING_PREFIX = "gcov_gcovr" # Build the gcovr report generation common arguments. - def args_builder_common(opts) - gcovr_opts = get_opts(opts) - + def args_builder_common(gcovr_opts, gcovr_version) args = "" - args += "--root \"#{gcovr_opts[:report_root] || '.'}\" " + args += "--root \"#{gcovr_opts[:report_root]}\" " unless gcovr_opts[:report_root].nil? args += "--config \"#{gcovr_opts[:config_file]}\" " unless gcovr_opts[:config_file].nil? args += "--filter \"#{gcovr_opts[:report_include]}\" " unless gcovr_opts[:report_include].nil? - args += "--exclude \"#{gcovr_opts[:report_exclude] || GCOV_FILTER_EXCLUDE}\" " + args += "--exclude \"#{gcovr_opts[:report_exclude]}\" " unless gcovr_opts[:report_exclude].nil? args += "--gcov-filter \"#{gcovr_opts[:gcov_filter]}\" " unless gcovr_opts[:gcov_filter].nil? args += "--gcov-exclude \"#{gcovr_opts[:gcov_exclude]}\" " unless gcovr_opts[:gcov_exclude].nil? args += "--exclude-directories \"#{gcovr_opts[:exclude_directories]}\" " unless gcovr_opts[:exclude_directories].nil? - args += "--branches " if gcovr_opts[:branches].nil? || gcovr_opts[:branches] # Defaults to enabled. + args += "--branches " if gcovr_opts[:branches] args += "--sort-uncovered " if gcovr_opts[:sort_uncovered] - args += "--sort-percentage " if gcovr_opts[:sort_percentage].nil? || gcovr_opts[:sort_percentage] # Defaults to enabled. + args += "--sort-percentage " if gcovr_opts[:sort_percentage] args += "--print-summary " if gcovr_opts[:print_summary] args += "--gcov-executable \"#{gcovr_opts[:gcov_executable]}\" " unless gcovr_opts[:gcov_executable].nil? args += "--exclude-unreachable-branches " if gcovr_opts[:exclude_unreachable_branches] @@ -136,23 +144,35 @@ def args_builder_common(opts) args += "--gcov-ignore-parse-errors " if gcovr_opts[:gcov_ignore_parse_errors] args += "--keep " if gcovr_opts[:keep] args += "--delete " if gcovr_opts[:delete] - args += "-j #{gcovr_opts[:num_parallel_threads]} " if !(gcovr_opts[:num_parallel_threads].nil?) && (gcovr_opts[:num_parallel_threads].is_a? Integer) - - [:fail_under_line, :fail_under_branch, :source_encoding, :object_directory].each do |opt| - unless gcovr_opts[opt].nil? - - value = gcovr_opts[opt] - if (opt == :fail_under_line) || (opt == :fail_under_branch) - if not value.is_a? Integer - puts "Option value #{opt} has to be an integer" - value = nil - elsif (value < 0) || (value > 100) - puts "Option value #{opt} has to be a percentage from 0 to 100" - value = nil - end + args += "-j #{gcovr_opts[:threads]} " if !(gcovr_opts[:threads].nil?) && (gcovr_opts[:threads].is_a? Integer) + + # Version check -- merge mode is only available and relevant as of gcovr 6.0 + if min_version?( gcovr_version, 6, 0 ) + args += "--merge-mode-functions \"#{gcovr_opts[:merge_mode_function]}\" " unless gcovr_opts[:merge_mode_function].nil? + end + + [:fail_under_line, + :fail_under_branch, + :fail_under_decision, + :fail_under_function, + :source_encoding, + :object_directory + ].each do |opt| + next if gcovr_opts[opt].nil? + + value = gcovr_opts[opt] + + # Value sanity checks for :fail_under_* settings + if opt.to_s =~ /fail_/ + if not value.is_a? Integer + raise CeedlingException.new(":gcov ↳ :gcovr ↳ :#{opt} => '#{value}' must be an integer") + elsif (value < 0) || (value > 100) + raise CeedlingException.new(":gcov ↳ :gcovr ↳ :#{opt} => '#{value}' must be an integer percentage 0 – 100") end - args += "--#{opt.to_s.gsub('_','-')} #{value} " unless value.nil? end + + # If the YAML key has a value, trasnform key into command line argument with value and concatenate + args += "--#{opt.to_s.gsub('_','-')} #{value} " unless value.nil? end return args @@ -161,20 +181,18 @@ def args_builder_common(opts) # Build the gcovr Cobertura XML report generation arguments. def args_builder_cobertura(opts, use_output_option=false) - gcovr_opts = get_opts(opts) + gcovr_opts = get_gcovr_opts(opts) args = "" # Determine if the Cobertura XML report is enabled. Defaults to disabled. - if is_report_enabled(opts, ReportTypes::COBERTURA) + if report_enabled?(opts, ReportTypes::COBERTURA) # Determine the Cobertura XML report file name. - artifacts_file_cobertura = GCOV_ARTIFACTS_FILE_COBERTURA + artifacts_file_cobertura = GCOV_GCOVR_ARTIFACTS_FILE_COBERTURA if !(gcovr_opts[:cobertura_artifact_filename].nil?) - artifacts_file_cobertura = File.join(GCOV_ARTIFACTS_PATH, gcovr_opts[:cobertura_artifact_filename]) - elsif !(gcovr_opts[:xml_artifact_filename].nil?) - artifacts_file_cobertura = File.join(GCOV_ARTIFACTS_PATH, gcovr_opts[:xml_artifact_filename]) + artifacts_file_cobertura = File.join(GCOV_GCOVR_ARTIFACTS_PATH, gcovr_opts[:cobertura_artifact_filename]) end - args += "--xml-pretty " if gcovr_opts[:xml_pretty] || gcovr_opts[:cobertura_pretty] + args += "--xml-pretty " if gcovr_opts[:cobertura_pretty] args += "--xml #{use_output_option ? "--output " : ""} \"#{artifacts_file_cobertura}\" " end @@ -184,15 +202,15 @@ def args_builder_cobertura(opts, use_output_option=false) # Build the gcovr SonarQube report generation arguments. def args_builder_sonarqube(opts, use_output_option=false) - gcovr_opts = get_opts(opts) + gcovr_opts = get_gcovr_opts(opts) args = "" # Determine if the gcovr SonarQube XML report is enabled. Defaults to disabled. - if is_report_enabled(opts, ReportTypes::SONARQUBE) + if report_enabled?(opts, ReportTypes::SONARQUBE) # Determine the SonarQube XML report file name. - artifacts_file_sonarqube = GCOV_ARTIFACTS_FILE_SONARQUBE + artifacts_file_sonarqube = GCOV_GCOVR_ARTIFACTS_FILE_SONARQUBE if !(gcovr_opts[:sonarqube_artifact_filename].nil?) - artifacts_file_sonarqube = File.join(GCOV_ARTIFACTS_PATH, gcovr_opts[:sonarqube_artifact_filename]) + artifacts_file_sonarqube = File.join(GCOV_GCOVR_ARTIFACTS_PATH, gcovr_opts[:sonarqube_artifact_filename]) end args += "--sonarqube #{use_output_option ? "--output " : ""} \"#{artifacts_file_sonarqube}\" " @@ -204,15 +222,15 @@ def args_builder_sonarqube(opts, use_output_option=false) # Build the gcovr JSON report generation arguments. def args_builder_json(opts, use_output_option=false) - gcovr_opts = get_opts(opts) + gcovr_opts = get_gcovr_opts( opts ) args = "" # Determine if the gcovr JSON report is enabled. Defaults to disabled. - if is_report_enabled(opts, ReportTypes::JSON) + if report_enabled?( opts, ReportTypes::JSON ) # Determine the JSON report file name. - artifacts_file_json = GCOV_ARTIFACTS_FILE_JSON + artifacts_file_json = GCOV_GCOVR_ARTIFACTS_FILE_JSON if !(gcovr_opts[:json_artifact_filename].nil?) - artifacts_file_json = File.join(GCOV_ARTIFACTS_PATH, gcovr_opts[:json_artifact_filename]) + artifacts_file_json = File.join(GCOV_GCOVR_ARTIFACTS_PATH, gcovr_opts[:json_artifact_filename]) end args += "--json-pretty " if gcovr_opts[:json_pretty] @@ -227,24 +245,23 @@ def args_builder_json(opts, use_output_option=false) # Build the gcovr HTML report generation arguments. def args_builder_html(opts, use_output_option=false) - gcovr_opts = get_opts(opts) + gcovr_opts = get_gcovr_opts(opts) args = "" - # Determine if the gcovr HTML report is enabled. Defaults to enabled. - html_enabled = (opts[:gcov_html_report].nil? && opts[:gcov_reports].empty?) || - is_report_enabled(opts, ReportTypes::HTML_BASIC) || - is_report_enabled(opts, ReportTypes::HTML_DETAILED) + # Determine if the gcovr HTML report is enabled. + html_enabled = report_enabled?(opts, ReportTypes::HTML_BASIC) || + report_enabled?(opts, ReportTypes::HTML_DETAILED) if html_enabled # Determine the HTML report file name. - artifacts_file_html = GCOV_ARTIFACTS_FILE_HTML + artifacts_file_html = GCOV_GCOVR_ARTIFACTS_FILE_HTML if !(gcovr_opts[:html_artifact_filename].nil?) - artifacts_file_html = File.join(GCOV_ARTIFACTS_PATH, gcovr_opts[:html_artifact_filename]) + artifacts_file_html = File.join(GCOV_GCOVR_ARTIFACTS_PATH, gcovr_opts[:html_artifact_filename]) end is_html_report_type_detailed = (opts[:gcov_html_report_type].is_a? String) && (opts[:gcov_html_report_type].casecmp("detailed") == 0) - args += "--html-details " if is_html_report_type_detailed || is_report_enabled(opts, ReportTypes::HTML_DETAILED) + args += "--html-details " if is_html_report_type_detailed || report_enabled?(opts, ReportTypes::HTML_DETAILED) args += "--html-title \"#{gcovr_opts[:html_title]}\" " unless gcovr_opts[:html_title].nil? args += "--html-absolute-paths " if !(gcovr_opts[:html_absolute_paths].nil?) && gcovr_opts[:html_absolute_paths] args += "--html-encoding \"#{gcovr_opts[:html_encoding]}\" " unless gcovr_opts[:html_encoding].nil? @@ -261,82 +278,148 @@ def args_builder_html(opts, use_output_option=false) end - # Generate a gcovr text report. - def make_text_report(opts, args_common) - gcovr_opts = get_opts(opts) + # Generate a gcovr text report + def generate_text_report(opts, args_common, boom) + gcovr_opts = get_gcovr_opts(opts) args_text = "" - message_text = "Creating a gcov text report" + message_text = "Generating a text coverage report" - if !(gcovr_opts[:text_artifact_filename].nil?) - artifacts_file_txt = File.join(GCOV_ARTIFACTS_PATH, gcovr_opts[:text_artifact_filename]) - args_text += "--output \"#{artifacts_file_txt}\" " - message_text += " in '#{GCOV_ARTIFACTS_PATH}'... " - else - message_text += "... " - end + filename = gcovr_opts[:text_artifact_filename] || 'coverage.txt' - print message_text - STDOUT.flush + artifacts_file_txt = File.join(GCOV_GCOVR_ARTIFACTS_PATH, filename) + args_text += "--output \"#{artifacts_file_txt}\" " + message_text += " in '#{GCOV_GCOVR_ARTIFACTS_PATH}'" - # Generate the text report. - run(args_common + args_text) + msg = @reportinator.generate_progress(message_text) + @loginator.log(msg, Verbosity::NORMAL) + + # Generate the text report + run( gcovr_opts, (args_common + args_text), boom ) end # Get the gcovr options from the project options. - def get_opts(opts) - return opts[GCOVR_SETTING_PREFIX.to_sym] || {} + def get_gcovr_opts(opts) + return opts[GCOVR_SETTING_PREFIX.to_sym] end - # Run gcovr with the given arguments. - def run(args) + # Run gcovr with the given arguments + def run(opts, args, boom) + command = @tool_executor.build_command_line(TOOLS_GCOV_GCOVR_REPORT, [], args) + + shell_result = nil + begin - command = @ceedling[:tool_executor].build_command_line(TOOLS_GCOV_GCOVR_POST_REPORT, [], args) - shell_result = @ceedling[:tool_executor].exec(command[:line], command[:options]) - @reportinator_helper.print_shell_result(shell_result) - rescue - # handle any unforeseen issues with called tool - exitcode = $?.exitstatus - show_gcovr_message(exitcode) - exit(exitcode) + shell_result = @tool_executor.exec( command ) + rescue ShellException => ex + result = ex.shell_result + @reportinator_helper.print_shell_result( result ) + raise(ex) if gcovr_exec_exception?( opts, result[:exit_code], boom ) end + + @reportinator_helper.print_shell_result( shell_result ) end - # Get the gcovr version number as components. - # Returns [major, minor]. + # Get the gcovr version number as components + # Return {:major, :minor} def get_gcovr_version() - version_number_major = 0 - version_number_minor = 0 + major = 0 + minor = 0 + + command = @tool_executor.build_command_line(TOOLS_GCOV_GCOVR_REPORT, [], "--version") - command = @ceedling[:tool_executor].build_command_line(TOOLS_GCOV_GCOVR_POST_REPORT, [], "--version") - shell_result = @ceedling[:tool_executor].exec(command[:line], command[:options]) + msg = @reportinator.generate_progress("Collecting gcovr version for conditional feature handling") + @loginator.log( msg, Verbosity::OBNOXIOUS ) + + shell_result = @tool_executor.exec( command ) version_number_match_data = shell_result[:output].match(/gcovr ([0-9]+)\.([0-9]+)/) if !(version_number_match_data.nil?) && !(version_number_match_data[1].nil?) && !(version_number_match_data[2].nil?) - version_number_major = version_number_match_data[1].to_i - version_number_minor = version_number_match_data[2].to_i + major = version_number_match_data[1].to_i + minor = version_number_match_data[2].to_i + else + raise CeedlingException.new( "Could not collect `gcovr` version from its command line" ) end - return version_number_major, version_number_minor + return {:major => major, :minor => minor} end - # Show a more human-friendly message on gcovr return code - def show_gcovr_message(exitcode) - if ((exitcode & 2) == 2) - puts "The line coverage is less than the minimum" + # Process version hash from `get_gcovr_version()` + def min_version?(version, major, minor) + # Meet minimum requirement if major version is greater than minimum major threshold + return true if version[:major] > major + + # Meet minimum requirement only if greater than or equal to minor version for the same major version + return true if version[:major] == major and version[:minor] >= minor + + # Version is less than major.minor + return false + end + + + # Output to console a human-friendly message on certain coverage failure exit codes + # Perform the logic on whether to raise an exception + def gcovr_exec_exception?(opts, exitcode, boom) + + # Special handling of exit code 2 with --fail-under-line + if ((exitcode & 2) == 2) and !opts[:gcovr][:fail_under_line].nil? + msg = "Line coverage is less than the configured gcovr minimum of #{opts[:gcovr][:fail_under_line]}%" + if boom + raise CeedlingException.new(msg) + else + @loginator.log( msg, Verbosity::COMPLAIN ) + # Clear bit in exit code + exitcode &= ~2 + end + end + + # Special handling of exit code 4 with --fail-under-branch + if ((exitcode & 4) == 4) and !opts[:gcovr][:fail_under_branch].nil? + msg = "Branch coverage is less than the configured gcovr minimum of #{opts[:gcovr][:fail_under_branch]}%" + if boom + raise CeedlingException.new(msg) + else + @loginator.log( msg, Verbosity::COMPLAIN ) + # Clear bit in exit code + exitcode &= ~4 + end end - if ((exitcode & 4) == 4) - puts "The branch coverage is less than the minimum" + + # Special handling of exit code 8 with --fail-under-decision + if ((exitcode & 8) == 8) and !opts[:gcovr][:fail_under_decision].nil? + msg = "Decision coverage is less than the configured gcovr minimum of #{opts[:gcovr][:fail_under_decision]}%" + if boom + raise CeedlingException.new(msg) + else + @loginator.log( msg, Verbosity::COMPLAIN ) + # Clear bit in exit code + exitcode &= ~8 + end + end + + # Special handling of exit code 16 with --fail-under-function + if ((exitcode & 16) == 16) and !opts[:gcovr][:fail_under_function].nil? + msg = "Function coverage is less than the configured gcovr minimum of #{opts[:gcovr][:fail_under_function]}%" + if boom + raise CeedlingException.new(msg) + else + @loginator.log( msg, Verbosity::COMPLAIN ) + # Clear bit in exit code + exitcode &= ~16 + end end + + # A non-zero exit code is a problem + return (exitcode != 0) end # Returns true if the given report type is enabled, otherwise returns false. - def is_report_enabled(opts, report_type) - return !(opts.nil?) && !(opts[:gcov_reports].nil?) && (opts[:gcov_reports].map(&:upcase).include? report_type.upcase) + def report_enabled?(opts, report_type) + return opts[:gcov_reports].map(&:upcase).include?( report_type.upcase ) end end diff --git a/plugins/gcov/lib/reportgenerator_reportinator.rb b/plugins/gcov/lib/reportgenerator_reportinator.rb index d4a885c97..c98e36e2b 100644 --- a/plugins/gcov/lib/reportgenerator_reportinator.rb +++ b/plugins/gcov/lib/reportgenerator_reportinator.rb @@ -1,50 +1,77 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'benchmark' require 'reportinator_helper' +require 'ceedling/constants' +require 'ceedling/exceptions' class ReportGeneratorReportinator + attr_reader :artifacts_path + def initialize(system_objects) + @artifacts_path = GCOV_REPORT_GENERATOR_ARTIFACTS_PATH @ceedling = system_objects - @reportinator_helper = ReportinatorHelper.new + @reportinator_helper = ReportinatorHelper.new(system_objects) + + # Validate the `reportgenerator` tool since it's used to generate reports + @ceedling[:tool_validator].validate( + tool: TOOLS_GCOV_REPORTGENERATOR_REPORT, + boom: true + ) + + # Validate the `gcov` report tool since it's used to generate .gcov files processed by `reportgenerator` + # Note: This gcov tool is a different configuration than the gcov tool used for coverage summaries + @ceedling[:tool_validator].validate( + tool: TOOLS_GCOV_REPORT, + boom: true + ) + + # Convenient instance variable references + @loginator = @ceedling[:loginator] + @reportinator = @ceedling[:reportinator] + @tool_executor = @ceedling[:tool_executor] end # Generate the ReportGenerator report(s) specified in the options. - def make_reports(opts) + def generate_reports(opts) shell_result = nil total_time = Benchmark.realtime do rg_opts = get_opts(opts) - print "Creating gcov results report(s) with ReportGenerator in '#{GCOV_REPORT_GENERATOR_PATH}'... " - STDOUT.flush + msg = @reportinator.generate_heading( "Running ReportGenerator Coverage Reports" ) + @loginator.log( msg ) + + opts[:gcov_reports].each do |report| + msg = @reportinator.generate_progress("Generating #{report} coverage report in '#{GCOV_REPORT_GENERATOR_ARTIFACTS_PATH}'") + @loginator.log( msg ) + end # Cleanup any existing .gcov files to avoid reporting old coverage results. for gcov_file in Dir.glob("*.gcov") File.delete(gcov_file) end - # Use a custom gcov executable, if specified. - GCOV_TOOL_CONFIG[:executable] = rg_opts[:gcov_executable] unless rg_opts[:gcov_executable].nil? - - # Avoid running gcov on the mock, test, unity, and cexception gcov notes files to save time. - gcno_exclude_str = "#{opts[:cmock_mock_prefix]}.*" - gcno_exclude_str += "|#{opts[:project_test_file_prefix]}.*" - gcno_exclude_str += "|#{VENDORS_FILES.join('|')}" + gcno_exclude_str = "" # Avoid running gcov on custom specified .gcno files. - if !(rg_opts.nil?) && !(rg_opts[:gcov_exclude].nil?) && !(rg_opts[:gcov_exclude].empty?) - for gcno_exclude_expression in rg_opts[:gcov_exclude] - if !(gcno_exclude_expression.nil?) && !(gcno_exclude_expression.empty?) - # We want to filter .gcno files, not .gcov files. - # We will generate .gcov files from .gcno files. - gcno_exclude_expression = gcno_exclude_expression.chomp("\\.gcov") - gcno_exclude_expression = gcno_exclude_expression.chomp(".gcov") - # The .gcno extension will be added later as we create the regex. - gcno_exclude_expression = gcno_exclude_expression.chomp("\\.gcno") - gcno_exclude_expression = gcno_exclude_expression.chomp(".gcno") - # Append the custom expression. - gcno_exclude_str += "|#{gcno_exclude_expression}" - end + for gcno_exclude_expression in rg_opts[:gcov_exclude] + if !(gcno_exclude_expression.nil?) && !(gcno_exclude_expression.empty?) + # We want to filter .gcno files, not .gcov files. + # We will generate .gcov files from .gcno files. + gcno_exclude_expression = gcno_exclude_expression.chomp("\\.gcov") + gcno_exclude_expression = gcno_exclude_expression.chomp(".gcov") + # The .gcno extension will be added later as we create the regex. + gcno_exclude_expression = gcno_exclude_expression.chomp("\\.gcno") + gcno_exclude_expression = gcno_exclude_expression.chomp(".gcno") + # Append the custom expression. + gcno_exclude_str += "|#{gcno_exclude_expression}" end end @@ -52,8 +79,7 @@ def make_reports(opts) # Generate .gcov files by running gcov on gcov notes files (*.gcno). for gcno_filepath in Dir.glob(File.join(GCOV_BUILD_PATH, "**", "*.gcno")) - match_data = gcno_filepath.match(gcno_exclude_regex) - if match_data.nil? || (match_data[1].nil? && match_data[1].nil?) + if not (gcno_filepath =~ gcno_exclude_regex) # Skip path that matches exclude pattern # Ensure there is a matching gcov data file. if File.file?(gcno_filepath.gsub(".gcno", ".gcda")) run_gcov("\"#{gcno_filepath}\"") @@ -66,21 +92,31 @@ def make_reports(opts) args = args_builder(opts) # Generate the report(s). - shell_result = run(args) + begin + shell_result = run(args) + rescue ShellException => ex + shell_result = ex.shell_result + # Re-raise + raise ex + ensure + # Cleanup .gcov files. + for gcov_file in Dir.glob("*.gcov") + File.delete(gcov_file) + end + end else - puts "\nWarning: No matching .gcno coverage files found." + @loginator.log( "No matching .gcno coverage files found", Verbosity::COMPLAIN ) end - # Cleanup .gcov files. - for gcov_file in Dir.glob("*.gcov") - File.delete(gcov_file) - end end if shell_result shell_result[:time] = total_time @reportinator_helper.print_shell_result(shell_result) end + + # White space log line + @loginator.log( '' ) end @@ -111,9 +147,6 @@ def make_reports(opts) REPORT_GENERATOR_SETTING_PREFIX = "gcov_report_generator" - # Deep clone the gcov tool config, so we can modify it locally if specified via options. - GCOV_TOOL_CONFIG = Marshal.load(Marshal.dump(TOOLS_GCOV_GCOV_POST_REPORT)) - # Build the ReportGenerator arguments. def args_builder(opts) rg_opts = get_opts(opts) @@ -121,52 +154,42 @@ def args_builder(opts) args = "" args += "\"-reports:*.gcov\" " - args += "\"-targetdir:\"#{GCOV_REPORT_GENERATOR_PATH}\"\" " + args += "\"-targetdir:\"#{GCOV_REPORT_GENERATOR_ARTIFACTS_PATH}\"\" " # Build the report types argument. - if !(opts.nil?) && !(opts[:gcov_reports].nil?) && !(opts[:gcov_reports].empty?) - args += "\"-reporttypes:" - - for report_type in opts[:gcov_reports] - rg_report_type = REPORT_TYPE_TO_REPORT_GENERATOR_REPORT_NAME[report_type.upcase] - if !(rg_report_type.nil?) - args += rg_report_type + ";" - report_type_count = report_type_count + 1 - end - end - - # Removing trailing ';' after the last report type. - args = args.chomp(";") + args += "\"-reporttypes:" - # Append a space seperator after the report type. - args += "\" " + for report_type in opts[:gcov_reports] + rg_report_type = REPORT_TYPE_TO_REPORT_GENERATOR_REPORT_NAME[report_type.upcase] + if !(rg_report_type.nil?) + args += rg_report_type + ";" + report_type_count = report_type_count + 1 + end end - # Build the source directories argument. - args += "\"-sourcedirs:.;" - if !(opts[:collection_paths_source].nil?) - args += opts[:collection_paths_source].join(';') - end + # Removing trailing ';' after the last report type. args = args.chomp(";") + + # Append a space separator after the report type. args += "\" " + # Build the source directories argument. + args += "\"-sourcedirs:.;#{opts[:collection_paths_source].join(';')}\" " + args += "\"-historydir:#{rg_opts[:history_directory]}\" " unless rg_opts[:history_directory].nil? args += "\"-plugins:#{rg_opts[:plugins]}\" " unless rg_opts[:plugins].nil? args += "\"-assemblyfilters:#{rg_opts[:assembly_filters]}\" " unless rg_opts[:assembly_filters].nil? args += "\"-classfilters:#{rg_opts[:class_filters]}\" " unless rg_opts[:class_filters].nil? - file_filters = rg_opts[:file_filters] || @ceedling[:tool_executor_helper].osify_path_separators(GCOV_REPORT_GENERATOR_FILE_FILTERS) - args += "\"-filefilters:#{file_filters}\" " - args += "\"-verbosity:#{rg_opts[:verbosity] || "Warning"}\" " + args += "\"-filefilters:#{rg_opts[:file_filters]}\" " unless rg_opts[:file_filters].nil? + args += "\"-verbosity:#{rg_opts[:verbosity]}\" " unless rg_opts[:verbosity].nil? args += "\"-tag:#{rg_opts[:tag]}\" " unless rg_opts[:tag].nil? args += "\"settings:createSubdirectoryForAllReportTypes=true\" " unless report_type_count <= 1 args += "\"settings:numberOfReportsParsedInParallel=#{rg_opts[:num_parallel_threads]}\" " unless rg_opts[:num_parallel_threads].nil? args += "\"settings:numberOfReportsMergedInParallel=#{rg_opts[:num_parallel_threads]}\" " unless rg_opts[:num_parallel_threads].nil? # Append custom arguments. - if !(rg_opts[:custom_args].nil?) && !(rg_opts[:custom_args].empty?) - for custom_arg in rg_opts[:custom_args] - args += "\"#{custom_arg}\" " unless custom_arg.nil? || custom_arg.empty? - end + for custom_arg in rg_opts[:custom_args] + args += "\"#{custom_arg}\" " unless custom_arg.nil? || custom_arg.empty? end return args @@ -175,21 +198,23 @@ def args_builder(opts) # Get the ReportGenerator options from the project options. def get_opts(opts) - return opts[REPORT_GENERATOR_SETTING_PREFIX.to_sym] || {} + return opts[REPORT_GENERATOR_SETTING_PREFIX.to_sym] end # Run ReportGenerator with the given arguments. def run(args) - command = @ceedling[:tool_executor].build_command_line(TOOLS_GCOV_REPORTGENERATOR_POST_REPORT, [], args) - return @ceedling[:tool_executor].exec(command[:line], command[:options]) + command = @tool_executor.build_command_line(TOOLS_GCOV_REPORTGENERATOR_REPORT, [], args) + + return @tool_executor.exec( command ) end # Run gcov with the given arguments. def run_gcov(args) - command = @ceedling[:tool_executor].build_command_line(GCOV_TOOL_CONFIG, [], args) - return @ceedling[:tool_executor].exec(command[:line], command[:options]) + command = @tool_executor.build_command_line(TOOLS_GCOV_REPORT, [], args) + + return @tool_executor.exec( command ) end end diff --git a/plugins/gcov/lib/reportinator_helper.rb b/plugins/gcov/lib/reportinator_helper.rb index 92617fba5..9598c6902 100644 --- a/plugins/gcov/lib/reportinator_helper.rb +++ b/plugins/gcov/lib/reportinator_helper.rb @@ -1,15 +1,30 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'ceedling/constants' + class ReportinatorHelper - # Output the shell result to the console. - def print_shell_result(shell_result) - if !(shell_result.nil?) - puts "Done in %.3f seconds." % shell_result[:time] - - if !(shell_result[:output].nil?) && (shell_result[:output].length > 0) - puts shell_result[:output] - end + def initialize(system_objects) + # Convenience alias + @loginator = system_objects[:loginator] + end + + # Output the shell result to the console. + def print_shell_result(shell_result) + if !(shell_result.nil?) + msg = "Done in %.3f seconds." % shell_result[:time] + @loginator.log(msg, Verbosity::NORMAL) + + if !(shell_result[:output].nil?) && (shell_result[:output].length > 0) + @loginator.log(shell_result[:output], Verbosity::OBNOXIOUS) end end + end end diff --git a/plugins/html_tests_report/README.md b/plugins/html_tests_report/README.md deleted file mode 100644 index 939c066d1..000000000 --- a/plugins/html_tests_report/README.md +++ /dev/null @@ -1,38 +0,0 @@ -html_tests_report -================ - -## Overview - -The html_tests_report plugin creates an HTML file of test results, -which makes the results easier to read. The HTML file is output to the appropriate -`/artifacts/` directory (e.g. `artifacts/test/` for test tasks, -`artifacts/gcov/` for gcov, or `artifacts/bullseye/` for bullseye runs). - -## Setup - -Enable the plugin in your project.yml by adding `html_tests_report` to the list -of enabled plugins. - -``` YAML -:plugins: - :enabled: - - html_tests_report -``` - -## Configuration - -Optionally configure the output / artifact filename in your project.yml with -the `artifact_filename` configuration option. The default filename is -`report.html`. - -You can also configure the path that this artifact is stored. This can be done -by setting `path`. The default is that it will be placed in a subfolder under -the `build` directory. - -If you use some means for continuous integration, you may also want to add -.xsl file to CI's configuration for proper parsing of .xml report. - -``` YAML -:html_tests_report: - :artifact_filename: report_test.html -``` diff --git a/plugins/json_tests_report/README.md b/plugins/json_tests_report/README.md deleted file mode 100644 index 8e5a1e571..000000000 --- a/plugins/json_tests_report/README.md +++ /dev/null @@ -1,36 +0,0 @@ -json_tests_report -================= - -## Overview - -The json_tests_report plugin creates a JSON file of test results, which is -handy for Continuous Integration build servers or as input into other -reporting tools. The JSON file is output to the appropriate -`/artifacts/` directory (e.g. `artifacts/test/` for test tasks, -`artifacts/gcov/` for gcov, or `artifacts/bullseye/` for bullseye runs). - -## Setup - -Enable the plugin in your project.yml by adding `json_tests_report` to the list -of enabled plugins. - -``` YAML -:plugins: - :enabled: - - json_tests_report -``` - -## Configuration - -Optionally configure the output / artifact filename in your project.yml with -the `artifact_filename` configuration option. The default filename is -`report.json`. - -You can also configure the path that this artifact is stored. This can be done -by setting `path`. The default is that it will be placed in a subfolder under -the `build` directory. - -``` YAML -:json_tests_report: - :artifact_filename: report_spectuluarly.json -``` diff --git a/plugins/json_tests_report/lib/json_tests_report.rb b/plugins/json_tests_report/lib/json_tests_report.rb deleted file mode 100644 index 8b02e580b..000000000 --- a/plugins/json_tests_report/lib/json_tests_report.rb +++ /dev/null @@ -1,83 +0,0 @@ -require 'ceedling/plugin' -require 'ceedling/constants' -require 'json' - -class JsonTestsReport < Plugin - def setup - @results_list = {} - @test_counter = 0 - end - - def post_test_fixture_execute(arg_hash) - context = arg_hash[:context] - - @results_list[context] = [] if @results_list[context].nil? - - @results_list[context] << arg_hash[:result_file] - end - - def post_build - @results_list.each_key do |context| - results = @ceedling[:plugin_reportinator].assemble_test_results(@results_list[context]) - - artifact_filename = @ceedling[:configurator].project_config_hash[:json_tests_report_artifact_filename] || 'report.json' - artifact_fullpath = @ceedling[:configurator].project_config_hash[:json_tests_report_path] || File.join(PROJECT_BUILD_ARTIFACTS_ROOT, context.to_s) - file_path = File.join(artifact_fullpath, artifact_filename) - - @ceedling[:file_wrapper].open(file_path, 'w') do |f| - @test_counter = 1 - - json = { - "FailedTests" => write_failures(results[:failures]), - "PassedTests" => write_tests(results[:successes]), - "IgnoredTests" => write_tests(results[:ignores]), - "Summary" => write_statistics(results[:counts]) - } - - f << JSON.pretty_generate(json) - end - end - end - - private - - def write_failures(results) - retval = [] - results.each do |result| - result[:collection].each do |item| - @test_counter += 1 - retval << { - "file" => result[:source][:file], - "test" => item[:test], - "line" => item[:line], - "message" => item[:message] - } - end - end - return retval.uniq - end - - def write_tests(results) - retval = [] - results.each do |result| - result[:collection].each do |item| - @test_counter += 1 - retval << { - "file" => result[:source][:file], - "test" => item[:test] - } - end - end - return retval - end - - def write_statistics(counts) - return { - "total_tests" => counts[:total], - "passed" => (counts[:total] - counts[:ignored] - counts[:failed]), - "ignored" => counts[:ignored], - "failures" => counts[:failed] - } - end - -end diff --git a/plugins/junit_tests_report/README.md b/plugins/junit_tests_report/README.md deleted file mode 100644 index 1259fd668..000000000 --- a/plugins/junit_tests_report/README.md +++ /dev/null @@ -1,36 +0,0 @@ -junit_tests_report -==================== - -## Overview - -The junit_tests_report plugin creates an XML file of test results in JUnit -format, which is handy for Continuous Integration build servers or as input -into other reporting tools. The XML file is output to the appropriate -`/artifacts/` directory (e.g. `artifacts/test/` for test tasks, -`artifacts/gcov/` for gcov, or `artifacts/bullseye/` for bullseye runs). - -## Setup - -Enable the plugin in your project.yml by adding `junit_tests_report` -to the list of enabled plugins. - -``` YAML -:plugins: - :enabled: - - junit_tests_report -``` - -## Configuration - -Optionally configure the output / artifact filename in your project.yml with -the `artifact_filename` configuration option. The default filename is -`report.xml`. - -You can also configure the path that this artifact is stored. This can be done -by setting `path`. The default is that it will be placed in a subfolder under -the `build` directory. - -``` YAML -:junit_tests_report: - :artifact_filename: report_junit.xml -``` diff --git a/plugins/junit_tests_report/lib/junit_tests_report.rb b/plugins/junit_tests_report/lib/junit_tests_report.rb deleted file mode 100644 index 310439380..000000000 --- a/plugins/junit_tests_report/lib/junit_tests_report.rb +++ /dev/null @@ -1,134 +0,0 @@ -require 'ceedling/plugin' -require 'ceedling/constants' - -class JunitTestsReport < Plugin - - def setup - @results_list = {} - @test_counter = 0 - @time_result = [] - end - - def post_test_fixture_execute(arg_hash) - context = arg_hash[:context] - - @results_list[context] = [] if (@results_list[context].nil?) - - @results_list[context] << arg_hash[:result_file] - @time_result << arg_hash[:shell_result][:time] - - end - - def post_build - @results_list.each_key do |context| - results = @ceedling[:plugin_reportinator].assemble_test_results(@results_list[context]) - - artifact_filename = @ceedling[:configurator].project_config_hash[:junit_tests_report_artifact_filename] || 'report.xml' - artifact_fullpath = @ceedling[:configurator].project_config_hash[:junit_tests_report_path] || File.join(PROJECT_BUILD_ARTIFACTS_ROOT, context.to_s) - file_path = File.join(artifact_fullpath, artifact_filename) - - @ceedling[:file_wrapper].open( file_path, 'w' ) do |f| - @testsuite_counter = 0 - @testcase_counter = 0 - suites = reorganise_results( results ) - - write_header( results, f ) - suites.each{|suite| write_suite( suite, f ) } - write_footer( f ) - end - end - end - - private - - def write_header( results, stream ) - results[:counts][:time] = @time_result.reduce(0, :+) - stream.puts '' - stream.puts('' % results[:counts]) - end - - def write_footer( stream ) - stream.puts '' - end - - def reorganise_results( results ) - # Reorganise the output by test suite instead of by result - suites = Hash.new{ |h,k| h[k] = {collection: [], total: 0, success: 0, failed: 0, ignored: 0, errors: 0, stdout: []} } - results[:successes].each do |result| - source = result[:source] - name = source[:file].sub(/\..{1,4}$/, "") - suites[name][:collection] += result[:collection].map{|test| test.merge(result: :success)} - suites[name][:total] += result[:collection].length - suites[name][:success] += result[:collection].length - end - results[:failures].each do |result| - source = result[:source] - name = source[:file].sub(/\..{1,4}$/, "") - suites[name][:collection] += result[:collection].map{|test| test.merge(result: :failed)} - suites[name][:total] += result[:collection].length - suites[name][:failed] += result[:collection].length - end - results[:ignores].each do |result| - source = result[:source] - name = source[:file].sub(/\..{1,4}$/, "") - suites[name][:collection] += result[:collection].map{|test| test.merge(result: :ignored)} - suites[name][:total] += result[:collection].length - suites[name][:ignored] += result[:collection].length - end - results[:stdout].each do |result| - source = result[:source] - name = source[:file].sub(/\..{1,4}$/, "") - suites[name][:stdout] += result[:collection] - end - suites.map{|name, data| data.merge(name: name) } - end - - def write_suite( suite, stream ) - suite[:time] = @time_result.shift - stream.puts(' ' % suite) - - suite[:collection].each do |test| - write_test( test, stream ) - end - - unless suite[:stdout].empty? - stream.puts(' ') - suite[:stdout].each do |line| - line.gsub!(/&/, '&') - line.gsub!(//, '>') - line.gsub!(/"/, '"') - line.gsub!(/'/, ''') - stream.puts(line) - end - stream.puts(' ') - end - - stream.puts(' ') - end - - def write_test( test, stream ) - test[:test].gsub!(/&/, '&') - test[:test].gsub!(//, '>') - test[:test].gsub!(/"/, '"') - test[:test].gsub!(/'/, ''') - - case test[:result] - when :success - stream.puts(' ' % test) - when :failed - stream.puts(' ' % test) - if test[:message].empty? - stream.puts(' ') - else - stream.puts(' ' % test[:message]) - end - stream.puts(' ') - when :ignored - stream.puts(' ' % test) - stream.puts(' ') - stream.puts(' ') - end - end -end diff --git a/plugins/module_generator/README.md b/plugins/module_generator/README.md index a3c2c7ad9..3f263a8fb 100644 --- a/plugins/module_generator/README.md +++ b/plugins/module_generator/README.md @@ -1,10 +1,10 @@ ceedling-module-generator ========================= -## Overview +## Plugin Overview The module_generator plugin adds a pair of new commands to Ceedling, allowing -you to make or remove modules according to predefined templates. WIth a single call, +you to make or remove modules according to predefined templates. With a single call, Ceedling can generate a source, header, and test file for a new module. If given a pattern, it can even create a series of submodules to support specific design patterns. Finally, it can just as easily remove related modules, avoiding the need to delete @@ -22,6 +22,8 @@ specified a different default (see configuration). It will create three files: `MadScience.c`, `MadScience.h`, and `TestMadScience.c`. *NOTE* that it is important that there are no spaces between the brackets. We know, it's annoying... but it's the rules. +### Patterns + You can also create an entire pattern of files. To do that, just add a second argument to the pattern ID. Something like this: @@ -33,6 +35,78 @@ In this example, we'd create 9 files total: 3 headers, 3 source files, and 3 tes files would be named `SecretLairModel`, `SecretLairConductor`, and `SecretLairHardware`. Isn't that nice? +### Paths + +The directories found in the project `:paths:` are reused. You can also specify an alternative default generation path using: +``` +:module_generator: + :path_src: src/ + :path_inc: src/ + :path_tst: test/ +``` + +But what if I don't want it to place my new files in the default location? + +It can do that too! You can give it a hint as to where to find your files. The pattern matching +here is fairly basic, but it is usually sufficient. It works perfectly if your directory structure +matches a common pattern. For example, let's say you issue this command: + +``` +ceedling module:create[lab:SecretLair,mch] +``` + +Say your directory structure looks like this: + +``` +:paths: + :source: + - lab/src + - lair/src + - other/src + :test: + - lab/test + - lair/test + - other/test +``` + +In this case, the `lab:` hint would make the module generator guess you want your files here: + + - source files: `lab/src` (because it's a close match) + - include files: `lab/src` (because no include paths were listed) + - test files: `lab/test` (because it's a close match) + +Instead, if your directory structure looks like this: + +``` +:paths: + :source: + - src/** #this might contain subfolders lab, lair, and other + :include: + - inc/** #again, this might contain subfolders lab, lair, other, and shared + :test: + - test +``` + +In this case, the `lab:` hint would make the module generator guess you want your files here: + + - source files: `src/lab` (because it's a close match) + - include files: `inc/lab` (because it's a close match) + - test files: `test` (because there wasn't a close match, and this was the first entry on our list) + +You can see that more complicated structures will have files placed in the wrong place from time to +time... no worries... you can move the file after it's created... but if your project has any kind of +consistent structure, the guessing engine does a good job of making it work. + +Three more quick notes about the path-matching: + +1. You can give multiple ordered hints that map roughly to folder nesting! `lab:secret:lair` will + happily match to put `lair.c` in a folder like `my/lab/secret/`. + +2. Whenever the matcher fails to find a good candidate (or if it finds multiple equally good + candidates), it will always guess in the order you have the paths listed in your project.yml file + +## Stubbing + Similarly, you can create stubs for all functions in a header file just by making a single call to your handy `stub` feature, like this: @@ -40,8 +114,8 @@ to your handy `stub` feature, like this: ceedling module:stub[SecretLair] ``` -This call will look in SecretLair.h and will generate a file SecretLair.c that contains a stub -for each function declared in the header! Even better, if SecretLair.c already exists, it will +This call will look in `SecretLair.h` and will generate a file `SecretLair.c` that contains a stub +for each function declared in the header! Even better, if `SecretLair.c` already exists, it will add only new functions, leaving your existing calls alone so that it doesn't cause any problems. ## Configuration @@ -57,9 +131,15 @@ follows the default ceedling structure... but what if you have a different struc ``` :module_generator: :project_root: ./ - :source_root: source/ - :inc_root: includes/ - :test_root: tests/ + :naming: :bumpy + :includes: + - :src: [] + - :inc: [] + - :tst: [] + :boilerplates: + - :src: "" + - :inc: "" + - :tst: "" ``` Now I've redirected the location where modules are going to be generated. @@ -82,15 +162,28 @@ by adding to the `:includes` array. For example: ### Boilerplates You can specify the actual boilerplate used for each of your files. This is the handy place to -put that corporate copyright notice (or maybe a copyleft notice, if that's your perference?) +put that corporate copyright notice (or maybe a copyleft notice, if that's your preference?) + +Notice there is a separate template for source files, include files, and test files. Also, +your boilerplates can optionally contain `%1$s` which will inject the filename into that spot. ``` :module_generator: - :boilerplates: | - /*************************** - * This file is Awesome. * - * That is All. * - ***************************/ + :boilerplates: + :src: | + /*************************** + * %1$s + * This file is Awesome. + * That is All. + ***************************/ + :inc: | + /*************************** + * Header. Woo. * + ***************************/ + :tst: | + /*************************** + * My Awesome Test For %1$s + ***************************/ ``` ### Test Defines @@ -109,7 +202,7 @@ Finally, you can force a particular naming convention. Even if someone calls the with something like `MyNewModule`, if they have the naming convention set to `:caps`, it will generate files like `MY_NEW_MODULE.c`. This keeps everyone on your team behaving the same way. -Your options are as follows: +Your options for `:naming:` are as follows: - `:bumpy` - BumpyFilesLooksLikeSo - `:camel` - camelFilesAreSimilarButStartLow diff --git a/plugins/module_generator/Rakefile b/plugins/module_generator/Rakefile new file mode 100644 index 000000000..93dbd911f --- /dev/null +++ b/plugins/module_generator/Rakefile @@ -0,0 +1,205 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'rake' +require 'rbconfig' + +def windows?() + return (RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/) +end + +# Add `ruby` to the command line on Windows to execute the Ruby-based shell script bin/ceedling +CEEDLING_CLI_EXEC = "#{'ruby ' if windows?}../../../bin/ceedling" + +def prep_test + FileUtils.rm_rf Dir['./**/*.c'] + FileUtils.rm_rf Dir['./**/*.h'] + FileUtils.mkdir_p "./s/rev" + FileUtils.mkdir_p "./i/rev" + FileUtils.mkdir_p "./t/rev" + FileUtils.mkdir_p "./sub/s" + FileUtils.mkdir_p "./sub/i" + FileUtils.mkdir_p "./sub/t" +end + +def prep_stub(num) + FileUtils.cp_r("../assets/stubby#{num}.h","./i/stubby.h") +end + +def assert_file_exist(path) + if File.exist?(path) + puts "File #{path} exists." + else + raise "File #{path} doesn't exist after create" + end +end + +def assert_file_contains(path, expected) + if File.exist?(path) + actual = File.read(path) + if actual.match?(expected) + puts "File #{path} exists and contains specified contents." + else + puts "Expected content: #{expected}" # Debug logging + puts "Actual content: #{actual}" # Debug logging + raise "File #{path} exists but doesn't contain specified contents." + end + else + raise "File #{path} doesn't exist after create" + end +end + +def assert_file_not_exist(path) + unless File.exist?(path) + puts "File #{path} doesn't exist after destroy" + else + raise "File #{path} still exists after destroy." + end +end + +def assert_test_run_contains(expected) + retval = `#{CEEDLING_CLI_EXEC} clobber test:all 2>&1` + if (retval.include? expected) + puts "Testing included `#{expected}`" + else + puts retval # Debug logging + raise "Testing did not include `#{expected}`" + end +end + +def call_create(cmd) + retval = `#{CEEDLING_CLI_EXEC} module:create[#{cmd}] 2>&1` + puts retval # Debug logging + if retval.match? /Error/i + raise "Received error when creating:\n#{retval}" + else + puts "Created #{cmd}" + end +end + +def call_destroy(cmd) + retval = `#{CEEDLING_CLI_EXEC} module:destroy[#{cmd}] 2>&1` + puts retval # Debug logging + if retval.match? /Error/i + raise "Received error when destroying:\n#{retval}" + else + puts "Destroyed #{cmd}" + end +end + +def call_stub(cmd) + retval = `#{CEEDLING_CLI_EXEC} module:stub[#{cmd}] 2>&1` + puts retval # Debug logging + if retval.match? /Error/i + raise "Received error when stubbing:\n#{retval}" + else + puts "Stubbed #{cmd}" + end +end + +desc "Run integration test on example" +task :integration_test do + chdir("./example/") do + + # Start with a blank example project + prep_test + assert_test_run_contains("No tests executed") + + # Add a module without path. + # It should be added to first path on list of each category + puts "\nVerifying Default Create:" + call_create("a_file") + assert_file_exist("s/rev/a_file.c") + assert_file_exist("i/rev/a_file.h") + assert_file_exist("sub/t/test_a_file.c") + assert_test_run_contains("TESTED: 1") + + # Make sure that we can add modules properly when the directory + # pattern is subdirs with src, inc, and test folders each + puts "\nVerifying Subdirectory Create:" + call_create("sub:b_file") + assert_file_exist("sub/s/b_file.c") + assert_file_exist("sub/i/b_file.h") + assert_file_exist("sub/t/test_b_file.c") + assert_test_run_contains("TESTED: 2") + + # Make sure that we can add modules properly when the directory + # pattern is subdirs under the src, inc, and test folders + puts "\nVerifying Reverse Subdirectory Create:" + call_create("rev:c_file") + assert_file_exist("s/rev/c_file.c") + assert_file_exist("i/rev/c_file.h") + assert_file_exist("t/rev/test_c_file.c") + assert_test_run_contains("TESTED: 3") + + # Does our Boilerplate mechanism work? + puts "\nVerifying Boilerplate:" + assert_file_contains("s/rev/c_file.c", "MAY THE SOURCE BE WITH YOU") + assert_file_contains("i/rev/c_file.h", "feel included") + assert_file_contains("t/rev/test_c_file.c", "Don't Test Me, Sir") + + # Are other essentials being injected + puts "\nVerifying Guts:" + assert_file_contains("s/rev/a_file.c", "#include \"a_file.h\"") + assert_file_contains("i/rev/a_file.h", "#ifndef A_FILE_H") + assert_file_contains("sub/t/test_a_file.c", "test_a_file_NeedToImplement") + + # Destroy a module without path. + # It should be removed from first path on list of each category + puts "\nVerifying Default Destroy:" + call_destroy("a_file") + assert_file_not_exist("s/rev/a_file.c") + assert_file_not_exist("i/rev/a_file.h") + assert_file_not_exist("sub/t/test_a_file.c") + assert_test_run_contains("TESTED: 2") + + # Make sure that we can destroy modules properly when the directory + # pattern is subdirs with src, inc, and test folders each + puts "\nVerifying Subdirectory Destroy:" + call_destroy("sub:b_file") + assert_file_not_exist("sub/s/b_file.c") + assert_file_not_exist("sub/i/b_file.h") + assert_file_not_exist("sub/t/test_b_file.c") + assert_test_run_contains("TESTED: 1") + + # Make sure that we can destroy modules properly when the directory + # pattern is subdirs under the src, inc, and test folders + puts "\nVerifying Reverse Subdirectory Destroy:" + call_destroy("rev:c_file") + assert_file_not_exist("s/rev/c_file.c") + assert_file_not_exist("i/rev/c_file.h") + assert_file_not_exist("t/rev/test_c_file.c") + assert_test_run_contains("No tests executed") + + # Verify stubbing functionality can make a new source file + puts "\nVerifying Stubbing:" + prep_stub(1) + call_stub("i:stubby") + assert_file_contains("s/rev/stubby.c","void shorty") + + # Verify stubbing functionality can update a source file + puts "\nVerifying Stub Updating:" + prep_stub(2) + call_stub("i:stubby") + assert_file_contains("s/rev/stubby.c","void shorty") + assert_file_contains("s/rev/stubby.c","void shrimpy") + assert_file_contains("s/rev/stubby.c","int tiny") + + # Make sure that we can destroy modules properly even when the + # entire set doesn't exist + puts "\nVerifying Partial Destroy:" + call_destroy("i:stubby") + assert_file_not_exist("s/rev/stubby.c") + assert_file_not_exist("i/rev/stubby.h") + prep_test + + puts "\nPASSES MODULE SELF-TESTS" + + end +end + +task :default => [:integration_test] \ No newline at end of file diff --git a/plugins/module_generator/assets/stubby1.h b/plugins/module_generator/assets/stubby1.h new file mode 100644 index 000000000..4941469aa --- /dev/null +++ b/plugins/module_generator/assets/stubby1.h @@ -0,0 +1,13 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#ifndef STUBBY_H +#define STUBBY_H + +void shorty(int); + +#endif // STUBBY_H diff --git a/plugins/module_generator/assets/stubby2.h b/plugins/module_generator/assets/stubby2.h new file mode 100644 index 000000000..6460a5f9a --- /dev/null +++ b/plugins/module_generator/assets/stubby2.h @@ -0,0 +1,15 @@ +/* ========================================================================= + Ceedling - Test-Centered Build System for C + ThrowTheSwitch.org + Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams + SPDX-License-Identifier: MIT +========================================================================= */ + +#ifndef STUBBY_H +#define STUBBY_H + +void shrimpy(void); +void shorty(int); +int tiny(int a); + +#endif // STUBBY_H diff --git a/plugins/module_generator/config/module_generator.yml b/plugins/module_generator/config/module_generator.yml index cdb2da2eb..48d8942dc 100644 --- a/plugins/module_generator/config/module_generator.yml +++ b/plugins/module_generator/config/module_generator.yml @@ -1,4 +1,14 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + :module_generator: :project_root: ./ - :source_root: src/ - :test_root: test/ \ No newline at end of file + :naming: :snake #options: :bumpy, :camel, :caps, or :snake + :boilerplates: + :src: "" + :inc: "" + :tst: "" \ No newline at end of file diff --git a/plugins/module_generator/example/project.yml b/plugins/module_generator/example/project.yml new file mode 100644 index 000000000..b1c899158 --- /dev/null +++ b/plugins/module_generator/example/project.yml @@ -0,0 +1,175 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +--- +:project: + # how to use ceedling. If you're not sure, leave this as `gem` and `?` + :which_ceedling: ../../.. + :ceedling_version: '?' + + # optional features. If you don't need them, keep them turned off for performance + :use_mocks: TRUE + :use_test_preprocessor: :all + :use_backtrace: :none + + # tweak the way ceedling handles automatic tasks + :build_root: build + :test_file_prefix: test_ + :default_tasks: + - test:all + + # performance options. If your tools start giving mysterious errors, consider + # dropping this to 1 to force single-tasking + :test_threads: 8 + :compile_threads: 8 + + # enable release build (more details in release_build section below) + :release_build: FALSE + +# further details to configure the way Ceedling handles test code +:test_build: + :use_assembly: FALSE + +# further details to configure the way Ceedling handles release code +:release_build: + :output: MyApp.out + :use_assembly: FALSE + :artifacts: [] + +# Plugins are optional Ceedling features which can be enabled. Ceedling supports +# a variety of plugins which may effect the way things are compiled, reported, +# or may provide new command options. Refer to the readme in each plugin for +# details on how to use it. +:plugins: + :load_paths: [] + :enabled: + #- beep # beeps when finished, so you don't waste time waiting for ceedling + - module_generator # handy for quickly creating source, header, and test templates + #- gcov # test coverage using gcov. Requires gcc, gcov, and a coverage analyzer like gcovr + #- bullseye # test coverage using bullseye. Requires bullseye for your platform + #- command_hooks # write custom actions to be called at different points during the build process + #- compile_commands_json_db # generate a compile_commands.json file + #- dependencies # automatically fetch 3rd party libraries, etc. + #- subprojects # managing builds and test for static libraries + #- fake_function_framework # use FFF instead of CMock + + # Report options (You'll want to choose one stdout option, but may choose multiple stored options if desired) + #- test_suite_reporter + - report_tests_raw_output_log + - report_tests_pretty_stdout + #- report_tests_ide_stdout + #- report_tests_gtestlike_stdout + #- teamcity_tests_report + #- warnings_report + +# override the default extensions for your system and toolchain +:extension: + #:header: .h + #:source: .c + #:assembly: .s + #:dependencies: .d + #:object: .o + :executable: .out + #:testpass: .pass + #:testfail: .fail + #:subprojects: .a + +# This is where Ceedling should look for your source and test files. +# see documentation for the many options for specifying this. +:paths: + :test: + - +:sub/t + - +:t/** + :source: + - s/** + - sub/s + :include: + - i/** + - sub/i + :libraries: [] + +# You can even specify specific files to add or remove from your test +# and release collections. Usually it's better to use paths and let +# Ceedling do the work for you! +:files: + :test: [] + :source: [] + +# Compilation symbols to be injected into builds +# See documentation for advanced options: +# - Test name matchers for different symbols per test executable build +# - Referencing symbols in multiple lists using advanced YAML +# - Specifiying symbols used during test preprocessing +:defines: + :test: + - TEST # Simple list option to add symbol 'TEST' to compilation of all files in all test executables + :release: [] + + # Enable to inject name of a test as a unique compilation symbol into its respective executable build. + :use_test_definition: FALSE + +# Configure additional command line flags provided to tools used in each build step +# :flags: +# :release: +# :compile: # Add '-Wall' and '--02' to compilation of all files in release target +# - -Wall +# - --O2 +# :test: +# :compile: +# '(_|-)special': # Add '-pedantic' to compilation of all files in all test executables with '_special' or '-special' in their names +# - -pedantic +# '*': # Add '-foo' to compilation of all files in all test executables +# - -foo + +# Configuration Options specific to CMock. See CMock docs for details +: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 + +# Configuration options specific to Unity. +:unity: + :defines: + - UNITY_EXCLUDE_FLOAT + +# You can optionally have ceedling create environment variables for you before +# performing the rest of its tasks. +:environment: [] + +# LIBRARIES +# These libraries are automatically injected into the build process. Those specified as +# common will be used in all types of builds. Otherwise, libraries can be injected in just +# tests or releases. These options are MERGED with the options in supplemental yaml files. +:libraries: + :placement: :end + :flag: "-l${1}" + :path_flag: "-L ${1}" + :system: [] # for example, you might list 'm' to grab the math library + :test: [] + :release: [] + +:module_generator: + :project_root: ./ + :naming: :snake #options: :bumpy, :camel, :caps, or :snake + :boilerplates: + :src: "/* MAY THE SOURCE BE WITH YOU */" + :inc: | + /* ================================== + | It's important to make everyone + | feel included, particularly in + | when making important decisions. + ===================================*/ + :tst: "// Don't Test Me, Sir." diff --git a/plugins/module_generator/lib/module_generator.rb b/plugins/module_generator/lib/module_generator.rb index 9b9bfb125..b44447ec7 100755 --- a/plugins/module_generator/lib/module_generator.rb +++ b/plugins/module_generator/lib/module_generator.rb @@ -1,3 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'ceedling/plugin' require 'ceedling/constants' require 'erb' @@ -9,8 +16,13 @@ class ModuleGenerator < Plugin def create(module_name, optz={}) - require "generate_module.rb" #From Unity Scripts + # grab our own reference to the main configuration hash + @project_config = @ceedling[:configurator].project_config_hash + + # load the generate module script form Unity's collection of scripts. + require "generate_module.rb" + # if asked to destroy, do so. otherwise create (because isn't creating something always better?) if ((!optz.nil?) && (optz[:destroy])) UnityModuleGenerator.new( divine_options(optz) ).destroy(module_name) else @@ -19,25 +31,28 @@ def create(module_name, optz={}) end def stub_from_header(module_name, optz={}) - require "cmock.rb" #From CMock + + # grab our own reference to the main configuration hash + @project_config = @ceedling[:configurator].project_config_hash + + # load CMock to be used for stubbing here. + require "cmock.rb" + + # generate skeleton file stuboptz = divine_options(optz) - pathname = optz[:path_inc] || optz[:path_src] || "src" - filename = File.expand_path(optz[:module_root_path], File.join(pathname, module_name + ".h")) + stuboptz[:subdir] = nil + stuboptz[:mock_path] = stuboptz[:path_src] + filename = File.join(stuboptz[:path_inc], module_name + ".h") + puts stuboptz.to_yaml CMock.new(stuboptz).setup_skeletons(filename) end private def divine_options(optz={}) + # Build default configuration based on looking up other values unity_generator_options = { - :path_src => ((defined? MODULE_GENERATOR_SOURCE_ROOT ) ? MODULE_GENERATOR_SOURCE_ROOT.gsub('\\', '/').sub(/^\//, '').sub(/\/$/, '') : "src" ), - :path_inc => ((defined? MODULE_GENERATOR_INC_ROOT ) ? - MODULE_GENERATOR_INC_ROOT.gsub('\\', '/').sub(/^\//, '').sub(/\/$/, '') - : (defined? MODULE_GENERATOR_SOURCE_ROOT ) ? - MODULE_GENERATOR_SOURCE_ROOT.gsub('\\', '/').sub(/^\//, '').sub(/\/$/, '') - : "src" ), - :path_tst => ((defined? MODULE_GENERATOR_TEST_ROOT ) ? MODULE_GENERATOR_TEST_ROOT.gsub( '\\', '/').sub(/^\//, '').sub(/\/$/, '') : "test" ), :pattern => optz[:pattern], :test_prefix => ((defined? PROJECT_TEST_FILE_PREFIX ) ? PROJECT_TEST_FILE_PREFIX : "Test" ), :mock_prefix => ((defined? CMOCK_MOCK_PREFIX ) ? CMOCK_MOCK_PREFIX : "Mock" ), @@ -45,10 +60,28 @@ def divine_options(optz={}) :boilerplates => ((defined? MODULE_GENERATOR_BOILERPLATES) ? MODULE_GENERATOR_BOILERPLATES : {} ), :naming => ((defined? MODULE_GENERATOR_NAMING ) ? MODULE_GENERATOR_NAMING : nil ), :update_svn => ((defined? MODULE_GENERATOR_UPDATE_SVN ) ? MODULE_GENERATOR_UPDATE_SVN : false ), - :skeleton_path=> ((defined? MODULE_GENERATOR_SOURCE_ROOT ) ? MODULE_GENERATOR_SOURCE_ROOT.gsub('\\', '/').sub(/^\//, '').sub(/\/$/, '') : "src" ), :test_define => ((defined? MODULE_GENERATOR_TEST_DEFINE ) ? MODULE_GENERATOR_TEST_DEFINE : "TEST" ), + :path_src => ((defined? MODULE_GENERATOR_PATH_SRC ) ? MODULE_GENERATOR_PATH_SRC : nil ), + :path_inc => ((defined? MODULE_GENERATOR_PATH_INC ) ? MODULE_GENERATOR_PATH_INC : nil ), + :path_tst => ((defined? MODULE_GENERATOR_PATH_TST ) ? MODULE_GENERATOR_PATH_TST : nil ), } + # Add our lookup paths to this, based on overall project configuration + unity_generator_options[:paths_src] = @project_config[:collection_paths_source] || [ 'src' ] + unity_generator_options[:paths_inc] = @project_config[:collection_paths_include] || @project_config[:collection_paths_source] || [ 'src' ] + unity_generator_options[:paths_tst] = @project_config[:collection_paths_test] || [ 'test' ] + + # Flatten if necessary + if (unity_generator_options[:paths_src].class == Hash) + unity_generator_options[:paths_src] = unity_generator_options[:paths_src].values.flatten + end + if (unity_generator_options[:paths_inc].class == Hash) + unity_generator_options[:paths_inc] = unity_generator_options[:paths_inc].values.flatten + end + if (unity_generator_options[:paths_tst].class == Hash) + unity_generator_options[:paths_tst] = unity_generator_options[:paths_tst].values.flatten + end + # Read Boilerplate template file. if (defined? MODULE_GENERATOR_BOILERPLATE_FILES) @@ -67,11 +100,20 @@ def divine_options(optz={}) end end - # If using "create[:]" option from command line. - unless optz[:module_root_path].to_s.empty? - unity_generator_options[:path_src] = File.join(optz[:module_root_path], unity_generator_options[:path_src]) - unity_generator_options[:path_inc] = File.join(optz[:module_root_path], unity_generator_options[:path_inc]) - unity_generator_options[:path_tst] = File.join(optz[:module_root_path], unity_generator_options[:path_tst]) + # Check if using "create[:]" optional paths from command line. + if optz[:module_root_path].to_s.empty? + # No path specified. Use the one specified in the module generator section if it exists, + # else the first of each list because we have nothing else to base it on + unity_generator_options[:skeleton_path] ||= unity_generator_options[:paths_src][0] + unity_generator_options[:path_src] ||= unity_generator_options[:paths_src][0] + unity_generator_options[:path_inc] ||= unity_generator_options[:paths_inc][0] + unity_generator_options[:path_tst] ||= unity_generator_options[:paths_tst][0] + else + # A path was specified. Do our best to determine which is the best choice based on this information + unity_generator_options[:skeleton_path] = @ceedling[:file_finder_helper].find_best_path_in_collection(optz[:module_root_path], unity_generator_options[:paths_src], :ignore) || unity_generator_options[:paths_src][0] + unity_generator_options[:path_src] = @ceedling[:file_finder_helper].find_best_path_in_collection(optz[:module_root_path], unity_generator_options[:paths_src], :ignore) || unity_generator_options[:paths_src][0] + unity_generator_options[:path_inc] = @ceedling[:file_finder_helper].find_best_path_in_collection(optz[:module_root_path], unity_generator_options[:paths_inc], :ignore) || unity_generator_options[:paths_inc][0] + unity_generator_options[:path_tst] = @ceedling[:file_finder_helper].find_best_path_in_collection(optz[:module_root_path], unity_generator_options[:paths_tst], :ignore) || unity_generator_options[:paths_tst][0] end return unity_generator_options diff --git a/plugins/module_generator/module_generator.rake b/plugins/module_generator/module_generator.rake index f4ed9f113..e1340f231 100755 --- a/plugins/module_generator/module_generator.rake +++ b/plugins/module_generator/module_generator.rake @@ -1,3 +1,9 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= namespace :module do module_root_separator = ":" @@ -10,6 +16,7 @@ namespace :module do p = files.delete(pat) optz[:pattern] = p unless p.nil? end + files.each do |v| module_root_path, module_name = v.split(module_root_separator, 2) if module_name diff --git a/plugins/raw_output_report/README.md b/plugins/raw_output_report/README.md deleted file mode 100644 index 330e87d39..000000000 --- a/plugins/raw_output_report/README.md +++ /dev/null @@ -1,19 +0,0 @@ -ceedling-raw-output-report -========================== - -## Overview - -The raw-output-report allows you to capture all the output from the called -tools in a single document, so you can trace back through it later. This is -useful for debugging... but can eat through memory quickly if left running. - -## Setup - -Enable the plugin in your project.yml by adding `raw_output_report` -to the list of enabled plugins. - -``` YAML -:plugins: - :enabled: - - raw_output_report -``` diff --git a/plugins/raw_output_report/lib/raw_output_report.rb b/plugins/raw_output_report/lib/raw_output_report.rb deleted file mode 100644 index 014e67714..000000000 --- a/plugins/raw_output_report/lib/raw_output_report.rb +++ /dev/null @@ -1,41 +0,0 @@ -require 'ceedling/plugin' -require 'ceedling/constants' - -class RawOutputReport < Plugin - def setup - @log_paths = {} - end - - def post_test_fixture_execute(arg_hash) - output = strip_output(arg_hash[:shell_result][:output]) - write_raw_output_log(arg_hash, output) - end - - private - - def strip_output(raw_output) - output = "" - raw_output.each_line do |line| - next if line =~ /^\n$/ - next if line =~ /^.*:\d+:.*:(IGNORE|PASS|FAIL)/ - return output if line =~/^-----------------------\n$/ - output << line - end - end - def write_raw_output_log(arg_hash, output) - logging = generate_log_path(arg_hash) - @ceedling[:file_wrapper].write(logging[:path], output , logging[:flags]) unless logging.nil? - end - - def generate_log_path(arg_hash) - f_name = File.basename(arg_hash[:result_file], '.pass') - base_path = File.join(PROJECT_BUILD_ARTIFACTS_ROOT, arg_hash[:context].to_s) - file_path = File.join(base_path, f_name + '.log') - - if @ceedling[:file_wrapper].exist?(base_path) - return { path: file_path, flags: 'w' } - end - - nil - end -end diff --git a/plugins/report_build_warnings_log/README.md b/plugins/report_build_warnings_log/README.md new file mode 100644 index 000000000..1a71faf1c --- /dev/null +++ b/plugins/report_build_warnings_log/README.md @@ -0,0 +1,40 @@ +# Ceedling Plugin: Build Warnings Log + +Capture build process warnings from command tools to a plain text log file. + +# Plugin Overview + +This plugin captures warning messages output by command line tools throughout a +build. At the end of a build, any collected warning messages are written to one +or more plain text log files. + +Warning messages are collected for all compilation-related builds and +differentiated by build context — `test`, `release`, or plugin-modified build +(e.g. `gcov`). + +Ceedling warning messages or warning messages from code generation will not +appear in log files; warnings are only collected from build step command line +tools for the predefined build steps of preprocessing, compilation, and +linking. + +Log files are written to `/artifacts//`. + +# Setup + +Enable the plugin in your Ceedling project file: + +```yaml +:plugins: + :enabled: + - report_build_warnings_log +``` + +# Configuration + +To change the default filename of `warning.log`, add your desired filename to +your configuration file using `:report_build_warnings_log:` ↳ `:filename`. + +```yaml +:report_build_warnings_log: + :filename: more_better_filename.ext +``` diff --git a/plugins/report_build_warnings_log/config/defaults.yml b/plugins/report_build_warnings_log/config/defaults.yml new file mode 100644 index 000000000..dfa1f8c2d --- /dev/null +++ b/plugins/report_build_warnings_log/config/defaults.yml @@ -0,0 +1,12 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + +--- +:report_build_warnings_log: + :filename: warnings.log +... \ No newline at end of file diff --git a/plugins/report_build_warnings_log/lib/report_build_warnings_log.rb b/plugins/report_build_warnings_log/lib/report_build_warnings_log.rb new file mode 100644 index 000000000..2c28a04ee --- /dev/null +++ b/plugins/report_build_warnings_log/lib/report_build_warnings_log.rb @@ -0,0 +1,143 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + +require 'ceedling/plugin' +require 'ceedling/constants' + +class ReportBuildWarningsLog < Plugin + + # `Plugin` setup() + def setup + # Create structure of @warnings hash with default values + @warnings = Hash.new() do |h,k| + # k => :context + h[k] = { + collection: [], + } + end + + # Ceedling can run with multiple threads, provide a lock to use around @warnings + @mutex = Mutex.new() + + # Get default (default.yml) / user-set log filename in project configuration + @log_filename = @ceedling[:configurator].report_build_warnings_log_filename + + # Convenient instance variable references + @file_wrapper = @ceedling[:file_wrapper] + @loginator = @ceedling[:loginator] + @reportinator = @ceedling[:reportinator] + end + + # `Plugin` build step hook + def post_mock_preprocess(arg_hash) + # After preprocessing, parse output, store warning if found + process_output( + arg_hash[:context], + arg_hash[:shell_result][:output], + @warnings + ) + end + + # `Plugin` build step hook + def post_test_preprocess(arg_hash) + # After preprocessing, parse output, store warning if found + process_output( + arg_hash[:context], + arg_hash[:shell_result][:output], + @warnings + ) + end + + # `Plugin` build step hook + def post_compile_execute(arg_hash) + # After compiling, parse output, store warning if found + process_output( + arg_hash[:context], + arg_hash[:shell_result][:output], + @warnings + ) + end + + # `Plugin` build step hook + def post_link_execute(arg_hash) + # After linking, parse output, store warning if found + process_output( + arg_hash[:context], + arg_hash[:shell_result][:output], + @warnings + ) + end + + # `Plugin` build step hook + def post_build() + # Write collected warnings to log(s) + write_logs( @warnings, @log_filename ) + end + + # `Plugin` build step hook + def post_error() + # Write collected warnings to log(s) + write_logs( @warnings, @log_filename ) + end + + ### Private ### + + private + + # Extract warning messages and store to hash in thread-safe manner + def process_output(context, output, hash) + # If $stderr/$stdout does not contain "warning", bail out + return if !(output =~ /warning/i) + + # Store warning message + @mutex.synchronize do + hash[context][:collection] << output + end + end + + # Walk warnings hash and write contents to log file(s) + def write_logs( warnings, filename ) + msg = @reportinator.generate_heading( "Running Warnings Report" ) + @loginator.log( msg ) + + empty = false + + @mutex.synchronize { empty = warnings.empty? } + + if empty + @loginator.log( "Build produced no warnings.\n" ) + return + end + + @mutex.synchronize do + warnings.each do |context, hash| + log_filepath = form_log_filepath( context, filename ) + + msg = @reportinator.generate_progress( "Generating artifact #{log_filepath}" ) + @loginator.log( msg ) + + File.open( log_filepath, 'w' ) do |f| + hash[:collection].each { |warning| f << warning } + end + end + end + + # White space at command line after progress messages + @loginator.log( '' ) + end + + def form_log_filepath(context, filename) + path = File.join( PROJECT_BUILD_ARTIFACTS_ROOT, context.to_s ) + filepath = File.join(path, filename) + + # Ensure containing artifact directory exists + @file_wrapper.mkdir( path ) + + return filepath + end +end diff --git a/plugins/report_tests_gtestlike_stdout/README.md b/plugins/report_tests_gtestlike_stdout/README.md new file mode 100644 index 000000000..802be6c76 --- /dev/null +++ b/plugins/report_tests_gtestlike_stdout/README.md @@ -0,0 +1,96 @@ +# Ceedling Plugin: GTest-like Test Suite Console Report + +Prints to the console ($stdout) test suite results in a GTest-like format. + +# Plugin Overview + +This plugin is intended to be used in place of the more commonly used "pretty" +test report plugin. Like its sibling, this plugin ollects raw test results from +the individual test executables of your test suite and presents them in a more +readable summary form — specifically the GoogleTest format. + +This plugin is most useful when using an IDE or working with a CI system that +understands the GTest console logging format. + +# Setup + +Enable the plugin in your Ceedling project by adding +`report_tests_gtestlike_stdout` to the list of enabled plugins instead of any +other `report_tests_*_stdout` plugin. + +```YAML +:plugins: + :enabled: + - report_tests_gtestlike_stdout +``` + +# Configuration + +No additional configuration is needed once the plugin is enabled. + +# Plugin Output + +## Ceedling mapped to GoogleTest reporting elements + +Ceedling's conventions and output map to GTest format as the following: + +* A Ceedling test file — ultimately an individual test executable — is a GTest + _test case_. +* A Ceedling test case (a.k.a. unit test) is a GTest _test_. +* Execution time is collected for each Ceedling test executable, not each + Ceedling test case. As such, the test report includes only execution time for + each GTest _test case_. Individual test execution times are reported as 0 ms. + +GoogleTest generates reporting output incrementally. Ceedling produces test +results incrementally as well, but its plugin reporting structure does not +collect and format statistics until the end of a build. This plugin duplicates +the tense of the wording in a GTest report, but it is unintentionally somewhat +misleading. + +## Example output (snippet) + +The GTest format is verbose. It lists all tests with success and failure results. + +The example output below shows the header and footer of test results for a suite +of 49 Ceedling tests in 18 test files but only includes logging for 6 tests. + +```sh + > ceedling test:all +``` + +``` +[==========] Running 49 tests from 18 test cases. +[----------] Global test environment set-up. + + ... + +[----------] 4 tests from test/TestUsartModel.c +[ RUN ] test/TestUsartModel.c.testGetBaudRateRegisterSettingShouldReturnAppropriateBaudRateRegisterSetting +[ OK ] test/TestUsartModel.c.testGetBaudRateRegisterSettingShouldReturnAppropriateBaudRateRegisterSetting (0 ms) +[ RUN ] test/TestUsartModel.c.testGetFormattedTemperatureFormatsTemperatureFromCalculatorAppropriately +[ OK ] test/TestUsartModel.c.testGetFormattedTemperatureFormatsTemperatureFromCalculatorAppropriately (0 ms) +[ RUN ] test/TestUsartModel.c.testShouldReturnErrorMessageUponInvalidTemperatureValue +[ OK ] test/TestUsartModel.c.testShouldReturnErrorMessageUponInvalidTemperatureValue (0 ms) +[ RUN ] test/TestUsartModel.c.testShouldReturnWakeupMessage +[ OK ] test/TestUsartModel.c.testShouldReturnWakeupMessage (0 ms) +[----------] 4 tests from test/TestUsartModel.c (1277 ms total) +[----------] 1 tests from test/TestMain.c +[ RUN ] test/TestMain.c.testMainShouldCallExecutorInitAndContinueToCallExecutorRunUntilHalted +[ OK ] test/TestMain.c.testMainShouldCallExecutorInitAndContinueToCallExecutorRunUntilHalted (0 ms) +[----------] 1 tests from test/TestMain.c (1351 ms total) +[----------] 1 tests from test/TestModel.c +[ RUN ] test/TestModel.c.testInitShouldCallSchedulerAndTemperatureFilterInit +test/TestModel.c(21): error: Function TaskScheduler_Init() called more times than expected. + Actual: FALSE + Expected: TRUE +[ FAILED ] test/TestModel.c.testInitShouldCallSchedulerAndTemperatureFilterInit (0 ms) +[----------] 1 tests from test/TestModel.c (581 ms total) + +[----------] Global test environment tear-down. +[==========] 49 tests from 18 test cases ran. +[ PASSED ] 48 tests. +[ FAILED ] 1 tests, listed below: +[ FAILED ] test/TestModel.c.testInitShouldCallSchedulerAndTemperatureFilterInit + + 1 FAILED TESTS +``` diff --git a/plugins/stdout_gtestlike_tests_report/assets/template.erb b/plugins/report_tests_gtestlike_stdout/assets/template.erb similarity index 91% rename from plugins/stdout_gtestlike_tests_report/assets/template.erb rename to plugins/report_tests_gtestlike_stdout/assets/template.erb index fb8e3b13a..b312cdb19 100644 --- a/plugins/stdout_gtestlike_tests_report/assets/template.erb +++ b/plugins/report_tests_gtestlike_stdout/assets/template.erb @@ -1,8 +1,7 @@ % ignored = hash[:results][:counts][:ignored] % failed = hash[:results][:counts][:failed] % stdout_count = hash[:results][:counts][:stdout] -% header_prepend = ((hash[:header].length > 0) ? "#{hash[:header]}: " : '') -% banner_width = 25 + header_prepend.length # widest message +% name_banner = (" #{hash[:header]}" + (' ' * (9 - hash[:header].length)))[0..9] % results = {} % hash[:results][:successes].each do |testresult| % results[ testresult[:source][:file] ] = testresult[:collection] @@ -28,7 +27,8 @@ % end % end - +[==========] +[<%=name_banner%>] [==========] Running <%=hash[:results][:counts][:total].to_s%> tests from <%=results.length.to_s%> test cases. [----------] Global test environment set-up. % results.each_pair do |modulename, moduledetails| @@ -57,17 +57,16 @@ [ OK ] <%=modulename%>.<%=item[:test]%> (0 ms) % end % end -[----------] <%=moduledetails.length.to_s%> tests from <%=modulename%> (0 ms total) +% duration = ((hash[:results][:times][modulename]) * 1000.0).round(0) +[----------] <%=moduledetails.length.to_s%> tests from <%=modulename%> (<%=duration%> ms total) % end % if (hash[:results][:counts][:total] > 0) [----------] Global test environment tear-down. -[==========] <%=hash[:results][:counts][:total].to_s%> tests from <%=hash[:results][:stdout].length.to_s%> test cases ran. +[==========] <%=hash[:results][:counts][:total].to_s%> tests from <%=results.length.to_s%> test cases ran. [ PASSED ] <%=hash[:results][:counts][:passed].to_s%> tests. % if (failed == 0) [ FAILED ] 0 tests. - - 0 FAILED TESTS % else [ FAILED ] <%=failed.to_s%> tests, listed below: % hash[:results][:failures].each do |failure| diff --git a/plugins/report_tests_gtestlike_stdout/config/report_tests_gtestlike_stdout.yml b/plugins/report_tests_gtestlike_stdout/config/report_tests_gtestlike_stdout.yml new file mode 100644 index 000000000..399c3d58a --- /dev/null +++ b/plugins/report_tests_gtestlike_stdout/config/report_tests_gtestlike_stdout.yml @@ -0,0 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +--- +:plugins: + # tell Ceedling we got results display taken care of + :display_raw_test_results: FALSE diff --git a/plugins/report_tests_gtestlike_stdout/lib/report_tests_gtestlike_stdout.rb b/plugins/report_tests_gtestlike_stdout/lib/report_tests_gtestlike_stdout.rb new file mode 100644 index 000000000..20e7553b7 --- /dev/null +++ b/plugins/report_tests_gtestlike_stdout/lib/report_tests_gtestlike_stdout.rb @@ -0,0 +1,69 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'ceedling/plugin' +require 'ceedling/defaults' + +class ReportTestsGtestlikeStdout < Plugin + + # `Plugin` setup() + def setup + @result_list = [] + @mutex = Mutex.new + + # Fetch the test results template for this plugin + template = @ceedling[:file_wrapper].read( File.join( @plugin_root_path, 'assets/template.erb' ) ) + + # Set the report template + @ceedling[:plugin_reportinator].register_test_results_template( template ) + end + + # `Plugin` build step hook -- collect result file paths after each test fixture execution + def post_test_fixture_execute(arg_hash) + # Thread-safe manipulation since test fixtures can be run in child processes + # spawned within multiple test execution threads. + @mutex.synchronize do + @result_list << arg_hash[:result_file] + end + end + + # `Plugin` build step hook -- render a report immediately upon build completion (that invoked tests) + def post_build() + # Ensure a test task was invoked as part of the build + return if not (@ceedling[:task_invoker].test_invoked?) + + results = @ceedling[:plugin_reportinator].assemble_test_results( @result_list ) + hash = { + :header => TEST_SYM.upcase(), + :results => results + } + + @ceedling[:plugin_reportinator].run_test_results_report(hash) + end + + # `Plugin` build step hook -- render a test results report on demand using results from a previous build + def summary() + # Build up the list of passing results from all tests + result_list = @ceedling[:file_path_utils].form_pass_results_filelist( + PROJECT_TEST_RESULTS_PATH, + COLLECTION_ALL_TESTS + ) + + hash = { + :header => TEST_SYM.upcase(), + # Collect all existing test results (success or failing) in the filesystem, + # limited to our test collection + :results => @ceedling[:plugin_reportinator].assemble_test_results( + result_list, + {:boom => false} + ) + } + + @ceedling[:plugin_reportinator].run_test_results_report( hash ) + end + +end diff --git a/plugins/report_tests_ide_stdout/README.md b/plugins/report_tests_ide_stdout/README.md new file mode 100644 index 000000000..03d466550 --- /dev/null +++ b/plugins/report_tests_ide_stdout/README.md @@ -0,0 +1,62 @@ +# Ceedling Plugin: IDE Test Suite Console Report + +Prints to the console ($stdout) test suite results with a test failure filepath and line number format understood by nearly any IDE. + +# Plugin Overview + +This plugin is intended to be used in place of the more commonly used "pretty" +test report plugin. Like its sibling, this plugin ollects raw test results from +the individual test executables of your test suite and presents them in a more +readable summary form. + +The format of test results produced by this plugin is identical to its prettier +sibling with one key difference — test failures are listed in such a way that +filepaths, line numbers, and test case function names can be easily parsed by +a typical IDE. The formatting of test failure messages uses a simple, defacto +standard of a sort recognized almost universally. + +The end result is that test failures in your IDE's build window can become +links that jump directly to failing test cases. + +# Setup + +Enable the plugin in your project.yml by adding `report_tests_ide_stdout` to +the list of enabled plugins instead of any other `report_tests_*_stdout` +plugin. + +``` YAML +:plugins: + :enabled: + - report_tests_ide_stdout +``` + +# Configuration + +No additional configuration is needed once the plugin is enabled. + +# Example Output + +```sh + > ceedling test:Model +``` + +``` +------------------- +FAILED TEST SUMMARY +------------------- +test/TestModel.c:21:testInitShouldCallSchedulerAndTemperatureFilterInit: "Function TaskScheduler_Init() called more times than expected." + +-------------------- +OVERALL TEST SUMMARY +-------------------- +TESTED: 1 +PASSED: 0 +FAILED: 1 +IGNORED: 0 + +--------------------- +BUILD FAILURE SUMMARY +--------------------- +Unit test failures. +``` + diff --git a/plugins/report_tests_ide_stdout/config/report_tests_ide_stdout.yml b/plugins/report_tests_ide_stdout/config/report_tests_ide_stdout.yml new file mode 100644 index 000000000..399c3d58a --- /dev/null +++ b/plugins/report_tests_ide_stdout/config/report_tests_ide_stdout.yml @@ -0,0 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +--- +:plugins: + # tell Ceedling we got results display taken care of + :display_raw_test_results: FALSE diff --git a/plugins/report_tests_ide_stdout/lib/report_tests_ide_stdout.rb b/plugins/report_tests_ide_stdout/lib/report_tests_ide_stdout.rb new file mode 100644 index 000000000..40c343fa5 --- /dev/null +++ b/plugins/report_tests_ide_stdout/lib/report_tests_ide_stdout.rb @@ -0,0 +1,72 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'ceedling/plugin' +require 'ceedling/defaults' + +class ReportTestsIdeStdout < Plugin + + # `Plugin` setup() + def setup + @result_list = [] + @mutex = Mutex.new + + # Set the report template (which happens to be the Ceedling default) + @ceedling[:plugin_reportinator].register_test_results_template( + DEFAULT_TESTS_RESULTS_REPORT_TEMPLATE + ) + end + + # `Plugin` build step hook -- collect result file paths after each test fixture execution + def post_test_fixture_execute(arg_hash) + # Thread-safe manipulation since test fixtures can be run in child processes + # spawned within multiple test execution threads. + @mutex.synchronize do + @result_list << arg_hash[:result_file] + end + end + + # `Plugin` build step hook -- render a report immediately upon build completion (that invoked tests) + def post_build() + # Ensure a test task was invoked as part of the build + return if (not @ceedling[:task_invoker].test_invoked?) + + results = @ceedling[:plugin_reportinator].assemble_test_results( @result_list ) + hash = { + :header => '', + :results => results + } + + @ceedling[:plugin_reportinator].run_test_results_report( hash ) do + message = '' + message = 'Unit test failures.' if (hash[:results][:counts][:failed] > 0) + message + end + end + + # `Plugin` build step hook -- render a test results report on demand using results from a previous build + def summary() + # Build up the list of passing results from all tests + result_list = @ceedling[:file_path_utils].form_pass_results_filelist( + PROJECT_TEST_RESULTS_PATH, + COLLECTION_ALL_TESTS + ) + + hash = { + :header => '', + # Collect all existing test results (success or failing) in the filesystem, + # limited to our test collection + :results => @ceedling[:plugin_reportinator].assemble_test_results( + result_list, + {:boom => false} + ) + } + + @ceedling[:plugin_reportinator].run_test_results_report( hash ) + end + +end diff --git a/plugins/report_tests_log_factory/README.md b/plugins/report_tests_log_factory/README.md new file mode 100644 index 000000000..95dcd3857 --- /dev/null +++ b/plugins/report_tests_log_factory/README.md @@ -0,0 +1,446 @@ +# Ceedling Plugin: Test Suite Report Log Factory + +Generate one or more built-in test suite reports — JSON, JUnit XML, CppUnit XML, or HTML — or create your own. + +# Plugin Overview + +Test reports are handy for all sorts of reasons. Various build and reporting tools are able to generate, visualize, or otherwise process results encoded in handy container formats including JSON and XML. + +This plugin generates one or more of up to four available test suite report formats: + +1. JSON +1. JUnit XML +1. CppUnit XML +1. HTML + +This plugin generates reports after test builds, storing them in your project `artifacts/` build path. + +With a limited amount of Ruby code, you can also create your own report without creating an entire Ceedling plugin. + +# _User Beware_ + +Test reports often lack well managed standards or even much documentation at all. Different revisions of the formats exist as do different flavors of the same version produced by different tools. + +If a test report produced by this plugin does not work for your needs or is not recognized by your report processing tool of choice, well, sadly, this is not all that uncommon. You have at least two options: + +1. Use a script or other tool to post-process the report into a format that works for you. You might be surprised how many of these hacks are commonly necessary and exist peppered throughout online forums. You can incorporate any such post-processing step by enabling the `command_hooks` Ceedling plugin (lower in the plugin list than this plugin) and configuring a Ceedling tool to run the needed transformation. +1. Use Ceedling's abilities plus features of this plugin (documented below) to generate your own test report with a minimal amount of Ruby code. + +# Setup + +Enable the plugin in your Ceedling project file by adding `report_tests_log_factory` to the list of enabled plugins. + +```yaml +:plugins: + :enabled: + - report_tests_log_factory +``` + +All generated reports are written to `/artifacts/`. Your Ceedling project file specifies `` as a required entry for any build. Your build's context defaults to `test`. Certain other test build plugins (e.g. GCov) provide a different context (e.g. `gcov`) for test builds, generally named after themselves. That is, for example, if this plugin is used in conjunction with a GCov coverage build, the reports will end up in a subdirectory other than `test/`, `gcov/`. + +# Configuration + +Enable the reports you wish to generate — `json`, `junit`, and/or `cppunit` — within the `:report_tests_log_factory` ↳ `:reports` configuration list. + +```yaml +:report_tests_log_factory: + # Any one or all four of the following... + :reports: + - json + - junit + - cppunit + - html +``` + +Each report is written to a default filename within `/artifacts/`: + +* JSON: _tests_report.json_ +* JUnit XML: _junit_tests_report.xml_ +* CppUnit XML: _cppunit_tests_report.xml_ +* HTML: _tests_report.html_ + +To change the output filename, specify it with the `:filename` key beneath the relevant report within the `:report_tests_log_factory` configuration block: + +```yaml +:report_tests_log_factory: + # Replace `` with one of the available options above. + # Each report can have its own sub-configuration block. + :reports: + - + :: + :filename: 'more_better_filename.ext' +``` + +# Built-in Reports + +## Execution duration values + +Some test reporting formats include the execution time (duration) for aspects of a test suite run. Various granularities exist from the total time for all tests to the time of each suite (per the relevant defition of a suite) to the time required to run individual test cases. See _CeedlingPacket_ for the details on time duration values. + +Ceedling automatically gathers all the relevant durations. In fact, Ceedling itself performs the needed timing and arithmetric in all cases, except one. Individual test case exection time tracking is specifically a [Unity] feature (see its documentation for more details). If enabled and if your platform supports the time mechanism Unity relies on, Ceedling will automatically collect test case time values and make them available to reports. + +To enable test case duration measurements, they must be enabled as a Unity compilation option. Add `UNITY_INCLUDE_EXEC_TIME` to Unity's compilation symbols (`:unity` ↳ `:defines`) in your Ceedling project file (below). This plugin and the core of Ceedling take care of the rest. Unity test case durations as reported by Ceedling default to 0 if this Unity compilation option is not configured. + +```yaml +:unity: + :defines: + - UNITY_INCLUDE_EXEC_TIME +``` + +_Note:_ Most test cases are quite short, and most computers are quite fast. As such, Unity test case execution time is often reported as 0 milliseconds as the CPU execution time for a test case typically remains in the microseconds range. Unity would require special rigging that is inconsistently available across platforms to measure test case durations at a finer resolution. + +[Unity]: https://github.com/ThrowTheSwitch/Unity + +## JSON Format + +[JSON] is “a lightweight data-interchange format.” JSON serializes common data structures into a human readable form. The format has several pros, including the ability for entirely different programming languages to ingest JSON and recreate these data structures. As such, this makes JSON a good report generation option as the result can be easily programmatically manipulated and put to use. + +Something like XML is a general purpose structure for, well, structuring data. XML has enough formality that XML formats can be validated with general purpose tools plus much more. JSON is much more flexible but rather tied to the data it encodes. Small changes to a data structure can have big impacts. + +The JSON this plugin generates uses an ad hoc set of data structures following no standard — though any other test framework outputting test results in JSON may look fairly similar. + +### Example JSON configuration YAML + +```yaml +:plugins: + :enabled: + - report_tests_log_factory + +:report_tests_log_factory: + :reports: + - json + # Default filename shown for completeness + # `:json` block only needed to override default + :json: + :filename: tests_report.json +``` + +[JSON]: https://www.json.org/ + +### Example JSON test report + +In the following example a single test file _TestUsartModel.c_ exercised four test cases. Two test cases passed, one test case failed, and one test case was ignored. + +```sh + > ceedling test:UsartModel +``` + +```json +{ + "FailedTests": [ + { + "file": "test/TestUsartModel.c", + "test": "testGetFormattedTemperatureFormatsTemperatureFromCalculatorAppropriately", + "line": 25, + "message": "Function TemperatureFilter_GetTemperatureInCelcius() called more times than expected." + } + ], + "PassedTests": [ + { + "file": "test/TestUsartModel.c", + "test": "testGetBaudRateRegisterSettingShouldReturnAppropriateBaudRateRegisterSetting" + }, + { + "file": "test/TestUsartModel.c", + "test": "testShouldReturnErrorMessageUponInvalidTemperatureValue" + } + ], + "IgnoredTests": [ + { + "file": "test/TestUsartModel.c", + "test": "testShouldReturnWakeupMessage" + } + ], + "Summary": { + "total_tests": 4, + "passed": 2, + "ignored": 1, + "failures": 1 + } +} +``` + +## JUnit XML Format + +[JUnit] holds a certain position among testing tools. While it is an xUnit-style framework specific to unit testing Java, it has influenced how Continuous Integration build tools operate, and its [JUnit XML report format][junit-xml-format] has become something of a defacto standard for test reports in any language. The JUnit XML format has been revised in various ways over time but generally has more available documentation than some other formats. + +[JUnit]: https://junit.org/ +[junit-xml-format]: https://docs.getxray.app/display/XRAY/Taking+advantage+of+JUnit+XML+reports + +### Example JUnit configuration YAML + +```yaml +:plugins: + :enabled: + - report_tests_log_factory + +:report_tests_log_factory: + :reports: + - junit + # Default filename shown for completeness + # `:junit` block only needed to override default + :junit: + :filename: junit_tests_report.xml +``` + +### Example JUnit test report + +In the following example a single test file _TestUsartModel.c_ exercised four test cases. Two test cases passed, one test case failed, and one test case was ignored (a.k.a. “skipped” in JUnit lingo). + +In mapping a Ceedling test suite to JUnit convetions, a Ceedling _test file_ becomes a JUnit _test suite_. + +```sh + > ceedling test:UsartModel +``` + +```xml + + + + + + + + + + + + + +``` + +## CppUnit XML Format + +[CppUnit] is an xUnit-style port of the JUnit framework to C/C++. Documentation for its XML test report is scattered and not easily linked. + +[CppUnit]: https://freedesktop.org/wiki/Software/cppunit/ + +### Example CppUnit configuration YAML + +```yaml +:plugins: + :enabled: + - report_tests_log_factory + +:report_tests_log_factory: + :reports: + - cppunit + # Default filename shown for completeness + # `:cppunit` block only needed to override default + :cppunit: + :filename: cppunit_tests_report.xml +``` + +### Example CppUnit test report + +In the following example a single test file _TestUsartModel.c_ exercised four test cases. Two test cases passed, one test case failed, and one test case was ignored. + +In mapping a Ceedling test suite to CppUnit convetions, a CppUnit test name is the concatenation of a Ceedling test filename and a test case function name. As such, a test filename will appear in the report a number of times equal to the number of test cases it holds. Test IDs are merely an incrementing count useful to uniquely identifying tests by number; no ordering or convention is enforced in generating them. + +```sh + > ceedling test:UsartModel +``` + +```xml + + + + + test/TestUsartModel.c::testGetFormattedTemperatureFormatsTemperatureFromCalculatorAppropriately + Assertion + + test/TestUsartModel.c + 25 + + Function TemperatureFilter_GetTemperatureInCelcius() called more times than expected. + + + + + test/TestUsartModel.c::testGetBaudRateRegisterSettingShouldReturnAppropriateBaudRateRegisterSetting + + + test/TestUsartModel.c::testShouldReturnErrorMessageUponInvalidTemperatureValue + + + + + test/TestUsartModel.c::testShouldReturnWakeupMessage + + + + 4 + 1 + 1 + 0 + 1 + + +``` + +## HTML Format + +This plugin creates an adhoc HTML page in a single file. + +### Example HTML configuration YAML + +```yaml +:plugins: + :enabled: + - report_tests_log_factory + +:report_tests_log_factory: + :reports: + - html + # Default filename shown for completeness + # `:html` block only needed to override default + :html: + :filename: tests_report.html +``` + +### Example HTML test report + +![](sample_html_report.png) + +# Creating Your Own Custom Report + +Creating your own report requires three steps: + +1. Choose a directory to hold your report Ruby code and add it to your `:plugins` ↳ `:load_paths` configuration. +1. Create a Ruby file in the directory from (1) per instructions that follow. +1. Enable your new report in your `:report_tests_log_factory` Ceedling configuration. + +## Custom report configuration + +Configuration steps, (1) and (3) above, are documented by example below. Conventions simplify the Ruby programming and require certain naming rules that extend into your project configuration. + +```yaml +:plugins: + :load_paths: # Paths can be relative or absolute + - scripts/ # Add /scripts to Ruby's load paths + :enabled: + - report_tests_log_factory + +:report_tests_log_factory: + :reports: + - fancy_shmancy # Your custom report must follow naming rules (below) +``` + +## Custom `TestsReporter` Ruby code + +To create a custom report, here's what you gotta do: + +1. Create a Ruby file in your configured additional load path named `_tests_reporter.rb`. `` should be in lower case and use underscores if you wish to seperate words (i.e. snakecase). +1. The Ruby code itself must subclass an existing plugin class, `TestsReporter`. +1. Your new subclass must be named `TestsReporter` where `` is the camelcase version of your report name from (1). +1. Fill out up to four methods in your custom `TestsReporter` subclass: + * `setup()` + * `header()` (optional) + * `body()` + * `footer()` (optional) + +Overriding the default filename of your custom report happens just as it does for the built-in reports. In fact, apart from the custom load path, the built-in reports documented above use the same mechanisms as a custom report. These Ruby files can and should be used as references. + +You may access `:report_tests_log_factory` configuration for your custom report using a handy utility method documented in a later section. + +### Sample `TestReporter` custom subclass + +The following code creates a simple, dummy report of the _FancyShmancy_ variety (note the name correspondence to the example configuration YAML above). + +```ruby +# Must include this `require` statement +require 'tests_reporter' + +# Your custom class must: +# 1. Follow the naming convention TestsReporter where +# corresponds to the entry in your +# `:report_tests_log_factory` configuration. +# 2. Sublcass `TestsReporter`. +class FancyShmancyTestsReporter < TestsReporter + + # Must include a method `setup()` that: + # 1. Calls `super()` with a default filename for the custom report. + # (No convention or limitations on filenames.) + # 2. Includes any needed instance variables. + # (`setup()` is effectively your constructor.) + def setup() + super( default_filename: 'fancy_shmancy_tests_report.xml' ) + end + + # If your report includes a header section, fill out this method. + # If no header in your report, this method is not needed in this file at all. + def header(results:, stream:) + stream.puts( '' ) + stream.puts( "" ) + end + + # Process test results into report records + def body(results:, stream:) + results.each do |result| + result[:collection].each do |item| + write_test( item, stream ) + end + end + end + + # If your report includes a footer section, fill out this method. + # If no footer in your report, this method is not needed in this file at all. + def footer(results:, stream:) + stream.puts( "" ) + end + + # If you want to break up your custom reporting code, create all the private + # methods you wish and call them as needed from `setup()`, `header()`, + # `body()`, and `footer()`. + + private + + # A simple helper method for a simple test report entry. + # This methid is not required by a custom `TestReporter` subclass. + def write_test(item, stream) + stream.puts( " #{item[:test]}" ) + end + +end +``` + +### Plugin hooks & test results data structure + +See [_PluginDevelopmentGuide_][custom-plugins] for documentation of the test results data structure (i.e. the `results` method arguments in above sample code). + +See this plugin's built-in `TestsReports` subclasses — `json_tests_reporter.rb`, `junit_tests_reporter.rb`, and `cppunit_tests_reporter.rb` — for examples of using test results. + +[custom-plugins]: ../docs/PluginDevelopmentGuide.md + +### `TestsReporter` utility methods + +#### Configuration access: `fetch_config_value(*keys)` + +You may call the private method `fetch_config_value(*keys)` of the parent class `TestReporters` from your custom subclass to retrieve configuration entries. + +This method automatically indexes into `:report_tests_log_factory` configuration to extract any needed configuration values for your custom report. If the configuration keys do not exist, it simply returns `nil`. Otherwise, it returns the hash, list, string, boolean, or numeric value for the specified key walk into your report's configuration. + +`fetch_config_value(*keys)` expects a list of keys and only accesses configuration beneath `:report_tests_log_factory` ↳ `:`. + +##### Example _FancyShmancy_ configuration + `TestsReporter.fetch_config_value()` calls + +```yaml +report_tests_log_factory: + :fancy_shmancy: + # Hypothetical feature to standardize test names before writing to report + :standardize: + :names: TRUE + :filters: + - '/^Foo/' + - '/Bar$/' +``` + +```ruby +# Calls from within FancyShmancyTestsReporter + +fetch_config_value( :standardize, :names ) => true + +fetch_config_value( :standardize, :filters ) => ['/^Foo/', '/Bar$/'] + +fetch_config_value( :does, :not, :exist ) => nil +``` diff --git a/plugins/report_tests_log_factory/config/defaults.yml b/plugins/report_tests_log_factory/config/defaults.yml new file mode 100644 index 000000000..c1843b349 --- /dev/null +++ b/plugins/report_tests_log_factory/config/defaults.yml @@ -0,0 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +--- +:report_tests_log_factory: + :reports: [] +... \ No newline at end of file diff --git a/plugins/report_tests_log_factory/lib/cppunit_tests_reporter.rb b/plugins/report_tests_log_factory/lib/cppunit_tests_reporter.rb new file mode 100644 index 000000000..004aebbc4 --- /dev/null +++ b/plugins/report_tests_log_factory/lib/cppunit_tests_reporter.rb @@ -0,0 +1,100 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'tests_reporter' + +class CppunitTestsReporter < TestsReporter + + def setup() + super( default_filename: 'cppunit_tests_report.xml' ) + @test_counter = 0 + end + + # CppUnit XML header + def header(results:, stream:) + stream.puts( '' ) + stream.puts( "" ) + end + + # CppUnit XML test list contents + def body(results:, stream:) + @test_counter = 1 + write_failures( results[:failures], stream ) + write_tests( results[:successes], stream, 'SuccessfulTests' ) + write_tests( results[:ignores], stream, 'IgnoredTests' ) + write_statistics( results[:counts], stream ) + end + + # CppUnit XML footer + def footer(results:, stream:) + stream.puts( "" ) + end + + ### Private + + private + + def write_failures(results, stream) + if results.size.zero? + stream.puts( " " ) + return + end + + stream.puts( " " ) + + results.each do |result| + result[:collection].each do |item| + filename = result[:source][:file] + + stream.puts " " + stream.puts " #{filename}::#{item[:test]}" + stream.puts " Assertion" + stream.puts " " + stream.puts " #{filename}" + stream.puts " #{item[:line]}" + stream.puts " " + stream.puts " #{item[:message]}" + stream.puts " " + @test_counter += 1 + end + end + + stream.puts( " " ) + end + + def write_tests(results, stream, tag) + if results.size.zero? + stream.puts( " <#{tag}/>" ) + return + end + + stream.puts( " <#{tag}>" ) + + results.each do |result| + result[:collection].each do |item| + filename = result[:source][:file] + stream.puts( " " ) + stream.puts( " #{filename}::#{item[:test]}" ) + stream.puts( " " ) + @test_counter += 1 + end + end + + stream.puts " " + end + + def write_statistics(counts, stream) + stream.puts( " " ) + stream.puts( " #{counts[:total]}" ) + stream.puts( " #{counts[:ignored]}" ) + stream.puts( " #{counts[:failed]}" ) + stream.puts( " 0" ) + stream.puts( " #{counts[:failed]}" ) + stream.puts( " " ) + end + +end diff --git a/plugins/html_tests_report/lib/html_tests_report.rb b/plugins/report_tests_log_factory/lib/html_tests_reporter.rb similarity index 75% rename from plugins/html_tests_report/lib/html_tests_report.rb rename to plugins/report_tests_log_factory/lib/html_tests_reporter.rb index 31b5440d8..3c8c3e57d 100644 --- a/plugins/html_tests_report/lib/html_tests_report.rb +++ b/plugins/report_tests_log_factory/lib/html_tests_reporter.rb @@ -1,47 +1,20 @@ -require 'ceedling/plugin' -require 'ceedling/constants' +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= -class HtmlTestsReport < Plugin - def setup - @results_list = {} - @test_counter = 0 - end - - def post_test_fixture_execute(arg_hash) - context = arg_hash[:context] - - @results_list[context] = [] if @results_list[context].nil? - - @results_list[context] << arg_hash[:result_file] - end - - def post_build - @results_list.each_key do |context| - results = @ceedling[:plugin_reportinator].assemble_test_results(@results_list[context]) - - artifact_filename = @ceedling[:configurator].project_config_hash[:html_tests_report_artifact_filename] || 'report.html' - artifact_fullpath = @ceedling[:configurator].project_config_hash[:html_tests_report_path] || File.join(PROJECT_BUILD_ARTIFACTS_ROOT, context.to_s) - file_path = File.join(artifact_fullpath, artifact_filename) +require 'tests_reporter' - @ceedling[:file_wrapper].open(file_path, 'w') do |f| - @test_counter = 1 - write_results(results, f) - end - end - end +class HtmlTestsReporter < TestsReporter - private - - def write_results(results, stream) - write_header(stream) - write_statistics(results[:counts], stream) - write_failures(results[:failures], stream) - write_tests(results[:ignores], stream, "Ignored Tests", "ignored") - write_tests(results[:successes], stream, "Success Tests", "success") - write_footer(stream) + def setup() + super( default_filename: 'tests_report.html' ) end - - def write_header(stream) + + # HTML header + def header(results:, stream:) stream.puts "" stream.puts '' stream.puts '' @@ -95,6 +68,24 @@ def write_header(stream) stream.puts '' end + # CppUnit XML test list contents + def body(results:, stream:) + write_statistics( results[:counts], stream) + write_failures( results[:failures], stream) + write_tests( results[:ignores], stream, "Ignored Tests", "ignored" ) + write_tests( results[:successes], stream, "Success Tests", "success" ) + end + + # HTML footer + def footer(results:, stream:) + stream.puts '' + stream.puts '' + end + + ### Private + + private + def write_statistics(counts, stream) stream.puts '

Summary

' stream.puts '' @@ -111,17 +102,15 @@ def write_statistics(counts, stream) end def write_failures(results, stream) - if results.size.zero? - return - end + return if results.size.zero? - stream.puts '

Failed Test

' + stream.puts '

Failed Tests

' stream.puts '
' stream.puts '' stream.puts '' results.each do |result| - filename = result[:source][:path] + result[:source][:file] + filename = result[:source][:file] @first_row = true result[:collection].each do |item| @@ -152,9 +141,7 @@ def write_failures(results, stream) end def write_tests(results, stream, title, style) - if results.size.zero? - return - end + return if results.size.zero? stream.puts "

#{title}

" stream.puts "
FileLocationMessage
" @@ -162,7 +149,7 @@ def write_tests(results, stream, title, style) stream.puts '' results.each do |result| - filename = result[:source][:path] + result[:source][:file] + filename = result[:source][:file] @first_row = true result[:collection].each do |item| @@ -191,8 +178,4 @@ def write_tests(results, stream, title, style) stream.puts "
" end - def write_footer(stream) - stream.puts '' - stream.puts '' - end end diff --git a/plugins/report_tests_log_factory/lib/json_tests_reporter.rb b/plugins/report_tests_log_factory/lib/json_tests_reporter.rb new file mode 100644 index 000000000..d29008f41 --- /dev/null +++ b/plugins/report_tests_log_factory/lib/json_tests_reporter.rb @@ -0,0 +1,72 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'json' +require 'tests_reporter' + +class JsonTestsReporter < TestsReporter + + def setup() + super( default_filename: 'tests_report.json' ) + end + + def body(results:, stream:) + hash = { + "FailedTests" => write_failures( results[:failures] ), + "PassedTests" => write_tests( results[:successes] ), + "IgnoredTests" => write_tests( results[:ignores] ), + "Summary" => write_statistics( results[:counts] ) + } + + stream << JSON.pretty_generate(hash) + end + + ### Private + + private + + def write_failures(results) + # Array of hashes relating a source file, test, and test failure + failures = [] + results.each do |result| + result[:collection].each do |item| + failures << { + "file" => result[:source][:file], + "test" => item[:test], + "line" => item[:line], + "message" => item[:message] + } + end + end + return failures.uniq + end + + def write_tests(results) + # Array of hashes relating a source file and test + successes = [] + results.each do |result| + result[:collection].each do |item| + successes << { + "file" => result[:source][:file], + "test" => item[:test] + } + end + end + return successes + end + + def write_statistics(counts) + # Hash of keys:values for statistics + return { + "total_tests" => counts[:total], + "passed" => (counts[:total] - counts[:ignored] - counts[:failed]), + "ignored" => counts[:ignored], + "failures" => counts[:failed] + } + end + +end \ No newline at end of file diff --git a/plugins/report_tests_log_factory/lib/junit_tests_reporter.rb b/plugins/report_tests_log_factory/lib/junit_tests_reporter.rb new file mode 100644 index 000000000..9d187fba2 --- /dev/null +++ b/plugins/report_tests_log_factory/lib/junit_tests_reporter.rb @@ -0,0 +1,197 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'tests_reporter' + +class JunitTestsReporter < TestsReporter + + def setup() + super( default_filename: 'junit_tests_report.xml' ) + end + + def header(results:, stream:) + stream.puts( '' ) + stream.puts( + '' % results[:total_time] + ) + end + + def body(results:, stream:) + suites = reorganize_results( results ) + + suites.each do |suite| + write_suite( suite, stream ) + end + end + + def footer(results:, stream:) + stream.puts( '' ) + end + + ### Private + + private + + # Reorganize test results by test executable instead of by result category + # Original success structure: successeses { file => test_cases[] } + # Reorganized test results: file => test_cases[{... result: :success}] + def reorganize_results( results ) + # Create structure of hash with default values + suites = Hash.new() do |h,k| + h[k] = { + collection: [], + total: 0, + success: 0, + failed: 0, + ignored: 0, + errors: 0, + time: 0, + stdout: [] + } + end + + results[:successes].each do |result| + # Extract filepath + source = result[:source][:file] + + # Filepath minus file extension + name = source.sub( /#{File.extname(source)}$/, '' ) + + # Sanitize: Ensure no nil elements + result[:collection].compact! + + # Sanitize: Ensure no empty test result hashes + result[:collection].select! {|test| !test.empty?() } + + # Add success test cases to full test case collection and update statistics + suites[name][:collection] += result[:collection].map{|test| test.merge(result: :success)} + suites[name][:total] += result[:collection].length + suites[name][:success] += result[:collection].length + suites[name][:time] = results[:times][source] + end + + results[:failures].each do |result| + # Extract filepath + source = result[:source][:file] + + # Filepath minus file extension + name = source.sub( /#{File.extname(source)}$/, '' ) + + # Sanitize: Ensure no nil elements + result[:collection].compact! + + # Sanitize: Ensure no empty test result hashes + result[:collection].select! {|test| !test.empty?() } + + # Add failure test cases to full test case collection and update statistics + suites[name][:collection] += result[:collection].map{|test| test.merge(result: :failed)} + suites[name][:total] += result[:collection].length + suites[name][:failed] += result[:collection].length + suites[name][:time] = results[:times][source] + end + + results[:ignores].each do |result| + # Extract filepath + source = result[:source][:file] + + # Filepath minus file extension + name = source.sub( /#{File.extname(source)}$/, '' ) + + # Sanitize: Ensure no nil elements + result[:collection].compact! + + # Sanitize: Ensure no empty test result hashes + result[:collection].select! {|test| !test.empty?() } + + # Add ignored test cases to full test case collection and update statistics + suites[name][:collection] += result[:collection].map{|test| test.merge(result: :ignored)} + suites[name][:total] += result[:collection].length + suites[name][:ignored] += result[:collection].length + suites[name][:time] = results[:times][source] + end + + results[:stdout].each do |result| + # Extract filepath + source = result[:source][:file] + # Filepath minus file extension + name = source.sub( /#{File.extname(source)}$/, '' ) + + # Add $stdout messages to collection + suites[name][:stdout] += result[:collection] + end + + # Add name to suite hashes (duplicating the key for suites) + suites.map{|name, data| data.merge(name: name) } + end + + def write_suite( suite, stream ) + stream.puts( + ' ' % suite[:time] + ) + + suite[:collection].each do |test| + write_test( test, stream ) + end + + unless suite[:stdout].empty? + stream.puts(' ') + suite[:stdout].each do |line| + line.gsub!(/&/, '&') + line.gsub!(//, '>') + line.gsub!(/"/, '"') + line.gsub!(/'/, ''') + stream.puts( line ) + end + stream.puts(' ') + end + + stream.puts(' ') + end + + def write_test( test, stream ) + test[:test].gsub!(/&/, '&') + test[:test].gsub!(//, '>') + test[:test].gsub!(/"/, '"') + test[:test].gsub!(/'/, ''') + + case test[:result] + when :success + stream.puts( + ' ' % test + ) + + when :failed + stream.puts( + ' ' % test + ) + + if test[:message].empty? + stream.puts( ' ' ) + else + stream.puts( ' ' % test[:message] ) + end + + stream.puts( ' ' ) + + when :ignored + stream.puts( ' ' % test ) + stream.puts( ' ' ) + stream.puts( ' ' ) + end + end +end diff --git a/plugins/report_tests_log_factory/lib/report_tests_log_factory.rb b/plugins/report_tests_log_factory/lib/report_tests_log_factory.rb new file mode 100644 index 000000000..e18559034 --- /dev/null +++ b/plugins/report_tests_log_factory/lib/report_tests_log_factory.rb @@ -0,0 +1,135 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'ceedling/plugin' + +class ReportTestsLogFactory < Plugin + + # `Plugin` setup() + def setup + # Hash: Context => Array of test executable results files + @results = {} + + # Get our test suite reports' configuration + config = @ceedling[:setupinator].config_hash + @config = config[:report_tests_log_factory] + + # Get list of enabled reports + reports = @config[:reports] + + # Array of Reporter subclass objects + @reporters = load_reporters( reports, @config ) + + # Disable this plugin if no reports configured + @enabled = !(reports.empty?) + + @mutex = Mutex.new() + + @loginator = @ceedling[:loginator] + @reportinator = @ceedling[:reportinator] + end + + # `Plugin` build step hook -- collect context:results_filepath after test fixture runs + def post_test_fixture_execute(arg_hash) + # Do nothing if no reports configured + return if not @enabled + + # Get context from test run + context = arg_hash[:context] + + @mutex.synchronize do + # Create an empty array if context does not already exist as a key + @results[context] = [] if @results[context].nil? + + # Add results filepath to array at context key + @results[context] << arg_hash[:result_file] + end + end + + # `Plugin` build step hook -- process results into log files after test build completes + def post_build + # Do nothing if no reports configured + return if not @enabled + + empty = false + + @mutex.synchronize { empty = @results.empty? } + + # Do nothing if no results were generated (e.g. not a test build) + return if empty + + msg = @reportinator.generate_heading( "Running Test Suite Reports" ) + @loginator.log( msg ) + + @mutex.synchronize do + # For each configured reporter, generate a test suite report per test context + @results.each do |context, results_filepaths| + # Assemble results from all results filepaths collected + _results = @ceedling[:plugin_reportinator].assemble_test_results( results_filepaths ) + + # Provide results to each Reporter + @reporters.each do |reporter| + filepath = File.join( PROJECT_BUILD_ARTIFACTS_ROOT, context.to_s, reporter.filename ) + + msg = @reportinator.generate_progress( "Generating artifact #{filepath}" ) + @loginator.log( msg ) + + reporter.write( filepath: filepath, results: _results ) + end + end + end + + # White space at command line after progress messages + @loginator.log( '' ) + end + + ### Private + + private + + def load_reporters(reports, config) + reporters = [] + + # For each report name string in configuration, dynamically load the corresponding + # Reporter subclass by convention + + # The steps below limit the set up complexity that would otherwise be + # required of a user's custom Reporter subclass + reports.each do |report| + # Enforce lowercase convention internally + report = report.downcase() + + # Convert report configuration name 'foo_bar' to 'FooBarTestReporter' class name + # 1. Convert 'x_Y' (snake case) to camel case ('xY') + # 2. Capitalize first character of config name and add rest of class name + _reporter = report.gsub(/_./) {|match| match.upcase().delete('_') } + _reporter = _reporter[0].capitalize() + _reporter[1..-1] + 'TestsReporter' + + # Load each Reporter sublcass Ruby file dynamically by convention + # For custom user subclasses, requires directoy in :plugins ↳ :load_paths + require "#{report}_tests_reporter" + + # Dynamically instantiate Reporter subclass object + reporter = eval( "#{_reporter}.new(handle: :#{report})" ) + + # Inject configuration + reporter.config = config[report.to_sym] + + # Inject utilty object + reporter.config_walkinator = @ceedling[:config_walkinator] + + # Perform Reporter sublcass set up + reporter.setup() + + # Add new object to our internal list + reporters << reporter + end + + return reporters + end + +end diff --git a/plugins/report_tests_log_factory/lib/tests_reporter.rb b/plugins/report_tests_log_factory/lib/tests_reporter.rb new file mode 100644 index 000000000..427244a0e --- /dev/null +++ b/plugins/report_tests_log_factory/lib/tests_reporter.rb @@ -0,0 +1,70 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +class TestsReporter + + # Dependency injection + attr_writer :config_walkinator + + # Setup value injection + attr_writer :config + + # Publicly accessible filename for the resulting report + attr_reader :filename + + def initialize(handle:) + @handle = handle + + # Safe default filename in case user's custom subclass forgets to call + # setup() with a default filename. + # If the report is named 'foo_bar' in project configuration, the + # fallback filename is 'foo_bar.report' + @filename = "#{handle}.report" + end + + def setup(default_filename:) + @filename = update_filename( default_filename ) + end + + # Write report contents to file + def write(filepath:, results:) + File.open( filepath, 'w' ) do |f| + header( results: results, stream: f ) + body( results: results, stream: f ) + footer( results: results, stream: f ) + end + end + + def header(results:, stream:) + # Override in subclass to do something + end + + def body(results:, stream:) + # Override in subclass to do something + end + + def footer(results:, stream:) + # Override in subclass to do something + end + + ### Private + + private + + def update_filename(default_filename) + # Fetch configured filename if it exists, otherwise return default filename + filename, _ = @config_walkinator.fetch_value( :filename, hash:@config, default:default_filename ) + return filename + end + + # Handy convenience method for subclasses + def fetch_config_value(*keys) + result, _ = @config_walkinator.fetch_value( *keys, hash:@config ) + return result + end + +end \ No newline at end of file diff --git a/plugins/report_tests_log_factory/sample_html_report.png b/plugins/report_tests_log_factory/sample_html_report.png new file mode 100644 index 000000000..2cc6468cc Binary files /dev/null and b/plugins/report_tests_log_factory/sample_html_report.png differ diff --git a/plugins/report_tests_pretty_stdout/README.md b/plugins/report_tests_pretty_stdout/README.md new file mode 100644 index 000000000..a0a368982 --- /dev/null +++ b/plugins/report_tests_pretty_stdout/README.md @@ -0,0 +1,54 @@ +# Ceedling Plugin: Pretty Test Suite Console Report + +Prints to the console ($stdout) simple, readable test suite results. + +# Plugin Overview + +This plugin is intended to be the default option for formatting a test suite's +results when displayed at the console. It collects raw test results from the +individual test executables of your test suite and presents them in a more +readable summary form. + +# Setup + +Enable the plugin in your project.yml by adding `report_tests_pretty_stdout` to +the list of enabled plugins instead of any other `report_tests_*_stdout` +plugin. + +``` YAML +:plugins: + :enabled: + - report_tests_pretty_stdout +``` + +# Configuration + +No additional configuration is needed once the plugin is enabled. + +# Example Output + +```sh + > ceedling test:Model +``` + +``` +------------------- +FAILED TEST SUMMARY +------------------- +[test/TestModel.c] + Test: testInitShouldCallSchedulerAndTemperatureFilterInit + At line (21): "Function TaskScheduler_Init() called more times than expected." + +-------------------- +OVERALL TEST SUMMARY +-------------------- +TESTED: 1 +PASSED: 0 +FAILED: 1 +IGNORED: 0 + +--------------------- +BUILD FAILURE SUMMARY +--------------------- +Unit test failures. +``` diff --git a/plugins/stdout_pretty_tests_report/assets/template.erb b/plugins/report_tests_pretty_stdout/assets/template.erb similarity index 74% rename from plugins/stdout_pretty_tests_report/assets/template.erb rename to plugins/report_tests_pretty_stdout/assets/template.erb index 52b29f7f0..609a43eca 100644 --- a/plugins/stdout_pretty_tests_report/assets/template.erb +++ b/plugins/report_tests_pretty_stdout/assets/template.erb @@ -20,11 +20,11 @@ [<%=ignore[:source][:file]%>] % ignore[:collection].each do |item| Test: <%=item[:test]%> -% if (not item[:message].empty?) +% if (not item[:message].empty?) At line (<%=item[:line]%>): "<%=item[:message]%>" -% else +% else At line (<%=item[:line]%>) -% end +% end % end % end @@ -34,19 +34,23 @@ % hash[:results][:failures].each do |failure| [<%=failure[:source][:file]%>] % failure[:collection].each do |item| +% header = "At line (#{item[:line]}):" Test: <%=item[:test]%> -% if (not item[:message].empty?) - At line (<%=item[:line]%>): "<%=item[:message]%>" -% else - At line (<%=item[:line]%>) -% end +% if (not item[:message].empty?) +% # Cause multline error messages to wrap with indentation aligned with line header +% msg = item[:message].split("\n").enum_for(:each_with_index).map{|line,i| ((i >= 1) ? (' ' * (header.size + 3)) : '') + line}.join("\n") + <%=header%> "<%=msg%>" +% else + <%=header%> +% end % end % end % end % total_string = hash[:results][:counts][:total].to_s % format_string = "%#{total_string.length}i" -<%=@ceedling[:plugin_reportinator].generate_banner(header_prepend + 'OVERALL TEST SUMMARY')%> +% decorator = (failed > 0) ? LogLabels::FAIL : LogLabels::PASS +<%=@ceedling[:plugin_reportinator].generate_banner(header_prepend + @loginator.decorate('OVERALL TEST SUMMARY', decorator))%> % if (hash[:results][:counts][:total] > 0) TESTED: <%=hash[:results][:counts][:total].to_s%> PASSED: <%=sprintf(format_string, hash[:results][:counts][:passed])%> diff --git a/plugins/report_tests_pretty_stdout/config/report_tests_pretty_stdout.yml b/plugins/report_tests_pretty_stdout/config/report_tests_pretty_stdout.yml new file mode 100644 index 000000000..399c3d58a --- /dev/null +++ b/plugins/report_tests_pretty_stdout/config/report_tests_pretty_stdout.yml @@ -0,0 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +--- +:plugins: + # tell Ceedling we got results display taken care of + :display_raw_test_results: FALSE diff --git a/plugins/report_tests_pretty_stdout/lib/report_tests_pretty_stdout.rb b/plugins/report_tests_pretty_stdout/lib/report_tests_pretty_stdout.rb new file mode 100644 index 000000000..992949e5d --- /dev/null +++ b/plugins/report_tests_pretty_stdout/lib/report_tests_pretty_stdout.rb @@ -0,0 +1,73 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'ceedling/plugin' +require 'ceedling/defaults' + +class ReportTestsPrettyStdout < Plugin + + # `Plugin` setup() + def setup + @result_list = [] + @mutex = Mutex.new + + # Fetch the test results template for this plugin + template = @ceedling[:file_wrapper].read( File.join( @plugin_root_path, 'assets/template.erb' ) ) + + # Set the report template + @ceedling[:plugin_reportinator].register_test_results_template( template ) + end + + # `Plugin` build step hook -- collect result file paths after each test fixture execution + def post_test_fixture_execute(arg_hash) + # Thread-safe manipulation since test fixtures can be run in child processes + # spawned within multiple test execution threads. + @mutex.synchronize do + @result_list << arg_hash[:result_file] + end + end + + # `Plugin` build step hook -- render a report immediately upon build completion (that invoked tests) + def post_build() + # Ensure a test task was invoked as part of the build + return if not (@ceedling[:task_invoker].test_invoked?) + + results = @ceedling[:plugin_reportinator].assemble_test_results( @result_list ) + hash = { + :header => '', + :results => results + } + + @ceedling[:plugin_reportinator].run_test_results_report(hash) do + message = '' + message = 'Unit test failures.' if (results[:counts][:failed] > 0) + message + end + end + + # `Plugin` build step hook -- render a test results report on demand using results from a previous build + def summary() + # Build up the list of passing results from all tests + result_list = @ceedling[:file_path_utils].form_pass_results_filelist( + PROJECT_TEST_RESULTS_PATH, + COLLECTION_ALL_TESTS + ) + + hash = { + :header => '', + # Collect all existing test results (success or failing) in the filesystem, + # limited to our test collection + :results => @ceedling[:plugin_reportinator].assemble_test_results( + result_list, + {:boom => false} + ) + } + + @ceedling[:plugin_reportinator].run_test_results_report( hash ) + end + +end diff --git a/plugins/report_tests_raw_output_log/README.md b/plugins/report_tests_raw_output_log/README.md new file mode 100644 index 000000000..560d4b116 --- /dev/null +++ b/plugins/report_tests_raw_output_log/README.md @@ -0,0 +1,50 @@ +# Ceedling Plugin: Raw Test Output Logs + +Capture extra console output — typically `printf()`-style statements — from +test cases to log files. + +# Plugin Overview + +This plugin gathers and filters console output from test executables into log +files. Though not required, it is usually used in addition to the +`report_tests_*_stdout` plugins that gather and format test results for display +at the console. + +Debugging in unit tested code is often accomplished with simple `printf()`- +style calls to dump information to the console. This plugin's log files can be +helpful in supporting debugging efforts or quality validation. + +## Test executable output + +Ceedling and Unity cooperate to extract console statements from test executable +runs. Unity-based test executables print test case pass/fail status messages +and test case accounting to the console ($stdout). + +Ceedling and various reporting plugins gather all this, including unrecognized +output, to format it and present summaries at the console. + +This plugin captures the unrecognized output to log files. + +## Log files + +Log files are only created if test executables produce console output apart from +expected Unity test results as described above. Log files are named for the +respective test executables. + +Builds are differentiated by build context — `test`, `release`, or +plugin-modified build (e.g. `gcov`). Log files are written to `/artifacts//.raw.log`. + +# Setup + +Enable the plugin in your Ceedling project: + +``` YAML +:plugins: + :enabled: + - report_tests_raw_output_log +``` + +# Configuration + +No additional configuration is needed once the plugin is enabled. \ No newline at end of file diff --git a/plugins/report_tests_raw_output_log/lib/report_tests_raw_output_log.rb b/plugins/report_tests_raw_output_log/lib/report_tests_raw_output_log.rb new file mode 100644 index 000000000..a1fd89ac5 --- /dev/null +++ b/plugins/report_tests_raw_output_log/lib/report_tests_raw_output_log.rb @@ -0,0 +1,129 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'ceedling/plugin' +require 'ceedling/constants' + +class ReportTestsRawOutputLog < Plugin + # `Plugin` setup() + def setup + # @raw_output hash with default values + @raw_output = {} + + # Ceedling can run with multiple threads, provide a lock to use around @raw_output + @mutex = Mutex.new() + + # Convenient instance variable references + @file_wrapper = @ceedling[:file_wrapper] + @loginator = @ceedling[:loginator] + @reportinator = @ceedling[:reportinator] + end + + # `Plugin` build step hook + def post_test_fixture_execute(arg_hash) + output = extract_output( arg_hash[:shell_result][:output] ) + + # Bail out early + return if output.empty? + + # After test fixture execution, parse output, store any raw console statements + @mutex.synchronize do + process_output( + arg_hash[:context], + arg_hash[:test_name], + output, + @raw_output + ) + end + end + + # `Plugin` build step hook + def post_build() + # Write collected raw output to log(s) + write_logs( @raw_output ) + end + + # `Plugin` build step hook + def post_error() + # Write collected raw output to log(s) + write_logs( @raw_output ) + end + + ### Private ### + + private + + # Pick apart test executable console output to find any lines not specific to a test case + def extract_output(raw_output) + output = [] + + raw_output.each_line do |line| + # Skip blank lines + next if line =~ /^\s*\n$/ + + # Skip test case reporting lines + next if line =~ /^.+:\d+:.+:(IGNORE|PASS|FAIL)/ + + # Return early if we get to test results summary footer + return output if line =~/^-+\n$/ + + # Capture all other console output from the test runner, including `printf()`-style debugging statements + output << line + end + + return output + end + + # Store raw output messages to hash in thread-safe manner + def process_output(context, test, output, hash) + # Store warning message + hash[context] = {} if hash[context].nil? + hash[context][test] = output + end + + def write_logs(hash) + msg = @reportinator.generate_heading( "Running Raw Tests Output Report" ) + @loginator.log( msg ) + + empty = false + + @mutex.synchronize { empty = hash.empty? } + + if empty + @loginator.log( "Tests produced no extra console output.\n" ) + return + end + + @mutex.synchronize do + hash.each do |context, tests| + tests.each do |test, output| + log_filepath = form_log_filepath( context, test ) + + msg = @reportinator.generate_progress( "Generating artifact #{log_filepath}" ) + @loginator.log( msg ) + + File.open( log_filepath, 'w' ) do |f| + output.each { |line| f << line } + end + end + end + end + + # White space at command line after progress messages + @loginator.log( '' ) + end + + def form_log_filepath(context, test) + path = File.join( PROJECT_BUILD_ARTIFACTS_ROOT, context.to_s ) + filepath = File.join(path, test + '.raw.log') + + # Ensure containing artifact directory exists + @file_wrapper.mkdir( path ) + + return filepath + end +end diff --git a/plugins/report_tests_teamcity_stdout/README.md b/plugins/report_tests_teamcity_stdout/README.md new file mode 100644 index 000000000..82204db0e --- /dev/null +++ b/plugins/report_tests_teamcity_stdout/README.md @@ -0,0 +1,94 @@ +# Ceedling Plugin: TeamCity Test Suite Console Report + +Prints to the console ($stdout) test suite build events and results in a format understood by the CI product TeamCity. + +# Plugin Overview + +This plugin is intended to be used within [TeamCity] Continuous Integration +(CI) builds. It processes Ceedling test suites and executable output into +TeamCity [service messages][service-messages]. Service messages are a specially +formatted type of log output that a TeamCity build server picks out of build +output to collect progress and metrics of various sorts. + +Typically, this plugin is used only in CI builds. Its output is unhelpful in +development builds locally. See the [Configuration](#configuration) section for +options on enabling the build in CI but disabling it locally. + +[TeamCity]: https://www.jetbrains.com/teamcity/ +[service-messages]: +https://www.jetbrains.com/help/teamcity/service-messages.html + +# Setup + +Enable the plugin in your Ceedling project file by adding +`report_tests_teamcity_stdout`. + +``` YAML +:plugins: + :enabled: + - report_tests_teamcity_stdout +``` + +# Configuration + +All the `report_tests_*_stdout` plugins may be enabled in various combinations. +But, some combinations may not make a great deal of sense. The TeamCity +plugin “plays nice” with all the others but is generally most appropriate +running in a CI build on a TeamCity server. Its output will clutter and obscure +console logging at a local development environment command line. + +You may enable the TeamCity plugin (above) but disable its operation using the +following. + +```YAML +:teamcity: + :build: FALSE +``` + +This may seem silly, right? Why enable the plugin and then disable it, +cancelling it out? The answer has to do with _where_ you use the second YAML +blurb configuration setting. + +Ceedling provides Mixins for applying configurations settings on top of your +base project configuraiton file. +See the [Mixins documentation][ceedling-mixins] for full details. + +[ceedling-mixins]: ../docs/CeedlingPacket.md#base-project-configuration-file-mixins-section-entries + +As an example, you might enable the plugin in the main project file that is +committed to your repository while disabling the plugin in your local user +project file that is ignored by your repository. In this way, the plugin would +run on a TeamCity build server but not in your local development environment. + +# Example Output + +TeamCity's convention for identifying tests uses the naming convention of the +underlying Java language in which TeamCity is written, +`package_or_namespace.ClassName.TestName`. + +This plugin maps Ceedling conventions to TeamCity test service messages as +`context.TestFilepath.TestCaseName`. + +* `context` Your build's context defaults to `test`. Certain other test build + plugins (e.g. GCov) provide a different context (`gcov`) for test builds, + generally named after themselves. +* `TestFilepath` This identifier is the relative filepath of the relevant test + file without a file extension (e.g. no `.c`). +* `TestCaseName` This identifier is a test case function name within a Ceedling test file. + +```sh + > ceedling test:UsartModel +``` + +``` +##teamcity[testSuiteStarted name='TestUsartModel' flowId='15'] +##teamcity[testStarted name='test.test/TestUsartModel.testGetBaudRateRegisterSettingShouldReturnAppropriateBaudRateRegisterSetting' flowId='15'] +##teamcity[testFinished name='test.test/TestUsartModel.testGetBaudRateRegisterSettingShouldReturnAppropriateBaudRateRegisterSetting' duration='81' flowId='15'] +##teamcity[testStarted name='test.test/TestUsartModel.testShouldReturnErrorMessageUponInvalidTemperatureValue' flowId='15'] +##teamcity[testFinished name='test.test/TestUsartModel.testShouldReturnErrorMessageUponInvalidTemperatureValue' duration='81' flowId='15'] +##teamcity[testStarted name='test.test/TestUsartModel.testGetFormattedTemperatureFormatsTemperatureFromCalculatorAppropriately' flowId='15'] +##teamcity[testFailed name='test.test/TestUsartModel.testGetFormattedTemperatureFormatsTemperatureFromCalculatorAppropriately' message='Function TemperatureFilter_GetTemperatureInCelcius() called more times than expected.' details='File: test/TestUsartModel.c Line: 25' flowId='15'] +##teamcity[testFinished name='test.test/TestUsartModel.testGetFormattedTemperatureFormatsTemperatureFromCalculatorAppropriately' duration='81' flowId='15'] +##teamcity[testIgnored name='test.test/TestUsartModel.testShouldReturnWakeupMessage' flowId='15'] +##teamcity[testSuiteFinished name='TestUsartModel' flowId='15'] +``` diff --git a/plugins/report_tests_teamcity_stdout/config/defaults.yml b/plugins/report_tests_teamcity_stdout/config/defaults.yml new file mode 100644 index 000000000..831244422 --- /dev/null +++ b/plugins/report_tests_teamcity_stdout/config/defaults.yml @@ -0,0 +1,12 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +--- +:teamcity: + # Override to FALSE in user, local project options to prevent CI $stdout messages + :build: TRUE +... \ No newline at end of file diff --git a/plugins/report_tests_teamcity_stdout/config/report_tests_teamcity_stdout.yml b/plugins/report_tests_teamcity_stdout/config/report_tests_teamcity_stdout.yml new file mode 100644 index 000000000..399c3d58a --- /dev/null +++ b/plugins/report_tests_teamcity_stdout/config/report_tests_teamcity_stdout.yml @@ -0,0 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +--- +:plugins: + # tell Ceedling we got results display taken care of + :display_raw_test_results: FALSE diff --git a/plugins/report_tests_teamcity_stdout/lib/report_tests_teamcity_stdout.rb b/plugins/report_tests_teamcity_stdout/lib/report_tests_teamcity_stdout.rb new file mode 100644 index 000000000..c26789062 --- /dev/null +++ b/plugins/report_tests_teamcity_stdout/lib/report_tests_teamcity_stdout.rb @@ -0,0 +1,163 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'ceedling/plugin' +require 'ceedling/defaults' + +class ReportTestsTeamcityStdout < Plugin + + # `Plugin` setup() + def setup + # TEAMCITY_BUILD defaults to true but can be overridden in a user + # project file to stop CI messages locally. + @output_enabled = TEAMCITY_BUILD + + # Provide thread-safety for multi-threaded builds + @mutex = Mutex.new + + # A counter incremented for each Ceedling test executable allowing + # concurrent executables to differentiate their service messages. + @flowid_count = 0 + + # A TeamCity suite correlates to a Ceedling test executable + # This hash relates each test filepath to a unique Flow ID + # (TeamCity uses Flow IDs to differentiate messages generated in concurrent threads) + @suites = {} + end + + # `Plugin` build step hook + def pre_test(test) + return if !@output_enabled + + # Generate a new Flow ID and store it in test hash + @mutex.synchronize do + @flowid_count += 1 + @suites[test] = { :flowid => @flowid_count } + end + + # Generate first TeamCity service message + @mutex.synchronize do + teamcity_service_message( + "testSuiteStarted name='#{File.basename(test, '.*')}'", + @suites[test][:flowid] + ) + end + end + + # `Plugin` build step hook + def post_test_fixture_execute(arg_hash) + return if !@output_enabled + + flowId = nil + test_key = arg_hash[:test_filepath] + + # Get unique Flow ID + @mutex.synchronize do + flowId = @suites[test_key][:flowid] + end + + # Gather test results for this test executable + results = @ceedling[:plugin_reportinator].assemble_test_results( [arg_hash[:result_file]] ) + + # Apportion total run time of test executable equally to each test case within it. + duration_ms = results[:total_time] * 1000.0 + avg_duration = (duration_ms / [1, results[:counts][:passed] + results[:counts][:failed]].max).round + + context = arg_hash[:context] + + # Handle test case successes within the test executable + results[:successes].each do |success| + filepath = File.join( success[:source][:dirname], File.basename( success[:source][:basename], '.*' ) ) + + # puts(success) + success[:collection].each do |test| + _test = test[:test] + + # Create Java-like name per TeamCity convention `package_or_namespace.ClassName.TestName` + # ==> `context.TestFilepath.TestCaseName` + teamcity_service_message( + "testStarted name='#{context}.#{filepath}.#{_test}'", + flowId + ) + + teamcity_service_message( + "testFinished name='#{context}.#{filepath}.#{_test}' duration='#{avg_duration}'", + flowId + ) + end + end + + # Handle test case failures within the test executable + results[:failures].each do |failure| + failure[:collection].each do |test| + filepath = File.join( failure[:source][:dirname], File.basename( failure[:source][:basename], '.*' ) ) + + _test = test[:test] + _message = test[:message] + + teamcity_service_message( + "testStarted name='#{context}.#{filepath}.#{_test}'", + flowId + ) + + # Create Java-like name per TeamCity convention `package_or_namespace.ClassName.TestName` + # ==> `context.TestFilepath.TestCaseName` + _message = + "testFailed name='#{context}.#{filepath}.#{_test}' " + + "message='#{escape(_message)}' " + + "details='File: #{failure[:source][:file]} Line: #{test[:line]}'" + + teamcity_service_message( _message, flowId ) + + teamcity_service_message( + "testFinished name='#{context}.#{filepath}.#{_test}' duration='#{avg_duration}'", + flowId + ) + end + end + + # Handle ignored tests + results[:ignores].each do |ignored| + ignored[:collection].each do |test| + filepath = File.join( ignored[:source][:dirname], File.basename( ignored[:source][:basename], '.*' ) ) + _test = test[:test] + + service_message = "testIgnored name='#{context}.#{filepath}.#{_test}'" + + teamcity_service_message( service_message, flowId ) + end + end + + end + + # `Plugin` build step hook + def post_test(test) + return if !@output_enabled + + @mutex.synchronize do + teamcity_service_message( + "testSuiteFinished name='#{File.basename(test, '.*')}'", + @suites[test][:flowid] + ) + end + end + + ### Private + + private + + def escape(string) + # https://www.jetbrains.com/help/teamcity/service-messages.html#Escaped+Values + string.gsub(/['|\[\]]/, '|\0').gsub('\r', '|r').gsub('\n', '|n') + end + + def teamcity_service_message(content, flowId=0) + # https://www.jetbrains.com/help/teamcity/service-messages.html + puts "##teamcity[#{content} flowId='#{flowId}']" + end + +end diff --git a/plugins/stdout_gtestlike_tests_report/README.md b/plugins/stdout_gtestlike_tests_report/README.md deleted file mode 100644 index 9ab60847e..000000000 --- a/plugins/stdout_gtestlike_tests_report/README.md +++ /dev/null @@ -1,19 +0,0 @@ -ceedling-stdout-gtestlike-tests-report -====================== - -## Overview - -The stdout_gtestlike_tests_report replaces the normal ceedling "pretty" output with -a variant that resembles the output of gtest. This is most helpful when trying to -integrate into an IDE or CI that is meant to work with google test. - -## Setup - -Enable the plugin in your project.yml by adding `stdout_gtestlike_tests_report` -to the list of enabled plugins. - -``` YAML -:plugins: - :enabled: - - stdout_gtestlike_tests_report -``` diff --git a/plugins/stdout_gtestlike_tests_report/assets/template.erb copy b/plugins/stdout_gtestlike_tests_report/assets/template.erb copy deleted file mode 100644 index a90f495e2..000000000 --- a/plugins/stdout_gtestlike_tests_report/assets/template.erb copy +++ /dev/null @@ -1,59 +0,0 @@ -% ignored = hash[:results][:counts][:ignored] -% failed = hash[:results][:counts][:failed] -% stdout_count = hash[:results][:counts][:stdout] -% header_prepend = ((hash[:header].length > 0) ? "#{hash[:header]}: " : '') -% banner_width = 25 + header_prepend.length # widest message - - -% if (stdout_count > 0) -[==========] Running <%=hash[:results][:counts][:total].to_s%> tests from <%=hash[:results][:stdout].length.to_s%> test cases. -[----------] Global test environment set-up. -% end -% if (failed > 0) -% hash[:results][:failures].each do |failure| -[----------] <%=failure[:collection].length.to_s%> tests from <%=failure[:source][:file]%> -% failure[:collection].each do |item| -[ RUN ] <%=failure[:source][:file]%>.<%=item[:test]%> -% if (not item[:message].empty?) -<%=failure[:source][:file]%>(<%=item[:line]%>): error: <%=item[:message]%> - -% m = item[:message].match(/Expected\s+(.*)\s+Was\s+([^\.]*)\./) -% if m.nil? - Actual: FALSE - Expected: TRUE -% else - Actual: <%=m[2]%> - Expected: <%=m[1]%> -% end -% else -<%=failure[:source][:file]%>(<%=item[:line]%>): fail: <%=item[:message]%> - Actual: FALSE - Expected: TRUE -% end -[ FAILED ] <%=failure[:source][:file]%>.<%=item[:test]%> (0 ms) -% end -[----------] <%=failure[:collection].length.to_s%> tests from <%=failure[:source][:file]%> (0 ms total) -% end -% end -% if (hash[:results][:counts][:total] > 0) -[----------] Global test environment tear-down. -[==========] <%=hash[:results][:counts][:total].to_s%> tests from <%=hash[:results][:stdout].length.to_s%> test cases ran. -[ PASSED ] <%=hash[:results][:counts][:passed].to_s%> tests. -% if (failed == 0) -[ FAILED ] 0 tests. - - 0 FAILED TESTS -% else -[ FAILED ] <%=failed.to_s%> tests, listed below: -% hash[:results][:failures].each do |failure| -% failure[:collection].each do |item| -[ FAILED ] <%=failure[:source][:file]%>.<%=item[:test]%> -% end -% end - - <%=failed.to_s%> FAILED TESTS -% end -% else - -No tests executed. -% end diff --git a/plugins/stdout_gtestlike_tests_report/config/stdout_gtestlike_tests_report.yml b/plugins/stdout_gtestlike_tests_report/config/stdout_gtestlike_tests_report.yml deleted file mode 100644 index c25acf511..000000000 --- a/plugins/stdout_gtestlike_tests_report/config/stdout_gtestlike_tests_report.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -:plugins: - # tell Ceedling we got results display taken care of - :display_raw_test_results: FALSE diff --git a/plugins/stdout_gtestlike_tests_report/lib/stdout_gtestlike_tests_report.rb b/plugins/stdout_gtestlike_tests_report/lib/stdout_gtestlike_tests_report.rb deleted file mode 100644 index a51438a38..000000000 --- a/plugins/stdout_gtestlike_tests_report/lib/stdout_gtestlike_tests_report.rb +++ /dev/null @@ -1,43 +0,0 @@ -require 'ceedling/plugin' -require 'ceedling/defaults' - -class StdoutGtestlikeTestsReport < Plugin - - def setup - @result_list = [] - @plugin_root = File.expand_path(File.join(File.dirname(__FILE__), '..')) - template = @ceedling[:file_wrapper].read(File.join(@plugin_root, 'assets/template.erb')) - @ceedling[:plugin_reportinator].register_test_results_template( template ) - end - - def post_test_fixture_execute(arg_hash) - return if not (arg_hash[:context] == TEST_SYM) - - @result_list << arg_hash[:result_file] - end - - def post_build - return if not (@ceedling[:task_invoker].test_invoked?) - - results = @ceedling[:plugin_reportinator].assemble_test_results(@result_list) - hash = { - :header => '', - :results => results - } - - @ceedling[:plugin_reportinator].run_test_results_report(hash) - end - - def summary - result_list = @ceedling[:file_path_utils].form_pass_results_filelist( PROJECT_TEST_RESULTS_PATH, COLLECTION_ALL_TESTS ) - - # get test results for only those tests in our configuration and of those only tests with results on disk - hash = { - :header => '', - :results => @ceedling[:plugin_reportinator].assemble_test_results(result_list, {:boom => false}) - } - - @ceedling[:plugin_reportinator].run_test_results_report(hash) - end - -end diff --git a/plugins/stdout_ide_tests_report/README.md b/plugins/stdout_ide_tests_report/README.md deleted file mode 100644 index ed6c65582..000000000 --- a/plugins/stdout_ide_tests_report/README.md +++ /dev/null @@ -1,18 +0,0 @@ -ceedling-stdout-ide-tests-report -================================ - -## Overview - -The stdout_ide_tests_report replaces the normal ceedling "pretty" output with -a simplified variant intended to be easily parseable. - -## Setup - -Enable the plugin in your project.yml by adding `stdout_ide_tests_report` -to the list of enabled plugins. - -``` YAML -:plugins: - :enabled: - - stdout_ide_tests_report -``` diff --git a/plugins/stdout_ide_tests_report/config/stdout_ide_tests_report.yml b/plugins/stdout_ide_tests_report/config/stdout_ide_tests_report.yml deleted file mode 100644 index c25acf511..000000000 --- a/plugins/stdout_ide_tests_report/config/stdout_ide_tests_report.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -:plugins: - # tell Ceedling we got results display taken care of - :display_raw_test_results: FALSE diff --git a/plugins/stdout_ide_tests_report/lib/stdout_ide_tests_report.rb b/plugins/stdout_ide_tests_report/lib/stdout_ide_tests_report.rb deleted file mode 100644 index 48b3e819c..000000000 --- a/plugins/stdout_ide_tests_report/lib/stdout_ide_tests_report.rb +++ /dev/null @@ -1,44 +0,0 @@ -require 'ceedling/plugin' -require 'ceedling/defaults' - -class StdoutIdeTestsReport < Plugin - - def setup - @result_list = [] - end - - def post_test_fixture_execute(arg_hash) - return if not (arg_hash[:context] == TEST_SYM) - - @result_list << arg_hash[:result_file] - end - - def post_build - return if (not @ceedling[:task_invoker].test_invoked?) - - results = @ceedling[:plugin_reportinator].assemble_test_results(@result_list) - hash = { - :header => '', - :results => results - } - - @ceedling[:plugin_reportinator].run_test_results_report(hash) do - message = '' - message = 'Unit test failures.' if (hash[:results][:counts][:failed] > 0) - message - end - end - - def summary - result_list = @ceedling[:file_path_utils].form_pass_results_filelist( PROJECT_TEST_RESULTS_PATH, COLLECTION_ALL_TESTS ) - - # get test results for only those tests in our configuration and of those only tests with results on disk - hash = { - :header => '', - :results => @ceedling[:plugin_reportinator].assemble_test_results(result_list, {:boom => false}) - } - - @ceedling[:plugin_reportinator].run_test_results_report(hash) - end - -end diff --git a/plugins/stdout_pretty_tests_report/README.md b/plugins/stdout_pretty_tests_report/README.md deleted file mode 100644 index 7e1be2382..000000000 --- a/plugins/stdout_pretty_tests_report/README.md +++ /dev/null @@ -1,20 +0,0 @@ -ceedling-pretty-tests-report -============================ - -## Overview - -The stdout_pretty_tests_report is the default output of ceedling. Instead of -showing most of the raw output of CMock, Ceedling, etc., it shows a simplified -view. It also creates a nice summary at the end of execution which groups the -results into ignored and failed tests. - -## Setup - -Enable the plugin in your project.yml by adding `stdout_pretty_tests_report` -to the list of enabled plugins. - -``` YAML -:plugins: - :enabled: - - stdout_pretty_tests_report -``` diff --git a/plugins/stdout_pretty_tests_report/config/stdout_pretty_tests_report.yml b/plugins/stdout_pretty_tests_report/config/stdout_pretty_tests_report.yml deleted file mode 100644 index c25acf511..000000000 --- a/plugins/stdout_pretty_tests_report/config/stdout_pretty_tests_report.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -:plugins: - # tell Ceedling we got results display taken care of - :display_raw_test_results: FALSE diff --git a/plugins/stdout_pretty_tests_report/lib/stdout_pretty_tests_report.rb b/plugins/stdout_pretty_tests_report/lib/stdout_pretty_tests_report.rb deleted file mode 100644 index 018388fc1..000000000 --- a/plugins/stdout_pretty_tests_report/lib/stdout_pretty_tests_report.rb +++ /dev/null @@ -1,47 +0,0 @@ -require 'ceedling/plugin' -require 'ceedling/defaults' - -class StdoutPrettyTestsReport < Plugin - - def setup - @result_list = [] - @plugin_root = File.expand_path(File.join(File.dirname(__FILE__), '..')) - template = @ceedling[:file_wrapper].read(File.join(@plugin_root, 'assets/template.erb')) - @ceedling[:plugin_reportinator].register_test_results_template( template ) - end - - def post_test_fixture_execute(arg_hash) - return if not (arg_hash[:context] == TEST_SYM) - - @result_list << arg_hash[:result_file] - end - - def post_build - return if not (@ceedling[:task_invoker].test_invoked?) - - results = @ceedling[:plugin_reportinator].assemble_test_results(@result_list) - hash = { - :header => '', - :results => results - } - - @ceedling[:plugin_reportinator].run_test_results_report(hash) do - message = '' - message = 'Unit test failures.' if (results[:counts][:failed] > 0) - message - end - end - - def summary - result_list = @ceedling[:file_path_utils].form_pass_results_filelist( PROJECT_TEST_RESULTS_PATH, COLLECTION_ALL_TESTS ) - - # get test results for only those tests in our configuration and of those only tests with results on disk - hash = { - :header => '', - :results => @ceedling[:plugin_reportinator].assemble_test_results(result_list, {:boom => false}) - } - - @ceedling[:plugin_reportinator].run_test_results_report(hash) - end - -end diff --git a/plugins/subprojects/README.md b/plugins/subprojects/README.md deleted file mode 100644 index e51a4e60e..000000000 --- a/plugins/subprojects/README.md +++ /dev/null @@ -1,63 +0,0 @@ -ceedling-subprojects -==================== - -Plugin for supporting subprojects that are built as static libraries. It continues to support -dependency tracking, without getting confused between your main project files and your -subproject files. It accepts different compiler flags and linker flags, allowing you to -optimize for your situation. - -First, you're going to want to add the extension to your list of known extensions: - -``` -:extension: - :subprojects: '.a' -``` - -Define a new section called :subprojects. There, you can list as many subprojects -as you may need under the :paths key. For each, you specify a unique place to build -and a unique name. - -``` -:subprojects: - :paths: - - :name: libprojectA - :source: - - ./subprojectA/first/dir - - ./subprojectA/second/dir - :include: - - ./subprojectA/include/dir - :build_root: ./subprojectA/build/dir - :defines: - - DEFINE_JUST_FOR_THIS_FILE - - AND_ANOTHER - - :name: libprojectB - :source: - - ./subprojectB/only/dir - :include: - - ./subprojectB/first/include/dir - - ./subprojectB/second/include/dir - :build_root: ./subprojectB/build/dir - :defines: [] #none for this one -``` - -You can specify the compiler and linker, just as you would a release build: - -``` -:tools: - :subprojects_compiler: - :executable: gcc - :arguments: - - -g - - -I"$": COLLECTION_PATHS_SUBPROJECTS - - -D$: COLLECTION_DEFINES_SUBPROJECTS - - -c "${1}" - - -o "${2}" - :subprojects_linker: - :executable: ar - :arguments: - - rcs - - ${2} - - ${1} -``` - -That's all there is to it! Happy Hacking! diff --git a/plugins/subprojects/config/defaults.yml b/plugins/subprojects/config/defaults.yml deleted file mode 100644 index 1045a595f..000000000 --- a/plugins/subprojects/config/defaults.yml +++ /dev/null @@ -1,33 +0,0 @@ ---- -#:extension: -# :subprojects: '.a' - -:subprojects: - :paths: [] -# - :name: subprojectA -# :source: -# - ./first/subproject/dir -# - ./second/subproject/dir -# :include: -# - ./first/include/dir -# :build_root: ./subproject/build/dir -# :defines: -# - FIRST_DEFINE - -:tools: - :subprojects_compiler: - :executable: gcc - :arguments: - - -g - - -I"$": COLLECTION_PATHS_SUBPROJECTS - - -D$: COLLECTION_DEFINES_SUBPROJECTS - - -c "${1}" - - -o "${2}" - :subprojects_linker: - :executable: ar - :arguments: - - rcs - - ${2} - - ${1} - -... diff --git a/plugins/subprojects/lib/subprojects.rb b/plugins/subprojects/lib/subprojects.rb deleted file mode 100644 index 559251ed8..000000000 --- a/plugins/subprojects/lib/subprojects.rb +++ /dev/null @@ -1,92 +0,0 @@ -require 'ceedling/plugin' -require 'ceedling/constants' - -SUBPROJECTS_ROOT_NAME = 'subprojects' -SUBPROJECTS_TASK_ROOT = SUBPROJECTS_ROOT_NAME + ':' -SUBPROJECTS_SYM = SUBPROJECTS_ROOT_NAME.to_sym - -class Subprojects < Plugin - - def setup - @plugin_root = File.expand_path(File.join(File.dirname(__FILE__), '..')) - - # Add to the test paths - SUBPROJECTS_PATHS.each do |subproj| - subproj[:source].each do |path| - COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR << path - end - subproj[:include].each do |path| - COLLECTION_PATHS_TEST_SUPPORT_SOURCE_INCLUDE_VENDOR << path - end - end - - # Gather information about the subprojects - @subprojects = {} - @subproject_lookup_by_path = {} - SUBPROJECTS_PATHS.each do |subproj| - @subprojects[ subproj[:name] ] = subproj.clone - @subprojects[ subproj[:name] ][:c] = [] - @subprojects[ subproj[:name] ][:asm] = [] - subproj[:source].each do |path| - search_path = "#{path[-1].match(/\\|\//) ? path : "#{path}/"}*#{EXTENSION_SOURCE}" - @subprojects[ subproj[:name] ][:c] += Dir[search_path] - if (EXTENSION_ASSEMBLY && !EXTENSION_ASSEMBLY.empty?) - search_path = "#{path[-1].match(/\\|\//) ? path : "#{path}/"}*#{EXTENSION_ASSEMBLY}" - @subprojects[ subproj[:name] ][:asm] += Dir[search_path] - end - end - @subproject_lookup_by_path[ subproj[:build_root] ] = subproj[:name] - end - end - - def find_my_project( c_file, file_type = :c ) - @subprojects.each_pair do |subprojname, subproj| - return subprojname if (subproj[file_type].include?(c_file)) - end - end - - def find_my_paths( c_file, file_type = :c ) - @subprojects.each_pair do |subprojname, subproj| - return (subproj[:source] + (subproj[:include] || [])) if (subproj[file_type].include?(c_file)) - end - return [] - end - - def find_my_defines( c_file, file_type = :c ) - @subprojects.each_pair do |subprojname, subproj| - return (subproj[:defines] || []) if (subproj[file_type].include?(c_file)) - end - return [] - end - - def list_all_object_files_for_subproject( lib_name ) - subproj = File.basename(lib_name, EXTENSION_SUBPROJECTS) - objpath = "#{@subprojects[subproj][:build_root]}/out/c" - bbb = @subprojects[subproj][:c].map{|f| "#{objpath}/#{File.basename(f,EXTENSION_SOURCE)}#{EXTENSION_OBJECT}" } - bbb - end - - def find_library_source_file_for_object( obj_name ) - cname = "#{File.basename(obj_name, EXTENSION_OBJECT)}#{EXTENSION_SOURCE}" - dname = File.dirname(obj_name)[0..-7] - pname = @subproject_lookup_by_path[dname] - return @ceedling[:file_finder].find_file_from_list(cname, @subprojects[pname][:c], :error) - end - - def find_library_assembly_file_for_object( obj_name ) - cname = "#{File.basename(obj_name, EXTENSION_OBJECT)}#{EXTENSION_ASEMBLY}" - dname = File.dirname(obj_name)[0..-7] - pname = @subproject_lookup_by_path[dname] - return @ceedling[:file_finder].find_file_from_list(cname, @subprojects[pname][:asm], :error) - end - - def replace_constant(constant, new_value) - Object.send(:remove_const, constant.to_sym) if (Object.const_defined? constant) - Object.const_set(constant, new_value) - end - -end - -# end blocks always executed following rake run -END { -} diff --git a/plugins/subprojects/subprojects.rake b/plugins/subprojects/subprojects.rake deleted file mode 100644 index 0025c3ecd..000000000 --- a/plugins/subprojects/subprojects.rake +++ /dev/null @@ -1,78 +0,0 @@ - - -SUBPROJECTS_PATHS.each do |subproj| - - subproj_source = subproj[:source] - subproj_include = subproj[:include] - subproj_name = subproj[:name] - subproj_build_root = subproj[:build_root] - subproj_build_out = "#{subproj[:build_root]}/out" - subproj_build_c = "#{subproj[:build_root]}/out/c" - subproj_build_asm = "#{subproj[:build_root]}/out/asm" - subproj_directories = [ subproj_build_root, subproj_build_out, subproj_build_c, subproj_build_asm ] - - subproj_directories.each do |subdir| - directory(subdir) - end - - CLEAN.include(File.join(subproj_build_root, '*')) - CLEAN.include(File.join(subproj_build_out, '*')) - - CLOBBER.include(File.join(subproj_build_root, '**/*')) - - # Add a rule for building the actual static library from our object files - rule(/#{subproj_build_root}#{'.+\\'+EXTENSION_SUBPROJECTS}$/ => [ - proc do |task_name| - @ceedling[SUBPROJECTS_SYM].list_all_object_files_for_subproject(task_name) - end - ]) do |bin_file| - @ceedling[:generator].generate_executable_file( - TOOLS_SUBPROJECTS_LINKER, - SUBPROJECTS_SYM, - bin_file.prerequisites, - bin_file.name, - @ceedling[:file_path_utils].form_test_build_map_filepath(bin_file.name)) - end - - # Add a rule for building object files from assembly files to link into a library - if (RELEASE_BUILD_USE_ASSEMBLY) - rule(/#{subproj_build_asm}#{'.+\\'+EXTENSION_OBJECT}$/ => [ - proc do |task_name| - @ceedling[SUBPROJECTS_SYM].find_library_assembly_file_for_object(task_name) - end - ]) do |object| - @ceedling[SUBPROJECTS_SYM].replace_constant(:COLLECTION_PATHS_SUBPROJECTS, @ceedling[SUBPROJECTS_SYM].find_my_paths(object.source, :asm)) - @ceedling[SUBPROJECTS_SYM].replace_constant(:COLLECTION_DEFINES_SUBPROJECTS, @ceedling[SUBPROJECTS_SYM].find_my_defines(object.source, :asm)) - @ceedling[:generator].generate_object_file( - TOOLS_SUBPROJECTS_ASSEMBLER, - OPERATION_ASSEMBLE_SYM, - SUBPROJECTS_SYM, - object.source, - object.name ) - end - end - - # Add a rule for building object files from C files to link into a library - rule(/#{subproj_build_c}#{'.+\\'+EXTENSION_OBJECT}$/ => [ - proc do |task_name| - @ceedling[SUBPROJECTS_SYM].find_library_source_file_for_object(task_name) - end - ]) do |object| - @ceedling[SUBPROJECTS_SYM].replace_constant(:COLLECTION_PATHS_SUBPROJECTS, @ceedling[SUBPROJECTS_SYM].find_my_paths(object.source, :c)) - @ceedling[SUBPROJECTS_SYM].replace_constant(:COLLECTION_DEFINES_SUBPROJECTS, @ceedling[SUBPROJECTS_SYM].find_my_defines(object.source, :c)) - @ceedling[:generator].generate_object_file( - TOOLS_SUBPROJECTS_COMPILER, - OPERATION_COMPILE_SYM, - SUBPROJECTS_SYM, - object.source, - object.name, - @ceedling[:file_path_utils].form_release_build_c_list_filepath( object.name ) ) - end - - # Add the subdirectories involved to our list of those that should be autogenerated - task :directories => subproj_directories.clone - - # Finally, add the static library to our RELEASE build dependency list - task RELEASE_SYM => ["#{subproj_build_root}/#{subproj_name}#{EXTENSION_SUBPROJECTS}"] -end - diff --git a/plugins/teamcity_tests_report/README.md b/plugins/teamcity_tests_report/README.md deleted file mode 100644 index 9fcda7d5b..000000000 --- a/plugins/teamcity_tests_report/README.md +++ /dev/null @@ -1,18 +0,0 @@ -ceedling-teamcity-tests-report -============================== - -## Overview - -The teamcity_tests_report replaces the normal ceedling "pretty" output with -a version that has results tagged to be consumed with the teamcity CI server. - -## Setup - -Enable the plugin in your project.yml by adding `teamcity_tests_report` -to the list of enabled plugins. - -``` YAML -:plugins: - :enabled: - - teamcity_tests_report -``` diff --git a/plugins/teamcity_tests_report/config/teamcity_tests_report.yml b/plugins/teamcity_tests_report/config/teamcity_tests_report.yml deleted file mode 100644 index c25acf511..000000000 --- a/plugins/teamcity_tests_report/config/teamcity_tests_report.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -:plugins: - # tell Ceedling we got results display taken care of - :display_raw_test_results: FALSE diff --git a/plugins/teamcity_tests_report/lib/teamcity_tests_report.rb b/plugins/teamcity_tests_report/lib/teamcity_tests_report.rb deleted file mode 100644 index 4b9616b35..000000000 --- a/plugins/teamcity_tests_report/lib/teamcity_tests_report.rb +++ /dev/null @@ -1,57 +0,0 @@ -require 'ceedling/plugin' -require 'ceedling/defaults' - -class TeamcityTestsReport < Plugin - - def setup - @suite_started = nil - @output_enabled = !defined?(TEAMCITY_BUILD) || TEAMCITY_BUILD - end - - def escape(string) - string.gsub(/['|\[\]]/, '|\0').gsub('\r', '|r').gsub('\n', '|n') - end - - def pre_test(test) - teamcity_message "testSuiteStarted name='#{File.basename(test, '.c')}'" - @suite_started = Time.now - end - - def post_test(test) - teamcity_message "testSuiteFinished name='#{File.basename(test, '.c')}'" - end - - def post_test_fixture_execute(arg_hash) - duration = (Time.now - @suite_started) * 1000 - results = @ceedling[:plugin_reportinator].assemble_test_results([arg_hash[:result_file]]) - avg_duration = (duration / [1, results[:counts][:passed] + results[:counts][:failed]].max).round - - results[:successes].each do |success| - success[:collection].each do |test| - teamcity_message "testStarted name='#{test[:test]}'" - teamcity_message "testFinished name='#{test[:test]}' duration='#{avg_duration}'" - end - end - - results[:failures].each do |failure| - failure[:collection].each do |test| - teamcity_message "testStarted name='#{test[:test]}'" - teamcity_message "testFailed name='#{test[:test]}' message='#{escape(test[:message])}' details='File: #{failure[:source][:file]} Line: #{test[:line]}'" - teamcity_message "testFinished name='#{test[:test]}' duration='#{avg_duration}'" - end - end - - results[:ignores].each do |failure| - failure[:collection].each do |test| - teamcity_message "testIgnored name='#{test[:test]}' message='#{escape(test[:message])}'" - end - end - - # We ignore stdout - end - - def teamcity_message(content) - puts "##teamcity[#{content}]" unless !@output_enabled - end - -end diff --git a/plugins/warnings_report/README.md b/plugins/warnings_report/README.md deleted file mode 100644 index fd7fae5d9..000000000 --- a/plugins/warnings_report/README.md +++ /dev/null @@ -1,19 +0,0 @@ -warnings-report -=============== - -## Overview - -The warnings_report captures all warnings throughout the build process -and collects them into a single report at the end of execution. It places all -of this into a warnings file in the output artifact directory. - -## Setup - -Enable the plugin in your project.yml by adding `warnings_report` -to the list of enabled plugins. - -``` YAML -:plugins: - :enabled: - - warnings_report -``` diff --git a/plugins/warnings_report/lib/warnings_report.rb b/plugins/warnings_report/lib/warnings_report.rb deleted file mode 100644 index d4f43fb57..000000000 --- a/plugins/warnings_report/lib/warnings_report.rb +++ /dev/null @@ -1,69 +0,0 @@ -require 'ceedling/plugin' -require 'ceedling/constants' - -class WarningsReport < Plugin - def setup - @stderr_redirect = nil - @log_paths = {} - end - - def pre_compile_execute(arg_hash) - # at beginning of compile, override tool's stderr_redirect so we can parse $stderr + $stdout - set_stderr_redirect(arg_hash) - end - - def post_compile_execute(arg_hash) - # after compilation, grab output for parsing/logging, restore stderr_redirect, log warning if it exists - output = arg_hash[:shell_result][:output] - restore_stderr_redirect(arg_hash) - write_warning_log(arg_hash[:context], output) - end - - def pre_link_execute(arg_hash) - # at beginning of link, override tool's stderr_redirect so we can parse $stderr + $stdout - set_stderr_redirect(arg_hash) - end - - def post_link_execute(arg_hash) - # after linking, grab output for parsing/logging, restore stderr_redirect, log warning if it exists - output = arg_hash[:shell_result][:output] - restore_stderr_redirect(arg_hash) - write_warning_log(arg_hash[:context], output) - end - - private - - def set_stderr_redirect(hash) - @stderr_redirect = hash[:tool][:stderr_redirect] - hash[:tool][:stderr_redirect] = StdErrRedirect::AUTO - end - - def restore_stderr_redirect(hash) - hash[:tool][:stderr_redirect] = @stderr_redirect - end - - def write_warning_log(context, output) - # if $stderr/$stdout contain "warning", log it - if output =~ /warning/i - # generate a log path & file io write flags - logging = generate_log_path(context) - @ceedling[:file_wrapper].write(logging[:path], output + "\n", logging[:flags]) unless logging.nil? - end - end - - def generate_log_path(context) - # if path has already been generated, return it & 'append' file io flags (append to log) - return { path: @log_paths[context], flags: 'a' } unless @log_paths[context].nil? - - # first time through, generate path & 'write' file io flags (create new log) - base_path = File.join(PROJECT_BUILD_ARTIFACTS_ROOT, context.to_s) - file_path = File.join(base_path, 'warnings.log') - - if @ceedling[:file_wrapper].exist?(base_path) - @log_paths[context] = file_path - return { path: file_path, flags: 'w' } - end - - nil - end -end diff --git a/plugins/xml_tests_report/README.md b/plugins/xml_tests_report/README.md deleted file mode 100644 index 529e33b94..000000000 --- a/plugins/xml_tests_report/README.md +++ /dev/null @@ -1,39 +0,0 @@ -xml_tests_report -================ - -## Overview - -The xml_tests_report plugin creates an XML file of test results in xUnit -format, which is handy for Continuous Integration build servers or as input -into other reporting tools. The XML file is output to the appropriate -`/artifacts/` directory (e.g. `artifacts/test/` for test tasks, -`artifacts/gcov/` for gcov, or `artifacts/bullseye/` for bullseye runs). - -## Setup - -Enable the plugin in your project.yml by adding `xml_tests_report` to the list -of enabled plugins. - -``` YAML -:plugins: - :enabled: - - xml_tests_report -``` - -## Configuration - -Optionally configure the output / artifact filename in your project.yml with -the `artifact_filename` configuration option. The default filename is -`report.xml`. - -You can also configure the path that this artifact is stored. This can be done -by setting `path`. The default is that it will be placed in a subfolder under -the `build` directory. - -If you use some means for continuous integration, you may also want to add -.xsl file to CI's configuration for proper parsing of .xml report. - -``` YAML -:xml_tests_report: - :artifact_filename: report_xunit.xml -``` diff --git a/plugins/xml_tests_report/lib/xml_tests_report.rb b/plugins/xml_tests_report/lib/xml_tests_report.rb deleted file mode 100644 index 6dde0f0de..000000000 --- a/plugins/xml_tests_report/lib/xml_tests_report.rb +++ /dev/null @@ -1,111 +0,0 @@ -require 'ceedling/plugin' -require 'ceedling/constants' - -class XmlTestsReport < Plugin - def setup - @results_list = {} - @test_counter = 0 - end - - def post_test_fixture_execute(arg_hash) - context = arg_hash[:context] - - @results_list[context] = [] if @results_list[context].nil? - - @results_list[context] << arg_hash[:result_file] - end - - def post_build - @results_list.each_key do |context| - results = @ceedling[:plugin_reportinator].assemble_test_results(@results_list[context]) - - artifact_filename = @ceedling[:configurator].project_config_hash[:xml_tests_report_artifact_filename] || 'report.xml' - artifact_fullpath = @ceedling[:configurator].project_config_hash[:xml_tests_report_path] || File.join(PROJECT_BUILD_ARTIFACTS_ROOT, context.to_s) - file_path = File.join(artifact_fullpath, artifact_filename) - - @ceedling[:file_wrapper].open(file_path, 'w') do |f| - @test_counter = 1 - write_results(results, f) - end - end - end - - private - - def write_results(results, stream) - write_header(stream) - write_failures(results[:failures], stream) - write_tests(results[:successes], stream, 'SuccessfulTests') - write_tests(results[:ignores], stream, 'IgnoredTests') - write_statistics(results[:counts], stream) - write_footer(stream) - end - - def write_header(stream) - stream.puts "" - stream.puts '' - end - - def write_failures(results, stream) - if results.size.zero? - stream.puts "\t" - return - end - - stream.puts "\t" - - results.each do |result| - result[:collection].each do |item| - filename = result[:source][:file] - - stream.puts "\t\t" - stream.puts "\t\t\t#{filename}::#{item[:test]}" - stream.puts "\t\t\tAssertion" - stream.puts "\t\t\t" - stream.puts "\t\t\t\t#{filename}" - stream.puts "\t\t\t\t#{item[:line]}" - stream.puts "\t\t\t" - stream.puts "\t\t\t#{item[:message]}" - stream.puts "\t\t" - @test_counter += 1 - end - end - - stream.puts "\t" - end - - def write_tests(results, stream, tag) - if results.size.zero? - stream.puts "\t<#{tag}/>" - return - end - - stream.puts "\t<#{tag}>" - - results.each do |result| - result[:collection].each do |item| - filename = result[:source][:file] - stream.puts "\t\t" - stream.puts "\t\t\t#{filename}::#{item[:test]}" - stream.puts "\t\t" - @test_counter += 1 - end - end - - stream.puts "\t" - end - - def write_statistics(counts, stream) - stream.puts "\t" - stream.puts "\t\t#{counts[:total]}" - stream.puts "\t\t#{counts[:ignored]}" - stream.puts "\t\t#{counts[:failed]}" - stream.puts "\t\t0" - stream.puts "\t\t#{counts[:failed]}" - stream.puts "\t" - end - - def write_footer(stream) - stream.puts '' - end -end diff --git a/spec/build_invoker_utils_spec.rb b/spec/build_invoker_utils_spec.rb deleted file mode 100644 index bd01c3c6a..000000000 --- a/spec/build_invoker_utils_spec.rb +++ /dev/null @@ -1,54 +0,0 @@ -require 'spec_helper' -require 'ceedling/build_invoker_utils' -require 'ceedling/constants' -require 'ceedling/streaminator' -require 'ceedling/configurator' - -describe BuildInvokerUtils do - before(:each) do - # this will always be mocked - @configurator = Configurator.new({:configurator_setup => nil, :configurator_builder => nil, - :configurator_plugins => nil, :cmock_builder => nil, - :yaml_wrapper => nil, :system_wrapper => nil}) - @streaminator = Streaminator.new({:streaminator_helper => nil, :verbosinator => nil, - :loginator => nil, :stream_wrapper => nil}) - - # this is what is being tested - @bi_utils = described_class.new({:configurator => @configurator, :streaminator => @streaminator}) - - # these keep the actual test cleaner - @exception_msg = 'Don\'t know how to build task \'xyz\'' - @basic_msg = "ERROR: Rake could not find file referenced in source or test: 'xyz'. Possible stale dependency." - @deep_dep_msg = "Try fixing #include statements or adding missing file. Then run '#{REFRESH_TASK_ROOT}#{TEST_SYM}' task and try again." - @exception = RuntimeError.new(@exception_msg) - end - - describe '#process_exception' do - - it 'passes given error if message does not contain handled messagr' do - expect{@bi_utils.process_exception(ArgumentError.new('oops...'), TEST_SYM)}.to raise_error(ArgumentError) - end - - it 'prints to stderr for test with test_build flag set true' do - allow(@streaminator).to receive(:stderr_puts).with(@basic_msg) - allow(@configurator).to receive(:project_use_deep_dependencies).and_return(false) - expect{@bi_utils.process_exception(@exception, TEST_SYM, true)}.to raise_error(RuntimeError) - end - - it 'prints to stderr for test with test_build flag set false' do - allow(@streaminator).to receive(:stderr_puts).with(@basic_msg.sub(' or test', '')) - allow(@configurator).to receive(:project_use_deep_dependencies).and_return(false) - expect{@bi_utils.process_exception(@exception, TEST_SYM, false)}.to raise_error(RuntimeError) - end - - it 'prints to stderr with extra message when deep dependencies is true' do - allow(@streaminator).to receive(:stderr_puts).with(@basic_msg.sub(' or test', '')) - allow(@configurator).to receive(:project_use_deep_dependencies).and_return(true) - allow(@streaminator).to receive(:stderr_puts).with(@deep_dep_msg) - - expect{@bi_utils.process_exception(@exception, TEST_SYM, false)}.to raise_error(RuntimeError) - end - - - end -end diff --git a/spec/ceedling_spec.rb b/spec/ceedling_spec.rb deleted file mode 100644 index 21f5d9f1d..000000000 --- a/spec/ceedling_spec.rb +++ /dev/null @@ -1,154 +0,0 @@ -require 'spec_helper' -require 'ceedling' - -describe 'Ceedling' do - context 'location' do - it 'should return the location of the ceedling gem directory' do - # create test state/variables - ceedling_path = File.join(File.dirname(__FILE__), '..').gsub('spec','lib') - # mocks/stubs/expected calls - # execute method - location = Ceedling.location - # validate results - expect(location).to eq(ceedling_path) - end - end - - context 'load_path' do - it 'should return the location of the plugins directory' do - # create test state/variables - load_path = File.join(File.dirname(__FILE__), '..').gsub('spec','lib') - load_path = File.join( load_path, 'plugins' ) - # mocks/stubs/expected calls - # execute method - location = Ceedling.load_path - # validate results - expect(location).to eq(load_path) - end - end - - context 'rakefile' do - it 'should return the location of the ceedling rakefile' do - # create test state/variables - rakefile_path = File.join(File.dirname(__FILE__), '..').gsub('spec','lib') - rakefile_path = File.join( rakefile_path, 'lib', 'ceedling', 'rakefile.rb' ) - # mocks/stubs/expected calls - # execute method - location = Ceedling.rakefile - # validate results - expect(location).to eq(rakefile_path) - end - end - - context 'load_project' do - it 'should load the project with the default yaml file' do - # create test state/variables - ENV.delete('CEEDLING_MAIN_PROJECT_FILE') - rakefile_path = File.join(File.dirname(__FILE__), '..').gsub('spec','lib') - rakefile_path = File.join( rakefile_path, 'lib', 'ceedling', 'rakefile.rb' ) - # mocks/stubs/expected calls - expect(Ceedling).to receive(:load).with(rakefile_path) - # execute method - Ceedling.load_project - # validate results - expect(ENV['CEEDLING_MAIN_PROJECT_FILE']).to eq('./project.yml') - end - - it 'should load the project with the specified yaml file' do - # create test state/variables - ENV.delete('CEEDLING_MAIN_PROJECT_FILE') - rakefile_path = File.join(File.dirname(__FILE__), '..').gsub('spec','lib') - rakefile_path = File.join( rakefile_path, 'lib', 'ceedling', 'rakefile.rb' ) - # mocks/stubs/expected calls - expect(Ceedling).to receive(:load).with(rakefile_path) - # execute method - Ceedling.load_project(config: './foo.yml') - # validate results - expect(ENV['CEEDLING_MAIN_PROJECT_FILE']).to eq('./foo.yml') - end - - it 'should load the project with the yaml file specified by the existing environment variable' do - # create test state/variables - ENV['CEEDLING_MAIN_PROJECT_FILE'] = './bar.yml' - rakefile_path = File.join(File.dirname(__FILE__), '..').gsub('spec','lib') - rakefile_path = File.join( rakefile_path, 'lib', 'ceedling', 'rakefile.rb' ) - # mocks/stubs/expected calls - expect(Ceedling).to receive(:load).with(rakefile_path) - # execute method - Ceedling.load_project - # validate results - expect(ENV['CEEDLING_MAIN_PROJECT_FILE']).to eq('./bar.yml') - end - - it 'should load the project with the specified plugins enabled' do - # create test state/variables - DEFAULT_CEEDLING_CONFIG[:plugins][:enabled].clear() - DEFAULT_CEEDLING_CONFIG[:plugins][:load_paths].clear() - spec_double = double('spec-double') - rakefile_path = File.join(File.dirname(__FILE__), '..').gsub('spec','lib') - rakefile_path = File.join( rakefile_path, 'lib', 'ceedling', 'rakefile.rb' ) - # mocks/stubs/expected calls - expect(Gem::Specification).to receive(:find_by_name).with('ceedling-foo').and_return(spec_double) - expect(spec_double).to receive(:gem_dir).and_return('dummy/path') - expect(Ceedling).to receive(:require).with('ceedling/defaults') - expect(Ceedling).to receive(:load).with(rakefile_path) - # execute method - Ceedling.load_project( config: './foo.yml', - plugins: ['foo']) - # validate results - expect(ENV['CEEDLING_MAIN_PROJECT_FILE']).to eq('./foo.yml') - end - - it 'should set the project root if the root key is provided' do - # create test state/variables - Object.send(:remove_const, :PROJECT_ROOT) - DEFAULT_CEEDLING_CONFIG[:plugins][:enabled].clear() - DEFAULT_CEEDLING_CONFIG[:plugins][:load_paths].clear() - rakefile_path = File.join(File.dirname(__FILE__), '..').gsub('spec','lib') - rakefile_path = File.join( rakefile_path, 'lib', 'ceedling', 'rakefile.rb' ) - # mocks/stubs/expected calls - expect(Ceedling).to receive(:load).with(rakefile_path) - # execute method - Ceedling.load_project( config: './foo.yml', - root: './') - # validate results - expect(ENV['CEEDLING_MAIN_PROJECT_FILE']).to eq('./foo.yml') - expect(PROJECT_ROOT).to eq('./') - end - end - - context 'register_plugin' do - it 'should register a plugin' do - # create test state/variables - DEFAULT_CEEDLING_CONFIG[:plugins][:enabled].clear() - DEFAULT_CEEDLING_CONFIG[:plugins][:load_paths].clear() - spec_double = double('spec-double') - # mocks/stubs/expected calls - expect(Gem::Specification).to receive(:find_by_name).with('ceedling-foo').and_return(spec_double) - expect(spec_double).to receive(:gem_dir).and_return('dummy/path') - expect(Ceedling).to receive(:require).with('ceedling/defaults') - # execute method - Ceedling.register_plugin('foo') - # validate results - expect(DEFAULT_CEEDLING_CONFIG[:plugins][:enabled]).to eq ["foo"] - expect(DEFAULT_CEEDLING_CONFIG[:plugins][:load_paths]).to eq(["dummy/path"]) - end - - it 'should register a plugin with an alternative prefix' do - # create test state/variables - DEFAULT_CEEDLING_CONFIG[:plugins][:enabled].clear() - DEFAULT_CEEDLING_CONFIG[:plugins][:load_paths].clear() - spec_double = double('spec-double') - # mocks/stubs/expected calls - expect(Gem::Specification).to receive(:find_by_name).with('prefix-foo').and_return(spec_double) - expect(spec_double).to receive(:gem_dir).and_return('dummy/path') - expect(Ceedling).to receive(:require).with('ceedling/defaults') - # execute method - Ceedling.register_plugin('foo','prefix-') - # validate results - expect(DEFAULT_CEEDLING_CONFIG[:plugins][:enabled]).to eq(["foo"]) - expect(DEFAULT_CEEDLING_CONFIG[:plugins][:load_paths]).to eq(["dummy/path"]) - end - end -end - diff --git a/spec/config_walkinator_spec.rb b/spec/config_walkinator_spec.rb new file mode 100644 index 000000000..81e174182 --- /dev/null +++ b/spec/config_walkinator_spec.rb @@ -0,0 +1,62 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'spec_helper' +require 'ceedling/config_walkinator' + +describe ConfigWalkinator do + before(:each) do + @cw = described_class.new + end + + describe '#fetch_value' do + + it 'fetches a boolean 1 levels in' do + config = {:foo => false} + expect(@cw.fetch_value(:foo, hash:config)).to eq([false, 1]) + + config = {:foo => true} + expect(@cw.fetch_value(:foo, hash:config)).to eq([true, 1]) + end + + it 'fetches a list two levels in' do + config = {:foo => {:bar => [1, 2, 3]}} + + expect(@cw.fetch_value(:foo, :bar, hash:config)).to eq([[1,2,3], 2]) + end + + it 'fetches a hash 3 levels in' do + config = {:foo => {:bar => {:baz => {:setting => 5}}}} + + expect(@cw.fetch_value(:foo, :bar, :baz, hash:config)).to eq([{:setting => 5}, 3]) + end + + it 'fetches nothing for nil config hash' do + expect(@cw.fetch_value(:foo, :bar, :baz, hash:nil)).to eq([nil, 0]) + end + + it 'fetches nothing for a level deeper than exists' do + config = {:foo => {:bar => 'a'}} + + expect(@cw.fetch_value(:foo, :bar, :oops, hash:config)).to eq([nil, 2]) + end + + it 'fetches nothing if non-symbol provided as key' do + config = {:foo => {:bar => 'a'}} + + expect(@cw.fetch_value(:foo, :bar, 'a', hash:config)).to eq([nil, 2]) + end + + it 'fetches the provided default value if a key does not exist' do + config = {:foo => {:bar => {:baz => true}}} + + expect(@cw.fetch_value(:foo, :bar, :oops, hash:config, default:false)).to eq([false, 2]) + end + + end + +end diff --git a/spec/configurator_builder_spec.rb b/spec/configurator_builder_spec.rb index fe66d49be..5f271d1b7 100644 --- a/spec/configurator_builder_spec.rb +++ b/spec/configurator_builder_spec.rb @@ -1,6 +1,15 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + #derived from test_graveyard/unit/busted/configurator_builder_test.rb require 'spec_helper' +require 'ceedling/configurator_builder' describe ConfiguratorBuilder do xit "is scary" diff --git a/spec/configurator_helper_spec.rb b/spec/configurator_helper_spec.rb index 8ec76aa45..da45e483d 100644 --- a/spec/configurator_helper_spec.rb +++ b/spec/configurator_helper_spec.rb @@ -1,3 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'spec_helper' describe "ConfiguratorHelper" do diff --git a/spec/configurator_spec.rb b/spec/configurator_spec.rb index b4abcb899..7ea7ebb6d 100644 --- a/spec/configurator_spec.rb +++ b/spec/configurator_spec.rb @@ -1,4 +1,12 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'spec_helper' +require 'ceedling/configurator' describe Configurator do describe "#standardize_paths" do diff --git a/spec/file_finder_helper_spec.rb b/spec/file_finder_helper_spec.rb index f12a4cd98..b90176fa5 100644 --- a/spec/file_finder_helper_spec.rb +++ b/spec/file_finder_helper_spec.rb @@ -1,19 +1,26 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'spec_helper' require 'ceedling/file_finder_helper' require 'ceedling/constants' -require 'ceedling/streaminator' +require 'ceedling/loginator' FILE_LIST = ['some/dir/a.c', 'some/dir/a.h', \ 'another/place/b.c','another/place/b.h',\ - 'here/c.cpp', 'here/c.hpp',\ - 'copy/c.cpp', 'copy/c.hpp'].freeze + 'here/src/c.cpp', 'here/inc/c.hpp',\ + 'copy/SRC/c.cpp', 'copy/inc/c.hpp'].freeze describe FileFinderHelper do before(:each) do # this will always be mocked - @streaminator = Streaminator.new({:streaminator_helper => nil, :verbosinator => nil, :loginator => nil, :stream_wrapper => nil}) + @loginator = Loginator.new({:verbosinator => nil, :file_wrapper => nil, :system_wrapper => nil}) - @ff_helper = described_class.new({:streaminator => @streaminator}) + @ff_helper = described_class.new({:loginator => @loginator}) end @@ -23,7 +30,20 @@ expect(@ff_helper.find_file_in_collection('b.h', FILE_LIST, :ignore)).to eq(FILE_LIST[3]) end - xit 'handles duplicate files' do + it 'handles duplicate files with best match' do + expect(@ff_helper.find_file_in_collection('c.hpp', FILE_LIST, :ignore)).to eq(FILE_LIST[5]) + expect(@ff_helper.find_file_in_collection('c.hpp', FILE_LIST, :ignore, 'inc/c.hpp')).to eq(FILE_LIST[5]) + expect(@ff_helper.find_file_in_collection('c.hpp', FILE_LIST, :ignore, 'here/inc/c.hpp')).to eq(FILE_LIST[5]) + expect(@ff_helper.find_file_in_collection('c.hpp', FILE_LIST, :ignore, 'copy/inc/c.hpp')).to eq(FILE_LIST[7]) + + expect(@ff_helper.find_file_in_collection('c.cpp', FILE_LIST, :ignore)).to eq(FILE_LIST[4]) + expect(@ff_helper.find_file_in_collection('c.cpp', FILE_LIST, :ignore, 'src/c.cpp')).to eq(FILE_LIST[4]) + expect(@ff_helper.find_file_in_collection('c.cpp', FILE_LIST, :ignore, 'SRC/c.cpp')).to eq(FILE_LIST[6]) + expect(@ff_helper.find_file_in_collection('c.cpp', FILE_LIST, :ignore, 'test/src/c.cpp')).to eq(FILE_LIST[4]) + expect(@ff_helper.find_file_in_collection('c.cpp', FILE_LIST, :ignore, 'meh/SRC/c.cpp')).to eq(FILE_LIST[6]) + expect(@ff_helper.find_file_in_collection('c.cpp', FILE_LIST, :ignore, 'c/c.cpp')).to eq(FILE_LIST[4]) + expect(@ff_helper.find_file_in_collection('c.cpp', FILE_LIST, :ignore, 'copy/meh/c.cpp')).to eq(FILE_LIST[6]) + expect(@ff_helper.find_file_in_collection('c.cpp', FILE_LIST, :ignore, 'here/too/and/fro/c.cpp')).to eq(FILE_LIST[4]) end context 'file not found' do @@ -36,14 +56,14 @@ end it 'outputs a complaint if complain is warn' do - msg = 'WARNING: Found no file \'d.c\' in search paths.' - expect(@streaminator).to receive(:stderr_puts).with(msg, Verbosity::COMPLAIN) + msg = 'Found no file `d.c` in search paths.' + expect(@loginator).to receive(:log).with(msg, Verbosity::COMPLAIN) @ff_helper.find_file_in_collection('d.c', FILE_LIST, :warn) end it 'outputs and raises an error if complain is error' do - msg = 'ERROR: Found no file \'d.c\' in search paths.' - allow(@streaminator).to receive(:stderr_puts).with(msg, Verbosity::ERRORS) do + msg = 'Found no file `d.c` in search paths.' + allow(@loginator).to receive(:log).with(msg, Verbosity::ERRORS) do expect{@ff_helper.find_file_in_collection('d.c', FILE_LIST, :warn)}.to raise_error end end diff --git a/spec/gcov/gcov_deployment_spec.rb b/spec/gcov/gcov_deployment_spec.rb index 599e58b3d..096916a7f 100644 --- a/spec/gcov/gcov_deployment_spec.rb +++ b/spec/gcov/gcov_deployment_spec.rb @@ -1,3 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'spec_system_helper' require 'gcov/gcov_test_cases_spec' @@ -6,6 +13,7 @@ include CeedlingTestCases include GcovTestCases before :all do + determine_reports_to_test @c = SystemContext.new @c.deploy_gem end @@ -26,47 +34,101 @@ it { can_test_projects_with_gcov_with_success } it { can_test_projects_with_gcov_with_fail } - it { can_test_projects_with_gcov_with_fail_because_of_uncovered_files } - it { can_test_projects_with_gcov_with_success_because_of_ignore_uncovered_list } - it { can_test_projects_with_gcov_with_success_because_of_ignore_uncovered_list_with_globs } + # TODO: Restore these tests when the :abort_on_uncovered option is restored in the Gcov plugin + # it { can_test_projects_with_gcov_with_fail_because_of_uncovered_files } + # it { can_test_projects_with_gcov_with_success_because_of_ignore_uncovered_list } + # it { can_test_projects_with_gcov_with_success_because_of_ignore_uncovered_list_with_globs } it { can_test_projects_with_gcov_with_compile_error } it { can_fetch_project_help_for_gcov } - it { can_create_html_report } + it { can_create_html_reports } + it { can_create_html_reports_from_crashing_test_runner_with_enabled_debug_for_test_cases_not_causing_crash } + it { can_create_html_reports_from_crashing_test_runner_with_enabled_debug_with_zero_coverage } + it { can_create_html_reports_from_test_runner_with_enabled_debug_with_100_coverage_when_excluding_crashing_test_case } end - describe "command: `ceedling example [example]`" do + describe "command: `ceedling example temp_sensor`" do describe "temp_sensor" do before do @c.with_context do output = `bundle exec ruby -S ceedling example temp_sensor 2>&1` - expect(output).to match(/created!/) + expect(output).to match(/created/) end end it "should be testable" do @c.with_context do Dir.chdir "temp_sensor" do - @output = `bundle exec ruby -S ceedling gcov:all 2>&1` - expect(@output).to match(/TESTED:\s+47/) - expect(@output).to match(/PASSED:\s+47/) - - expect(@output).to match(/AdcConductor\.c Lines executed:/i) - expect(@output).to match(/AdcHardware\.c Lines executed:/i) - expect(@output).to match(/AdcModel\.c Lines executed:/i) - expect(@output).to match(/Executor\.c Lines executed:/i) - expect(@output).to match(/Main\.c Lines executed:/i) - expect(@output).to match(/Model\.c Lines executed:/i) + @output = `bundle exec ruby -S ceedling --mixin=add_gcov gcov:all 2>&1` + expect(@output).to match(/TESTED:\s+51/) + expect(@output).to match(/PASSED:\s+51/) + + expect(@output).to match(/AdcConductor\.c \| Lines executed:/i) + expect(@output).to match(/AdcHardware\.c \| Lines executed:/i) + expect(@output).to match(/AdcModel\.c \| Lines executed:/i) + expect(@output).to match(/Executor\.c \| Lines executed:/i) + expect(@output).to match(/Main\.c \| Lines executed:/i) + expect(@output).to match(/Model\.c \| Lines executed:/i) # there are more, but this is a good place to stop. - @output = `bundle exec ruby -S ceedling utils:gcov` - expect(@output).to match(/For now, creating only an HtmlBasic report\./) - expect(@output).to match(/Creating (?:a )?gcov (?:results)?(?:HTML)? report(?:\(s\))? in 'build\/artifacts\/gcov'\.\.\. Done/) - expect(File.exist?('build/artifacts/gcov/GcovCoverageResults.html')).to eq true + expect(File.exist?('build/artifacts/gcov/gcovr/GcovCoverageResults.html')).to eq true + end + end + end + + it "should be able to test a single module (it should INHERIT file-specific flags)" do + @c.with_context do + Dir.chdir "temp_sensor" do + @output = `bundle exec ruby -S ceedling --mixin=add_gcov gcov:TemperatureCalculator 2>&1` + expect(@output).to match(/TESTED:\s+2/) + expect(@output).to match(/PASSED:\s+2/) + + expect(@output).to match(/TemperatureCalculator\.c \| Lines executed:/i) + end + end + end + + it "should be able to test multiple files matching a pattern" do + @c.with_context do + Dir.chdir "temp_sensor" do + @output = `bundle exec ruby -S ceedling --mixin=add_gcov gcov:pattern[Temp] 2>&1` + expect(@output).to match(/TESTED:\s+6/) + expect(@output).to match(/PASSED:\s+6/) + + expect(@output).to match(/TemperatureCalculator\.c \| Lines executed:/i) + expect(@output).to match(/TemperatureFilter\.c \| Lines executed:/i) + end + end + end + + it "should be able to test all files matching in a path" do + @c.with_context do + Dir.chdir "temp_sensor" do + @output = `bundle exec ruby -S ceedling --mixin=add_gcov gcov:path[adc] 2>&1` + expect(@output).to match(/TESTED:\s+15/) + expect(@output).to match(/PASSED:\s+15/) + expect(@output).to match(/AdcConductor\.c \| Lines executed:/i) + expect(@output).to match(/AdcHardware\.c \| Lines executed:/i) + expect(@output).to match(/AdcModel\.c \| Lines executed:/i) end end end + + it "should be able to test specific test cases in a file" do + @c.with_context do + Dir.chdir "temp_sensor" do + @output = `bundle exec ruby -S ceedling --mixin=add_gcov gcov:path[adc] --test-case="RunShouldNot" 2>&1` + expect(@output).to match(/TESTED:\s+2/) + expect(@output).to match(/PASSED:\s+2/) + + expect(@output).to match(/AdcConductor\.c \| Lines executed:/i) + expect(@output).to match(/AdcHardware\.c \| Lines executed:/i) + expect(@output).to match(/AdcModel\.c \| Lines executed:/i) + end + end + end + end end end diff --git a/spec/gcov/gcov_test_cases_spec.rb b/spec/gcov/gcov_test_cases_spec.rb index d98b21776..5c7254480 100644 --- a/spec/gcov/gcov_test_cases_spec.rb +++ b/spec/gcov/gcov_test_cases_spec.rb @@ -1,3 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'fileutils' require 'tmpdir' require 'yaml' @@ -7,6 +14,31 @@ module GcovTestCases + def determine_reports_to_test + @gcov_reports = [] + + begin + `gcovr --version 2>&1` + @gcov_reports << :gcovr if $?.exitstatus == 0 + rescue + puts "No GCOVR exec to test against" + end + + begin + `reportgenerator --version 2>&1` + @gcov_reports << :reportgenerator if $?.exitstatus == 0 + rescue + puts "No ReportGenerator exec to test against" + end + end + + def prep_project_yml_for_coverage + FileUtils.cp test_asset_path("project_as_gem.yml"), "project.yml" + @c.uncomment_project_yml_option_for_test("- gcov") + @c.comment_project_yml_option_for_test("- gcovr") unless @gcov_reports.include? :gcovr + @c.uncomment_project_yml_option_for_test("- ReportGenerator") if @gcov_reports.include? :reportgenerator + end + def _add_gcov_section_in_project(project_file_path, name, values) project_file_contents = File.readlines(project_file_path) name_index = project_file_contents.index(":gcov:\n") @@ -53,13 +85,13 @@ def add_gcov_option(option, value) def can_test_projects_with_gcov_with_success @c.with_context do Dir.chdir @proj_name do - FileUtils.cp test_asset_path("project_with_guts_gcov.yml"), "project.yml" + prep_project_yml_for_coverage FileUtils.cp test_asset_path("example_file.h"), 'src/' FileUtils.cp test_asset_path("example_file.c"), 'src/' FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/' - output = `bundle exec ruby -S ceedling gcov:all` - expect($?.exitstatus).to match(0) # Since a test either pass or are ignored, we return success here + output = `bundle exec ruby -S ceedling gcov:all 2>&1` + expect($?.exitstatus).to match(0) # Since test cases either pass or are ignored, Ceedling exits with success expect(output).to match(/TESTED:\s+\d/) expect(output).to match(/PASSED:\s+\d/) expect(output).to match(/FAILED:\s+\d/) @@ -71,13 +103,13 @@ def can_test_projects_with_gcov_with_success def can_test_projects_with_gcov_with_fail @c.with_context do Dir.chdir @proj_name do - FileUtils.cp test_asset_path("project_with_guts_gcov.yml"), "project.yml" + prep_project_yml_for_coverage FileUtils.cp test_asset_path("example_file.h"), 'src/' FileUtils.cp test_asset_path("example_file.c"), 'src/' FileUtils.cp test_asset_path("test_example_file.c"), 'test/' output = `bundle exec ruby -S ceedling gcov:all 2>&1` - expect($?.exitstatus).to match(1) # Since a test fails, we return error here + expect($?.exitstatus).to match(1) # Unit test failures => Ceedling exit code 1 expect(output).to match(/TESTED:\s+\d/) expect(output).to match(/PASSED:\s+\d/) expect(output).to match(/FAILED:\s+\d/) @@ -86,111 +118,226 @@ def can_test_projects_with_gcov_with_fail end end - def can_test_projects_with_gcov_with_fail_because_of_uncovered_files + # TODO: Restore this test when the :abort_on_uncovered option is restored in the Gcov plugin + # def can_test_projects_with_gcov_with_fail_because_of_uncovered_files + # @c.with_context do + # Dir.chdir @proj_name do + # prep_project_yml_for_coverage + # add_gcov_option("abort_on_uncovered", "TRUE") + # FileUtils.cp test_asset_path("example_file.h"), 'src/' + # FileUtils.cp test_asset_path("example_file.c"), 'src/' + # FileUtils.cp test_asset_path("uncovered_example_file.c"), 'src/' + # FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/' + + # output = `bundle exec ruby -S ceedling gcov:all 2>&1` + # expect($?.exitstatus).to match(1) # Gcov causes Ceedlng to exit with error + # expect(output).to match(/TESTED:\s+\d/) + # expect(output).to match(/PASSED:\s+\d/) + # expect(output).to match(/FAILED:\s+\d/) + # expect(output).to match(/IGNORED:\s+\d/) + # end + # end + # end + + # TODO: Restore this test when the :abort_on_uncovered option is restored in the Gcov plugin + # def can_test_projects_with_gcov_with_success_because_of_ignore_uncovered_list + # @c.with_context do + # Dir.chdir @proj_name do + # prep_project_yml_for_coverage + # add_gcov_option("abort_on_uncovered", "TRUE") + # add_gcov_section("uncovered_ignore_list", ["src/foo_file.c"]) + # FileUtils.cp test_asset_path("example_file.h"), "src/" + # FileUtils.cp test_asset_path("example_file.c"), "src/" + # # "src/foo_file.c" is in the ignore uncovered list + # FileUtils.cp test_asset_path("uncovered_example_file.c"), "src/foo_file.c" + # FileUtils.cp test_asset_path("test_example_file_success.c"), "test/" + + # output = `bundle exec ruby -S ceedling gcov:all 2>&1` + # expect($?.exitstatus).to match(0) + # expect(output).to match(/TESTED:\s+\d/) + # expect(output).to match(/PASSED:\s+\d/) + # expect(output).to match(/FAILED:\s+\d/) + # expect(output).to match(/IGNORED:\s+\d/) + # end + # end + # end + + # TODO: Restore this test when the :abort_on_uncovered option is restored in the Gcov plugin + # def can_test_projects_with_gcov_with_success_because_of_ignore_uncovered_list_with_globs + # @c.with_context do + # Dir.chdir @proj_name do + # prep_project_yml_for_coverage + # add_gcov_option("abort_on_uncovered", "TRUE") + # add_gcov_section("uncovered_ignore_list", ["src/B/**"]) + # FileUtils.mkdir_p(["src/A", "src/B/C"]) + # FileUtils.cp test_asset_path("example_file.h"), "src/A" + # FileUtils.cp test_asset_path("example_file.c"), "src/A" + # # "src/B/**" is in the ignore uncovered list + # FileUtils.cp test_asset_path("uncovered_example_file.c"), "src/B/C/foo_file.c" + # FileUtils.cp test_asset_path("test_example_file_success.c"), "test/" + + # output = `bundle exec ruby -S ceedling gcov:all 2>&1` + # expect($?.exitstatus).to match(1) # Unit test failures => Ceedling exit code 1 + # expect(output).to match(/TESTED:\s+\d/) + # expect(output).to match(/PASSED:\s+\d/) + # expect(output).to match(/FAILED:\s+\d/) + # expect(output).to match(/IGNORED:\s+\d/) + # end + # end + # end + + + def can_test_projects_with_gcov_with_compile_error @c.with_context do Dir.chdir @proj_name do - FileUtils.cp test_asset_path("project_with_guts_gcov.yml"), "project.yml" - add_gcov_option("abort_on_uncovered", "TRUE") + prep_project_yml_for_coverage FileUtils.cp test_asset_path("example_file.h"), 'src/' FileUtils.cp test_asset_path("example_file.c"), 'src/' - FileUtils.cp test_asset_path("uncovered_example_file.c"), 'src/' - FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/' + FileUtils.cp test_asset_path("test_example_file_boom.c"), 'test/' output = `bundle exec ruby -S ceedling gcov:all 2>&1` - expect($?.exitstatus).to match(255) # Since a test fails, we return error here - expect(output).to match(/TESTED:\s+\d/) - expect(output).to match(/PASSED:\s+\d/) - expect(output).to match(/FAILED:\s+\d/) - expect(output).to match(/IGNORED:\s+\d/) + expect($?.exitstatus).to match(1) # Since a test explodes, Ceedling exits with error + expect(output).to match(/(?:ERROR: Ceedling Failed)|(?:Ceedling could not complete operations because of errors)/) end end end - def can_test_projects_with_gcov_with_success_because_of_ignore_uncovered_list + def can_fetch_project_help_for_gcov @c.with_context do Dir.chdir @proj_name do - FileUtils.cp test_asset_path("project_with_guts_gcov.yml"), "project.yml" - add_gcov_option("abort_on_uncovered", "TRUE") - add_gcov_section("uncovered_ignore_list", ["src/foo_file.c"]) - FileUtils.cp test_asset_path("example_file.h"), "src/" - FileUtils.cp test_asset_path("example_file.c"), "src/" - FileUtils.cp test_asset_path("uncovered_example_file.c"), "src/foo_file.c" # "src/foo_file.c" is in the ignore uncovered list - FileUtils.cp test_asset_path("test_example_file_success.c"), "test/" - - output = `bundle exec ruby -S ceedling gcov:all 2>&1` + prep_project_yml_for_coverage + output = `bundle exec ruby -S ceedling help 2>&1` expect($?.exitstatus).to match(0) - expect(output).to match(/TESTED:\s+\d/) - expect(output).to match(/PASSED:\s+\d/) - expect(output).to match(/FAILED:\s+\d/) - expect(output).to match(/IGNORED:\s+\d/) + expect(output).to match(/ceedling gcov:\*/i) + expect(output).to match(/ceedling gcov:all/i) end end end - def can_test_projects_with_gcov_with_success_because_of_ignore_uncovered_list_with_globs + def can_create_html_reports @c.with_context do Dir.chdir @proj_name do - FileUtils.cp test_asset_path("project_with_guts_gcov.yml"), "project.yml" - add_gcov_option("abort_on_uncovered", "TRUE") - add_gcov_section("uncovered_ignore_list", ["src/B/**"]) - FileUtils.mkdir_p(["src/A", "src/B/C"]) - FileUtils.cp test_asset_path("example_file.h"), "src/A" - FileUtils.cp test_asset_path("example_file.c"), "src/A" - FileUtils.cp test_asset_path("uncovered_example_file.c"), "src/B/C/foo_file.c" # "src/B/**" is in the ignore uncovered list - FileUtils.cp test_asset_path("test_example_file_success.c"), "test/" + prep_project_yml_for_coverage + FileUtils.cp test_asset_path("example_file.h"), 'src/' + FileUtils.cp test_asset_path("example_file.c"), 'src/' + FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/' output = `bundle exec ruby -S ceedling gcov:all 2>&1` - expect($?.exitstatus).to match(0) - expect(output).to match(/TESTED:\s+\d/) - expect(output).to match(/PASSED:\s+\d/) - expect(output).to match(/FAILED:\s+\d/) - expect(output).to match(/IGNORED:\s+\d/) + if @gcov_reports.include? :gcovr + expect(output).to match(/Generating HTML coverage report in 'build\/artifacts\/gcov\/gcovr'\.\.\./) + expect(File.exist?('build/artifacts/gcov/gcovr/GcovCoverageResults.html')).to eq true + end + if @gcov_reports.include? :modulegenerator + expect(output).to match(/Generating HtmlBasic coverage report in 'build\/artifacts\/gcov\/ReportGenerator'\.\.\./) + expect(File.exist?('build/artifacts/gcov/ReportGenerator/summary.htm')).to eq true + end end end end - - def can_test_projects_with_gcov_with_compile_error + def can_create_html_reports_from_crashing_test_runner_with_enabled_debug_for_test_cases_not_causing_crash @c.with_context do Dir.chdir @proj_name do - FileUtils.cp test_asset_path("project_with_guts_gcov.yml"), "project.yml" + prep_project_yml_for_coverage FileUtils.cp test_asset_path("example_file.h"), 'src/' FileUtils.cp test_asset_path("example_file.c"), 'src/' - FileUtils.cp test_asset_path("test_example_file_boom.c"), 'test/' + FileUtils.cp test_asset_path("test_example_file_crash.c"), 'test/' + + @c.merge_project_yml_for_test({:project => { :use_backtrace => :gdb }}) output = `bundle exec ruby -S ceedling gcov:all 2>&1` - expect($?.exitstatus).to match(1) # Since a test explodes, we return error here - expect(output).to match(/ERROR: Ceedling Failed/) + expect($?.exitstatus).to match(1) # Ceedling should exit with error because of failed test due to crash + expect(output).to match(/crashed/i) + expect(output).to match(/Unit test failures./) + expect(File.exist?('./build/gcov/results/test_example_file_crash.fail')) + output_rd = File.read('./build/gcov/results/test_example_file_crash.fail') + expect(output_rd =~ /test_add_numbers_will_fail \(\) at test\/test_example_file_crash.c\:14/ ) + expect(output).to match(/TESTED:\s+2/) + expect(output).to match(/PASSED:\s+(?:0|1)/) + expect(output).to match(/FAILED:\s+(?:1|2)/) + expect(output).to match(/IGNORED:\s+0/) + expect(output).to match(/example_file.c \| Lines executed:5?0.00% of 4/) + if @gcov_reports.include? :gcovr + expect(output).to match(/Generating HTML coverage report in 'build\/artifacts\/gcov\/gcovr'\.\.\./) + expect(File.exist?('build/artifacts/gcov/gcovr/GcovCoverageResults.html')).to eq true + end + if @gcov_reports.include? :modulegenerator + expect(output).to match(/Generating HtmlBasic coverage report in 'build\/artifacts\/gcov\/ReportGenerator'\.\.\./) + expect(File.exist?('build/artifacts/gcov/ReportGenerator/summary.htm')).to eq true + end end end end - def can_fetch_project_help_for_gcov + def can_create_html_reports_from_crashing_test_runner_with_enabled_debug_with_zero_coverage @c.with_context do Dir.chdir @proj_name do - FileUtils.cp test_asset_path("project_with_guts_gcov.yml"), "project.yml" - output = `bundle exec ruby -S ceedling help` - expect($?.exitstatus).to match(0) - expect(output).to match(/ceedling gcov:\*/i) - expect(output).to match(/ceedling gcov:all/i) - expect(output).to match(/ceedling gcov:delta/i) - expect(output).to match(/ceedling utils:gcov/i) + prep_project_yml_for_coverage + FileUtils.cp test_asset_path("example_file.h"), 'src/' + FileUtils.cp test_asset_path("example_file.c"), 'src/' + FileUtils.cp test_asset_path("test_example_file_crash.c"), 'test/' + + @c.merge_project_yml_for_test({:project => { :use_backtrace => :gdb }}) + + output = `bundle exec ruby -S ceedling gcov:all --exclude_test_case=test_add_numbers_adds_numbers 2>&1` + expect($?.exitstatus).to match(1) # Ceedling should exit with error because of failed test due to crash + expect(output).to match(/Test Case Crashed/i) + expect(output).to match(/Unit test failures./) + expect(File.exist?('./build/gcov/results/test_example_file_crash.fail')) + output_rd = File.read('./build/gcov/results/test_example_file_crash.fail') + expect(output_rd =~ /test_add_numbers_will_fail \(\) at test\/test_example_file_crash.c\:14/ ) + expect(output).to match(/TESTED:\s+1/) + expect(output).to match(/PASSED:\s+0/) + expect(output).to match(/FAILED:\s+1/) + expect(output).to match(/IGNORED:\s+0/) + expect(output).to match(/example_file.c \| Lines executed:0.00% of 4/) + + if @gcov_reports.include? :gcovr + expect(output).to match(/Generating HTML coverage report in 'build\/artifacts\/gcov\/gcovr'\.\.\./) + expect(File.exist?('build/artifacts/gcov/gcovr/GcovCoverageResults.html')).to eq true + end + if @gcov_reports.include? :modulegenerator + expect(output).to match(/Generating HtmlBasic coverage report in 'build\/artifacts\/gcov\/ReportGenerator'\.\.\./) + expect(File.exist?('build/artifacts/gcov/ReportGenerator/summary.htm')).to eq true + end end end end - def can_create_html_report + def can_create_html_reports_from_test_runner_with_enabled_debug_with_100_coverage_when_excluding_crashing_test_case @c.with_context do Dir.chdir @proj_name do - FileUtils.cp test_asset_path("project_with_guts_gcov.yml"), "project.yml" + prep_project_yml_for_coverage FileUtils.cp test_asset_path("example_file.h"), 'src/' FileUtils.cp test_asset_path("example_file.c"), 'src/' - FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/' + FileUtils.cp test_asset_path("test_example_file_crash.c"), 'test/' + + add_test_case = "\nvoid test_difference_between_two_numbers(void)\n"\ + "{\n" \ + " TEST_ASSERT_EQUAL_INT(0, difference_between_numbers(1,1));\n" \ + "}\n" + + updated_test_file = File.read('test/test_example_file_crash.c').split("\n") + updated_test_file.insert(updated_test_file.length(), add_test_case) + File.write('test/test_example_file_crash.c', updated_test_file.join("\n"), mode: 'w') + + output = `bundle exec ruby -S ceedling gcov:all --exclude_test_case=test_add_numbers_will_fail 2>&1` + expect($?.exitstatus).to match(0) + expect(File.exist?('./build/gcov/results/test_example_file_crash.pass')) + expect(output).to match(/TESTED:\s+2/) + expect(output).to match(/PASSED:\s+2/) + expect(output).to match(/FAILED:\s+0/) + expect(output).to match(/IGNORED:\s+0/) + expect(output).to match(/example_file.c \| Lines executed:100.00% of 4/) - output = `bundle exec ruby -S ceedling gcov:all` - output = `bundle exec ruby -S ceedling utils:gcov` - expect(output).to match(/Creating gcov results report\(s\) in 'build\/artifacts\/gcov'\.\.\. Done/) - expect(File.exist?('build/artifacts/gcov/GcovCoverageResults.html')).to eq true + if @gcov_reports.include? :gcovr + expect(output).to match(/Generating HTML coverage report in 'build\/artifacts\/gcov\/gcovr'\.\.\./) + expect(File.exist?('build/artifacts/gcov/gcovr/GcovCoverageResults.html')).to eq true + end + if @gcov_reports.include? :modulegenerator + expect(output).to match(/Generating HtmlBasic coverage report in 'build\/artifacts\/gcov\/ReportGenerator'\.\.\./) + expect(File.exist?('build/artifacts/gcov/ReportGenerator/summary.htm')).to eq true + end end end end diff --git a/spec/generator_test_results_sanity_checker_spec.rb b/spec/generator_test_results_sanity_checker_spec.rb index 2a09646d8..77b43081a 100644 --- a/spec/generator_test_results_sanity_checker_spec.rb +++ b/spec/generator_test_results_sanity_checker_spec.rb @@ -1,16 +1,32 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'spec_helper' require 'ceedling/generator_test_results_sanity_checker' require 'ceedling/constants' -require 'ceedling/streaminator' +require 'ceedling/loginator' require 'ceedling/configurator' describe GeneratorTestResultsSanityChecker do before(:each) do - # this will always be mocked - @configurator = Configurator.new({:configurator_setup => nil, :configurator_builder => nil, :configurator_plugins => nil, :cmock_builder => nil, :yaml_wrapper => nil, :system_wrapper => nil}) - @streaminator = Streaminator.new({:streaminator_helper => nil, :verbosinator => nil, :loginator => nil, :stream_wrapper => nil}) + # These will always be mocked + @loginator = Loginator.new({:verbosinator => nil, :file_wrapper => nil, :system_wrapper => nil}) + @configurator = Configurator.new({ + :configurator_setup => nil, + :configurator_builder => nil, + :configurator_plugins => nil, + :config_walkinator => nil, + :yaml_wrapper => nil, + :system_wrapper => nil, + :loginator => @loginator, + :reportinator => nil + }) - @sanity_checker = described_class.new({:configurator => @configurator, :streaminator => @streaminator}) + @sanity_checker = described_class.new({:configurator => @configurator, :loginator => @loginator}) @results = {} @results[:ignores] = ['', '', ''] @@ -46,7 +62,7 @@ @configurator.sanity_checks = TestResultsSanityChecks::NORMAL @results[:counts][:ignored] = 0 allow(@configurator).to receive(:extension_executable).and_return('') - allow(@streaminator).to receive(:stderr_puts) + allow(@loginator).to receive(:log) expect{@sanity_checker.verify(@results, 3)}.to raise_error(RuntimeError) end @@ -54,7 +70,7 @@ @configurator.sanity_checks = TestResultsSanityChecks::NORMAL @results[:counts][:failed] = 0 allow(@configurator).to receive(:extension_executable).and_return('') - allow(@streaminator).to receive(:stderr_puts) + allow(@loginator).to receive(:log) expect{@sanity_checker.verify(@results, 3)}.to raise_error(RuntimeError) end @@ -62,21 +78,21 @@ @configurator.sanity_checks = TestResultsSanityChecks::NORMAL @results[:counts][:total] = 0 allow(@configurator).to receive(:extension_executable).and_return('') - allow(@streaminator).to receive(:stderr_puts) + allow(@loginator).to receive(:log) expect{@sanity_checker.verify(@results, 3)}.to raise_error(RuntimeError) end it 'rasies error if thorough check fails for error code not 255 not equal' do @configurator.sanity_checks = TestResultsSanityChecks::THOROUGH allow(@configurator).to receive(:extension_executable).and_return('') - allow(@streaminator).to receive(:stderr_puts) + allow(@loginator).to receive(:log) expect{@sanity_checker.verify(@results, 2)}.to raise_error(RuntimeError) end it 'rasies error if thorough check fails for error code 255 less than 255' do @configurator.sanity_checks = TestResultsSanityChecks::THOROUGH allow(@configurator).to receive(:extension_executable).and_return('') - allow(@streaminator).to receive(:stderr_puts) + allow(@loginator).to receive(:log) expect{@sanity_checker.verify(@results, 255)}.to raise_error(RuntimeError) end diff --git a/spec/generator_test_results_spec.rb b/spec/generator_test_results_spec.rb index 90df1d077..51c55ac33 100644 --- a/spec/generator_test_results_spec.rb +++ b/spec/generator_test_results_spec.rb @@ -1,9 +1,16 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'spec_helper' require 'ceedling/generator_test_results_sanity_checker' require 'ceedling/generator_test_results' require 'ceedling/yaml_wrapper' require 'ceedling/constants' -require 'ceedling/streaminator' +require 'ceedling/loginator' require 'ceedling/configurator' NORMAL_OUTPUT = @@ -55,14 +62,30 @@ describe GeneratorTestResults do before(:each) do # these will always be mocked - @configurator = Configurator.new({:configurator_setup => nil, :configurator_builder => nil, :configurator_plugins => nil, :cmock_builder => nil, :yaml_wrapper => nil, :system_wrapper => nil}) - @streaminator = Streaminator.new({:streaminator_helper => nil, :verbosinator => nil, :loginator => nil, :stream_wrapper => nil}) + # this will always be mocked + @loginator = Loginator.new({:verbosinator => nil, :file_wrapper => nil, :system_wrapper => nil}) + @configurator = Configurator.new({ + :configurator_setup => nil, + :configurator_builder => nil, + :configurator_plugins => nil, + :config_walkinator => nil, + :yaml_wrapper => nil, + :system_wrapper => nil, + :loginator => @loginator, + :reportinator => nil + }) # these will always be used as is. @yaml_wrapper = YamlWrapper.new - @sanity_checker = GeneratorTestResultsSanityChecker.new({:configurator => @configurator, :streaminator => @streaminator}) - - @generate_test_results = described_class.new({:configurator => @configurator, :generator_test_results_sanity_checker => @sanity_checker, :yaml_wrapper => @yaml_wrapper}) + @sanity_checker = GeneratorTestResultsSanityChecker.new({:configurator => @configurator, :loginator => @loginator}) + + @generate_test_results = described_class.new( + { + :configurator => @configurator, + :generator_test_results_sanity_checker => @sanity_checker, + :yaml_wrapper => @yaml_wrapper + } + ) end after(:each) do @@ -72,36 +95,37 @@ end describe '#process_and_write_results' do - it 'handles an empty input' do - @generate_test_results.process_and_write_results({:output => ''}, TEST_OUT_FILE, 'some/place/test_example.c') - expect(IO.read(TEST_OUT_FILE)).to eq(IO.read('spec/support/test_example_empty.pass')) + it 'raises on an empty input' do + expect{ + @generate_test_results.process_and_write_results('test_example.out', {:output => ''}, TEST_OUT_FILE, 'some/place/test_example.c') + }.to raise_error( /Could not parse/i ) + end + + it 'raises on mangled test output' do + expect{ + @generate_test_results.process_and_write_results('test_example.out', {:output => MANGLED_OUTPUT}, TEST_OUT_FILE, 'some/place/test_example.c') + }.to raise_error( /Could not parse/i ) end it 'handles a normal test output' do - @generate_test_results.process_and_write_results({:output => NORMAL_OUTPUT}, TEST_OUT_FILE, 'some/place/test_example.c') + @generate_test_results.process_and_write_results('test_example.out', {:output => NORMAL_OUTPUT}, TEST_OUT_FILE, 'some/place/test_example.c') expect(IO.read(TEST_OUT_FILE)).to eq(IO.read('spec/support/test_example.pass')) end it 'handles a normal test output with time' do - @generate_test_results.process_and_write_results({:output => NORMAL_OUTPUT, :time => 0.01234}, TEST_OUT_FILE, 'some/place/test_example.c') + @generate_test_results.process_and_write_results('test_example.out', {:output => NORMAL_OUTPUT, :time => 0.01234}, TEST_OUT_FILE, 'some/place/test_example.c') expect(IO.read(TEST_OUT_FILE)).to eq(IO.read('spec/support/test_example_with_time.pass')) end it 'handles a normal test output with ignores' do - @generate_test_results.process_and_write_results({:output => IGNORE_OUTPUT}, TEST_OUT_FILE, 'some/place/test_example.c') + @generate_test_results.process_and_write_results('test_example.out', {:output => IGNORE_OUTPUT}, TEST_OUT_FILE, 'some/place/test_example.c') expect(IO.read(TEST_OUT_FILE)).to eq(IO.read('spec/support/test_example_ignore.pass')) end it 'handles a normal test output with failures' do allow(@configurator).to receive(:extension_testfail).and_return('.fail') - @generate_test_results.process_and_write_results({:output => FAIL_OUTPUT}, TEST_OUT_FILE, 'some/place/test_example.c') + @generate_test_results.process_and_write_results('test_example.out', {:output => FAIL_OUTPUT}, TEST_OUT_FILE, 'some/place/test_example.c') expect(IO.read(TEST_OUT_FILE_FAIL)).to eq(IO.read('spec/support/test_example.fail')) end - - it 'handles a mangled test output as gracefully as it can' do - @generate_test_results.process_and_write_results({:output => MANGLED_OUTPUT}, TEST_OUT_FILE, 'some/place/test_example.c') - expect(IO.read(TEST_OUT_FILE)).to eq(IO.read('spec/support/test_example_mangled.pass')) - end - end end diff --git a/spec/manual/stress_test.rb b/spec/manual/stress_test.rb new file mode 100644 index 000000000..35a98cec9 --- /dev/null +++ b/spec/manual/stress_test.rb @@ -0,0 +1,24 @@ +iterations = (ARGV[0] || 25).to_i +puts "Stress Testing Each Scenario #{iterations} times..." + +require 'open3' + +defaults = { :dir => File.expand_path(File.dirname(__FILE__)) + '/../../examples/temp_sensor' } + +tasks = { + 'ceedling clobber test:all' => defaults, + 'ceedling -v=4 clobber test:all' => defaults, + 'ceedling test:all' => defaults, + 'ceedling --verbosity=obnoxious --mixin=add_unity_helper --mixin=add_gcov clobber test:all' => defaults, +} + +tasks.each_pair do |k,v| + Dir.chdir(v[:dir]) do + iterations.times do |i| + puts "=============== RUNNING ITERATION #{i+1}:\n#{k.to_s}\n===============\n\n" + stdout, stderr, status = Open3.capture3(k) + puts stdout,stderr,status + raise "\n\nCrashed on #{k} Iteration #{i+1}" unless status.success? + end + end +end \ No newline at end of file diff --git a/spec/par_map_spec.rb b/spec/par_map_spec.rb deleted file mode 100644 index 5a7e9267d..000000000 --- a/spec/par_map_spec.rb +++ /dev/null @@ -1,57 +0,0 @@ -require 'ceedling/par_map' - -def short_task_should(n) - done_count = 0 - par_map(n, [0.01] * (n - 1) + [0] * 10) do |seconds| - sleep(seconds) - if seconds == 1 then - done_count.should >= 10 - else - done_count += 1 - end - end -end - -describe "par_map" do - it "should run shorter tasks while larger tasks are blocking (with 2 threads)" do - short_task_should(2) - end - - it "should run shorter tasks while larger tasks are blocking (with 3 threads)" do - short_task_should(3) - end - - it "should run shorter tasks while larger tasks are blocking (with 4 threads)" do - short_task_should(4) - end - - #the following two tests are still slightly nondeterministic and may occasionally - # show false positives (though we think we've gotten it pretty stable) - it "should collide if multiple threads are used" do - is_running = false - # we are trying to maximize the potential for collision by varying the sleep - # delay between threads - collision = false - par_map(4, (1..5).to_a) do |x| - if is_running then - collision = true - end - is_running = true - sleep(0.01 * x) - is_running = false - end - expect(collision).to eq true - end - - it "should be serial if only one thread is used" do - is_running = false - par_map(1, (1..5).to_a) do |x| - expect(is_running).to eq false - is_running = true - sleep(0.01 * x) - is_running = false - end - end - - -end diff --git a/spec/preprocessinator_extractor_spec.rb b/spec/preprocessinator_extractor_spec.rb index 6acbf3200..eabca9296 100644 --- a/spec/preprocessinator_extractor_spec.rb +++ b/spec/preprocessinator_extractor_spec.rb @@ -1,81 +1,330 @@ -# derived from test_graveyard/unit/preprocessinator_extractor_test.rb +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= -require 'spec_helper' require 'ceedling/preprocessinator_extractor' describe PreprocessinatorExtractor do - context "#extract_base_file_from_preprocessed_expansion" do - it "should extract text from the original file and keep #pragma statements" do - file_path = "path/to/WANT.c" - input_str = [ + context "#extract_file_as_array_from_expansion" do + it "should simply extract text of original file from preprocessed expansion" do + filepath = "path/to/WANT.c" + + file_contents = [ '# 1 "some/file/we/do/not/care/about.c" 5', - '#pragma shit', 'some_text_we_do_not_want();', - '# 1 "some/file/we/DO/WANT.c" 99999', - 'some_text_we_do_not_want();', - '#pragma want', + '# 11 "some/file/we/DO/WANT.c" 99999', # Beginning of block to extract + 'some_text_we_do_want();', # Line to extract + '', # Blank line to extract + 'some_awesome_text_we_want_so_hard();', # Line to extract + 'holy_crepes_more_awesome_text();', # Line to extract + '# 3 "some/other/file/we/ignore.c" 5', # End of block to extract + ] + + expected = [ + 'some_text_we_do_want();', + '', 'some_awesome_text_we_want_so_hard();', - 'holy_crepes_more_awesome_text();', - '# oh darn', - '# 1 "some/useless/file.c" 9', - 'a set of junk', - 'more junk', - '# 1 "holy/shoot/yes/WANT.c" 10', - 'some_additional_awesome_want_text();', + 'holy_crepes_more_awesome_text();' + ] + + input = StringIO.new( file_contents.join( "\n" ) ) + + expect( subject.extract_file_as_array_from_expansion( input, filepath ) ).to eq expected + end + + it "should extract text of original file from preprocessed expansion preserving #directives and cleaning up whitespace)" do + filepath = "this/path/MY_FILE.C" + + file_contents = [ + '# 1 "MY_FILE.C" 99999', # Beginning of block to extract + ' ', # Whitespace collapse & preserve as blank line + '#pragma yo sup', # Line to extract -- #pragma is not end-of-block preprocessor directive + '#define FOO(...)', # Line to extract -- #define is not end-of-block preprocessor directive + 'void some_function(void) {', # Line to extract + ' do_something(); ', # Line to extract with leading whitespace that should remain + ' }', # Line to extract with leading whitespace that should remain + "\t", # Whitespace collapse & preserve as blank line + '# 1 "some/useless/file.c"' # End of block to extract + ] + + expected = [ + '', + '#pragma yo sup', + '#define FOO(...)', + 'void some_function(void) {', + ' do_something();', + ' }', + '' ] - expect_str = [ + input = StringIO.new( file_contents.join( "\n" ) ) + + expect( subject.extract_file_as_array_from_expansion( input, filepath ) ).to eq expected + end + + it "should extract text of original file from preprocessed expansion with complex preprocessor directive sequence" do + filepath = "dir/our_file.c" + + file_contents = [ + '# 1 "dir/our_file.c" 123', # Beginning of file / block to extract + 'some_text_we_do_want();', # Line to extract + 'some_awesome_text_we_want_so_hard();', # Line to extract + '# 987 "some preprocessor directive"', # End of block to extract (faux preprocessor directive) + '', 'some_text_we_do_not_want();', - '#pragma want', + '# 15 "dir/our_file.c" 9', # Beginning of block to extract + 'more_text_we_want();', # Line to extract + 'void some_function(void) { func(); }', # Line to extract + '# 9 "dir/our_file.c" 77', # Continuation of block to extract + 'some code', # Line to extract + 'test statements', # Line to extract + '# 6 "dir/our_file.c" 19', # Continuation of block to extract + 'some_additional_awesomely_wanted_text();' # Line to extract + ] # End of file / end of block to extract + + expected = [ + 'some_text_we_do_want();', 'some_awesome_text_we_want_so_hard();', - 'holy_crepes_more_awesome_text();', - 'some_additional_awesome_want_text();', + 'more_text_we_want();', + 'void some_function(void) { func(); }', + 'some code', + 'test statements', + 'some_additional_awesomely_wanted_text();' ] - expect(File).to receive(:readlines).with(file_path).and_return( input_str ) + input = StringIO.new( file_contents.join( "\n" ) ) - expect(subject.extract_base_file_from_preprocessed_expansion(file_path)).to eq expect_str + expect( subject.extract_file_as_array_from_expansion(input, filepath) ).to eq expected end - - # These were originally hinted at by the old test, but we don't see anything - # in the implementation that does this. They are here as reminders in the future. - # # xit "should ignore formatting" - # # xit "should ignore whitespace" end - context "#extract_base_file_from_preprocessed_directives" do - it "should extract last chunk of text after last '#'line containing file name of our filepath" do - file_path = "path/to/WANT.c" - input_str = [ + context "#extract_file_as_string_from_expansion" do + it "should simply extract text of original file from preprocessed expansion" do + filepath = "path/to/WANT.c" + + file_contents = [ '# 1 "some/file/we/do/not/care/about.c" 5', - '#pragma trash', 'some_text_we_do_not_want();', - '# 1 "some/file/we/DO/WANT.c" 99999', - 'some_text_we_do_not_want();', - '#pragma want', - 'some_creepy_text_we_not_want();', - '# 1 "some/useless/file.c" 9', - 'a set of junk', - 'more junk', - '# 1 "holy/shoot/yes/WANT.c" 10', - '#pragma want', - '#define INCREDIBLE_DEFINE 911', - 'some_additional_awesome_want_text();', - 'holy_crepes_more_awesome_text();', - '# oh darn', + '# 11 "some/file/we/DO/WANT.c" 99999', # Beginning of block to extract + 'some_text_we_do_want();', # Line to extract + '', # Blank line to extract + 'some_awesome_text_we_want_so_hard();', # Line to extract + 'holy_crepes_more_awesome_text();', # Line to extract + '# 3 "some/other/file/we/ignore.c" 5', # End of block to extract + ] + + expected = [ + 'some_text_we_do_want();', + '', + 'some_awesome_text_we_want_so_hard();', + 'holy_crepes_more_awesome_text();' + ].join( "\n" ) + + input = StringIO.new( file_contents.join( "\n" ) ) + + expect( subject.extract_file_as_string_from_expansion( input, filepath ) ).to eq expected + end + end + + context "#extract_test_directive_macro_calls" do + it "should extract any and all test directive macro calls from test file text" do + file_text = <<~FILE_TEXT + TEST_SOURCE_FILE("foo/bar/file.c")TEST_SOURCE_FILE("yo/data.c") + + TEST_INCLUDE_PATH("some/inc/dir") + SOME_MACRO(TEST_INCLUDE_PATH("another/dir")) TEST_INCLUDE_PATH("hello/there") + FILE_TEXT + + expected = [ + 'TEST_SOURCE_FILE("foo/bar/file.c")', + 'TEST_SOURCE_FILE("yo/data.c")', + 'TEST_INCLUDE_PATH("some/inc/dir")', + 'TEST_INCLUDE_PATH("another/dir")', + 'TEST_INCLUDE_PATH("hello/there")' + ] + + expect( subject.extract_test_directive_macro_calls( file_text ) ).to eq expected + end + end + + context "#extract_pragmas" do + it "should extract any and all pragmas from file text" do + file_text = <<~FILE_TEXT + SOME_MACRO("yo") + + #define PI 3.14159 + + #pragma pack(1) + + extern void func_sig(int, byte); + #define SQUARE(x) ((x) * (x)) + + #pragma TOOL command \\ + with_some_args \\ + that wrap + + #define MAX(a, b) ((a) > (b) ? (a) : (b)) + + #pragma warning(disable : 4996) + #pragma GCC optimize("O3") + + SOME_OTHER_MACRO("more") + FILE_TEXT + + expected = [ + "#pragma pack(1)", + [ + "#pragma TOOL command \\", + " with_some_args \\", + " that wrap" + ], + "#pragma warning(disable : 4996)", + "#pragma GCC optimize(\"O3\")" ] - expect_str = [ - '#pragma want', - '#define INCREDIBLE_DEFINE 911', - 'some_additional_awesome_want_text();', - 'holy_crepes_more_awesome_text();', - '# oh darn', + expect( subject.extract_pragmas( file_text ) ).to eq expected + end + end + + context "#extract_include_guard" do + it "should extract a simple include guard from among file text" do + file_text = <<~FILE_TEXT + #ifndef _HEADER_INCLUDE_GUARD_ + #define _HEADER_INCLUDE_GUARD_ + + ... + + #endif // _HEADER_INCLUDE_GUARD_ + FILE_TEXT + + expect( subject.extract_include_guard( file_text ) ).to eq '_HEADER_INCLUDE_GUARD_' + end + + it "should extract the first text that looks like an include guard from among file text" do + file_text = <<~FILE_TEXT + + #ifndef HEADER_INCLUDE_GUARD + + #define HEADER_INCLUDE_GUARD + + #ifndef DUMMY_INCLUDE_GUARD + #define DUMMY_INCLUDE_GUARD + + #endif // HEADER_INCLUDE_GUARD + FILE_TEXT + + expect( subject.extract_include_guard( file_text ) ).to eq 'HEADER_INCLUDE_GUARD' + end + + it "should not extract an include guard from among file text" do + file_text = <<~FILE_TEXT + #ifndef SOME_GUARD_NAME + #define OME_GUARD_NAME + + #define SOME_GUARD_NAME + + #endif // SOME_GUARD_NAME + FILE_TEXT + + expect( subject.extract_include_guard( file_text ) ).to eq nil + end + end + + context "#extract_macro_defs" do + it "should extract any and all macro defintions from file text" do + + # Note aspects of this heredoc text block under test: + # - Macros beginning indented + # - Repeated whitespace characters + # - Single line and multiline macro definitions + # - Whitespace after continuation slashes (eliminated in extraction) + # - No empty lines between macro definitions + + file_text = <<~FILE_TEXT + SOME_MACRO("yo") + + #define PI 3.14159 + + #pragma GCC something + + extern void func_sig(int, byte); + #define SQUARE(x) ((x) * (x)) + + #define MAX(a, b) ((a) > (b) ? (a) : (b)) + + extern void function(void); + + #define MACRO(num, str) {\\ + printf("%d", num);\\ + printf(" is"); \\ + printf(" %s number", str);\\ + printf("\\n");\\ + } + + #define LONG_STRING "This is a very long string that \\ + continues on the next line" + #define MULTILINE_MACRO do { \\ + something(); \\ + something_else(); \\ + } while(0) + + SOME_OTHER_MACRO("more") + FILE_TEXT + + expected = [ + "#define PI 3.14159", + "#define SQUARE(x) ((x) * (x))", + "#define MAX(a, b) ((a) > (b) ? (a) : (b))", + [ + "#define MACRO(num, str) {\\", + " printf(\"%d\", num);\\", + " printf(\" is\"); \\", + " printf(\" %s number\", str);\\", + " printf(\"\\n\");\\", + " }" + ], + [ + "#define LONG_STRING \"This is a very long string that \\", + " continues on the next line\"" + ], + [ + "#define MULTILINE_MACRO do { \\", + " something(); \\", + " something_else(); \\", + " } while(0)" + ] ] - expect(File).to receive(:readlines).with(file_path).and_return( input_str ) + expect( subject.extract_macro_defs( file_text, nil ) ).to eq expected + end + + it "should ignore include guard among macro defintions in file text" do + file_text = <<~FILE_TEXT + #ifndef _INCLUDE_GUARD_ + #define _INCLUDE_GUARD_ + + #define PI 3.14159 + + #define LONG_STRING "This is a very long string that \\ + continues on the next line" - expect(subject.extract_base_file_from_preprocessed_directives(file_path)).to eq expect_str + SOME_OTHER_MACRO("more") + FILE_TEXT + + expected = [ + "#define PI 3.14159", + [ + "#define LONG_STRING \"This is a very long string that \\", + " continues on the next line\"" + ] + ] + + expect( subject.extract_macro_defs( file_text, '_INCLUDE_GUARD_' ) ).to eq expected end + end + end diff --git a/spec/preprocessinator_includes_handler_spec.rb b/spec/preprocessinator_includes_handler_spec.rb index 4b0cdc4f3..2ff677b2c 100644 --- a/spec/preprocessinator_includes_handler_spec.rb +++ b/spec/preprocessinator_includes_handler_spec.rb @@ -1,283 +1,289 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'spec_helper' require 'ceedling/preprocessinator_includes_handler' describe PreprocessinatorIncludesHandler do before :each do - @configurator = double('configurator') - @tool_executor = double('tool_executor') - @task_invoker = double('task_invoker') - @file_path_utils = double('file_path_utils') - @yaml_wrapper = double('yaml_wrapper') - @file_wrapper = double('file_wrapper') - @file_finder = double('file_finder') + @configurator = double('configurator') + @tool_executor = double('tool_executor') + @test_context_extractor = double('test_context_extractor') + @yaml_wrapper = double('yaml_wrapper') + @loginator = double('loginator') + @reportinator = double('reportinator') end subject do PreprocessinatorIncludesHandler.new( - :configurator => @configurator, - :tool_executor => @tool_executor, - :task_invoker => @task_invoker, - :file_path_utils => @file_path_utils, - :yaml_wrapper => @yaml_wrapper, - :file_wrapper => @file_wrapper, - :file_finder => @file_finder + :configurator => @configurator, + :tool_executor => @tool_executor, + :test_context_extractor => @test_context_extractor, + :yaml_wrapper => @yaml_wrapper, + :loginator => @loginator, + :reportinator => @reportinator ) end - context 'invoke_shallow_includes_list' do - it 'should invoke the rake task which will build included files' do - # create test state/variables - # mocks/stubs/expected calls - inc_list_double = double('inc-list-double') - expect(@file_path_utils).to receive(:form_preprocessed_includes_list_filepath).with('some_source_file.c').and_return(inc_list_double) - expect(@task_invoker).to receive(:invoke_test_shallow_include_lists).with( [inc_list_double] ) - # execute method - subject.invoke_shallow_includes_list('some_source_file.c') - # validate results - end - end + #TODO REWRITE TESTS FOR THIS MODULE: + # context 'invoke_shallow_includes_list' do + # it 'should invoke the rake task which will build included files' do + # # create test state/variables + # # mocks/stubs/expected calls + # inc_list_double = double('inc-list-double') + # expect(@file_path_utils).to receive(:form_preprocessed_includes_list_filepath).with('some_source_file.c').and_return(inc_list_double) + # expect(@task_invoker).to receive(:invoke_test_shallow_include_lists).with( [inc_list_double] ) + # # execute method + # subject.invoke_shallow_includes_list('some_source_file.c') + # # validate results + # end + # end - context 'form_shallow_dependencies_rule' do - it 'should return an annotated dependency rule generated by the preprocessor' do - # create test state/variables - # mocks/stubs/expected calls - expect(@file_path_utils).to receive(:form_temp_path).with('some_source_file.c','_').and_return('_some_source_file.c') - contents_double = double('contents-double') - expect(@file_wrapper).to receive(:read).with('some_source_file.c').and_return(contents_double) - expect(contents_double).to receive(:valid_encoding?).and_return(true) - expect(contents_double).to receive(:gsub!).with(/^\s*#include\s+[\"<]\s*(\S+)\s*[\">]/, "#include \"\\1\"\n#include \"@@@@\\1\"") - expect(contents_double).to receive(:gsub!).with(/^\s*TEST_FILE\(\s*\"\s*(\S+)\s*\"\s*\)/, "#include \"\\1\"\n#include \"@@@@\\1\"") - expect(@file_wrapper).to receive(:write).with('_some_source_file.c', contents_double) - expect(@configurator).to receive(:tools_test_includes_preprocessor).and_return('cpp') - command_double = double('command-double') - expect(@tool_executor).to receive(:build_command_line).with('cpp', [], '_some_source_file.c').and_return(command_double) - expect(command_double).to receive(:[]).with(:line).and_return('cpp') - expect(command_double).to receive(:[]).with(:options).and_return(['arg1','arg2']) - output_double = double('output-double') - expect(@tool_executor).to receive(:exec).with('cpp',['arg1','arg2']).and_return(output_double) - expect(output_double).to receive(:[]).with(:output).and_return('make-rule').twice() - # execute method - results = subject.form_shallow_dependencies_rule('some_source_file.c') - # validate results - expect(results).to eq 'make-rule' - end - end + # context 'form_shallow_dependencies_rule' do + # it 'should return an annotated dependency rule generated by the preprocessor' do + # # create test state/variables + # # mocks/stubs/expected calls + # expect(@file_path_utils).to receive(:form_temp_path).with('some_source_file.c','_').and_return('_some_source_file.c') + # contents_double = double('contents-double') + # expect(@file_wrapper).to receive(:read).with('some_source_file.c').and_return(contents_double) + # expect(contents_double).to receive(:valid_encoding?).and_return(true) + # expect(contents_double).to receive(:gsub!).with(/^\s*#include\s+[\"<]\s*(\S+)\s*[\">]/, "#include \"\\1\"\n#include \"@@@@\\1\"") + # expect(contents_double).to receive(:gsub!).with(/^\s*TEST_FILE\(\s*\"\s*(\S+)\s*\"\s*\)/, "#include \"\\1\"\n#include \"@@@@\\1\"") + # expect(@file_wrapper).to receive(:write).with('_some_source_file.c', contents_double) + # expect(@configurator).to receive(:tools_test_includes_preprocessor).and_return('cpp') + # command_double = double('command-double') + # expect(@tool_executor).to receive(:build_command_line).with('cpp', [], '_some_source_file.c').and_return(command_double) + # expect(command_double).to receive(:[]).with(:line).and_return('cpp') + # expect(command_double).to receive(:[]).with(:options).and_return(['arg1','arg2']) + # output_double = double('output-double') + # expect(@tool_executor).to receive(:exec).with('cpp',['arg1','arg2']).and_return(output_double) + # expect(output_double).to receive(:[]).with(:output).and_return('make-rule').twice() + # # execute method + # results = subject.form_shallow_dependencies_rule('some_source_file.c') + # # validate results + # expect(results).to eq 'make-rule' + # end + # end - context 'extract_includes_helper' do - it 'should return the list of direct dependencies for the given test file' do - # create test state/variables - # mocks/stubs/expected calls - expect(@configurator).to receive(:extension_header).and_return('.h') - expect(@configurator).to receive(:extension_source).and_return('.c') - expect(@configurator).to receive(:project_config_hash).and_return( {:cmock_mock_prefix => 'mock_'}) - expect(@configurator).to receive(:tools_test_includes_preprocessor) - expect(@configurator).to receive(:project_config_hash).and_return({ }) - expect(@file_path_utils).to receive(:form_temp_path).and_return("/_dummy_file.c") - expect(@file_wrapper).to receive(:read).and_return("") - expect(@file_wrapper).to receive(:write) - expect(@tool_executor).to receive(:build_command_line).and_return({:line => "", :options => ""}) - expect(@tool_executor).to receive(:exec).and_return({ :output => %q{ - _test_DUMMY.o: Build/temp/_test_DUMMY.c \ - source/new_some_header1.h \ - source/some_header1.h \ - source/some_lib/some_header2.h \ - source/some_other_lib/some_header2.h \ - source/DUMMY.c \ - @@@@new_some_header1.h \ - @@@@some_header1.h \ - @@@@some_lib/some_header2.h \ - @@@@some_other_lib/some_header2.h \ - @@@@source/DUMMY.c - }}) - # execute method - results = subject.extract_includes_helper("/dummy_file_1.c", [], [], []) - # validate results - expect(results).to eq [ - [ 'source/new_some_header1.h', - 'source/some_header1.h', - 'source/some_lib/some_header2.h', - 'source/some_other_lib/some_header2.h', - 'source/DUMMY.c'], - [], [] - ] - end + # context 'extract_includes_helper' do + # it 'should return the list of direct dependencies for the given test file' do + # # create test state/variables + # # mocks/stubs/expected calls + # expect(@configurator).to receive(:extension_header).and_return('.h') + # expect(@configurator).to receive(:extension_source).and_return('.c') + # expect(@configurator).to receive(:project_config_hash).and_return( {:cmock_mock_prefix => 'mock_'}) + # expect(@configurator).to receive(:tools_test_includes_preprocessor) + # expect(@configurator).to receive(:project_config_hash).and_return({ }) + # expect(@file_path_utils).to receive(:form_temp_path).and_return("/_dummy_file.c") + # expect(@file_wrapper).to receive(:read).and_return("") + # expect(@file_wrapper).to receive(:write) + # expect(@tool_executor).to receive(:build_command_line).and_return({:line => "", :options => ""}) + # expect(@tool_executor).to receive(:exec).and_return({ :output => %q{ + # _test_DUMMY.o: Build/temp/_test_DUMMY.c \ + # source/new_some_header1.h \ + # source/some_header1.h \ + # source/some_lib/some_header2.h \ + # source/some_other_lib/some_header2.h \ + # source/DUMMY.c \ + # @@@@new_some_header1.h \ + # @@@@some_header1.h \ + # @@@@some_lib/some_header2.h \ + # @@@@some_other_lib/some_header2.h \ + # @@@@source/DUMMY.c + # }}) + # # execute method + # results = subject.extract_includes_helper("/dummy_file_1.c", [], [], []) + # # validate results + # expect(results).to eq [ + # [ 'source/new_some_header1.h', + # 'source/some_header1.h', + # 'source/some_lib/some_header2.h', + # 'source/some_other_lib/some_header2.h', + # 'source/DUMMY.c'], + # [], [] + # ] + # end - it 'should correctly handle path separators' do - # create test state/variables - # mocks/stubs/expected calls - expect(@configurator).to receive(:extension_header).and_return('.h') - expect(@configurator).to receive(:extension_source).and_return('.c') - expect(@configurator).to receive(:project_config_hash).and_return( {:cmock_mock_prefix => 'mock_'}) - expect(@configurator).to receive(:tools_test_includes_preprocessor) - expect(@configurator).to receive(:project_config_hash).and_return({ }) - expect(@file_path_utils).to receive(:form_temp_path).and_return("/_dummy_file.c") - expect(@file_wrapper).to receive(:read).and_return("") - expect(@file_wrapper).to receive(:write) - expect(@tool_executor).to receive(:build_command_line).and_return({:line => "", :options => ""}) - expect(@tool_executor).to receive(:exec).and_return({ :output => %q{ - _test_DUMMY.o: Build/temp/_test_DUMMY.c \ - source\some_header1.h \ - source\some_lib\some_header2.h \ - source\some_lib1\some_lib\some_header2.h \ - source\some_other_lib\some_header2.h \ - @@@@some_header1.h \ - @@@@some_lib/some_header2.h \ - @@@@some_lib1/some_lib/some_header2.h \ - @@@@some_other_lib/some_header2.h - }}) - # execute method - results = subject.extract_includes_helper("/dummy_file_2.c", [], [], []) - # validate results - expect(results).to eq [ - ['source/some_header1.h', - 'source/some_lib/some_header2.h', - 'source/some_lib1/some_lib/some_header2.h', - 'source/some_other_lib/some_header2.h'], - [], [] - ] - end + # it 'should correctly handle path separators' do + # # create test state/variables + # # mocks/stubs/expected calls + # expect(@configurator).to receive(:extension_header).and_return('.h') + # expect(@configurator).to receive(:extension_source).and_return('.c') + # expect(@configurator).to receive(:project_config_hash).and_return( {:cmock_mock_prefix => 'mock_'}) + # expect(@configurator).to receive(:tools_test_includes_preprocessor) + # expect(@configurator).to receive(:project_config_hash).and_return({ }) + # expect(@file_path_utils).to receive(:form_temp_path).and_return("/_dummy_file.c") + # expect(@file_wrapper).to receive(:read).and_return("") + # expect(@file_wrapper).to receive(:write) + # expect(@tool_executor).to receive(:build_command_line).and_return({:line => "", :options => ""}) + # expect(@tool_executor).to receive(:exec).and_return({ :output => %q{ + # _test_DUMMY.o: Build/temp/_test_DUMMY.c \ + # source\some_header1.h \ + # source\some_lib\some_header2.h \ + # source\some_lib1\some_lib\some_header2.h \ + # source\some_other_lib\some_header2.h \ + # @@@@some_header1.h \ + # @@@@some_lib/some_header2.h \ + # @@@@some_lib1/some_lib/some_header2.h \ + # @@@@some_other_lib/some_header2.h + # }}) + # # execute method + # results = subject.extract_includes_helper("/dummy_file_2.c", [], [], []) + # # validate results + # expect(results).to eq [ + # ['source/some_header1.h', + # 'source/some_lib/some_header2.h', + # 'source/some_lib1/some_lib/some_header2.h', + # 'source/some_other_lib/some_header2.h'], + # [], [] + # ] + # end - it 'exclude annotated headers with no matching "real" header' do - # create test state/variables - # mocks/stubs/expected calls - expect(@configurator).to receive(:extension_header).and_return('.h') - expect(@configurator).to receive(:extension_source).and_return('.c') - expect(@configurator).to receive(:project_config_hash).and_return( {:cmock_mock_prefix => 'mock_'}) - expect(@configurator).to receive(:tools_test_includes_preprocessor) - expect(@configurator).to receive(:project_config_hash).and_return({ }) - expect(@file_path_utils).to receive(:form_temp_path).and_return("/_dummy_file.c") - expect(@file_wrapper).to receive(:read).and_return("") - expect(@file_wrapper).to receive(:write) - expect(@tool_executor).to receive(:build_command_line).and_return({:line => "", :options => ""}) - expect(@tool_executor).to receive(:exec).and_return({ :output => %q{ - _test_DUMMY.o: Build/temp/_test_DUMMY.c \ - source/some_header1.h \ - @@@@some_header1.h \ - @@@@some_lib/some_header2.h - }}) - # execute method - results = subject.extract_includes_helper("/dummy_file_3.c", [], [], []) - # validate results - expect(results).to eq [ - ['source/some_header1.h'], - [], [] - ] - end + # it 'exclude annotated headers with no matching "real" header' do + # # create test state/variables + # # mocks/stubs/expected calls + # expect(@configurator).to receive(:extension_header).and_return('.h') + # expect(@configurator).to receive(:extension_source).and_return('.c') + # expect(@configurator).to receive(:project_config_hash).and_return( {:cmock_mock_prefix => 'mock_'}) + # expect(@configurator).to receive(:tools_test_includes_preprocessor) + # expect(@configurator).to receive(:project_config_hash).and_return({ }) + # expect(@file_path_utils).to receive(:form_temp_path).and_return("/_dummy_file.c") + # expect(@file_wrapper).to receive(:read).and_return("") + # expect(@file_wrapper).to receive(:write) + # expect(@tool_executor).to receive(:build_command_line).and_return({:line => "", :options => ""}) + # expect(@tool_executor).to receive(:exec).and_return({ :output => %q{ + # _test_DUMMY.o: Build/temp/_test_DUMMY.c \ + # source/some_header1.h \ + # @@@@some_header1.h \ + # @@@@some_lib/some_header2.h + # }}) + # # execute method + # results = subject.extract_includes_helper("/dummy_file_3.c", [], [], []) + # # validate results + # expect(results).to eq [ + # ['source/some_header1.h'], + # [], [] + # ] + # end - it 'should correctly filter secondary dependencies' do - # create test state/variables - # mocks/stubs/expected calls - expect(@configurator).to receive(:extension_header).and_return('.h') - expect(@configurator).to receive(:extension_source).and_return('.c') - expect(@configurator).to receive(:project_config_hash).and_return( {:cmock_mock_prefix => 'mock_'}) - expect(@configurator).to receive(:tools_test_includes_preprocessor) - expect(@configurator).to receive(:project_config_hash).and_return({ }) - expect(@file_path_utils).to receive(:form_temp_path).and_return("/_dummy_file.c") - expect(@file_wrapper).to receive(:read).and_return("") - expect(@file_wrapper).to receive(:write) - expect(@tool_executor).to receive(:build_command_line).and_return({:line => "", :options => ""}) - expect(@tool_executor).to receive(:exec).and_return({ :output => %q{ - _test_DUMMY.o: Build/temp/_test_DUMMY.c \ - source\some_header1.h \ - source\some_lib\some_header2.h \ - source\some_lib1\some_lib\some_header2.h \ - source\some_other_lib\some_header2.h \ - source\some_other_lib\another.h \ - @@@@some_header1.h \ - @@@@some_lib/some_header2.h \ - @@@@lib/some_header2.h \ - @@@@some_other_lib/some_header2.h - }}) - # execute method - results = subject.extract_includes_helper("/dummy_file_4.c", [], [], []) - # validate results - expect(results).to eq [ - ['source/some_header1.h', - 'source/some_lib/some_header2.h', - 'source/some_other_lib/some_header2.h'], - [], [] - ] - end + # it 'should correctly filter secondary dependencies' do + # # create test state/variables + # # mocks/stubs/expected calls + # expect(@configurator).to receive(:extension_header).and_return('.h') + # expect(@configurator).to receive(:extension_source).and_return('.c') + # expect(@configurator).to receive(:project_config_hash).and_return( {:cmock_mock_prefix => 'mock_'}) + # expect(@configurator).to receive(:tools_test_includes_preprocessor) + # expect(@configurator).to receive(:project_config_hash).and_return({ }) + # expect(@file_path_utils).to receive(:form_temp_path).and_return("/_dummy_file.c") + # expect(@file_wrapper).to receive(:read).and_return("") + # expect(@file_wrapper).to receive(:write) + # expect(@tool_executor).to receive(:build_command_line).and_return({:line => "", :options => ""}) + # expect(@tool_executor).to receive(:exec).and_return({ :output => %q{ + # _test_DUMMY.o: Build/temp/_test_DUMMY.c \ + # source\some_header1.h \ + # source\some_lib\some_header2.h \ + # source\some_lib1\some_lib\some_header2.h \ + # source\some_other_lib\some_header2.h \ + # source\some_other_lib\another.h \ + # @@@@some_header1.h \ + # @@@@some_lib/some_header2.h \ + # @@@@lib/some_header2.h \ + # @@@@some_other_lib/some_header2.h + # }}) + # # execute method + # results = subject.extract_includes_helper("/dummy_file_4.c", [], [], []) + # # validate results + # expect(results).to eq [ + # ['source/some_header1.h', + # 'source/some_lib/some_header2.h', + # 'source/some_other_lib/some_header2.h'], + # [], [] + # ] + # end - it 'should return the list of direct dependencies for the given source file' do - # create test state/variables - # mocks/stubs/expected calls - expect(@configurator).to receive(:extension_header).and_return('.h') - expect(@configurator).to receive(:extension_source).and_return('.c') - expect(@configurator).to receive(:project_config_hash).and_return( {:cmock_mock_prefix => 'mock_'}) - expect(@configurator).to receive(:tools_test_includes_preprocessor) - expect(@configurator).to receive(:project_config_hash).and_return({ }) - expect(@file_path_utils).to receive(:form_temp_path).and_return("/_dummy_file.c") - expect(@file_wrapper).to receive(:read).and_return("") - expect(@file_wrapper).to receive(:write) - expect(@tool_executor).to receive(:build_command_line).and_return({:line => "", :options => ""}) - expect(@tool_executor).to receive(:exec).and_return({ :output => %q{ - _DUMMY.o: Build/temp/_DUMMY.c \ - source/new_some_header1_DUMMY.h \ - source/some_header1__DUMMY.h \ - @@@@new_some_header1_DUMMY.h \ - @@@@some_header1__DUMMY.h \ - }}) - # execute method - results = subject.extract_includes_helper("/dummy_file_5.c", [], [], []) - # validate results - expect(results).to eq [ - [ 'source/new_some_header1_DUMMY.h', - 'source/some_header1__DUMMY.h'], - [], [] - ] - end - end + # it 'should return the list of direct dependencies for the given source file' do + # # create test state/variables + # # mocks/stubs/expected calls + # expect(@configurator).to receive(:extension_header).and_return('.h') + # expect(@configurator).to receive(:extension_source).and_return('.c') + # expect(@configurator).to receive(:project_config_hash).and_return( {:cmock_mock_prefix => 'mock_'}) + # expect(@configurator).to receive(:tools_test_includes_preprocessor) + # expect(@configurator).to receive(:project_config_hash).and_return({ }) + # expect(@file_path_utils).to receive(:form_temp_path).and_return("/_dummy_file.c") + # expect(@file_wrapper).to receive(:read).and_return("") + # expect(@file_wrapper).to receive(:write) + # expect(@tool_executor).to receive(:build_command_line).and_return({:line => "", :options => ""}) + # expect(@tool_executor).to receive(:exec).and_return({ :output => %q{ + # _DUMMY.o: Build/temp/_DUMMY.c \ + # source/new_some_header1_DUMMY.h \ + # source/some_header1__DUMMY.h \ + # @@@@new_some_header1_DUMMY.h \ + # @@@@some_header1__DUMMY.h \ + # }}) + # # execute method + # results = subject.extract_includes_helper("/dummy_file_5.c", [], [], []) + # # validate results + # expect(results).to eq [ + # [ 'source/new_some_header1_DUMMY.h', + # 'source/some_header1__DUMMY.h'], + # [], [] + # ] + # end + # end - context 'extract_includes' do - it 'should correctly filter auto link deep dependencies with mocks' do - # create test state/variables - # mocks/stubs/expected calls - expect(@configurator).to receive(:project_config_hash).and_return({:cmock_mock_prefix => 'mock_', - :project_auto_link_deep_dependencies => true, - :collection_paths_include => []}).at_least(:once) - expect(@configurator).to receive(:extension_header).and_return('.h').exactly(3).times - expect(@configurator).to receive(:extension_source).and_return('.c').exactly(3).times - expect(@configurator).to receive(:tools_test_includes_preprocessor).exactly(3).times - expect(@file_wrapper).to receive(:read).and_return("").exactly(3).times - expect(@file_wrapper).to receive(:write).exactly(3).times - expect(@file_finder).to receive(:find_compilation_input_file).and_return("assets\example_file.c") - expect(@tool_executor).to receive(:build_command_line).and_return({:line => "", :options => ""}).exactly(3).times - expect(@file_path_utils).to receive(:form_temp_path).and_return("_test_DUMMY.c") - expect(@file_path_utils).to receive(:form_temp_path).and_return("assets\_example_file.h") - expect(@file_path_utils).to receive(:form_temp_path).and_return("assets\_example_file.c") - expect(@tool_executor).to receive(:exec).and_return({ :output => %q{ - _test_DUMMY.o: build/temp/_test_DUMMY.c \ - assets\example_file.h \ - build\mocks\mock_dependency.h \ - @@@@assets/example_file.h \ - @@@@build/mocks/mock_dependency.h - }}) - expect(@tool_executor).to receive(:exec).and_return({ :output => %q{ - assets\_example_file.o: assets\_example_file.h - }}) - expect(@tool_executor).to receive(:exec).and_return({ :output => %q{ - assets\_example_file.o: assets\_example_file.c \ - source\dependency.h \ - @@@@source/dependency.h - }}) - # execute method - results = subject.extract_includes("test_dummy.c") - # validate results - expect(results).to eq [ - 'assets/example_file.h', - 'build/mocks/mock_dependency.h'] - end - end + # context 'extract_includes' do + # it 'should correctly filter auto link deep dependencies with mocks' do + # # create test state/variables + # # mocks/stubs/expected calls + # expect(@configurator).to receive(:project_config_hash).and_return({:cmock_mock_prefix => 'mock_', + # :project_auto_link_deep_dependencies => true, + # :collection_paths_include => []}).at_least(:once) + # expect(@configurator).to receive(:extension_header).and_return('.h').exactly(3).times + # expect(@configurator).to receive(:extension_source).and_return('.c').exactly(3).times + # expect(@configurator).to receive(:tools_test_includes_preprocessor).exactly(3).times + # expect(@file_wrapper).to receive(:read).and_return("").exactly(3).times + # expect(@file_wrapper).to receive(:write).exactly(3).times + # expect(@file_finder).to receive(:find_build_input_file).and_return("assets\example_file.c") + # expect(@tool_executor).to receive(:build_command_line).and_return({:line => "", :options => ""}).exactly(3).times + # expect(@file_path_utils).to receive(:form_temp_path).and_return("_test_DUMMY.c") + # expect(@file_path_utils).to receive(:form_temp_path).and_return("assets\_example_file.h") + # expect(@file_path_utils).to receive(:form_temp_path).and_return("assets\_example_file.c") + # expect(@tool_executor).to receive(:exec).and_return({ :output => %q{ + # _test_DUMMY.o: build/temp/_test_DUMMY.c \ + # assets\example_file.h \ + # build\mocks\mock_dependency.h \ + # @@@@assets/example_file.h \ + # @@@@build/mocks/mock_dependency.h + # }}) + # expect(@tool_executor).to receive(:exec).and_return({ :output => %q{ + # assets\_example_file.o: assets\_example_file.h + # }}) + # expect(@tool_executor).to receive(:exec).and_return({ :output => %q{ + # assets\_example_file.o: assets\_example_file.c \ + # source\dependency.h \ + # @@@@source/dependency.h + # }}) + # # execute method + # results = subject.extract_includes("test_dummy.c") + # # validate results + # expect(results).to eq [ + # 'assets/example_file.h', + # 'build/mocks/mock_dependency.h'] + # end + # end - context 'invoke_shallow_includes_list' do - it 'should invoke the rake task which will build included files' do - # create test state/variables - # mocks/stubs/expected calls - expect(@yaml_wrapper).to receive(:dump).with('some_source_file.c', []) - # execute method - subject.write_shallow_includes_list('some_source_file.c', []) - # validate results - end - end + # context 'invoke_shallow_includes_list' do + # it 'should invoke the rake task which will build included files' do + # # create test state/variables + # # mocks/stubs/expected calls + # expect(@yaml_wrapper).to receive(:dump).with('some_source_file.c', []) + # # execute method + # subject.write_shallow_includes_list('some_source_file.c', []) + # # validate results + # end + # end end diff --git a/spec/reportinator_spec.rb b/spec/reportinator_spec.rb index 7290ee18a..c7107fb4f 100644 --- a/spec/reportinator_spec.rb +++ b/spec/reportinator_spec.rb @@ -1,3 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'spec_helper' require 'ceedling/reportinator' diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 348dee569..317dcb5c6 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'require_all' require 'constructor' @@ -9,6 +16,7 @@ $: << File.join(here, '../lib') $: << File.join(here, '../vendor/cmock/lib') +$: << File.join(here, '../vendor/unity/auto') support_files = File.join(File.dirname(__FILE__), "support/**/*.rb") require_all Dir.glob(support_files, File::FNM_PATHNAME) @@ -19,8 +27,5 @@ # # ceedling_files = File.join(File.dirname(__FILE__), '../lib/**/*.rb') # # require_all Dir.glob(ceedling_files, File::FNM_PATHNAME) -require 'ceedling/preprocessinator_extractor' -require 'ceedling/configurator_builder' -require 'ceedling/configurator' diff --git a/spec/spec_system_helper.rb b/spec/spec_system_helper.rb index 590a12248..4278f1129 100644 --- a/spec/spec_system_helper.rb +++ b/spec/spec_system_helper.rb @@ -1,22 +1,15 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'fileutils' require 'tmpdir' require 'ceedling/yaml_wrapper' require 'spec_helper' -if Gem.ruby_version >= Gem::Version.new("2.5.0") - Modulegenerator = Struct.new(:project_root, :source_root, :inc_root, :test_root, keyword_init: true) do - def initialize(project_root: "./", source_root: "src/", inc_root: "src/", test_root: "test/") - super - end - end -else - Modulegenerator = Struct.new(:project_root, :source_root, :inc_root, :test_root) do - def initialize(project_root: "./", source_root: "src/", inc_root: "src/", test_root: "test/") - super(project_root, source_root, inc_root, test_root) - end - end -end - def test_asset_path(asset_file_name) File.join(File.dirname(__FILE__), '..', 'assets', asset_file_name) end @@ -29,13 +22,6 @@ def convert_slashes(path) end end -def add_project_settings(project_file_path, settings) - yaml_wrapper = YamlWrapper.new - project_hash = yaml_wrapper.load(project_file_path) - project_hash.deep_merge(settings) - yaml_wrapper.dump(project_file_path, project_hash) -end - class GemDirLayout attr_reader :gem_dir_base_name @@ -66,11 +52,17 @@ def done! end def deploy_gem - git_repo = File.expand_path(File.join(File.dirname(__FILE__), '..')) - bundler_gem_file_data = [ %Q{source "http://rubygems.org/"}, - %Q{gem "rake"}, - %Q{gem "ceedling", :path => '#{git_repo}'} - ].join("\n") + git_repo = File.expand_path( File.join( File.dirname( __FILE__ ), '..') ) + bundler_gem_file_data = [ + %Q{source "http://rubygems.org/"}, + %Q{gem "rake"}, + %Q{gem "constructor"}, + %Q{gem "diy"}, + %Q{gem "thor"}, + %Q{gem "deep_merge"}, + %Q{gem "unicode-display_width"}, + %Q{gem "ceedling", :path => '#{git_repo}'} + ].join("\n") File.open(File.join(@dir, "Gemfile"), "w+") do |f| f.write(bundler_gem_file_data) @@ -87,6 +79,7 @@ def deploy_gem end end end + end # Does a few things: @@ -105,16 +98,19 @@ def with_context ENV['RUBYLIB'] = @gem.lib ENV['RUBYPATH'] = @gem.bin + ENV['LANG'] = 'en_US.UTF-8' + ENV['LANGUAGE'] = 'en_US.UTF-8' + ENV['LC_ALL'] = 'en_US.UTF-8' + yield end end end + ############################################################ + # Functions for manipulating environment settings during tests: def backup_env - # Force a deep clone. Hacktacular, but works. - - yaml_wrapper = YamlWrapper.new - @_env = yaml_wrapper.load_string(ENV.to_hash.to_yaml) + @_env = ENV.to_hash end def reduce_env(destroy_keys=[]) @@ -128,6 +124,10 @@ def constrain_env def restore_env if @_env + # delete environment variables we've added since we started + ENV.to_hash.each_pair {|k,v| ENV.delete(k) unless @_env.include?(k) } + + # restore environment variables we've modified since we started @_env.each_pair {|k,v| ENV[k] = v} else raise InvalidBackupEnv.new @@ -143,9 +143,58 @@ def with_constrained_env restore_env end end + + ############################################################ + # Functions for manipulating project.yml files during tests: + def merge_project_yml_for_test(settings, show_final=false) + yaml_wrapper = YamlWrapper.new + project_hash = yaml_wrapper.load('project.yml') + project_hash.deep_merge!(settings) + puts "\n\n#{project_hash.to_yaml}\n\n" if show_final + yaml_wrapper.dump('project.yml', project_hash) + end + + def append_project_yml_for_test(new_args) + fake_prj_yml= "#{File.read('project.yml')}\n#{new_args}" + File.write('project.yml', fake_prj_yml, mode: 'w') + end + + def uncomment_project_yml_option_for_test(option) + fake_prj_yml= File.read('project.yml').gsub(/\##{option}/,option) + File.write('project.yml', fake_prj_yml, mode: 'w') + end + + def comment_project_yml_option_for_test(option) + fake_prj_yml= File.read('project.yml').gsub(/#{option}/,"##{option}") + File.write('project.yml', fake_prj_yml, mode: 'w') + end + ############################################################ end module CeedlingTestCases + def can_report_version_no_git_commit_sha + @c.with_context do + # Version without Git commit short SHA file in project + output = `bundle exec ruby -S ceedling version 2>&1` + expect($?.exitstatus).to match(0) + expect(output).to match(/Ceedling => \d\.\d\.\d\n/) + end + end + + def can_report_version_with_git_commit_sha + # Version with Git commit short SHA file in root of project + # Creating the commit file before building + installing the gem simulates the CI process + File.open('GIT_COMMIT_SHA', 'w') do |f| + f << '---{-@' + end + + @c.with_context do + output = `bundle exec ruby -S ceedling version 2>&1` + expect($?.exitstatus).to match(0) + expect(output).to match(/Ceedling => \d\.\d\.\d----{-@\n/) + end + end + def can_create_projects @c.with_context do Dir.chdir @proj_name do @@ -153,15 +202,15 @@ def can_create_projects expect(File.exist?("src")).to eq true expect(File.exist?("test")).to eq true expect(File.exist?("test/support")).to eq true - expect(File.exist?("test/support/.gitkeep")).to eq true end end end - def has_an_ignore + def has_git_support @c.with_context do Dir.chdir @proj_name do expect(File.exist?(".gitignore")).to eq true + expect(File.exist?("test/support/.gitkeep")).to eq true end end end @@ -170,7 +219,7 @@ def can_upgrade_projects @c.with_context do output = `bundle exec ruby -S ceedling upgrade #{@proj_name} 2>&1` expect($?.exitstatus).to match(0) - expect(output).to match(/upgraded!/i) + expect(output).to match(/Upgraded/i) Dir.chdir @proj_name do expect(File.exist?("project.yml")).to eq true expect(File.exist?("src")).to eq true @@ -180,7 +229,7 @@ def can_upgrade_projects end end - def can_upgrade_projects_even_if_test_support_folder_does_not_exists + def can_upgrade_projects_even_if_test_support_folder_does_not_exist @c.with_context do output = `bundle exec ruby -S ceedling upgrade #{@proj_name} 2>&1` FileUtils.rm_rf("#{@proj_name}/test/support") @@ -192,7 +241,7 @@ def can_upgrade_projects_even_if_test_support_folder_does_not_exists File.write("#{@proj_name}/project.yml", updated_prj_yml.join("\n"), mode: 'w') expect($?.exitstatus).to match(0) - expect(output).to match(/upgraded!/i) + expect(output).to match(/Upgraded/i) Dir.chdir @proj_name do expect(File.exist?("project.yml")).to eq true expect(File.exist?("src")).to eq true @@ -206,7 +255,7 @@ def cannot_upgrade_non_existing_project @c.with_context do output = `bundle exec ruby -S ceedling upgrade #{@proj_name} 2>&1` expect($?.exitstatus).to match(1) - expect(output).to match(/rescue in upgrade/i) + expect(output).to match(/Could not find an existing project/i) end end @@ -295,53 +344,52 @@ def can_test_projects_with_success_default end end - def can_test_projects_with_unity_exec_time + def can_test_projects_with_named_verbosity @c.with_context do Dir.chdir @proj_name do FileUtils.cp test_asset_path("example_file.h"), 'src/' FileUtils.cp test_asset_path("example_file.c"), 'src/' FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/' - settings = { :unity => { :defines => [ "UNITY_INCLUDE_EXEC_TIME" ] } } - add_project_settings("project.yml", settings) - output = `bundle exec ruby -S ceedling 2>&1` + output = `bundle exec ruby -S ceedling --verbosity=obnoxious 2>&1` expect($?.exitstatus).to match(0) # Since a test either pass or are ignored, we return success here expect(output).to match(/TESTED:\s+\d/) expect(output).to match(/PASSED:\s+\d/) expect(output).to match(/FAILED:\s+\d/) expect(output).to match(/IGNORED:\s+\d/) + + expect(output).to match (/:post_test_fixture_execute/) end end end - def can_test_projects_with_test_and_vendor_defines_with_success + def can_test_projects_with_numerical_verbosity @c.with_context do Dir.chdir @proj_name do FileUtils.cp test_asset_path("example_file.h"), 'src/' FileUtils.cp test_asset_path("example_file.c"), 'src/' - FileUtils.cp test_asset_path("test_example_file_unity_printf.c"), 'test/' - settings = { :unity => { :defines => [ "UNITY_INCLUDE_PRINT_FORMATTED" ] }, - :defines => { :test_example_file_unity_printf => [ "TEST" ] } - } - add_project_settings("project.yml", settings) + FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/' - output = `bundle exec ruby -S ceedling 2>&1` + output = `bundle exec ruby -S ceedling -v=4 2>&1` expect($?.exitstatus).to match(0) # Since a test either pass or are ignored, we return success here expect(output).to match(/TESTED:\s+\d/) expect(output).to match(/PASSED:\s+\d/) expect(output).to match(/FAILED:\s+\d/) expect(output).to match(/IGNORED:\s+\d/) + + expect(output).to match (/:post_test_fixture_execute/) end end end - def can_test_projects_with_enabled_auto_link_deep_deependency_with_success + def can_test_projects_with_unity_exec_time @c.with_context do Dir.chdir @proj_name do - FileUtils.copy_entry test_asset_path("auto_link_deep_dependencies/src/"), 'src/' - FileUtils.cp_r test_asset_path("auto_link_deep_dependencies/test/."), 'test/' - settings = { :project => { :auto_link_deep_dependencies => true } } - add_project_settings("project.yml", settings) + FileUtils.cp test_asset_path("example_file.h"), 'src/' + FileUtils.cp test_asset_path("example_file.c"), 'src/' + FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/' + settings = { :unity => { :defines => [ "UNITY_INCLUDE_EXEC_TIME" ] } } + @c.merge_project_yml_for_test(settings) output = `bundle exec ruby -S ceedling 2>&1` expect($?.exitstatus).to match(0) # Since a test either pass or are ignored, we return success here @@ -353,14 +401,16 @@ def can_test_projects_with_enabled_auto_link_deep_deependency_with_success end end - def can_test_projects_with_enabled_preprocessor_directives_with_success + def can_test_projects_with_test_and_vendor_defines_with_success @c.with_context do Dir.chdir @proj_name do - FileUtils.cp test_asset_path("test_example_with_parameterized_tests.c"), 'test/' - settings = { :project => { :use_preprocessor_directives => true }, - :unity => { :use_param_tests => true } + FileUtils.cp test_asset_path("example_file.h"), 'src/' + FileUtils.cp test_asset_path("example_file.c"), 'src/' + FileUtils.cp test_asset_path("test_example_file_unity_printf.c"), 'test/' + settings = { :unity => { :defines => [ "UNITY_INCLUDE_PRINT_FORMATTED" ] }, + :defines => { :test => { :example_file_unity_printf => [ "TEST" ] } } } - add_project_settings("project.yml", settings) + @c.merge_project_yml_for_test(settings) output = `bundle exec ruby -S ceedling 2>&1` expect($?.exitstatus).to match(0) # Since a test either pass or are ignored, we return success here @@ -377,11 +427,31 @@ def can_test_projects_with_test_name_replaced_defines_with_success Dir.chdir @proj_name do FileUtils.copy_entry test_asset_path("tests_with_defines/src/"), 'src/' FileUtils.cp_r test_asset_path("tests_with_defines/test/."), 'test/' - settings = { :defines => { :test => [ "STANDARD_CONFIG" ], - :test_adc_hardware_special => [ "TEST", "SPECIFIC_CONFIG" ] - } + settings = { :defines => { :test => { '*' => [ "TEST", "STANDARD_CONFIG" ], + 'test_adc_hardware_special.c' => [ "TEST", "SPECIFIC_CONFIG" ], + } } + } + @c.merge_project_yml_for_test(settings) + + output = `bundle exec ruby -S ceedling 2>&1` + expect($?.exitstatus).to match(0) # Since a test either passes or is ignored, we return success here + expect(output).to match(/TESTED:\s+\d/) + expect(output).to match(/PASSED:\s+\d/) + expect(output).to match(/FAILED:\s+\d/) + expect(output).to match(/IGNORED:\s+\d/) + end + end + end + + # Ceedling :use_test_preprocessor is disabled + def can_test_projects_unity_parameterized_test_cases_with_success + @c.with_context do + Dir.chdir @proj_name do + FileUtils.cp test_asset_path("test_example_with_parameterized_tests.c"), 'test/' + settings = { :project => { :use_test_preprocessor => :none }, + :unity => { :use_param_tests => true } } - add_project_settings("project.yml", settings) + @c.merge_project_yml_for_test(settings) output = `bundle exec ruby -S ceedling 2>&1` expect($?.exitstatus).to match(0) # Since a test either pass or are ignored, we return success here @@ -393,6 +463,113 @@ def can_test_projects_with_test_name_replaced_defines_with_success end end + def can_test_projects_with_preprocessing_for_test_files_symbols_undefined + @c.with_context do + Dir.chdir @proj_name do + FileUtils.cp test_asset_path("tests_with_preprocessing/test/test_adc_hardwareA.c"), 'test/' + FileUtils.cp test_asset_path("tests_with_preprocessing/src/adc_hardwareA.c"), 'src/' + FileUtils.cp test_asset_path("tests_with_preprocessing/src/adc_hardwareA.h"), 'src/' + FileUtils.cp test_asset_path("tests_with_preprocessing/src/adc_hardware_configuratorA.h"), 'src/' + # Rely on undefined symbols in our C files + # 2 enabled intentionally failing test cases (no mocks generated) + settings = { :project => { :use_test_preprocessor => :tests }, + } + @c.merge_project_yml_for_test(settings) + + output = `bundle exec ruby -S ceedling test:adc_hardwareA 2>&1` + expect($?.exitstatus).to match(1) # Intentional test failure in successful build + expect(output).to match(/TESTED:\s+2/) + expect(output).to match(/PASSED:\s+0/) + expect(output).to match(/FAILED:\s+2/) + end + end + end + + def can_test_projects_with_preprocessing_for_test_files_symbols_defined + @c.with_context do + Dir.chdir @proj_name do + FileUtils.cp test_asset_path("tests_with_preprocessing/test/test_adc_hardwareA.c"), 'test/' + FileUtils.cp test_asset_path("tests_with_preprocessing/src/adc_hardwareA.c"), 'src/' + FileUtils.cp test_asset_path("tests_with_preprocessing/src/adc_hardwareA.h"), 'src/' + FileUtils.cp test_asset_path("tests_with_preprocessing/src/adc_hardware_configuratorA.h"), 'src/' + # 1 enabled passing test case with 1 mock used + settings = { :project => { :use_test_preprocessor => :tests }, + :defines => { :test => ['PREPROCESSING_TESTS'] } + } + @c.merge_project_yml_for_test(settings) + + output = `bundle exec ruby -S ceedling test:adc_hardwareA 2>&1` + expect($?.exitstatus).to match(0) # Successful build and tests + expect(output).to match(/TESTED:\s+1/) + expect(output).to match(/PASSED:\s+1/) + expect(output).to match(/FAILED:\s+0/) + end + end + end + + def can_test_projects_with_preprocessing_for_mocks_success + @c.with_context do + Dir.chdir @proj_name do + FileUtils.cp test_asset_path("tests_with_preprocessing/test/test_adc_hardwareB.c"), 'test/' + FileUtils.cp test_asset_path("tests_with_preprocessing/src/adc_hardwareB.c"), 'src/' + FileUtils.cp test_asset_path("tests_with_preprocessing/src/adc_hardwareB.h"), 'src/' + FileUtils.cp test_asset_path("tests_with_preprocessing/src/adc_hardware_configuratorB.h"), 'src/' + # 1 test case with 1 mocked function + settings = { :project => { :use_test_preprocessor => :mocks }, + :defines => { :test => ['PREPROCESSING_MOCKS'] } + } + @c.merge_project_yml_for_test(settings) + + output = `bundle exec ruby -S ceedling test:adc_hardwareB 2>&1` + expect($?.exitstatus).to match(0) # Successful build and tests + expect(output).to match(/TESTED:\s+1/) + expect(output).to match(/PASSED:\s+1/) + expect(output).to match(/FAILED:\s+0/) + end + end + end + + def can_test_projects_with_preprocessing_for_mocks_intentional_build_failure + @c.with_context do + Dir.chdir @proj_name do + FileUtils.cp test_asset_path("tests_with_preprocessing/test/test_adc_hardwareB.c"), 'test/' + FileUtils.cp test_asset_path("tests_with_preprocessing/src/adc_hardwareB.c"), 'src/' + FileUtils.cp test_asset_path("tests_with_preprocessing/src/adc_hardwareB.h"), 'src/' + FileUtils.cp test_asset_path("tests_with_preprocessing/src/adc_hardware_configuratorB.h"), 'src/' + # 1 test case with a missing mocked function + settings = { :project => { :use_test_preprocessor => :mocks } + } + @c.merge_project_yml_for_test(settings) + + output = `bundle exec ruby -S ceedling test:adc_hardwareB 2>&1` + expect($?.exitstatus).to match(1) # Failing build because of missing mock + expect(output).to match(/(undeclared|undefined|implicit).+Adc_Reset/) + end + end + end + + def can_test_projects_with_preprocessing_all + @c.with_context do + Dir.chdir @proj_name do + FileUtils.cp test_asset_path("tests_with_preprocessing/test/test_adc_hardwareC.c"), 'test/' + FileUtils.cp test_asset_path("tests_with_preprocessing/src/adc_hardwareC.c"), 'src/' + FileUtils.cp test_asset_path("tests_with_preprocessing/src/adc_hardwareC.h"), 'src/' + FileUtils.cp test_asset_path("tests_with_preprocessing/src/adc_hardware_configuratorC.h"), 'src/' + # 1 test case using 1 mock + settings = { :project => { :use_test_preprocessor => :all }, + :defines => { :test => ['PREPROCESSING_TESTS', 'PREPROCESSING_MOCKS'] } + } + @c.merge_project_yml_for_test(settings) + + output = `bundle exec ruby -S ceedling test:adc_hardwareC 2>&1` + expect($?.exitstatus).to match(0) # Successful build and tests + expect(output).to match(/TESTED:\s+1/) + expect(output).to match(/PASSED:\s+1/) + expect(output).to match(/FAILED:\s+0/) + end + end + end + def can_test_projects_with_fail @c.with_context do Dir.chdir @proj_name do @@ -453,7 +630,7 @@ def can_test_projects_with_compile_error output = `bundle exec ruby -S ceedling test:all 2>&1` expect($?.exitstatus).to match(1) # Since a test explodes, we return error here - expect(output).to match(/ERROR: Ceedling Failed/) + expect(output).to match(/(?:ERROR: Ceedling Failed)|(?:Ceedling could not complete operations because of errors)/) end end end @@ -468,7 +645,7 @@ def can_test_projects_with_both_mock_and_real_header FileUtils.cp test_asset_path("test_example_file_with_mock.c"), 'test/' output = `bundle exec ruby -S ceedling 2>&1` - expect($?.exitstatus).to match(0) # Since a test either pass or are ignored, we return success here + expect($?.exitstatus).to match(0) # Since a test either passed or was ignored, we return success here expect(output).to match(/TESTED:\s+\d/) expect(output).to match(/PASSED:\s+\d/) expect(output).to match(/FAILED:\s+\d/) @@ -477,20 +654,22 @@ def can_test_projects_with_both_mock_and_real_header end end - def uses_raw_output_report_plugin + def uses_report_tests_raw_output_log_plugin @c.with_context do Dir.chdir @proj_name do FileUtils.cp test_asset_path("example_file.h"), 'src/' FileUtils.cp test_asset_path("example_file.c"), 'src/' FileUtils.cp test_asset_path("test_example_file_verbose.c"), 'test/' + @c.uncomment_project_yml_option_for_test('- report_tests_raw_output_log') + output = `bundle exec ruby -S ceedling test:all 2>&1` expect($?.exitstatus).to match(0) # Since a test either pass or are ignored, we return success here expect(output).to match(/TESTED:\s+\d/) expect(output).to match(/PASSED:\s+\d/) expect(output).to match(/FAILED:\s+\d/) expect(output).to match(/IGNORED:\s+\d/) - expect(File.exist?("build/artifacts/test/test_example_file_verbose.log")).to eq true + expect(File.exist?("build/artifacts/test/test_example_file_verbose.raw.log")).to eq true end end end @@ -515,82 +694,22 @@ def can_fetch_project_help expect($?.exitstatus).to match(0) expect(output).to match(/ceedling clean/i) expect(output).to match(/ceedling clobber/i) - expect(output).to match(/ceedling logging/i) expect(output).to match(/ceedling module:create/i) expect(output).to match(/ceedling module:destroy/i) expect(output).to match(/ceedling summary/i) expect(output).to match(/ceedling test:\*/i) expect(output).to match(/ceedling test:all/i) - expect(output).to match(/ceedling test:delta/i) expect(output).to match(/ceedling version/i) end end end - def can_use_the_module_plugin - @c.with_context do - Dir.chdir @proj_name do - output = `bundle exec ruby -S ceedling module:create[ponies]` - expect($?.exitstatus).to match(0) - expect(output).to match(/Generate Complete/i) - output = `bundle exec ruby -S ceedling test:all` - expect($?.exitstatus).to match(0) - expect(output).to match(/Need to Implement ponies/) - output = `bundle exec ruby -S ceedling module:destroy[ponies]` - expect($?.exitstatus).to match(0) - expect(output).to match(/Destroy Complete/i) - - self.can_use_the_module_plugin_path_extension - self.can_use_the_module_plugin_with_include_path - end - end - end - - def can_use_the_module_plugin_path_extension - @c.with_context do - Dir.chdir @proj_name do - # Module creation - output = `bundle exec ruby -S ceedling module:create[myPonies:ponies]` - expect($?.exitstatus).to match(0) - expect(output).to match(/Generate Complete/i) - expect(File.exist?("myPonies/src/ponies.c")).to eq true - expect(File.exist?("myPonies/src/ponies.h")).to eq true - expect(File.exist?("myPonies/test/test_ponies.c")).to eq true - - # add module path to project file - settings = { :paths => { :test => [ "myPonies/test" ], - :source => [ "myPonies/src" ] - } - } - add_project_settings("project.yml", settings) - - # See if ceedling finds the test in the subdir - output = `bundle exec ruby -S ceedling test:all` - expect($?.exitstatus).to match(0) - expect(output).to match(/Need to Implement ponies/) - - # Module destruction - output = `bundle exec ruby -S ceedling module:destroy[myPonies:ponies]` - expect($?.exitstatus).to match(0) - expect(output).to match(/Destroy Complete/i) - expect(File.exist?("myPonies/src/ponies.c")).to eq false - expect(File.exist?("myPonies/src/ponies.h")).to eq false - expect(File.exist?("myPonies/test/test_ponies.c")).to eq false - end - end - end - - def can_run_single_test_with_full_test_case_name_from_test_file_with_success_cmdline_args_are_enabled + def can_run_single_test_with_full_test_case_name_from_test_file_with_success @c.with_context do Dir.chdir @proj_name do FileUtils.cp test_asset_path("example_file.h"), 'src/' FileUtils.cp test_asset_path("example_file.c"), 'src/' FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/' - enable_unity_extra_args = "\n:test_runner:\n"\ - " :cmdline_args: true\n" - fake_prj_yml= File.read('project.yml').split("\n") - fake_prj_yml.insert(fake_prj_yml.length() -1, enable_unity_extra_args) - File.write('project.yml', fake_prj_yml.join("\n"), mode: 'w') output = `bundle exec ruby -S ceedling test:test_example_file_success --test_case=test_add_numbers_adds_numbers 2>&1` @@ -603,17 +722,12 @@ def can_run_single_test_with_full_test_case_name_from_test_file_with_success_cmd end end - def can_run_single_test_with_partiall_test_case_name_from_test_file_with_enabled_cmdline_args_success + def can_run_single_test_with_partial_test_case_name_from_test_file_with_success @c.with_context do Dir.chdir @proj_name do FileUtils.cp test_asset_path("example_file.h"), 'src/' FileUtils.cp test_asset_path("example_file.c"), 'src/' FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/' - enable_unity_extra_args = "\n:test_runner:\n"\ - " :cmdline_args: true\n" - fake_prj_yml= File.read('project.yml').split("\n") - fake_prj_yml.insert(fake_prj_yml.length() -1, enable_unity_extra_args) - File.write('project.yml', fake_prj_yml.join("\n"), mode: 'w') output = `bundle exec ruby -S ceedling test:test_example_file_success --test_case=_adds_numbers 2>&1` @@ -626,17 +740,12 @@ def can_run_single_test_with_partiall_test_case_name_from_test_file_with_enabled end end - def none_of_test_is_executed_if_test_case_name_passed_does_not_fit_defined_in_test_file_and_cmdline_args_are_enabled + def none_of_test_is_executed_if_test_case_name_passed_does_not_fit_defined_in_test_file @c.with_context do Dir.chdir @proj_name do FileUtils.cp test_asset_path("example_file.h"), 'src/' FileUtils.cp test_asset_path("example_file.c"), 'src/' FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/' - enable_unity_extra_args = "\n:test_runner:\n"\ - " :cmdline_args: true\n" - fake_prj_yml= File.read('project.yml').split("\n") - fake_prj_yml.insert(fake_prj_yml.length() -1, enable_unity_extra_args) - File.write('project.yml', fake_prj_yml.join("\n"), mode: 'w') output = `bundle exec ruby -S ceedling test:test_example_file_success --test_case=zumzum 2>&1` @@ -652,11 +761,6 @@ def none_of_test_is_executed_if_test_case_name_and_exclude_test_case_name_is_the FileUtils.cp test_asset_path("example_file.h"), 'src/' FileUtils.cp test_asset_path("example_file.c"), 'src/' FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/' - enable_unity_extra_args = "\n:test_runner:\n"\ - " :cmdline_args: true\n" - fake_prj_yml= File.read('project.yml').split("\n") - fake_prj_yml.insert(fake_prj_yml.length() -1, enable_unity_extra_args) - File.write('project.yml', fake_prj_yml.join("\n"), mode: 'w') output = `bundle exec ruby -S ceedling test:test_example_file_success --test_case=_adds_numbers --exclude_test_case=_adds_numbers 2>&1` @@ -666,217 +770,168 @@ def none_of_test_is_executed_if_test_case_name_and_exclude_test_case_name_is_the end end - def exlcude_test_case_name_filter_works_and_only_one_test_case_is_executed + def confirm_if_notification_for_cmdline_args_not_enabled_is_disabled @c.with_context do Dir.chdir @proj_name do FileUtils.cp test_asset_path("example_file.h"), 'src/' FileUtils.cp test_asset_path("example_file.c"), 'src/' FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/' - enable_unity_extra_args = "\n:test_runner:\n"\ - " :cmdline_args: true\n" - fake_prj_yml= File.read('project.yml').split("\n") - fake_prj_yml.insert(fake_prj_yml.length() -1, enable_unity_extra_args) - File.write('project.yml', fake_prj_yml.join("\n"), mode: 'w') - output = `bundle exec ruby -S ceedling test:all --exclude_test_case=test_add_numbers_adds_numbers 2>&1` + output = `bundle exec ruby -S ceedling test:test_example_file_success 2>&1` expect($?.exitstatus).to match(0) # Since a test either pass or are ignored, we return success here - expect(output).to match(/TESTED:\s+1/) - expect(output).to match(/PASSED:\s+0/) + expect(output).to match(/TESTED:\s+2/) + expect(output).to match(/PASSED:\s+1/) expect(output).to match(/FAILED:\s+0/) expect(output).to match(/IGNORED:\s+1/) + expect(output).not_to match(/:cmdline_args/) end end end - def run_all_test_when_test_case_name_is_passed_but_cmdline_args_are_disabled_with_success + def exclude_test_case_name_filter_works_and_only_one_test_case_is_executed @c.with_context do Dir.chdir @proj_name do FileUtils.cp test_asset_path("example_file.h"), 'src/' FileUtils.cp test_asset_path("example_file.c"), 'src/' FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/' - output = `bundle exec ruby -S ceedling test:test_example_file_success --test_case=_adds_numbers 2>&1` + output = `bundle exec ruby -S ceedling test:all --exclude_test_case=test_add_numbers_adds_numbers 2>&1` expect($?.exitstatus).to match(0) # Since a test either pass or are ignored, we return success here - expect(output).to match(/TESTED:\s+2/) - expect(output).to match(/PASSED:\s+1/) + expect(output).to match(/TESTED:\s+1/) + expect(output).to match(/PASSED:\s+0/) expect(output).to match(/FAILED:\s+0/) expect(output).to match(/IGNORED:\s+1/) - expect(output).to match(/please add `:cmdline_args` under :test_runner option/) end end end - def can_use_the_module_plugin_with_include_path + def run_one_testcase_from_one_test_file_when_test_case_name_is_passed @c.with_context do Dir.chdir @proj_name do - # add include path to module generator - mod_gen = Modulegenerator.new(inc_root: "inc/") - settings = { :module_generator => { :project_root => mod_gen.project_root, - :source_root => mod_gen.source_root, - :inc_root => mod_gen.inc_root, - :test_root => mod_gen.test_root - } - } - add_project_settings("project.yml", settings) + FileUtils.cp test_asset_path("example_file.h"), 'src/' + FileUtils.cp test_asset_path("example_file.c"), 'src/' + FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/' - # module creation - output = `bundle exec ruby -S ceedling module:create[myPonies:ponies]` - expect($?.exitstatus).to match(0) - expect(output).to match(/Generate Complete/i) - expect(File.exist?("myPonies/src/ponies.c")).to eq true - expect(File.exist?("myPonies/inc/ponies.h")).to eq true - expect(File.exist?("myPonies/test/test_ponies.c")).to eq true + output = `bundle exec ruby -S ceedling test:test_example_file_success --test_case=_adds_numbers 2>&1` - # Module destruction - output = `bundle exec ruby -S ceedling module:destroy[myPonies:ponies]` - expect($?.exitstatus).to match(0) - expect(output).to match(/Destroy Complete/i) - expect(File.exist?("myPonies/src/ponies.c")).to eq false - expect(File.exist?("myPonies/inc/ponies.h")).to eq false - expect(File.exist?("myPonies/test/test_ponies.c")).to eq false + expect($?.exitstatus).to match(0) # Since a test either pass or are ignored, we return success here + expect(output).to match(/TESTED:\s+1/) + expect(output).to match(/PASSED:\s+1/) + expect(output).to match(/FAILED:\s+0/) + expect(output).to match(/IGNORED:\s+0/) end end end - def can_use_the_module_plugin_with_non_default_paths - @c.with_context do - Dir.chdir @proj_name do - # add paths to module generator - mod_gen = Modulegenerator.new(source_root: "foo/", inc_root: "bar/", test_root: "barz/") - settings = { :module_generator => { :project_root => mod_gen.project_root, - :source_root => mod_gen.source_root, - :inc_root => mod_gen.inc_root, - :test_root => mod_gen.test_root - } - } - add_project_settings("project.yml", settings) - - # module creation - output = `bundle exec ruby -S ceedling module:create[ponies]` - expect($?.exitstatus).to match(0) - expect(output).to match(/Generate Complete/i) - expect(File.exist?("foo/ponies.c")).to eq true - expect(File.exist?("bar/ponies.h")).to eq true - expect(File.exist?("barz/test_ponies.c")).to eq true - # Module destruction - output = `bundle exec ruby -S ceedling module:destroy[ponies]` - expect($?.exitstatus).to match(0) - expect(output).to match(/Destroy Complete/i) - expect(File.exist?("foo/ponies.c")).to eq false - expect(File.exist?("bar/ponies.h")).to eq false - expect(File.exist?("barz/test_ponies.c")).to eq false - end - end - end - - def handles_creating_the_same_module_twice_using_the_module_plugin + def test_run_of_projects_fail_because_of_crash_without_report @c.with_context do Dir.chdir @proj_name do - output = `bundle exec ruby -S ceedling module:create[unicorns]` - expect($?.exitstatus).to match(0) - expect(output).to match(/Generate Complete/i) - - output = `bundle exec ruby -S ceedling module:create[unicorns] 2>&1` - expect($?.exitstatus).to match(1) - expect(output).to match(/ERROR: Ceedling Failed/) - end - end - end + FileUtils.cp test_asset_path("example_file.h"), 'src/' + FileUtils.cp test_asset_path("example_file.c"), 'src/' + FileUtils.cp test_asset_path("test_example_file_crash.c"), 'test/' - def handles_creating_the_same_module_twice_using_the_module_plugin_extension - @c.with_context do - Dir.chdir @proj_name do - output = `bundle exec ruby -S ceedling module:create[myUnicorn:unicorns]` - expect($?.exitstatus).to match(0) - expect(output).to match(/Generate Complete/i) + @c.merge_project_yml_for_test({:project => { :use_backtrace => :none }}) - output = `bundle exec ruby -S ceedling module:create[myUnicorn:unicorns] 2>&1` - expect($?.exitstatus).to match(1) - expect(output).to match(/ERROR: Ceedling Failed/) + output = `bundle exec ruby -S ceedling test:all 2>&1` + expect($?.exitstatus).to match(1) # Test should fail because of crash + expect(output).to match(/Test Executable Crashed/i) + expect(output).to match(/Unit test failures./) + expect(!File.exist?('./build/test/results/test_add.fail')) end end end - def handles_destroying_a_module_that_does_not_exist_using_the_module_plugin + def test_run_of_projects_fail_because_of_crash_with_report @c.with_context do Dir.chdir @proj_name do - output = `bundle exec ruby -S ceedling module:destroy[unknown]` - expect($?.exitstatus).to match(0) + FileUtils.cp test_asset_path("example_file.h"), 'src/' + FileUtils.cp test_asset_path("example_file.c"), 'src/' + FileUtils.cp test_asset_path("test_example_file_crash.c"), 'test/' - expect(output).to match(/File src\/unknown\.c does not exist so cannot be removed\./) - expect(output).to match(/File src\/unknown\.h does not exist so cannot be removed\./) - expect(output).to match(/File test\/test_unknown\.c does not exist so cannot be removed\./) - expect(output).to match(/Destroy Complete/) + @c.merge_project_yml_for_test({:project => { :use_backtrace => :none }}) - self.handles_destroying_a_module_that_does_not_exist_using_the_module_plugin_path_extension + output = `bundle exec ruby -S ceedling test:all 2>&1` + expect($?.exitstatus).to match(1) # Test should fail because of crash + expect(output).to match(/Test Executable Crashed/i) + expect(output).to match(/Unit test failures./) + expect(File.exist?('./build/test/results/test_example_file_crash.fail')) + output_rd = File.read('./build/test/results/test_example_file_crash.fail') + expect(output_rd =~ /test_add_numbers_will_fail \(\) at test\/test_example_file_crash.c\:14/ ) end end end - def handles_destroying_a_module_that_does_not_exist_using_the_module_plugin_path_extension + def execute_all_test_cases_from_crashing_test_runner_and_return_test_report_with_failue @c.with_context do Dir.chdir @proj_name do - output = `bundle exec ruby -S ceedling module:destroy[myUnknownModule:unknown]` - expect($?.exitstatus).to match(0) + FileUtils.cp test_asset_path("example_file.h"), 'src/' + FileUtils.cp test_asset_path("example_file.c"), 'src/' + FileUtils.cp test_asset_path("test_example_file_crash.c"), 'test/' + + @c.merge_project_yml_for_test({:project => { :use_backtrace => :gdb }}) - expect(output).to match(/File myUnknownModule\/src\/unknown\.c does not exist so cannot be removed\./) - expect(output).to match(/File myUnknownModule\/src\/unknown\.h does not exist so cannot be removed\./) - expect(output).to match(/File myUnknownModule\/test\/test_unknown\.c does not exist so cannot be removed\./) - expect(output).to match(/Destroy Complete/) + output = `bundle exec ruby -S ceedling test:all 2>&1` + expect($?.exitstatus).to match(1) # Test should fail because of crash + expect(output).to match(/Test Case Crashed/i) + expect(output).to match(/Unit test failures./) + expect(File.exist?('./build/test/results/test_example_file_crash.fail')) + output_rd = File.read('./build/test/results/test_example_file_crash.fail') + expect(output_rd =~ /test_add_numbers_will_fail \(\) at test\/test_example_file_crash.c\:14/ ) + expect(output).to match(/TESTED:\s+2/) + expect(output).to match(/PASSED:\s+(?:0|1)/) + expect(output).to match(/FAILED:\s+(?:1|2)/) + expect(output).to match(/IGNORED:\s+0/) end end end - def test_run_of_projects_fail_because_of_sigsegv_without_report + def execute_and_collect_debug_logs_from_crashing_test_case_defined_by_test_case_argument_with_enabled_debug @c.with_context do Dir.chdir @proj_name do FileUtils.cp test_asset_path("example_file.h"), 'src/' FileUtils.cp test_asset_path("example_file.c"), 'src/' - FileUtils.cp test_asset_path("test_example_file_sigsegv.c"), 'test/' + FileUtils.cp test_asset_path("test_example_file_crash.c"), 'test/' - output = `bundle exec ruby -S ceedling test:all 2>&1` - expect($?.exitstatus).to match(1) # Test should fail as sigsegv is called - expect(output).to match(/Segmentation fault \(core dumped\)/) - expect(output).to match(/No tests executed./) - expect(!File.exists?('./build/test/results/test_add.fail')) + @c.merge_project_yml_for_test({:project => { :use_backtrace => :gdb }}) + + output = `bundle exec ruby -S ceedling test:all --test_case=test_add_numbers_will_fail 2>&1` + expect($?.exitstatus).to match(1) # Test should fail because of crash + expect(output).to match(/Test Case Crashed/i) + expect(output).to match(/Unit test failures./) + expect(File.exist?('./build/test/results/test_example_file_crash.fail')) + output_rd = File.read('./build/test/results/test_example_file_crash.fail') + expect(output_rd =~ /test_add_numbers_will_fail \(\) at test\/test_example_file_crash.c\:14/ ) + expect(output).to match(/TESTED:\s+1/) + expect(output).to match(/PASSED:\s+(?:0|1)/) + expect(output).to match(/FAILED:\s+(?:1|2)/) + expect(output).to match(/IGNORED:\s+0/) end end end - def test_run_of_projects_fail_because_of_sigsegv_with_report + def execute_and_collect_debug_logs_from_crashing_test_case_defined_by_exclude_test_case_argument_with_enabled_debug @c.with_context do Dir.chdir @proj_name do FileUtils.cp test_asset_path("example_file.h"), 'src/' FileUtils.cp test_asset_path("example_file.c"), 'src/' - FileUtils.cp test_asset_path("test_example_file_sigsegv.c"), 'test/' - - add_line = false - updated_prj_yml = [] - File.read('project.yml').split("\n").each do |line| - if line =~ /\:project\:/ - add_line = true - updated_prj_yml.append(line) - else - if add_line - updated_prj_yml.append(' :use_backtrace_gdb_reporter: TRUE') - add_line = false - end - updated_prj_yml.append(line) - end - end + FileUtils.cp test_asset_path("test_example_file_crash.c"), 'test/' - File.write('project.yml', updated_prj_yml.join("\n"), mode: 'w') + @c.merge_project_yml_for_test({:project => { :use_backtrace => :gdb }}) - output = `bundle exec ruby -S ceedling test:all 2>&1` - expect($?.exitstatus).to match(1) # Test should fail as sigsegv is called - expect(output).to match(/Program received signal SIGSEGV, Segmentation fault./) + output = `bundle exec ruby -S ceedling test:all --exclude_test_case=add_numbers_adds_numbers 2>&1` + expect($?.exitstatus).to match(1) # Test should fail because of crash + expect(output).to match(/Test Case Crashed/i) expect(output).to match(/Unit test failures./) - expect(File.exists?('./build/test/results/test_example_file_sigsegv.fail')) - output_rd = File.read('./build/test/results/test_example_file_sigsegv.fail') - expect(output_rd =~ /test_add_numbers_will_fail \(\) at test\/test_example_file_sigsegv.c\:14/ ) + expect(File.exist?('./build/test/results/test_example_file_crash.fail')) + output_rd = File.read('./build/test/results/test_example_file_crash.fail') + expect(output_rd =~ /test_add_numbers_will_fail \(\) at test\/test_example_file_crash.c\:14/ ) + expect(output).to match(/TESTED:\s+1/) + expect(output).to match(/PASSED:\s+(?:0|1)/) + expect(output).to match(/FAILED:\s+(?:1|2)/) + expect(output).to match(/IGNORED:\s+0/) end end end diff --git a/spec/support/other_target.yml b/spec/support/other_target.yml index e69de29bb..0d173d5bc 100644 --- a/spec/support/other_target.yml +++ b/spec/support/other_target.yml @@ -0,0 +1,7 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + diff --git a/spec/support/target.yml b/spec/support/target.yml index e69de29bb..0d173d5bc 100644 --- a/spec/support/target.yml +++ b/spec/support/target.yml @@ -0,0 +1,7 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + diff --git a/spec/system/deployment_as_gem_spec.rb b/spec/system/deployment_as_gem_spec.rb new file mode 100644 index 000000000..e7604cf53 --- /dev/null +++ b/spec/system/deployment_as_gem_spec.rb @@ -0,0 +1,75 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'spec_system_helper' + +describe "Ceedling" do + include CeedlingTestCases + + before :all do + @c = SystemContext.new + @c.deploy_gem + end + + after :all do + FileUtils.rm_rf( 'GIT_COMMIT_SHA' ) + @c.done! + end + + before { @proj_name = "fake_project" } + after { @c.with_context { FileUtils.rm_rf @proj_name } } + + describe "deployed as a gem" do + before do + FileUtils.rm_rf( 'GIT_COMMIT_SHA' ) + @c.with_context do + `bundle exec ruby -S ceedling new #{@proj_name} 2>&1` + end + end + + it { can_report_version_no_git_commit_sha } + it { can_report_version_with_git_commit_sha } + it { can_create_projects } + it { does_not_contain_a_vendor_directory } + it { can_fetch_non_project_help } + it { can_fetch_project_help } + it { can_test_projects_with_success } + it { can_test_projects_with_success_test_alias } + it { can_test_projects_with_test_name_replaced_defines_with_success } + it { can_test_projects_unity_parameterized_test_cases_with_success } + it { can_test_projects_with_preprocessing_for_test_files_symbols_undefined } + it { can_test_projects_with_preprocessing_for_test_files_symbols_defined } + it { can_test_projects_with_preprocessing_for_mocks_success } + it { can_test_projects_with_preprocessing_for_mocks_intentional_build_failure } + it { can_test_projects_with_preprocessing_all } + it { can_test_projects_with_success_default } + it { can_test_projects_with_unity_exec_time } + it { can_test_projects_with_test_and_vendor_defines_with_success } + it { can_test_projects_with_fail } + it { can_test_projects_with_fail_alias } + it { can_test_projects_with_fail_default } + it { can_test_projects_with_compile_error } + it { can_test_projects_with_both_mock_and_real_header } + it { can_test_projects_with_success_when_space_appears_between_hash_and_include } + it { can_test_projects_with_named_verbosity } + it { can_test_projects_with_numerical_verbosity } + it { uses_report_tests_raw_output_log_plugin } + it { test_run_of_projects_fail_because_of_crash_without_report } + it { test_run_of_projects_fail_because_of_crash_with_report } + it { execute_all_test_cases_from_crashing_test_runner_and_return_test_report_with_failue } + it { execute_and_collect_debug_logs_from_crashing_test_case_defined_by_test_case_argument_with_enabled_debug } + it { execute_and_collect_debug_logs_from_crashing_test_case_defined_by_exclude_test_case_argument_with_enabled_debug } + it { confirm_if_notification_for_cmdline_args_not_enabled_is_disabled } + it { can_run_single_test_with_full_test_case_name_from_test_file_with_success } + it { can_run_single_test_with_partial_test_case_name_from_test_file_with_success } + it { exclude_test_case_name_filter_works_and_only_one_test_case_is_executed } + it { none_of_test_is_executed_if_test_case_name_passed_does_not_fit_defined_in_test_file } + it { none_of_test_is_executed_if_test_case_name_and_exclude_test_case_name_is_the_same } + it { run_one_testcase_from_one_test_file_when_test_case_name_is_passed } + end + +end diff --git a/spec/system/deployment_as_vendor_spec.rb b/spec/system/deployment_as_vendor_spec.rb new file mode 100644 index 000000000..9783aa38d --- /dev/null +++ b/spec/system/deployment_as_vendor_spec.rb @@ -0,0 +1,122 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'spec_system_helper' + +describe "Ceedling" do + include CeedlingTestCases + + before :all do + @c = SystemContext.new + @c.deploy_gem + end + + after :all do + # Ensure version commit file is cleaned up + FileUtils.rm_rf( 'GIT_COMMIT_SHA' ) + @c.done! + end + + before { @proj_name = "fake_project" } + after { @c.with_context { FileUtils.rm_rf @proj_name } } + + describe "deployed in a project's `vendor` directory." do + before do + # Ensure version commit file is cleaned up + FileUtils.rm_rf( 'GIT_COMMIT_SHA' ) + @c.with_context do + `bundle exec ruby -S ceedling new --local --docs #{@proj_name} 2>&1` + end + end + + it { can_report_version_no_git_commit_sha } + it { can_report_version_with_git_commit_sha } + it { can_create_projects } + it { contains_a_vendor_directory } + it { contains_documentation } + it { can_fetch_non_project_help } + it { can_fetch_project_help } + it { can_test_projects_with_success } + it { can_test_projects_with_success_test_alias } + it { can_test_projects_with_test_name_replaced_defines_with_success } + it { can_test_projects_unity_parameterized_test_cases_with_success } + it { can_test_projects_with_preprocessing_for_test_files_symbols_undefined } + it { can_test_projects_with_preprocessing_for_test_files_symbols_defined } + it { can_test_projects_with_preprocessing_for_mocks_success } + it { can_test_projects_with_preprocessing_for_mocks_intentional_build_failure } + it { can_test_projects_with_preprocessing_all } + it { can_test_projects_with_success_default } + it { can_test_projects_with_unity_exec_time } + it { can_test_projects_with_test_and_vendor_defines_with_success } + it { can_test_projects_with_fail } + it { can_test_projects_with_fail_alias } + it { can_test_projects_with_fail_default } + it { can_test_projects_with_compile_error } + it { can_test_projects_with_both_mock_and_real_header } + it { can_test_projects_with_success_when_space_appears_between_hash_and_include } + it { can_test_projects_with_named_verbosity } + it { can_test_projects_with_numerical_verbosity } + it { uses_report_tests_raw_output_log_plugin } + it { test_run_of_projects_fail_because_of_crash_without_report } + it { test_run_of_projects_fail_because_of_crash_with_report } + it { execute_all_test_cases_from_crashing_test_runner_and_return_test_report_with_failue } + it { execute_and_collect_debug_logs_from_crashing_test_case_defined_by_test_case_argument_with_enabled_debug } + it { execute_and_collect_debug_logs_from_crashing_test_case_defined_by_exclude_test_case_argument_with_enabled_debug } + it { confirm_if_notification_for_cmdline_args_not_enabled_is_disabled } + it { can_run_single_test_with_full_test_case_name_from_test_file_with_success } + it { can_run_single_test_with_partial_test_case_name_from_test_file_with_success } + it { exclude_test_case_name_filter_works_and_only_one_test_case_is_executed } + it { none_of_test_is_executed_if_test_case_name_passed_does_not_fit_defined_in_test_file } + it { none_of_test_is_executed_if_test_case_name_and_exclude_test_case_name_is_the_same } + it { run_one_testcase_from_one_test_file_when_test_case_name_is_passed } + end + + describe "deployed in a project's `vendor` directory with git support." do + before do + @c.with_context do + `bundle exec ruby -S ceedling new --local --docs --gitsupport #{@proj_name} 2>&1` + end + end + + it { can_create_projects } + it { has_git_support } + it { contains_a_vendor_directory } + it { contains_documentation } + it { can_test_projects_with_success } + end + + describe "deployed in a project's `vendor` directory without docs." do + before do + @c.with_context do + `bundle exec ruby -S ceedling new --local #{@proj_name} 2>&1` + end + end + + it { can_create_projects } + it { contains_a_vendor_directory } + it { does_not_contain_documentation } + it { can_fetch_non_project_help } + it { can_fetch_project_help } + it { can_test_projects_with_success } + it { can_test_projects_with_success_test_alias } + it { can_test_projects_with_test_name_replaced_defines_with_success } + it { can_test_projects_unity_parameterized_test_cases_with_success } + it { can_test_projects_with_preprocessing_for_test_files_symbols_undefined } + it { can_test_projects_with_preprocessing_for_test_files_symbols_defined } + it { can_test_projects_with_preprocessing_for_mocks_success } + it { can_test_projects_with_preprocessing_for_mocks_intentional_build_failure } + it { can_test_projects_with_preprocessing_all } + it { can_test_projects_with_success_default } + it { can_test_projects_with_unity_exec_time } + it { can_test_projects_with_test_and_vendor_defines_with_success } + it { can_test_projects_with_fail } + it { can_test_projects_with_fail_alias } + it { can_test_projects_with_fail_default } + it { can_test_projects_with_compile_error } + end + +end diff --git a/spec/system/deployment_spec.rb b/spec/system/deployment_spec.rb deleted file mode 100644 index 9eefcccd6..000000000 --- a/spec/system/deployment_spec.rb +++ /dev/null @@ -1,274 +0,0 @@ -require 'spec_system_helper' - -describe "Ceedling" do - include CeedlingTestCases - - before :all do - @c = SystemContext.new - @c.deploy_gem - end - - after :all do - @c.done! - end - - before { @proj_name = "fake_project" } - after { @c.with_context { FileUtils.rm_rf @proj_name } } - - describe "deployed in a project's `vendor` directory." do - before do - @c.with_context do - `bundle exec ruby -S ceedling new --local --docs #{@proj_name} 2>&1` - end - end - - it { can_create_projects } - it { contains_a_vendor_directory } - it { contains_documentation } - it { can_fetch_non_project_help } - it { can_fetch_project_help } - it { can_test_projects_with_success } - it { can_test_projects_with_success_test_alias } - it { can_test_projects_with_test_name_replaced_defines_with_success } - it { can_test_projects_with_success_default } - it { can_test_projects_with_unity_exec_time } - it { can_test_projects_with_test_and_vendor_defines_with_success } - it { can_test_projects_with_fail } - it { can_test_projects_with_fail_alias } - it { can_test_projects_with_fail_default } - it { can_test_projects_with_compile_error } - it { can_test_projects_with_both_mock_and_real_header } - it { can_test_projects_with_success_when_space_appears_between_hash_and_include } - it { uses_raw_output_report_plugin } - it { can_use_the_module_plugin } - it { can_use_the_module_plugin_path_extension } - it { can_use_the_module_plugin_with_include_path } - it { can_use_the_module_plugin_with_non_default_paths } - it { handles_creating_the_same_module_twice_using_the_module_plugin } - it { handles_creating_the_same_module_twice_using_the_module_plugin_extension } - it { handles_destroying_a_module_that_does_not_exist_using_the_module_plugin } - it { handles_destroying_a_module_that_does_not_exist_using_the_module_plugin_path_extension } - it { test_run_of_projects_fail_because_of_sigsegv_without_report } - it { test_run_of_projects_fail_because_of_sigsegv_with_report } - it { can_run_single_test_with_full_test_case_name_from_test_file_with_success_cmdline_args_are_enabled } - it { can_run_single_test_with_partiall_test_case_name_from_test_file_with_enabled_cmdline_args_success } - it { exlcude_test_case_name_filter_works_and_only_one_test_case_is_executed } - it { none_of_test_is_executed_if_test_case_name_passed_does_not_fit_defined_in_test_file_and_cmdline_args_are_enabled } - it { none_of_test_is_executed_if_test_case_name_and_exclude_test_case_name_is_the_same } - it { run_all_test_when_test_case_name_is_passed_but_cmdline_args_are_disabled_with_success } - end - - describe "deployed in a project's `vendor` directory with gitignore." do - before do - @c.with_context do - `bundle exec ruby -S ceedling new --local --docs --gitignore #{@proj_name} 2>&1` - end - end - - it { can_create_projects } - it { has_an_ignore } - it { contains_a_vendor_directory } - it { contains_documentation } - it { can_test_projects_with_success } - it { can_use_the_module_plugin } - end - - - - describe "deployed in a project's `vendor` directory without docs." do - before do - @c.with_context do - `bundle exec ruby -S ceedling new --local #{@proj_name} 2>&1` - end - end - - it { can_create_projects } - it { contains_a_vendor_directory } - it { does_not_contain_documentation } - it { can_fetch_non_project_help } - it { can_fetch_project_help } - it { can_test_projects_with_success } - it { can_test_projects_with_success_test_alias } - it { can_test_projects_with_test_name_replaced_defines_with_success } - it { can_test_projects_with_success_default } - it { can_test_projects_with_unity_exec_time } - it { can_test_projects_with_test_and_vendor_defines_with_success } - it { can_test_projects_with_fail } - it { can_test_projects_with_fail_alias } - it { can_test_projects_with_fail_default } - it { can_test_projects_with_compile_error } - it { can_use_the_module_plugin } - it { can_use_the_module_plugin_path_extension } - it { can_use_the_module_plugin_with_include_path } - it { handles_creating_the_same_module_twice_using_the_module_plugin } - it { handles_destroying_a_module_that_does_not_exist_using_the_module_plugin } - it { handles_destroying_a_module_that_does_not_exist_using_the_module_plugin_path_extension } - end - - describe "ugrade a project's `vendor` directory" do - before do - @c.with_context do - `bundle exec ruby -S ceedling new --local #{@proj_name} 2>&1` - end - end - - it { can_create_projects } - it { contains_a_vendor_directory } - it { does_not_contain_documentation } - it { can_fetch_non_project_help } - it { can_fetch_project_help } - it { can_test_projects_with_success } - it { can_test_projects_with_success_test_alias } - it { can_test_projects_with_success_default } - it { can_test_projects_with_unity_exec_time } - it { can_test_projects_with_test_and_vendor_defines_with_success } - it { can_test_projects_with_fail } - it { can_test_projects_with_fail_alias } - it { can_test_projects_with_fail_default } - it { can_test_projects_with_compile_error } - it { can_use_the_module_plugin } - it { can_use_the_module_plugin_path_extension } - it { can_use_the_module_plugin_with_include_path } - it { can_use_the_module_plugin_with_non_default_paths } - it { handles_creating_the_same_module_twice_using_the_module_plugin } - it { handles_creating_the_same_module_twice_using_the_module_plugin_extension } - it { handles_destroying_a_module_that_does_not_exist_using_the_module_plugin } - it { handles_destroying_a_module_that_does_not_exist_using_the_module_plugin_path_extension } - - it { can_upgrade_projects } - it { can_upgrade_projects_even_if_test_support_folder_does_not_exists } - it { contains_a_vendor_directory } - it { does_not_contain_documentation } - it { can_fetch_non_project_help } - it { can_fetch_project_help } - it { can_test_projects_with_success } - it { can_test_projects_with_success_test_alias } - it { can_test_projects_with_success_default } - it { can_test_projects_with_unity_exec_time } - it { can_test_projects_with_test_and_vendor_defines_with_success } - it { can_test_projects_with_fail } - it { can_test_projects_with_fail_alias } - it { can_test_projects_with_fail_default } - it { can_test_projects_with_compile_error } - it { can_use_the_module_plugin } - it { can_use_the_module_plugin_path_extension } - it { can_use_the_module_plugin_with_include_path } - it { can_use_the_module_plugin_with_non_default_paths } - it { handles_creating_the_same_module_twice_using_the_module_plugin } - it { handles_creating_the_same_module_twice_using_the_module_plugin_extension } - it { handles_destroying_a_module_that_does_not_exist_using_the_module_plugin } - it { handles_destroying_a_module_that_does_not_exist_using_the_module_plugin_path_extension } - end - - describe "Cannot ugrade a non existing project" do - it { cannot_upgrade_non_existing_project } - end - - describe "deployed as a gem" do - before do - @c.with_context do - `bundle exec ruby -S ceedling new #{@proj_name} 2>&1` - end - end - - it { can_create_projects } - it { does_not_contain_a_vendor_directory } - it { can_fetch_non_project_help } - it { can_fetch_project_help } - it { can_test_projects_with_success } - it { can_test_projects_with_success_test_alias } - it { can_test_projects_with_test_name_replaced_defines_with_success } - it { can_test_projects_with_success_default } - it { can_test_projects_with_unity_exec_time } - it { can_test_projects_with_test_and_vendor_defines_with_success } - it { can_test_projects_with_fail } - it { can_test_projects_with_compile_error } - it { can_use_the_module_plugin } - it { can_use_the_module_plugin_path_extension } - it { can_use_the_module_plugin_with_include_path } - it { can_use_the_module_plugin_with_non_default_paths } - it { handles_creating_the_same_module_twice_using_the_module_plugin } - it { handles_creating_the_same_module_twice_using_the_module_plugin_extension } - it { handles_destroying_a_module_that_does_not_exist_using_the_module_plugin } - it { handles_destroying_a_module_that_does_not_exist_using_the_module_plugin_path_extension } - end - - describe "deployed with auto link deep denendencies" do - before do - @c.with_context do - `bundle exec ruby -S ceedling new --local #{@proj_name} 2>&1` - end - end - - it { can_create_projects } - it { can_test_projects_with_enabled_auto_link_deep_deependency_with_success } - end - - describe "deployed with enabled preprocessor directives" do - before do - @c.with_context do - `bundle exec ruby -S ceedling new --local #{@proj_name} 2>&1` - end - end - - it { can_create_projects } - it { can_test_projects_with_enabled_preprocessor_directives_with_success } - end - - describe "command: `ceedling examples`" do - before do - @c.with_context do - @output = `bundle exec ruby -S ceedling examples 2>&1` - end - end - - it "should list out all the examples" do - expect(@output).to match(/blinky/) - expect(@output).to match(/temp_sensor/) - end - end - - describe "command: `ceedling example [example]`" do - describe "temp_sensor" do - before do - @c.with_context do - output = `bundle exec ruby -S ceedling example temp_sensor 2>&1` - expect(output).to match(/created!/) - end - end - - it "should be testable" do - @c.with_context do - Dir.chdir "temp_sensor" do - @output = `bundle exec ruby -S ceedling test:all` - expect(@output).to match(/TESTED:\s+47/) - expect(@output).to match(/PASSED:\s+47/) - end - end - end - end - - # # blinky depends on avr-gcc. If you happen to have this installed, go - # # ahead and uncomment this test and run it. This will fail on CI, so I'm - # # removing it for now. - # - # describe "blinky" do - # before do - # @c.with_context do - # output = `bundle exec ruby -S ceedling example blinky 2>&1` - # expect(output).to match(/created!/) - # end - # end - # - # it "should be testable" do - # @c.with_context do - # Dir.chdir "blinky" do - # @output = `bundle exec ruby -S ceedling test:all` - # expect(@output).to match(/TESTED:\s+7/) - # expect(@output).to match(/PASSED:\s+7/) - # end - # end - # end - # end - end -end diff --git a/spec/system/example_temp_sensor_spec.rb b/spec/system/example_temp_sensor_spec.rb new file mode 100644 index 000000000..2b304692a --- /dev/null +++ b/spec/system/example_temp_sensor_spec.rb @@ -0,0 +1,264 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'spec_system_helper' + +describe "Ceedling" do + include CeedlingTestCases + + before :all do + @c = SystemContext.new + @c.deploy_gem + end + + after :all do + @c.done! + end + + before { @proj_name = "temp_sensor" } + after { @c.with_context { FileUtils.rm_rf @proj_name } } + + describe "command: `ceedling examples`" do + before do + @c.with_context do + @output = `bundle exec ruby -S ceedling examples 2>&1` + end + end + + it "should list out all the examples" do + expect(@output).to match(/temp_sensor/) + end + end + + describe "command: `ceedling example temp_sensor`" do + describe "temp_sensor" do + before do + @c.with_context do + output = `bundle exec ruby -S ceedling example temp_sensor 2>&1` + expect(output).to match(/created/) + end + end + + it "should be testable" do + @c.with_context do + Dir.chdir "temp_sensor" do + @output = `bundle exec ruby -S ceedling test:all 2>&1` + expect(@output).to match(/TESTED:\s+51/) + expect(@output).to match(/PASSED:\s+51/) + end + end + end + + it "should be able to test a single module (it includes file-specific flags)" do + @c.with_context do + Dir.chdir "temp_sensor" do + @output = `bundle exec ruby -S ceedling test:TemperatureCalculator 2>&1` + expect(@output).to match(/TESTED:\s+2/) + expect(@output).to match(/PASSED:\s+2/) + + expect(@output).to match(/TemperatureCalculator\.out/i) + end + end + end + + it "should be able to test multiple files matching a pattern" do + @c.with_context do + Dir.chdir "temp_sensor" do + @output = `bundle exec ruby -S ceedling test:pattern[Temp] 2>&1` + expect(@output).to match(/TESTED:\s+6/) + expect(@output).to match(/PASSED:\s+6/) + + expect(@output).to match(/TemperatureCalculator\.out/i) + expect(@output).to match(/TemperatureFilter\.out/i) + end + end + end + + it "should be able to test all files matching in a path" do + @c.with_context do + Dir.chdir "temp_sensor" do + @output = `bundle exec ruby -S ceedling test:path[adc] 2>&1` + expect(@output).to match(/TESTED:\s+15/) + expect(@output).to match(/PASSED:\s+15/) + + expect(@output).to match(/AdcModel\.out/i) + expect(@output).to match(/AdcHardware\.out/i) + expect(@output).to match(/AdcConductor\.out/i) + end + end + end + + it "should be able to test specific test cases in a file" do + @c.with_context do + Dir.chdir "temp_sensor" do + @output = `bundle exec ruby -S ceedling test:path[adc] --test-case="RunShouldNot" 2>&1` + expect(@output).to match(/TESTED:\s+2/) + expect(@output).to match(/PASSED:\s+2/) + + expect(@output).to match(/AdcModel\.out/i) + expect(@output).to match(/AdcHardware\.out/i) + expect(@output).to match(/AdcConductor\.out/i) + end + end + end + + it "should be able to test when using a custom Unity Helper file added by relative-path mixin" do + @c.with_context do + Dir.chdir "temp_sensor" do + @output = `bundle exec ruby -S ceedling test:all --verbosity=obnoxious --mixin=mixin/add_unity_helper.yml 2>&1` + expect(@output).to match(/Merging command line mixin using mixin\/add_unity_helper\.yml/) + expect(@output).to match(/TESTED:\s+51/) + expect(@output).to match(/PASSED:\s+51/) + end + end + end + + it "should be able to test when using a custom Unity Helper file added by simple-named mixin" do + @c.with_context do + Dir.chdir "temp_sensor" do + @output = `bundle exec ruby -S ceedling test:all --verbosity=obnoxious --mixin=add_unity_helper 2>&1` + expect(@output).to match(/Merging command line mixin using mixin\/add_unity_helper\.yml/) + expect(@output).to match(/TESTED:\s+51/) + expect(@output).to match(/PASSED:\s+51/) + end + end + end + + it "should be able to test when using a custom Unity Helper file added by env-named mixin" do + @c.with_context do + ENV['CEEDLING_MIXIN_1'] = 'mixin/add_unity_helper.yml' + Dir.chdir "temp_sensor" do + @output = `bundle exec ruby -S ceedling test:all --verbosity=obnoxious 2>&1` + expect(@output).to match(/Merging CEEDLING_MIXIN_1 mixin using mixin\/add_unity_helper\.yml/) + expect(@output).to match(/TESTED:\s+51/) + expect(@output).to match(/PASSED:\s+51/) + end + end + end + + it "should be able to report the assembly files found in paths" do + @c.with_context do + Dir.chdir "temp_sensor" do + @output = `bundle exec ruby -S ceedling files:assembly 2>&1` + + expect(@output).to match(/Assembly files: None/i) + end + end + end + + it "should be able to report the header files found in paths" do + @c.with_context do + Dir.chdir "temp_sensor" do + @output = `bundle exec ruby -S ceedling files:header 2>&1` + + expect(@output).to match(/Header files:/i) + expect(@output).to match(/src\/AdcModel\.h/i) + expect(@output).to match(/src\/AdcHardware\.h/i) + expect(@output).to match(/src\/AdcConductor\.h/i) + expect(@output).to match(/src\/Main\.h/i) + expect(@output).to match(/src\/UsartTransmitBufferStatus\.h/i) + #and many more + + expect(@output).to match(/test\/support\/UnityHelper\.h/i) + end + end + end + + it "should be able to report the source files found in paths" do + @c.with_context do + Dir.chdir "temp_sensor" do + @output = `bundle exec ruby -S ceedling files:source 2>&1` + + expect(@output).to match(/Source files:/i) + expect(@output).to match(/src\/AdcModel\.c/i) + expect(@output).to match(/src\/AdcHardware\.c/i) + expect(@output).to match(/src\/AdcConductor\.c/i) + expect(@output).to match(/src\/Main\.c/i) + expect(@output).to match(/src\/UsartTransmitBufferStatus\.c/i) + #and many more + + expect(@output).not_to match(/test\/support\/UnityHelper\.c/i) + end + end + end + + it "should be able to report the support files found in paths" do + @c.with_context do + Dir.chdir "temp_sensor" do + @output = `bundle exec ruby -S ceedling files:support 2>&1` + + expect(@output).to match(/Support files:/i) + expect(@output).to match(/test\/support\/UnityHelper\.c/i) + end + end + end + + it "should be able to report the test files found in paths" do + @c.with_context do + Dir.chdir "temp_sensor" do + @output = `bundle exec ruby -S ceedling files:test 2>&1` + + expect(@output).to match(/Test files:/i) + expect(@output).to match(/test\/adc\/TestAdcModel\.c/i) + expect(@output).to match(/test\/adc\/TestAdcHardware\.c/i) + expect(@output).to match(/test\/adc\/TestAdcConductor\.c/i) + expect(@output).to match(/test\/TestMain\.c/i) + expect(@output).to match(/test\/TestUsartBaudRateRegisterCalculator.c/i) + end + end + end + + it "should be able to report the header paths found in paths" do + @c.with_context do + Dir.chdir "temp_sensor" do + @output = `bundle exec ruby -S ceedling paths:include 2>&1` + + expect(@output).to match(/Include paths:/i) + expect(@output).to match(/src/i) + end + end + end + + it "should be able to report the source paths found in paths" do + @c.with_context do + Dir.chdir "temp_sensor" do + @output = `bundle exec ruby -S ceedling paths:source 2>&1` + + expect(@output).to match(/Source paths:/i) + expect(@output).to match(/src/i) + end + end + end + + it "should be able to report the support paths found in paths" do + @c.with_context do + Dir.chdir "temp_sensor" do + @output = `bundle exec ruby -S ceedling paths:support 2>&1` + + expect(@output).to match(/Support paths:/i) + expect(@output).to match(/test\/support/i) + end + end + end + + it "should be able to report the test paths found in paths" do + @c.with_context do + Dir.chdir "temp_sensor" do + @output = `bundle exec ruby -S ceedling paths:test 2>&1` + + expect(@output).to match(/Test paths:/i) + expect(@output).to match(/test/i) + expect(@output).to match(/test\/adc/i) + + expect(@output).not_to match(/test\/support/i) + end + end + end + + end + end +end diff --git a/spec/system/upgrade_as_vendor_spec.rb b/spec/system/upgrade_as_vendor_spec.rb new file mode 100644 index 000000000..4ea5473e5 --- /dev/null +++ b/spec/system/upgrade_as_vendor_spec.rb @@ -0,0 +1,68 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'spec_system_helper' + +describe "Ceedling" do + include CeedlingTestCases + + before :all do + @c = SystemContext.new + @c.deploy_gem + end + + after :all do + @c.done! + end + + before { @proj_name = "fake_project" } + after { @c.with_context { FileUtils.rm_rf @proj_name } } + + describe "upgrade a project's `vendor` directory" do + before do + @c.with_context do + `bundle exec ruby -S ceedling new --local #{@proj_name} 2>&1` + end + end + + it { can_create_projects } + it { contains_a_vendor_directory } + it { does_not_contain_documentation } + it { can_fetch_non_project_help } + it { can_fetch_project_help } + it { can_test_projects_with_success } + it { can_test_projects_with_success_test_alias } + it { can_test_projects_with_success_default } + it { can_test_projects_with_unity_exec_time } + it { can_test_projects_with_test_and_vendor_defines_with_success } + it { can_test_projects_with_fail } + it { can_test_projects_with_fail_alias } + it { can_test_projects_with_fail_default } + it { can_test_projects_with_compile_error } + + it { can_upgrade_projects } + it { can_upgrade_projects_even_if_test_support_folder_does_not_exist } + it { contains_a_vendor_directory } + it { does_not_contain_documentation } + it { can_fetch_non_project_help } + it { can_fetch_project_help } + it { can_test_projects_with_success } + it { can_test_projects_with_success_test_alias } + it { can_test_projects_with_success_default } + it { can_test_projects_with_unity_exec_time } + it { can_test_projects_with_test_and_vendor_defines_with_success } + it { can_test_projects_with_fail } + it { can_test_projects_with_fail_alias } + it { can_test_projects_with_fail_default } + it { can_test_projects_with_compile_error } + end + + describe "Cannot upgrade a non existing project" do + it { cannot_upgrade_non_existing_project } + end + +end diff --git a/spec/system_utils_spec.rb b/spec/system_utils_spec.rb index c922c1d8b..f37bdb479 100644 --- a/spec/system_utils_spec.rb +++ b/spec/system_utils_spec.rb @@ -1,3 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'spec_helper' require 'ceedling/system_utils' @@ -10,6 +17,8 @@ @sys_utils = described_class.new({:system_wrapper => @sys_wrapper}) @sys_utils.setup + + @echo_test_cmd = {:command=>'echo $version'} end describe '#setup' do @@ -21,7 +30,7 @@ expect(@sys_utils.instance_variable_get(:@tcsh_shell)).to eq(nil) - allow(@streaminator).to receive(:shell_backticks).with('echo $version').and_return({:exit_code => 0, :output =>'tcsh 1234567890'}) + allow(@loginator).to receive(:shell_backticks).with(@echo_test_cmd).and_return({:exit_code => 0, :output =>'tcsh 1234567890'}) @sys_utils.tcsh_shell? @sys_utils.setup @@ -32,24 +41,24 @@ describe '#tcsh_shell?' do it 'returns true if exit code is zero and output contains tcsh' do - allow(@streaminator).to receive(:shell_backticks).with('echo $version').and_return({:exit_code => 0, :output =>'tcsh 1234567890'}) + allow(@loginator).to receive(:shell_backticks).with(@echo_test_cmd).and_return({:exit_code => 0, :output =>'tcsh 1234567890'}) expect(@sys_utils.tcsh_shell?).to eq(true) end it 'returns false if exit code is not 0' do - allow(@streaminator).to receive(:shell_backticks).with('echo $version').and_return({:exit_code => 1, :output =>'tcsh 1234567890'}) + allow(@loginator).to receive(:shell_backticks).with(@echo_test_cmd).and_return({:exit_code => 1, :output =>'tcsh 1234567890'}) expect(@sys_utils.tcsh_shell?).to eq(false) end it 'returns false if output does not contain tcsh' do - allow(@streaminator).to receive(:shell_backticks).with('echo $version').and_return({:exit_code => 0, :output =>'???'}) + allow(@loginator).to receive(:shell_backticks).with(@echo_test_cmd).and_return({:exit_code => 0, :output =>'???'}) expect(@sys_utils.tcsh_shell?).to eq(false) end it 'returns last value if already run' do - allow(@streaminator).to receive(:shell_backticks).with('echo $version').and_return({:exit_code => 1, :output =>'???'}) + allow(@loginator).to receive(:shell_backticks).with(@echo_test_cmd).and_return({:exit_code => 1, :output =>'???'}) expect(@sys_utils.tcsh_shell?).to eq(false) - allow(@streaminator).to receive(:shell_backticks).with('echo $version').and_return({:exit_code => 0, :output =>'tcsh 1234567890'}) + allow(@loginator).to receive(:shell_backticks).with(@echo_test_cmd).and_return({:exit_code => 0, :output =>'tcsh 1234567890'}) expect(@sys_utils.tcsh_shell?).to eq(false) end end diff --git a/spec/target_loader_spec.rb b/spec/target_loader_spec.rb deleted file mode 100644 index c247931e1..000000000 --- a/spec/target_loader_spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'spec_helper' -require 'ceedling/target_loader' - - -describe TargetLoader do - describe '.inspect' do - - it 'raises NoTargets if targets does not exist' do - expect{TargetLoader.inspect({})}.to raise_error(TargetLoader::NoTargets) - end - - it 'raises NoDirectory if targets_directory inside of targets does not exist' do - expect{TargetLoader.inspect({:targets => {}})}.to raise_error(TargetLoader::NoDirectory) - end - - it 'raises NoDefault if default_target inside of targets does not exist' do - expect{TargetLoader.inspect({:targets => {:targets_directory => File.join('spec', 'support')}})}.to raise_error(TargetLoader::NoDefault) - end - - it 'raises NoSuchTarget if file does not exist' do - expect{TargetLoader.inspect({:targets => {:targets_directory => File.join('spec', 'other'), :default_target => 'target'}})}.to raise_error(TargetLoader::NoSuchTarget) - end - - it 'raises RequestReload if file exists' do - expect{TargetLoader.inspect({:targets => {:targets_directory => File.join('spec', 'support'), :default_target => 'target'}})}.to raise_error(TargetLoader::RequestReload) - expect{TargetLoader.inspect({:targets => {:targets_directory => File.join('spec', 'support'), :default_target => 'target'}}, 'other_target')}.to raise_error(TargetLoader::RequestReload) - end - - end -end diff --git a/spec/test_context_extractor_spec.rb b/spec/test_context_extractor_spec.rb new file mode 100644 index 000000000..38c6aef4d --- /dev/null +++ b/spec/test_context_extractor_spec.rb @@ -0,0 +1,358 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'spec_helper' +require 'ceedling/test_context_extractor' +require 'ceedling/exceptions' + +describe TestContextExtractor do + before(:each) do + # Mock injected dependencies + @configurator = double( "Configurator" ) # Use double() so we can mock needed methods that are added dynamically at startup + @file_wrapper = double( "FileWrapper" ) # Not actually exercised in these test cases + loginator = instance_double( "Loginator" ) + + # Ignore all logging calls + allow(loginator).to receive(:log) + + # Provide configurations + mock_prefix = 'mock_' + + # Rely on defaults in Unity's test runner generator + test_runner_config = { + :cmdline_args => false, + :mock_prefix => mock_prefix, + :mock_suffix => '', + :enforce_strict_ordering => false, + :defines => [], + :use_param_tests => false + } + + allow(@configurator).to receive(:cmock_mock_prefix).and_return( mock_prefix ) + allow(@configurator).to receive(:extension_header).and_return( '.h' ) + allow(@configurator).to receive(:extension_source).and_return( '.c' ) + allow(@configurator).to receive(:get_runner_config).and_return( test_runner_config ) + + @extractor = described_class.new( + { + :configurator => @configurator, + :file_wrapper => @file_wrapper, + :loginator => loginator + } + ) + end + + context "#lookup_full_header_includes_list" do + it "should provide empty list when no context extraction has occurred" do + expect( @extractor.lookup_full_header_includes_list( "path" ) ).to eq [] + end + end + + context "#lookup_header_includes_list" do + it "should provide empty list when no context extraction has occurred" do + expect( @extractor.lookup_header_includes_list( "path" ) ).to eq [] + end + end + + context "#lookup_include_paths_list" do + it "should provide empty list when no context extraction has occurred" do + expect( @extractor.lookup_include_paths_list( "path" ) ).to eq [] + end + end + + context "#lookup_source_includes_list" do + it "should provide empty list when no context extraction has occurred" do + expect( @extractor.lookup_source_includes_list( "path" ) ).to eq [] + end + end + + context "#lookup_build_directive_sources_list" do + it "should provide empty list when no context extraction has occurred" do + expect( @extractor.lookup_build_directive_sources_list( "path" ) ).to eq [] + end + end + + context "#lookup_test_cases" do + it "should provide empty list when no context extraction has occurred" do + expect( @extractor.lookup_test_cases( "path" ) ).to eq [] + end + end + + context "#lookup_test_runner_generator" do + it "should provide no generator when no context extraction has occurred" do + expect( @extractor.lookup_test_runner_generator( "path" ) ).to eq nil + end + end + + context "#lookup_raw_mock_list" do + it "should provide empty list when no context extraction has occurred" do + expect( @extractor.lookup_raw_mock_list( "path" ) ).to eq [] + end + end + + context "#code_lines" do + it "should clean code of encoding problems and comments" do + file_contents = <<~CONTENTS + /* TEST_SOURCE_FILE("foo.c") */ // Eliminate single line comment block + // TEST_SOURCE_FILE("bar.c") // Eliminate single line comment + Some text⛔ + /* // /* // Eliminate tricky comment block enclosing comments + TEST_SOURCE_FILE("boom.c") + */ // // Eliminate trailing single line comment following block comment + More text + #define STR1 "/* comment " // Strip out (single line) C string containing block comment + #define STR2 " /* comment " // Strip out (single line) C string containing block comment + CONTENTS + + got = [] + + @extractor.code_lines( StringIO.new( file_contents ) ) do |line| + line.strip! + got << line if !line.empty? + end + + expected = [ + 'Some text', # ⛔ removed with encoding sanitizing + 'More text', + "#define STR1", + "#define STR2" + ] + + expect( got ).to eq expected + end + + end + + context "#extract_includes" do + it "should extract #include directives from code" do + # Complex comments tested in `clean_code_line()` test case + file_contents = <<~CONTENTS + #include "some_source.h" + #include "more_source.h" + + #include "some_source.h" // Duplicate to be ignored + + #include "unity.h" + + #include "mock_File.h" + #include "mock_another_file.h" + #include " mock_another_file.h " // Duplicate to be ignored + CONTENTS + + input = StringIO.new( file_contents ) + + expected = [ + 'some_source.h', + 'more_source.h', + 'unity.h', + 'mock_File.h', + 'mock_another_file.h' + ] + + expect( @extractor.extract_includes( input ) ).to eq expected + end + end + + context "#collect_simple_context" do + it "should raise an execption for unknown symbol argument" do + expect{ @extractor.collect_simple_context( "path", StringIO.new(), :bad ) }.to raise_error( CeedlingException ) + end + + # collect_simple_context() + lookup_full_header_includes_list() + lookup_header_includes_list() + lookup_raw_mock_list() + it "should extract contents of #include directives" do + filepath = "path/tests/test_file.c" + + # Complex comments tested in `clean_code_line()` test case + file_contents = <<~CONTENTS + #include "some_source.h" + #include "more_source.h" + + #include "some_source.h" // Duplicate to be ignored + + #include "unity.h" + + #include "mock_File.h" + #include "mock_another_file.h" + #include " mock_another_file.h " // Duplicate to be ignored + CONTENTS + + input = StringIO.new( file_contents ) + + @extractor.collect_simple_context( filepath, input, :includes ) + + expected_full = [ + 'some_source.h', + 'more_source.h', + 'unity.h', + 'mock_File.h', + 'mock_another_file.h' + ] + + expected_trim = [ + 'some_source.h', + 'more_source.h' + ] + + expected_mocks = [ + 'mock_File', + 'mock_another_file' + ] + + expect( @extractor.lookup_full_header_includes_list( filepath ) ).to eq expected_full + + expect( @extractor.lookup_header_includes_list( filepath ) ).to eq expected_trim + + expect( @extractor.lookup_raw_mock_list( filepath ) ).to eq expected_mocks + end + + # collect_simple_context() + lookup_build_directive_sources_list() + it "should extract extra source files by build directive macros" do + filepath = "path/tests/testfile.c" + + # Complex comments tested in `clean_code_line()` test case + file_contents = <<~CONTENTS + TEST_SOURE_FILE("bad_directive.c") // Typo in macro name that blocks recognition + + TEST_SOURCE_FILE("a.c") TEST_SOURCE_FILE("b.c") // Repeated calls on same line + + TEST_SOURCE_FILE("path/baz.c") // Leading whitespace to ignore + TEST_SOURCE_FILE( "some\\path\\boo.c" ) // Spaces in macro call + path separators to fix + + TEST_SOURCE_FILE() // Incomplete macro call that should be ignored + CONTENTS + + input = StringIO.new( file_contents ) + + @extractor.collect_simple_context( filepath, input, :build_directive_source_files ) + + expected = [ + 'a.c', + 'b.c', + 'path/baz.c', + 'some/path/boo.c' + ] + + expect( @extractor.lookup_build_directive_sources_list( filepath ) ).to eq expected + end + + # collect_simple_context() + lookup_include_paths_list() + it "should extract extra header search paths by build directive macros" do + filepath = "path/tests/testfile.c" + + # Complex comments tested in `clean_code_line()` test case + file_contents = <<~CONTENTS + TEST_INCLUE_PATH("bad_directive/") // Typo in macro name that blocks recognition + + TEST_INCLUDE_PATH("a") TEST_INCLUDE_PATH("b") // Repeated calls on same line + + TEST_INCLUDE_PATH("this/path") // Leading whitespace to ignore + TEST_INCLUDE_PATH( "some\\dir\\path/" ) // Spaces in macro call + path separators to fix + + TEST_INCLUDE_PATH() // Incomplete macro call that should be ignored + CONTENTS + + input = StringIO.new( file_contents ) + + @extractor.collect_simple_context( filepath, input, :build_directive_include_paths ) + + expected = [ + 'a', + 'b', + 'this/path', + 'some/dir/path' + ] + + expect( @extractor.lookup_include_paths_list( filepath ) ).to eq expected + end + + # collect_simple_context() + lookup_all_include_paths() + it "should extract extra header search paths for multiple files" do + # First File + filepath = "path/tests/testfile.c" + + # Complex comments tested in `clean_code_line()` test case + file_contents = <<~CONTENTS + TEST_INCLUDE_PATH("this/path") + TEST_INCLUDE_PATH("some/other/path") + CONTENTS + + input = StringIO.new( file_contents ) + + @extractor.collect_simple_context( filepath, input, :build_directive_include_paths ) + + # Second File + filepath = "anotherfile.c" + + # Complex comments tested in `clean_code_line()` test case + file_contents = <<~CONTENTS + TEST_INCLUDE_PATH("more/paths") + TEST_INCLUDE_PATH("yet/more/paths") + TEST_INCLUDE_PATH("this/path") // Duplicated from first file + CONTENTS + + input = StringIO.new( file_contents ) + + @extractor.collect_simple_context( filepath, input, :build_directive_include_paths ) + + expected = [ + 'this/path', + 'some/other/path', + 'more/paths', + 'yet/more/paths' + ] + + expect( @extractor.lookup_all_include_paths() ).to eq expected + end + + # collect_simple_context() + lookup_test_cases() + it "should extract test case names with line numbers" do + filepath = "path/tests/testfile.c" + + # Comments exercised because test case extraction relies on Unity's generate_test_runner.rb. + # Unity's Ruby code has its own handling of comments + file_contents = <<~CONTENTS + + void test_this_function() { + // TEST_ASSERT_TRUE( 1 == 1); + } + + /* + void test_this_other_function() { // Ignored due to comment block + TEST_ASSERT_FALSE( 0 == 1 ); + } + */ + + void test_another_function( void ) + { + // TEST_ASSERT_TRUE( 1 == 1); + } + + void TestME() { // Ignored because of naming mismatch + TEST_ASSERT_TRUE( 1 == 1); + } + + // void test_somestuff(void) { // Ignored due to comment lines + // // TEST_ASSERT_TRUE( 1 == 1); + // } + + CONTENTS + + input = StringIO.new( file_contents ) + + @extractor.collect_simple_context( filepath, input, :test_runner_details ) + + expected = [ + {:line_number => 2, :test => 'test_this_function'}, + {:line_number => 12, :test => 'test_another_function'}, + ] + + expect( @extractor.lookup_test_cases( filepath ) ).to eq expected + end + + end + +end diff --git a/spec/tool_executor_helper_spec.rb b/spec/tool_executor_helper_spec.rb index ffa871c03..0e15197c9 100644 --- a/spec/tool_executor_helper_spec.rb +++ b/spec/tool_executor_helper_spec.rb @@ -1,115 +1,48 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + require 'spec_helper' require 'ceedling/constants' require 'ceedling/tool_executor_helper' require 'ceedling/system_wrapper' -require 'ceedling/streaminator' +require 'ceedling/loginator' require 'ceedling/system_utils' - -HAPPY_OUTPUT = - "> Shell executed command:\n" + - "'gcc ab.c'\n" + - "\n".freeze - -HAPPY_OUTPUT_WITH_STATUS = - "> Shell executed command:\n" + - "'gcc ab.c'\n" + - "> And exited with status: [1].\n" + - "\n".freeze - -HAPPY_OUTPUT_WITH_MESSAGE = - "> Shell executed command:\n" + - "'gcc ab.c'\n" + - "> Produced output:\n" + - "xyz\n" + - "\n".freeze - -HAPPY_OUTPUT_WITH_MESSAGE_AND_STATUS = - "> Shell executed command:\n" + - "'gcc ab.c'\n" + - "> Produced output:\n" + - "xyz\n" + - "> And exited with status: [1].\n" + - "\n".freeze - -ERROR_OUTPUT = - "ERROR: Shell command failed.\n" + - "> Shell executed command:\n" + - "'gcc ab.c'\n" + - "> And exited with status: [1].\n" + - "\n" - -ERROR_OUTPUT_WITH_MESSAGE = - "ERROR: Shell command failed.\n" + - "> Shell executed command:\n" + - "'gcc ab.c'\n" + - "> Produced output:\n" + - "xyz\n" + - "> And exited with status: [1].\n" + - "\n" +require 'ceedling/verbosinator' describe ToolExecutorHelper do before(:each) do # these will always be mocked - @sys_wraper = SystemWrapper.new - @sys_utils = SystemUtils.new({:system_wrapper => @sys_wraper}) - @streaminator = Streaminator.new({:streaminator_helper => nil, :verbosinator => nil, :loginator => nil, :stream_wrapper => @sys_wraper}) + @sys_wrapper = SystemWrapper.new + @sys_utils = SystemUtils.new({:system_wrapper => @sys_wrapper}) + @loginator = Loginator.new({:verbosinator => nil, :file_wrapper => nil, :system_wrapper => nil}) + @verbosinator = Verbosinator.new() - - @tool_exe_helper = described_class.new({:streaminator => @streaminator, :system_utils => @sys_utils, :system_wrapper => @sys_wraper}) + @tool_exe_helper = described_class.new( + { + :loginator => @loginator, + :system_utils => @sys_utils, + :system_wrapper => @sys_wrapper, + :verbosinator => @verbosinator + } + ) end - - describe '#stderr_redirection' do - it 'returns stderr_redirect if logging is false' do - expect(@tool_exe_helper.stderr_redirection({:stderr_redirect => StdErrRedirect::NONE}, false)).to eq(StdErrRedirect::NONE) - end - - it 'returns stderr_redirect if logging is true and is a string' do - expect(@tool_exe_helper.stderr_redirection({:stderr_redirect => 'abc'}, true)).to eq('abc') - end - - it 'returns AUTO if logging is true and stderr_redirect is not a string' do - expect(@tool_exe_helper.stderr_redirection({:stderr_redirect => StdErrRedirect::NONE}, true)).to eq(StdErrRedirect::AUTO) - end - end - - - describe '#background_exec_cmdline_prepend' do - it 'returns nil if tool_config is nil' do - expect(@tool_exe_helper.background_exec_cmdline_prepend(nil)).to be_nil - end - - it 'returns nil if tool_config[:background_exec] is nil' do - expect(@tool_exe_helper.background_exec_cmdline_prepend({:background_exec =>nil})).to be_nil - end - - it 'returns "start" if tool_config[:background_exec] is AUTO on windows' do - expect(@sys_wraper).to receive(:windows?).and_return(true) - expect(@tool_exe_helper.background_exec_cmdline_prepend({:background_exec =>BackgroundExec::AUTO})).to eq('start') - end - - it 'returns nil if tool_config[:background_exec] is AUTO not on windows' do - expect(@sys_wraper).to receive(:windows?).and_return(false) - expect(@tool_exe_helper.background_exec_cmdline_prepend({:background_exec =>BackgroundExec::AUTO})).to be_nil - end - - it 'returns "start" if tool_config[:background_exec] is WIN' do - expect(@tool_exe_helper.background_exec_cmdline_prepend({:background_exec =>BackgroundExec::WIN})).to eq('start') - end - end - describe '#osify_path_separators' do it 'returns path if system is not windows' do exe = '/just/some/executable.out' - expect(@sys_wraper).to receive(:windows?).and_return(false) + expect(@sys_wrapper).to receive(:windows?).and_return(false) expect(@tool_exe_helper.osify_path_separators(exe)).to eq(exe) end it 'returns modifed if system is windows' do exe = '/just/some/executable.exe' - expect(@sys_wraper).to receive(:windows?).and_return(true) + expect(@sys_wrapper).to receive(:windows?).and_return(true) expect(@tool_exe_helper.osify_path_separators(exe)).to eq("\\just\\some\\executable.exe") end end @@ -137,174 +70,150 @@ it 'returns "2>&1" if system is windows' do - expect(@sys_wraper).to receive(:windows?).and_return(true) + expect(@sys_wrapper).to receive(:windows?).and_return(true) expect(@tool_exe_helper.stderr_redirect_cmdline_append(@tool_config)).to eq('2>&1') end it 'returns "|&" if system is tcsh' do - expect(@sys_wraper).to receive(:windows?).and_return(false) + expect(@sys_wrapper).to receive(:windows?).and_return(false) expect(@sys_utils).to receive(:tcsh_shell?).and_return(true) expect(@tool_exe_helper.stderr_redirect_cmdline_append(@tool_config)).to eq('|&') end it 'returns "2>&1" if system is unix' do - expect(@sys_wraper).to receive(:windows?).and_return(false) + expect(@sys_wrapper).to receive(:windows?).and_return(false) expect(@sys_utils).to receive(:tcsh_shell?).and_return(false) expect(@tool_exe_helper.stderr_redirect_cmdline_append(@tool_config)).to eq('2>&1') end end end - - describe '#background_exec_cmdline_append' do - it 'returns nil if tool_config is nil' do - expect(@tool_exe_helper.background_exec_cmdline_append(nil)).to be_nil + describe '#log_results' do + it 'insufficient logging verbosity' do + # Do nothing + expect(@verbosinator).to receive(:should_output?).with(Verbosity::OBNOXIOUS).and_return(false) + @tool_exe_helper.log_results("gcc ab.c", {}) end - it 'returns nil if tool_config[:background_exec] is nil' do - tool_config = {:background_exec => nil} - expect(@tool_exe_helper.background_exec_cmdline_append(tool_config)).to be_nil - end + context 'when debug logging' do + before(:each) do + expect(@verbosinator).to receive(:should_output?).with(Verbosity::OBNOXIOUS).and_return(true) + expect(@verbosinator).to receive(:should_output?).with(Verbosity::DEBUG).and_return(true) + @shell_result = {:status => ''} + end - it 'returns nil if tool_config is set to none' do - tool_config = {:background_exec => BackgroundExec::NONE} - expect(@tool_exe_helper.background_exec_cmdline_append(tool_config)).to be_nil - end + it 'and $stderr and $stdout are both empty' do + @shell_result[:stderr] = '' + @shell_result[:stdout] = '' - it 'returns nil if tool_config is set to none' do - tool_config = {:background_exec => BackgroundExec::WIN} - expect(@tool_exe_helper.background_exec_cmdline_append(tool_config)).to be_nil - end + message = + "> Shell executed command:\n" + + "`gcc ab.c`\n" + + "> With $stdout: \n" + + "> With $stderr: \n" + + "> And terminated with status: \n" - it 'returns "&" if tool_config is set to UNIX' do - tool_config = {:background_exec => BackgroundExec::UNIX} - expect(@tool_exe_helper.background_exec_cmdline_append(tool_config)).to eq('&') - end + expect(@loginator).to receive(:log).with('', Verbosity::DEBUG) + expect(@loginator).to receive(:log).with(message, Verbosity::DEBUG) + expect(@loginator).to receive(:log).with('', Verbosity::DEBUG) - context 'when tool_config[:background_exec] BackgroundExec:AUTO' do - before(:each) do - @tool_config = {:background_exec => BackgroundExec::AUTO} + @tool_exe_helper.log_results("gcc ab.c", @shell_result) end + it 'and $stderr is not empty' do + @shell_result[:stderr] = "error output\n\n\n" + @shell_result[:stdout] = '' - it 'returns nil if system is windows' do - expect(@sys_wraper).to receive(:windows?).and_return(true) - expect(@tool_exe_helper.background_exec_cmdline_append(@tool_config)).to be_nil - end + message = + "> Shell executed command:\n" + + "`test.exe`\n" + + "> With $stdout: \n" + + "> With $stderr: \nerror output\n" + + "> And terminated with status: \n" - it 'returns "&" if system is not windows' do - expect(@sys_wraper).to receive(:windows?).and_return(false) - expect(@sys_wraper).to receive(:windows?).and_return(false) - expect(@tool_exe_helper.background_exec_cmdline_append(@tool_config)).to eq('&') - end - end - end + expect(@loginator).to receive(:log).with('', Verbosity::DEBUG) + expect(@loginator).to receive(:log).with(message, Verbosity::DEBUG) + expect(@loginator).to receive(:log).with('', Verbosity::DEBUG) - describe '#print_happy_results' do - context 'when exit code is 0' do - before(:each) do - @shell_result = {:exit_code => 0, :output => ""} + @tool_exe_helper.log_results("test.exe", @shell_result) end - it 'and boom is true displays output' do - expect(@streaminator).to receive(:stdout_puts).with(HAPPY_OUTPUT, Verbosity::OBNOXIOUS) - @tool_exe_helper.print_happy_results("gcc ab.c", @shell_result, true) - end + it 'and $stdout is not empty' do + @shell_result[:stderr] = '' + @shell_result[:stdout] = "output\n\n\n" - it 'and boom is true with message displays output' do - @shell_result[:output] = "xyz" - expect(@streaminator).to receive(:stdout_puts).with(HAPPY_OUTPUT_WITH_MESSAGE, Verbosity::OBNOXIOUS) - @tool_exe_helper.print_happy_results("gcc ab.c", @shell_result, true) - end + message = + "> Shell executed command:\n" + + "`utility --flag`\n" + + "> With $stdout: \noutput\n" + + "> With $stderr: \n" + + "> And terminated with status: \n" - it 'and boom is false displays output' do - expect(@streaminator).to receive(:stdout_puts).with(HAPPY_OUTPUT, Verbosity::OBNOXIOUS) - @tool_exe_helper.print_happy_results("gcc ab.c", @shell_result, false) - end + expect(@loginator).to receive(:log).with('', Verbosity::DEBUG) + expect(@loginator).to receive(:log).with(message, Verbosity::DEBUG) + expect(@loginator).to receive(:log).with('', Verbosity::DEBUG) - it 'and boom is false with message displays output' do - @shell_result[:output] = "xyz" - expect(@streaminator).to receive(:stdout_puts).with(HAPPY_OUTPUT_WITH_MESSAGE, Verbosity::OBNOXIOUS) - @tool_exe_helper.print_happy_results("gcc ab.c", @shell_result, false) + @tool_exe_helper.log_results("utility --flag", @shell_result) end end - context 'when exit code is not 0' do + context 'when obnoxious logging' do before(:each) do - @shell_result = {:exit_code => 1, :output => ""} + expect(@verbosinator).to receive(:should_output?).with(Verbosity::OBNOXIOUS).and_return(true) + expect(@verbosinator).to receive(:should_output?).with(Verbosity::DEBUG).and_return(false) + @shell_result = {} end - it 'and boom is true does not displays output' do - @tool_exe_helper.print_happy_results("gcc ab.c", @shell_result, true) - end + it 'and executable probably crashed' do + @shell_result[:output] = '' + @shell_result[:exit_code] = nil - it 'and boom is true with message does not displays output' do - @shell_result[:output] = "xyz" - @tool_exe_helper.print_happy_results("gcc ab.c", @shell_result, true) - end + message = + "> Shell executed command:\n" + + "`gcc ab.c`\n" + + "> And exited prematurely\n" - it 'and boom is false displays output' do - expect(@streaminator).to receive(:stdout_puts).with(HAPPY_OUTPUT_WITH_STATUS, Verbosity::OBNOXIOUS) - @tool_exe_helper.print_happy_results("gcc ab.c", @shell_result, false) - end + expect(@loginator).to receive(:log).with('', Verbosity::OBNOXIOUS) + expect(@loginator).to receive(:log).with(message, Verbosity::OBNOXIOUS) + expect(@loginator).to receive(:log).with('', Verbosity::OBNOXIOUS) - it 'and boom is false with message displays output' do - @shell_result[:output] = "xyz" - expect(@streaminator).to receive(:stdout_puts).with(HAPPY_OUTPUT_WITH_MESSAGE_AND_STATUS, Verbosity::OBNOXIOUS) - @tool_exe_helper.print_happy_results("gcc ab.c", @shell_result, false) + @tool_exe_helper.log_results("gcc ab.c", @shell_result) end - end - end - describe '#print_error_results' do - context 'when exit code is 0' do - before(:each) do - @shell_result = {:exit_code => 0, :output => ""} - end + it 'and executable produced output and zero exit code' do + @shell_result[:output] = 'some output' + @shell_result[:exit_code] = 0 - it 'and boom is true does not display output' do - @tool_exe_helper.print_error_results("gcc ab.c", @shell_result, true) - end + message = + "> Shell executed command:\n" + + "`test.exe --a_flag`\n" + + "> Produced output: \nsome output\n" + + "> And terminated with exit code: [0]\n" - it 'and boom is true with message does not display output' do - @shell_result[:output] = "xyz" - @tool_exe_helper.print_error_results("gcc ab.c", @shell_result, true) - end + expect(@loginator).to receive(:log).with('', Verbosity::OBNOXIOUS) + expect(@loginator).to receive(:log).with(message, Verbosity::OBNOXIOUS) + expect(@loginator).to receive(:log).with('', Verbosity::OBNOXIOUS) - it 'and boom is false does not display output' do - @tool_exe_helper.print_error_results("gcc ab.c", @shell_result, false) + @tool_exe_helper.log_results("test.exe --a_flag", @shell_result) end - it 'and boom is false with message does not display output' do - @shell_result[:output] = "xyz" - @tool_exe_helper.print_error_results("gcc ab.c", @shell_result, false) - end - end + it 'and executable produced output and non-zero exit code' do + @shell_result[:output] = 'some more output' + @shell_result[:exit_code] = 37 - context 'when exit code is non 0' do - before(:each) do - @shell_result = {:exit_code => 1, :output => ""} - end - - it 'and boom is true displays output' do - expect(@streaminator).to receive(:stderr_puts).with(ERROR_OUTPUT, Verbosity::ERRORS) - @tool_exe_helper.print_error_results("gcc ab.c", @shell_result, true) - end + message = + "> Shell executed command:\n" + + "`utility.out args`\n" + + "> Produced output: \nsome more output\n" + + "> And terminated with exit code: [37]\n" - it 'and boom is true with message displays output' do - @shell_result[:output] = "xyz" - expect(@streaminator).to receive(:stderr_puts).with(ERROR_OUTPUT_WITH_MESSAGE, Verbosity::ERRORS) - @tool_exe_helper.print_error_results("gcc ab.c", @shell_result, true) - end + expect(@loginator).to receive(:log).with('', Verbosity::OBNOXIOUS) + expect(@loginator).to receive(:log).with(message, Verbosity::OBNOXIOUS) + expect(@loginator).to receive(:log).with('', Verbosity::OBNOXIOUS) - it 'and boom is false dose not display output' do - @tool_exe_helper.print_error_results("gcc ab.c", @shell_result, false) - end - - it 'and boom is false with message does not display output' do - @shell_result[:output] = "xyz" - @tool_exe_helper.print_error_results("gcc ab.c", @shell_result, false) + @tool_exe_helper.log_results("utility.out args", @shell_result) end end + end end diff --git a/spec/uncatagorized_specs_spec.rb b/spec/uncatagorized_specs_spec.rb deleted file mode 100644 index 2f02bf5c6..000000000 --- a/spec/uncatagorized_specs_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'spec_helper' - -describe "uncategorized" do - describe "set_verbosity" do - # drived from test_graveyard/unit/busted/configurator_test.rb:21 - xit "needs to be defined" - end -end diff --git a/spec/uncategorized_specs_spec.rb b/spec/uncategorized_specs_spec.rb new file mode 100644 index 000000000..31cf27563 --- /dev/null +++ b/spec/uncategorized_specs_spec.rb @@ -0,0 +1,15 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + +require 'spec_helper' + +describe "uncategorized" do + describe "set_verbosity" do + # drived from test_graveyard/unit/busted/configurator_test.rb:21 + xit "needs to be defined" + end +end diff --git a/vendor/behaviors/Manifest.txt b/vendor/behaviors/Manifest.txt deleted file mode 100644 index 6c954ecce..000000000 --- a/vendor/behaviors/Manifest.txt +++ /dev/null @@ -1,9 +0,0 @@ -Manifest.txt -Rakefile -lib/behaviors.rb -lib/behaviors/reporttask.rb -test/behaviors_tasks_test.rb -test/behaviors_test.rb -test/tasks_test/lib/user.rb -test/tasks_test/Rakefile -test/tasks_test/test/user_test.rb diff --git a/vendor/behaviors/Rakefile b/vendor/behaviors/Rakefile deleted file mode 100644 index d4d68b995..000000000 --- a/vendor/behaviors/Rakefile +++ /dev/null @@ -1,19 +0,0 @@ -require 'rake' -require 'rubygems' -require 'hoe' - -Hoe.new('behaviors','1.0.3') do |p| - p.author = "Atomic Object LLC" - p.email = "dev@atomicobject.com" - p.url = "http://behaviors.rubyforge.org" - p.summary = "behavior-driven unit test helper" - p.description = <<-EOS -Behaviors allows for Test::Unit test case methods to be defined as -human-readable descriptions of program behavior. It also provides -Rake tasks to list the behaviors of your project. - EOS - p.test_globs = ['test/*_test.rb'] - - p.changes = <<-EOS - EOS -end diff --git a/vendor/behaviors/lib/behaviors.rb b/vendor/behaviors/lib/behaviors.rb deleted file mode 100644 index d8d70f704..000000000 --- a/vendor/behaviors/lib/behaviors.rb +++ /dev/null @@ -1,76 +0,0 @@ -=begin rdoc -= Usage -Behaviors provides a single method: should. - -Instead of naming test methods like: - - def test_something - end - -You declare test methods like: - - should "perform action" do - end - -You may omit the body of a should method to describe unimplemented behavior. - - should "perform other action" - -When you run your unit tests, empty should methods will appear as an 'UNIMPLEMENTED CASE' along with the described behavior. -This is useful for sketching out planned behavior quickly. - -Simply extend Behaviors in your TestCase to start using behaviors. - - require 'test/unit' - require 'behaviors' - require 'user' - - class UserTest < Test::Unit::TestCase - extend Behaviors - ... - end - -= Motivation -Test methods typically focus on the name of the method under test instead of its behavior. -Creating test methods with should statements focuses on the behavior of an object. -This helps you to think about the role of the object under test. - -Using a behavior-driven approach prevents the danger in assuming a one-to-one mapping of method names to -test method names. -As always, you get the most value by writing the tests first. - -For a more complete BDD framework, try RSpec http://rspec.rubyforge.org/ - -= Rake tasks - -You can define a Behaviors::ReportTask in your Rakefile to generate rake tasks that -summarize the behavior of your project. - -These tasks are named behaviors and behaviors_html. They will output to the -console or an html file in the doc directory with a list all of your should tests. - Behaviors::ReportTask.new do |t| - t.pattern = 'test/**/*_test.rb' - end - -You may also initialize the ReportTask with a custom name to associate with a particular suite of tests. - Behaviors::ReportTask.new(:widget_subsystem) do |t| - t.pattern = 'test/widgets/*_test.rb' - end - -The html report will be placed in the doc directory by default. -You can override this default by setting the html_dir in the ReportTask. - Behaviors::ReportTask.new do |t| - t.pattern = 'test/**/*_test.rb' - t.html_dir = 'behaviors_html_reports' - end -=end -module Behaviors - def should(behave,&block) - mname = "test_should_#{behave}" - if block - define_method mname, &block - else - puts ">>> UNIMPLEMENTED CASE: #{name.sub(/Test$/,'')} should #{behave}" - end - end -end diff --git a/vendor/behaviors/lib/behaviors/reporttask.rb b/vendor/behaviors/lib/behaviors/reporttask.rb deleted file mode 100644 index 51c0eca09..000000000 --- a/vendor/behaviors/lib/behaviors/reporttask.rb +++ /dev/null @@ -1,158 +0,0 @@ -require 'rake' -require 'rake/tasklib' - -module Behaviors -include Rake - - class ReportTask < TaskLib - attr_accessor :pattern - attr_accessor :html_dir - - def initialize(name=:behaviors) - @name = name - @html_dir = 'doc' - yield self if block_given? - define - end - - def define - desc "List behavioral definitions for the classes specified (use for= to further limit files included in report)" - task @name do - specifications.each do |spec| - puts "#{spec.name} should:\n" - spec.requirements.each do |req| - puts " - #{req}" - end - end - end - - desc "Generate html report of behavioral definitions for the classes specified (use for= to further limit files included in report)" - task "#{@name}_html" do - require 'erb' - txt =<<-EOS - - - - - -
Specifications
-<% specifications.each do |spec| %> -
-<%= spec.name %> should: -
    -<% spec.requirements.each do |req| %> -
  • <%= req %>
  • -<% end %> -
-
-<% end %> - - - EOS - output_dir = File.expand_path(@html_dir) - mkdir_p output_dir - output_filename = output_dir + "/behaviors.html" - File.open(output_filename,"w") do |f| - f.write ERB.new(txt).result(binding) - end - puts "(Wrote #{output_filename})" - end - end - - private - def test_files - test_list = FileList[@pattern] - if ENV['for'] - test_list = test_list.grep(/#{ENV['for']}/i) - end - test_list - end - - def specifications - test_files.map do |file| - spec = OpenStruct.new - m = %r".*/([^/].*)_test.rb".match(file) - class_name = titleize(m[1]) if m[1] - spec.name = class_name - spec.requirements = [] - File::readlines(file).each do |line| - if line =~ /^\s*should\s+\(?\s*["'](.*)["']/ - spec.requirements << $1 - end - end - spec - end - end - - ############################################################ - # STOLEN FROM inflector.rb - ############################################################ - #-- - # Copyright (c) 2005 David Heinemeier Hansson - # - # Permission is hereby granted, free of charge, to any person obtaining - # a copy of this software and associated documentation files (the - # "Software"), to deal in the Software without restriction, including - # without limitation the rights to use, copy, modify, merge, publish, - # distribute, sublicense, and/or sell copies of the Software, and to - # permit persons to whom the Software is furnished to do so, subject to - # the following conditions: - # - # The above copyright notice and this permission notice shall be - # included in all copies or substantial portions of the Software. - # - # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - #++ - def titleize(word) - humanize(underscore(word)).gsub(/\b([a-z])/) { $1.capitalize } - end - - def underscore(camel_cased_word) camel_cased_word.to_s.gsub(/::/, '/'). - gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').gsub(/([a-z\d])([A-Z])/,'\1_\2').tr("-", "_").downcase - end - - def humanize(lower_case_and_underscored_word) - lower_case_and_underscored_word.to_s.gsub(/_id$/, "").gsub(/_/, " ").capitalize - end - - end -end diff --git a/vendor/behaviors/test/behaviors_tasks_test.rb b/vendor/behaviors/test/behaviors_tasks_test.rb deleted file mode 100644 index 9382e0737..000000000 --- a/vendor/behaviors/test/behaviors_tasks_test.rb +++ /dev/null @@ -1,73 +0,0 @@ -require 'test/unit' -require 'fileutils' - -class BehaviorsTasksTest < Test::Unit::TestCase - include FileUtils - - def setup - @here = File.expand_path(File.dirname(__FILE__)) - @base_cmd = RUBY_PLATFORM[/mswin/] ? 'rake.cmd ' : 'rake ' - end - - # - # HELPERS - # - def run_behaviors_task - run_cmd "behaviors" - end - - def run_behaviors_html_task - run_cmd "behaviors_html" - end - - def run_cmd(cmd) - cd "#{@here}/tasks_test" do - @report = %x[ #{@base_cmd} #{cmd} ] - end - end - - def see_html_task_output_message - @html_output_filename = "#{@here}/tasks_test/behaviors_doc/behaviors.html" - assert_match(/Wrote #{@html_output_filename}/, @report) - end - - def see_that_html_report_file_exits - assert File.exists?(@html_output_filename), "html output file should exist" - end - - def html_report_file_should_contain(user_behaviors) - file_contents = File.read(@html_output_filename) - user_behaviors.each do |line| - assert_match(/#{line}/, file_contents) - end - rm_rf File.dirname(@html_output_filename) - end - - # - # TESTS - # - def test_that_behaviors_tasks_should_list_behavioral_definitions_for_the_classes_under_test - run_behaviors_task - user_behaviors = [ - "User should:", - " - be able set user name and age during construction", - " - be able to get user name and age", - " - be able to ask if a user is an adult" - ] - assert_match(/#{user_behaviors.join("\n")}/, @report) - end - - def test_that_behaviors_tasks_should_list_behavioral_definitions_for_the_classes_under_test_in_html_output - run_behaviors_html_task - see_html_task_output_message - see_that_html_report_file_exits - user_behaviors = [ - "User should:", - "be able set user name and age during construction", - "be able to get user name and age", - "be able to ask if a user is an adult" - ] - html_report_file_should_contain user_behaviors - end - -end diff --git a/vendor/behaviors/test/behaviors_test.rb b/vendor/behaviors/test/behaviors_test.rb deleted file mode 100644 index fd0a77fcb..000000000 --- a/vendor/behaviors/test/behaviors_test.rb +++ /dev/null @@ -1,50 +0,0 @@ -require 'test/unit' -require File.expand_path(File.dirname(__FILE__)) + '/../lib/behaviors' -require 'stringio' - -loading_developer_test_class_stdout = StringIO.new -saved_stdout = $stdout.dup -$stdout = loading_developer_test_class_stdout - -class DeveloperTest - extend Behaviors - attr_accessor :flunk_msg, :tested_code - - should "test their code" do - @tested_code = true - end - should "go to meetings" -end - -$stdout = saved_stdout -loading_developer_test_class_stdout.rewind -$loading_developer_test_class_output = loading_developer_test_class_stdout.read - -class BehaviorsTest < Test::Unit::TestCase - - - def setup - @target = DeveloperTest.new - assert_nil @target.tested_code, "block called too early" - end - - # - # TESTS - # - def test_should_called_with_a_block_defines_a_test - assert @target.methods.include?("test_should_test their code"), "Missing test method" - - @target.send("test_should_test their code") - - assert @target.tested_code, "block not called" - end - - def test_should_called_without_a_block_does_not_create_a_test_method - assert !@target.methods.include?("test_should_go to meetings"), "Should not have method" - end - - def test_should_called_without_a_block_will_give_unimplemented_output_when_class_loads - unimplemented_output = "UNIMPLEMENTED CASE: Developer should go to meetings" - assert_match(/#{unimplemented_output}/, $loading_developer_test_class_output) - end -end diff --git a/vendor/behaviors/test/tasks_test/Rakefile b/vendor/behaviors/test/tasks_test/Rakefile deleted file mode 100644 index ba71f715f..000000000 --- a/vendor/behaviors/test/tasks_test/Rakefile +++ /dev/null @@ -1,19 +0,0 @@ -require 'rake' -require 'rake/testtask' - -here = File.expand_path(File.dirname(__FILE__)) -require "#{here}/../../lib/behaviors/reporttask" - -desc 'Default: run unit tests.' -task :default => :test - -Rake::TestTask.new(:test) do |t| - t.libs << "#{here}/../../lib" - t.pattern = 'test/**/*_test.rb' - t.verbose = true -end - -Behaviors::ReportTask.new(:behaviors) do |t| - t.pattern = 'test/**/*_test.rb' - t.html_dir = 'behaviors_doc' -end diff --git a/vendor/behaviors/test/tasks_test/lib/user.rb b/vendor/behaviors/test/tasks_test/lib/user.rb deleted file mode 100644 index 40bc07ce4..000000000 --- a/vendor/behaviors/test/tasks_test/lib/user.rb +++ /dev/null @@ -1,2 +0,0 @@ -class User -end diff --git a/vendor/behaviors/test/tasks_test/test/user_test.rb b/vendor/behaviors/test/tasks_test/test/user_test.rb deleted file mode 100644 index ad3cd1b33..000000000 --- a/vendor/behaviors/test/tasks_test/test/user_test.rb +++ /dev/null @@ -1,17 +0,0 @@ -require 'test/unit' -require 'behaviors' - -require 'user' - -class UserTest < Test::Unit::TestCase - extend Behaviors - - def setup - end - - should "be able set user name and age during construction" - should "be able to get user name and age" - should "be able to ask if a user is an adult" - def test_DELETEME - end -end diff --git a/vendor/c_exception b/vendor/c_exception index 3667ee2d4..849abefaf 160000 --- a/vendor/c_exception +++ b/vendor/c_exception @@ -1 +1 @@ -Subproject commit 3667ee2d4428c7ca602f2a1ab925ab1a3c2c0a09 +Subproject commit 849abefafe2199d9f1afe8b0482761afddfc80c6 diff --git a/vendor/cmock b/vendor/cmock index 379a9a8d5..d5e938e4b 160000 --- a/vendor/cmock +++ b/vendor/cmock @@ -1 +1 @@ -Subproject commit 379a9a8d5dd5cdff8fd345710dd70ae26f966c71 +Subproject commit d5e938e4b1ffc64acc70c7cf9b7ac6591f806c0b diff --git a/vendor/diy/LICENSE.txt b/vendor/diy/LICENSE.txt new file mode 100644 index 000000000..ee8023c28 --- /dev/null +++ b/vendor/diy/LICENSE.txt @@ -0,0 +1,7 @@ +Copyright 2007-2024 Atomic Object + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/diy/README.rdoc b/vendor/diy/README.rdoc index 6dfef2e26..9d6177d70 100644 --- a/vendor/diy/README.rdoc +++ b/vendor/diy/README.rdoc @@ -2,16 +2,16 @@ * http://atomicobject.github.com/diy -== DESCRIPTION: +== Desciption DIY (Dependency Injection in YAML) is a simple dependency injection library which focuses on declarative composition of objects through constructor injection. -== INSTALL: +== Install * gem install diy -== SYNOPSIS: +== Synopsis === Common Usage @@ -77,7 +77,7 @@ as opposed to a comma-separated list: Sometimes you won't be able to rely on DIY's basic assumptions about class names and library files. * You can specify the 'class' option -* You can specify the 'library' option. If you do not, the library is inferred from the class name. +* You can specify the 'library' option. If you do not, the library is inferred from the class name. (Eg, My::Train::Station will be sought in "my/train/station.rb" engine: @@ -93,7 +93,7 @@ object names, you can specify them one-by-one: the_block: block === Non-singleton objects - + Non-singletons are named objects that provide a new instance every time you ask for them. By default, DIY considers all objects to be singletons. To override, use the "singleton" setting and set it to false: @@ -103,9 +103,9 @@ set it to false: === Sub-Contexts -Sub-contexts are useful for creating isolated object networks that may need to be instantiated -zero or many times in your application. Objects defined in subcontexts can reference "upward" to -their surroundings, as well as objects in the subcontext itself. +Sub-contexts are useful for creating isolated object networks that may need to be instantiated +zero or many times in your application. Objects defined in subcontexts can reference "upward" to +their surroundings, as well as objects in the subcontext itself. If you wanted to be able to make more than one Engine from the preceding examples, you might try: @@ -132,7 +132,7 @@ Subcontexts are not initialized until you call upon them, which you do using the === Direct Class References Occasionally you will have a class at your disposal that you'd like to provide directly as components -to other objects (as opposed to getting _instances_ of that class, you want to reference the class itself, eg, +to other objects (as opposed to getting _instances_ of that class, you want to reference the class itself, eg, to use its factory methods). Enter the "use_class_directly" flag: --- @@ -173,8 +173,8 @@ If this is not desired (in Rails, auto-require can lead to library double-load i can deactivate auto-require. There is a global default setting (handled in code) and a per-object override (handled in the context YAML): - DIY::Context.auto_require = false - + DIY::Context.auto_require = false + --- engine: auto_require: false @@ -193,41 +193,23 @@ It is possible to create factories automatically with DIY: Then you can use the factory to easily build objects: context = DIY::Context.from_file('context.yml') - context[:car_factory].create => + context[:car_factory].create => === Method Directive -This introduces the concept of first class methods. An object can now be constructed with a method object +This introduces the concept of first class methods. An object can now be constructed with a method object bound to a particular object in the diy context. - + --- trinket_builder: method build_trinket: object: trinket_builder method: build - -== LICENSE: - -(The MIT License) - -Copyright (c) 2007 Atomic Object - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -'Software'), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +== Authors + +* David Crosby (crosby@atomicobject.com) +* © 2007-2024 {Atomic Object}[http://www.atomicobject.com] +* More Atomic Object {open source}[http://www.atomicobject.com/pages/Software+Commons] projects diff --git a/vendor/diy/lib/diy.rb b/vendor/diy/lib/diy.rb index 581afc7e6..99420ec48 100644 --- a/vendor/diy/lib/diy.rb +++ b/vendor/diy/lib/diy.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + require 'diy/factory.rb' require 'yaml' require 'set' diff --git a/vendor/diy/lib/diy/factory.rb b/vendor/diy/lib/diy/factory.rb index d2566c5d1..317c2062a 100644 --- a/vendor/diy/lib/diy/factory.rb +++ b/vendor/diy/lib/diy/factory.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + module DIY #:nodoc:# class FactoryDef #:nodoc: attr_accessor :name, :target, :class_name, :library diff --git a/vendor/diy/sample_code/car.rb b/vendor/diy/sample_code/car.rb index 9a6a8ed9c..22cab62b9 100644 --- a/vendor/diy/sample_code/car.rb +++ b/vendor/diy/sample_code/car.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class Car attr_reader :engine, :chassis def initialize(arg_hash) diff --git a/vendor/diy/sample_code/chassis.rb b/vendor/diy/sample_code/chassis.rb index b745b0bab..5b3ef74b8 100644 --- a/vendor/diy/sample_code/chassis.rb +++ b/vendor/diy/sample_code/chassis.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class Chassis def to_s "Chassis" diff --git a/vendor/diy/sample_code/diy_example.rb b/vendor/diy/sample_code/diy_example.rb index 88d5b7e41..91cc96358 100644 --- a/vendor/diy/sample_code/diy_example.rb +++ b/vendor/diy/sample_code/diy_example.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + require "rubygems" require "diy" diff --git a/vendor/diy/sample_code/engine.rb b/vendor/diy/sample_code/engine.rb index 65c2dd501..830898d2c 100644 --- a/vendor/diy/sample_code/engine.rb +++ b/vendor/diy/sample_code/engine.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class Engine def to_s "Engine" diff --git a/vendor/diy/sample_code/objects.yml b/vendor/diy/sample_code/objects.yml index 6deb10043..642da564f 100644 --- a/vendor/diy/sample_code/objects.yml +++ b/vendor/diy/sample_code/objects.yml @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + --- car: compose: diff --git a/vendor/diy/test/constructor.rb b/vendor/diy/test/constructor.rb index 5fe8f3aef..514cac0a4 100644 --- a/vendor/diy/test/constructor.rb +++ b/vendor/diy/test/constructor.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + CONSTRUCTOR_VERSION = '1.0.2' #:nodoc:# class Class #:nodoc:# diff --git a/vendor/diy/test/diy_test.rb b/vendor/diy/test/diy_test.rb index 35402007b..8958a6e15 100644 --- a/vendor/diy/test/diy_test.rb +++ b/vendor/diy/test/diy_test.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + require File.dirname(__FILE__) + "/test_helper" require 'diy' require 'fileutils' diff --git a/vendor/diy/test/factory_test.rb b/vendor/diy/test/factory_test.rb index ed02f013d..04a2a0818 100644 --- a/vendor/diy/test/factory_test.rb +++ b/vendor/diy/test/factory_test.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + require File.dirname(__FILE__) + "/test_helper" require 'diy' require 'fileutils' diff --git a/vendor/diy/test/files/broken_construction.yml b/vendor/diy/test/files/broken_construction.yml index 1dacb0112..f599f849c 100644 --- a/vendor/diy/test/files/broken_construction.yml +++ b/vendor/diy/test/files/broken_construction.yml @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + dog_presenter: model: dog_model diff --git a/vendor/diy/test/files/cat/cat.rb b/vendor/diy/test/files/cat/cat.rb index 2d1751497..a71bfe045 100644 --- a/vendor/diy/test/files/cat/cat.rb +++ b/vendor/diy/test/files/cat/cat.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class Cat constructor :heritage, :food, :strict => true, :accessors => true end diff --git a/vendor/diy/test/files/cat/extra_conflict.yml b/vendor/diy/test/files/cat/extra_conflict.yml index 9c6b3757e..cce8d97ef 100644 --- a/vendor/diy/test/files/cat/extra_conflict.yml +++ b/vendor/diy/test/files/cat/extra_conflict.yml @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + the_cat_lineage: cat: diff --git a/vendor/diy/test/files/cat/heritage.rb b/vendor/diy/test/files/cat/heritage.rb index 617d47a58..80f7dd710 100644 --- a/vendor/diy/test/files/cat/heritage.rb +++ b/vendor/diy/test/files/cat/heritage.rb @@ -1,2 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class Heritage end diff --git a/vendor/diy/test/files/cat/needs_input.yml b/vendor/diy/test/files/cat/needs_input.yml index 9f622f22f..a005b5f1b 100644 --- a/vendor/diy/test/files/cat/needs_input.yml +++ b/vendor/diy/test/files/cat/needs_input.yml @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + cat: heritage: the_cat_lineage food: some_meat diff --git a/vendor/diy/test/files/cat/the_cat_lineage.rb b/vendor/diy/test/files/cat/the_cat_lineage.rb index b0f43084d..13b02a046 100644 --- a/vendor/diy/test/files/cat/the_cat_lineage.rb +++ b/vendor/diy/test/files/cat/the_cat_lineage.rb @@ -1 +1,9 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class TheCatLineage; end diff --git a/vendor/diy/test/files/dog/dog_model.rb b/vendor/diy/test/files/dog/dog_model.rb index 51e7df0d8..b333054ea 100644 --- a/vendor/diy/test/files/dog/dog_model.rb +++ b/vendor/diy/test/files/dog/dog_model.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class DogModel constructor :file_resolver, :other_thing, :strict => true, :accessors => true end diff --git a/vendor/diy/test/files/dog/dog_presenter.rb b/vendor/diy/test/files/dog/dog_presenter.rb index 786977dce..ed60d312a 100644 --- a/vendor/diy/test/files/dog/dog_presenter.rb +++ b/vendor/diy/test/files/dog/dog_presenter.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class DogPresenter constructor :model, :view, :strict => true, :accessors => true end diff --git a/vendor/diy/test/files/dog/dog_view.rb b/vendor/diy/test/files/dog/dog_view.rb index aae86bc38..d3d1d7be5 100644 --- a/vendor/diy/test/files/dog/dog_view.rb +++ b/vendor/diy/test/files/dog/dog_view.rb @@ -1,2 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class DogView end diff --git a/vendor/diy/test/files/dog/file_resolver.rb b/vendor/diy/test/files/dog/file_resolver.rb index 09cf18a8d..4068c2d32 100644 --- a/vendor/diy/test/files/dog/file_resolver.rb +++ b/vendor/diy/test/files/dog/file_resolver.rb @@ -1,2 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class FileResolver end diff --git a/vendor/diy/test/files/dog/other_thing.rb b/vendor/diy/test/files/dog/other_thing.rb index 48e6a953d..2a8418a5a 100644 --- a/vendor/diy/test/files/dog/other_thing.rb +++ b/vendor/diy/test/files/dog/other_thing.rb @@ -1,2 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class OtherThing end diff --git a/vendor/diy/test/files/dog/simple.yml b/vendor/diy/test/files/dog/simple.yml index 7737236ba..7d03e1ebc 100644 --- a/vendor/diy/test/files/dog/simple.yml +++ b/vendor/diy/test/files/dog/simple.yml @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + dog_presenter: model: dog_model view: dog_view diff --git a/vendor/diy/test/files/donkey/foo.rb b/vendor/diy/test/files/donkey/foo.rb index 5182cf3d5..c4928c8bc 100644 --- a/vendor/diy/test/files/donkey/foo.rb +++ b/vendor/diy/test/files/donkey/foo.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + module DiyTesting module Bar diff --git a/vendor/diy/test/files/donkey/foo/bar/qux.rb b/vendor/diy/test/files/donkey/foo/bar/qux.rb index bb05a0227..27eb519f3 100644 --- a/vendor/diy/test/files/donkey/foo/bar/qux.rb +++ b/vendor/diy/test/files/donkey/foo/bar/qux.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + module Foo module Bar diff --git a/vendor/diy/test/files/factory/beef.rb b/vendor/diy/test/files/factory/beef.rb index 2cd31a0ff..b144ae818 100644 --- a/vendor/diy/test/files/factory/beef.rb +++ b/vendor/diy/test/files/factory/beef.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class Beef def cook return "rare" diff --git a/vendor/diy/test/files/factory/dog.rb b/vendor/diy/test/files/factory/dog.rb index 06b9daf00..8ea303a45 100644 --- a/vendor/diy/test/files/factory/dog.rb +++ b/vendor/diy/test/files/factory/dog.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class Dog def woof "woof" diff --git a/vendor/diy/test/files/factory/factory.yml b/vendor/diy/test/files/factory/factory.yml index 8264d3747..6fbf598b6 100644 --- a/vendor/diy/test/files/factory/factory.yml +++ b/vendor/diy/test/files/factory/factory.yml @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + cat_factory: builds: kitten library: kitten diff --git a/vendor/diy/test/files/factory/farm/llama.rb b/vendor/diy/test/files/factory/farm/llama.rb index 40e9fa21b..266c17b21 100644 --- a/vendor/diy/test/files/factory/farm/llama.rb +++ b/vendor/diy/test/files/factory/farm/llama.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + module Farm class Llama def make_llama_noise diff --git a/vendor/diy/test/files/factory/farm/pork.rb b/vendor/diy/test/files/factory/farm/pork.rb index a5aa4e5bf..d80a1dce4 100644 --- a/vendor/diy/test/files/factory/farm/pork.rb +++ b/vendor/diy/test/files/factory/farm/pork.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + module Farm class Pork def oink diff --git a/vendor/diy/test/files/factory/kitten.rb b/vendor/diy/test/files/factory/kitten.rb index f27a3ef4f..55db09197 100644 --- a/vendor/diy/test/files/factory/kitten.rb +++ b/vendor/diy/test/files/factory/kitten.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class Kitten attr_accessor :a,:b diff --git a/vendor/diy/test/files/fud/objects.yml b/vendor/diy/test/files/fud/objects.yml index 1a152b9e9..046c17f97 100644 --- a/vendor/diy/test/files/fud/objects.yml +++ b/vendor/diy/test/files/fud/objects.yml @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + widget: lib: toy diff --git a/vendor/diy/test/files/fud/toy.rb b/vendor/diy/test/files/fud/toy.rb index 937b71d42..164370592 100644 --- a/vendor/diy/test/files/fud/toy.rb +++ b/vendor/diy/test/files/fud/toy.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class Toy constructor :widget, :trinket, :accessors => true, :strict => true diff --git a/vendor/diy/test/files/functions/attached_things_builder.rb b/vendor/diy/test/files/functions/attached_things_builder.rb index f67888a0a..a147e98c8 100644 --- a/vendor/diy/test/files/functions/attached_things_builder.rb +++ b/vendor/diy/test/files/functions/attached_things_builder.rb @@ -1,2 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class AttachedThingsBuilder end \ No newline at end of file diff --git a/vendor/diy/test/files/functions/invalid_method.yml b/vendor/diy/test/files/functions/invalid_method.yml index 96690c3e9..e950399a2 100644 --- a/vendor/diy/test/files/functions/invalid_method.yml +++ b/vendor/diy/test/files/functions/invalid_method.yml @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + thing_builder: method build_thing: diff --git a/vendor/diy/test/files/functions/method_extractor.rb b/vendor/diy/test/files/functions/method_extractor.rb index 55daf4684..9dc1e4710 100644 --- a/vendor/diy/test/files/functions/method_extractor.rb +++ b/vendor/diy/test/files/functions/method_extractor.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class MethodExtractor end \ No newline at end of file diff --git a/vendor/diy/test/files/functions/nonsingleton_objects.yml b/vendor/diy/test/files/functions/nonsingleton_objects.yml index 39b6fd6b5..debec8e62 100644 --- a/vendor/diy/test/files/functions/nonsingleton_objects.yml +++ b/vendor/diy/test/files/functions/nonsingleton_objects.yml @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + thing_builder: singleton: false diff --git a/vendor/diy/test/files/functions/objects.yml b/vendor/diy/test/files/functions/objects.yml index 4d0a05af5..4f6c9a658 100644 --- a/vendor/diy/test/files/functions/objects.yml +++ b/vendor/diy/test/files/functions/objects.yml @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + thing_builder: method_extractor: diff --git a/vendor/diy/test/files/functions/thing.rb b/vendor/diy/test/files/functions/thing.rb index 4bc652de1..1c025a7ae 100644 --- a/vendor/diy/test/files/functions/thing.rb +++ b/vendor/diy/test/files/functions/thing.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class Thing constructor :name, :ability, :accessors => true end \ No newline at end of file diff --git a/vendor/diy/test/files/functions/thing_builder.rb b/vendor/diy/test/files/functions/thing_builder.rb index 288bab453..2c0ad8205 100644 --- a/vendor/diy/test/files/functions/thing_builder.rb +++ b/vendor/diy/test/files/functions/thing_builder.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + require 'thing' class ThingBuilder diff --git a/vendor/diy/test/files/functions/things_builder.rb b/vendor/diy/test/files/functions/things_builder.rb index 198c85a92..d540fbda2 100644 --- a/vendor/diy/test/files/functions/things_builder.rb +++ b/vendor/diy/test/files/functions/things_builder.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class ThingsBuilder constructor :build_thing, :accessors => true end \ No newline at end of file diff --git a/vendor/diy/test/files/gnu/objects.yml b/vendor/diy/test/files/gnu/objects.yml index 39581ef74..3123b77b3 100644 --- a/vendor/diy/test/files/gnu/objects.yml +++ b/vendor/diy/test/files/gnu/objects.yml @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + injected: diff --git a/vendor/diy/test/files/gnu/thinger.rb b/vendor/diy/test/files/gnu/thinger.rb index 1d332f68f..dffea5772 100644 --- a/vendor/diy/test/files/gnu/thinger.rb +++ b/vendor/diy/test/files/gnu/thinger.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class Thinger diff --git a/vendor/diy/test/files/goat/base.rb b/vendor/diy/test/files/goat/base.rb index a4f5d0e95..dcfe0969a 100644 --- a/vendor/diy/test/files/goat/base.rb +++ b/vendor/diy/test/files/goat/base.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class Base def test_output(name) # See diy_context_test.rb diff --git a/vendor/diy/test/files/goat/can.rb b/vendor/diy/test/files/goat/can.rb index 0bd1eeb74..2a5ed1050 100644 --- a/vendor/diy/test/files/goat/can.rb +++ b/vendor/diy/test/files/goat/can.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + require 'base' class Can < Base def initialize diff --git a/vendor/diy/test/files/goat/goat.rb b/vendor/diy/test/files/goat/goat.rb index ad084d393..1ea1143a1 100644 --- a/vendor/diy/test/files/goat/goat.rb +++ b/vendor/diy/test/files/goat/goat.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + require 'base' class Goat < Base def initialize diff --git a/vendor/diy/test/files/goat/objects.yml b/vendor/diy/test/files/goat/objects.yml index a31123e92..f3cf06c85 100644 --- a/vendor/diy/test/files/goat/objects.yml +++ b/vendor/diy/test/files/goat/objects.yml @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + can: paper: diff --git a/vendor/diy/test/files/goat/paper.rb b/vendor/diy/test/files/goat/paper.rb index 2068e418e..ace5a2af2 100644 --- a/vendor/diy/test/files/goat/paper.rb +++ b/vendor/diy/test/files/goat/paper.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + require 'base' class Paper < Base def initialize diff --git a/vendor/diy/test/files/goat/plane.rb b/vendor/diy/test/files/goat/plane.rb index 712e9045e..ef84a9bd5 100644 --- a/vendor/diy/test/files/goat/plane.rb +++ b/vendor/diy/test/files/goat/plane.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + require 'base' class Plane < Base constructor :wings, :strict => true diff --git a/vendor/diy/test/files/goat/shirt.rb b/vendor/diy/test/files/goat/shirt.rb index 7b28becf9..86cfa73df 100644 --- a/vendor/diy/test/files/goat/shirt.rb +++ b/vendor/diy/test/files/goat/shirt.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + require 'base' class Shirt < Base def initialize diff --git a/vendor/diy/test/files/goat/wings.rb b/vendor/diy/test/files/goat/wings.rb index dc0e70c84..59ee4645e 100644 --- a/vendor/diy/test/files/goat/wings.rb +++ b/vendor/diy/test/files/goat/wings.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + require 'base' class Wings < Base def initialize diff --git a/vendor/diy/test/files/horse/holder_thing.rb b/vendor/diy/test/files/horse/holder_thing.rb index 54802165e..8e27c43b3 100644 --- a/vendor/diy/test/files/horse/holder_thing.rb +++ b/vendor/diy/test/files/horse/holder_thing.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class HolderThing constructor :thing_held, :strict => true, :accessors => true end diff --git a/vendor/diy/test/files/horse/objects.yml b/vendor/diy/test/files/horse/objects.yml index 54a0e9c2e..3430f53d6 100644 --- a/vendor/diy/test/files/horse/objects.yml +++ b/vendor/diy/test/files/horse/objects.yml @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + holder_thing: thing_held: this_context diff --git a/vendor/diy/test/files/namespace/animal/bird.rb b/vendor/diy/test/files/namespace/animal/bird.rb index 27be4740b..d1e4d8a38 100644 --- a/vendor/diy/test/files/namespace/animal/bird.rb +++ b/vendor/diy/test/files/namespace/animal/bird.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + module Animal class Bird constructor :sky, :accessors => true diff --git a/vendor/diy/test/files/namespace/animal/cat.rb b/vendor/diy/test/files/namespace/animal/cat.rb index 632257e92..a5c624701 100644 --- a/vendor/diy/test/files/namespace/animal/cat.rb +++ b/vendor/diy/test/files/namespace/animal/cat.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + module Animal class Cat constructor :road, :accessors => true diff --git a/vendor/diy/test/files/namespace/animal/reptile/hardshell/turtle.rb b/vendor/diy/test/files/namespace/animal/reptile/hardshell/turtle.rb index fd05febe6..075fa8607 100644 --- a/vendor/diy/test/files/namespace/animal/reptile/hardshell/turtle.rb +++ b/vendor/diy/test/files/namespace/animal/reptile/hardshell/turtle.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + module Animal module Reptile module Hardshell diff --git a/vendor/diy/test/files/namespace/animal/reptile/lizard.rb b/vendor/diy/test/files/namespace/animal/reptile/lizard.rb index d2c6c9600..c9ff3b072 100644 --- a/vendor/diy/test/files/namespace/animal/reptile/lizard.rb +++ b/vendor/diy/test/files/namespace/animal/reptile/lizard.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + module Animal module Reptile class Lizard diff --git a/vendor/diy/test/files/namespace/bad_module_specified.yml b/vendor/diy/test/files/namespace/bad_module_specified.yml index 7befcace0..824b432d7 100644 --- a/vendor/diy/test/files/namespace/bad_module_specified.yml +++ b/vendor/diy/test/files/namespace/bad_module_specified.yml @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + sky: diff --git a/vendor/diy/test/files/namespace/class_name_combine.yml b/vendor/diy/test/files/namespace/class_name_combine.yml index 77f66fc2b..4bfcfb862 100644 --- a/vendor/diy/test/files/namespace/class_name_combine.yml +++ b/vendor/diy/test/files/namespace/class_name_combine.yml @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + road: using_namespace Animal: diff --git a/vendor/diy/test/files/namespace/no_module_specified.yml b/vendor/diy/test/files/namespace/no_module_specified.yml index 065e83f63..6877bc04a 100644 --- a/vendor/diy/test/files/namespace/no_module_specified.yml +++ b/vendor/diy/test/files/namespace/no_module_specified.yml @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + sky: diff --git a/vendor/diy/test/files/namespace/objects.yml b/vendor/diy/test/files/namespace/objects.yml index 55511be1d..d1b8b3f2a 100644 --- a/vendor/diy/test/files/namespace/objects.yml +++ b/vendor/diy/test/files/namespace/objects.yml @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + road: sky: diff --git a/vendor/diy/test/files/namespace/road.rb b/vendor/diy/test/files/namespace/road.rb index bb050fbac..97e257dae 100644 --- a/vendor/diy/test/files/namespace/road.rb +++ b/vendor/diy/test/files/namespace/road.rb @@ -1,2 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class Road end diff --git a/vendor/diy/test/files/namespace/sky.rb b/vendor/diy/test/files/namespace/sky.rb index fc1e2bbce..7ff799ca4 100644 --- a/vendor/diy/test/files/namespace/sky.rb +++ b/vendor/diy/test/files/namespace/sky.rb @@ -1,2 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class Sky end diff --git a/vendor/diy/test/files/namespace/subcontext.yml b/vendor/diy/test/files/namespace/subcontext.yml index da6331102..82d7f8a9d 100644 --- a/vendor/diy/test/files/namespace/subcontext.yml +++ b/vendor/diy/test/files/namespace/subcontext.yml @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + road: sky: diff --git a/vendor/diy/test/files/non_singleton/air.rb b/vendor/diy/test/files/non_singleton/air.rb index 63414afab..054c99dab 100644 --- a/vendor/diy/test/files/non_singleton/air.rb +++ b/vendor/diy/test/files/non_singleton/air.rb @@ -1,2 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class Air end diff --git a/vendor/diy/test/files/non_singleton/fat_cat.rb b/vendor/diy/test/files/non_singleton/fat_cat.rb index 54c195c68..5e81189e2 100644 --- a/vendor/diy/test/files/non_singleton/fat_cat.rb +++ b/vendor/diy/test/files/non_singleton/fat_cat.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class FatCat constructor :thread_spinner, :tick, :yard, :accessors => true end diff --git a/vendor/diy/test/files/non_singleton/objects.yml b/vendor/diy/test/files/non_singleton/objects.yml index 77b4505cb..e35b05a1f 100644 --- a/vendor/diy/test/files/non_singleton/objects.yml +++ b/vendor/diy/test/files/non_singleton/objects.yml @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + air: thread_spinner: diff --git a/vendor/diy/test/files/non_singleton/pig.rb b/vendor/diy/test/files/non_singleton/pig.rb index 9d7501353..ea970c93a 100644 --- a/vendor/diy/test/files/non_singleton/pig.rb +++ b/vendor/diy/test/files/non_singleton/pig.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class Pig constructor :thread_spinner, :yard, :accessors => true end diff --git a/vendor/diy/test/files/non_singleton/thread_spinner.rb b/vendor/diy/test/files/non_singleton/thread_spinner.rb index 384cd111a..ae7da235f 100644 --- a/vendor/diy/test/files/non_singleton/thread_spinner.rb +++ b/vendor/diy/test/files/non_singleton/thread_spinner.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class ThreadSpinner constructor :air, :accessors => true end diff --git a/vendor/diy/test/files/non_singleton/tick.rb b/vendor/diy/test/files/non_singleton/tick.rb index e243c1657..712c29ea7 100644 --- a/vendor/diy/test/files/non_singleton/tick.rb +++ b/vendor/diy/test/files/non_singleton/tick.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class Tick constructor :thread_spinner, :accessors => true end diff --git a/vendor/diy/test/files/non_singleton/yard.rb b/vendor/diy/test/files/non_singleton/yard.rb index 43936a7a5..421bf39b2 100644 --- a/vendor/diy/test/files/non_singleton/yard.rb +++ b/vendor/diy/test/files/non_singleton/yard.rb @@ -1,2 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class Yard end diff --git a/vendor/diy/test/files/yak/core_model.rb b/vendor/diy/test/files/yak/core_model.rb index 539b56b66..85359405f 100644 --- a/vendor/diy/test/files/yak/core_model.rb +++ b/vendor/diy/test/files/yak/core_model.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class CoreModel constructor :data_source, :strict => true end diff --git a/vendor/diy/test/files/yak/core_presenter.rb b/vendor/diy/test/files/yak/core_presenter.rb index 6caca4d20..820ed47d8 100644 --- a/vendor/diy/test/files/yak/core_presenter.rb +++ b/vendor/diy/test/files/yak/core_presenter.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class CorePresenter constructor :model, :view, :strict => true end diff --git a/vendor/diy/test/files/yak/core_view.rb b/vendor/diy/test/files/yak/core_view.rb index 7e606daae..91abf533c 100644 --- a/vendor/diy/test/files/yak/core_view.rb +++ b/vendor/diy/test/files/yak/core_view.rb @@ -1 +1,9 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class CoreView; end diff --git a/vendor/diy/test/files/yak/data_source.rb b/vendor/diy/test/files/yak/data_source.rb index 772d3f4c7..d3992056a 100644 --- a/vendor/diy/test/files/yak/data_source.rb +++ b/vendor/diy/test/files/yak/data_source.rb @@ -1 +1,9 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class DataSource; end diff --git a/vendor/diy/test/files/yak/fringe_model.rb b/vendor/diy/test/files/yak/fringe_model.rb index 255a22eb2..6a2f864ed 100644 --- a/vendor/diy/test/files/yak/fringe_model.rb +++ b/vendor/diy/test/files/yak/fringe_model.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class FringeModel constructor :connected, :accessors => true, :strict => true end diff --git a/vendor/diy/test/files/yak/fringe_presenter.rb b/vendor/diy/test/files/yak/fringe_presenter.rb index e40443589..8fcf435f0 100644 --- a/vendor/diy/test/files/yak/fringe_presenter.rb +++ b/vendor/diy/test/files/yak/fringe_presenter.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class FringePresenter constructor :fringe_model, :fringe_view, :strict => true end diff --git a/vendor/diy/test/files/yak/fringe_view.rb b/vendor/diy/test/files/yak/fringe_view.rb index d406d3d08..0370187bb 100644 --- a/vendor/diy/test/files/yak/fringe_view.rb +++ b/vendor/diy/test/files/yak/fringe_view.rb @@ -1 +1,9 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class FringeView; end diff --git a/vendor/diy/test/files/yak/giant_squid.rb b/vendor/diy/test/files/yak/giant_squid.rb index 2ddc2cc2b..fe2f1b803 100644 --- a/vendor/diy/test/files/yak/giant_squid.rb +++ b/vendor/diy/test/files/yak/giant_squid.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class GiantSquid constructor :fringe_view, :core_model, :krill, :accessors => true end diff --git a/vendor/diy/test/files/yak/krill.rb b/vendor/diy/test/files/yak/krill.rb index 5e79f91ba..71a223530 100644 --- a/vendor/diy/test/files/yak/krill.rb +++ b/vendor/diy/test/files/yak/krill.rb @@ -1,2 +1,10 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + class Krill end diff --git a/vendor/diy/test/files/yak/my_objects.yml b/vendor/diy/test/files/yak/my_objects.yml index ddc8264ba..7e76dba0a 100644 --- a/vendor/diy/test/files/yak/my_objects.yml +++ b/vendor/diy/test/files/yak/my_objects.yml @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + core_model: compose: data_source diff --git a/vendor/diy/test/files/yak/sub_sub_context_test.yml b/vendor/diy/test/files/yak/sub_sub_context_test.yml index 465418ac1..b0d1af4f3 100644 --- a/vendor/diy/test/files/yak/sub_sub_context_test.yml +++ b/vendor/diy/test/files/yak/sub_sub_context_test.yml @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + core_model: compose: data_source diff --git a/vendor/diy/test/test_helper.rb b/vendor/diy/test/test_helper.rb index 90089f09b..979e2f521 100644 --- a/vendor/diy/test/test_helper.rb +++ b/vendor/diy/test/test_helper.rb @@ -1,3 +1,11 @@ +# ========================================================================= +# Ceedling - Test-Centered Build System for C +# ThrowTheSwitch.org +# Copyright (c) 2010-24 Mike Karlesky, Mark VanderVoord, & Greg Williams +# SPDX-License-Identifier: MIT +# ========================================================================= + + here = File.expand_path(File.dirname(__FILE__)) PROJ_ROOT = File.expand_path("#{here}/..") $: << "#{PROJ_ROOT}/lib" diff --git a/vendor/hardmock/CHANGES b/vendor/hardmock/CHANGES deleted file mode 100644 index 4b5184cef..000000000 --- a/vendor/hardmock/CHANGES +++ /dev/null @@ -1,78 +0,0 @@ -Hardmock 1.3.7 - -* BUG FIX: expects! could not setup expectations for more than one concrete method on an object, since the method aliasing and rewriting was only taking place when the background mock instance was first created. This logic has been updated and now you can do all the things you'd expect. - -Hardmock 1.3.6 - -* BUG FIX: In Rails apps (and others) Hardmock and Fixtures battled viciously over "setup" and "teardown" and "method_added" (and any other clever test enhancement tool, namely Mocha) causing unpredictable results, notably failure to auto-verify mocks after teardown (leading to false positive tests). - * The newly-added TestUnitBeforeAfter provides TestCase.before_setup and TestCase.after_teardown -- formal test wrapping hooks -- lets Hardmock provide its preparation and auto-verify behavior without contending for setup/teardown supremacy. - -Hardmock 1.3.5 - -* Aliased should_receive => expects and and_return => returns for easier transition from rspec mock and flexmock users. - -Hardmock 1.3.4 - -* Prevents accidental stubbing and mocking on NilClasses - -Hardmock 1.3.3 - -* stubs! and expects! no longer require that their target methods exist in reality (this used to prevent you from stubbing methods that "exist" by virtue of "method_missing" -* Tweaked inner metaclass code to avoid collisions with rspec's "metaid" stuff -* Moved this project's Rake tasks into rake_tasks... otherwise Rails will load them, if Hardmock is installed as a Rails plugin -* Alias added: 'verify_hardmocks' is now an alias for 'verify_mocks' (some internal projects were using this modified method name as a means of cooexisting with mocha) - -Hardmock 1.3.2 - -November 2007 - -* adds 'with' as an alternate syntax for specifying argument expectations. - -Hardmock 1.3.1 - -October 2007 - -* Can use stubs! on a mock object -* expects! now generates mocked methods that can safely transfer runtime blocks to the mock instance itself -* No longer need to call "prepare_hardmock_control" when using stubs in the absence of mocks -* Stubs of concrete class or instance methods are restored to original state in teardown - -Hardmock 1.3.0 - -October 2007 - -* Adds stubs! and expects! method to all objects and classes to support concrete stubbing/mocking. - -Hardmock 1.2.3 - -Sat Apr 28 01:16:15 EDT 2007 - -* Re-release of 1.2.2 (which was canceled)... tasks moved to lib/tasks - -Hardmock 1.2.2 - -Sat Apr 28 00:41:30 EDT 2007 - -* assert_error has been broken out into its own lib file -* Gem package can now run all tests successfully -* Internal code refactoring; a number of classes that were defined in hardmock.rb are now in their own files - -Hardmock 1.2.1 - -Sat Apr 28 00:41:30 EDT 2007 - -* (botched release, see 1.2.2) - -Hardmock 1.2.0 - -* You can now use "expect" in place of "expects" if you must. -* "inspect" has been added to the list of methods NOT erased by MethodCleanout. - -Hardmock 1.1.0 - -* "expects" replaces "expect" ("expect" now raises Hardmock::DeprecationError) -* "verify_mocks" is now implicit in teardown, you needn't call it anymore -* Mocking methods that Mock would otherwise inherit from Object (eg, to_s) is now possible -* require 'hardmock' is all that's required to use the library now; no need to include in TestCase - -(previously called CMock, translated to Hardmock on 2006-12-10) diff --git a/vendor/hardmock/LICENSE b/vendor/hardmock/LICENSE deleted file mode 100644 index 396211e4d..000000000 --- a/vendor/hardmock/LICENSE +++ /dev/null @@ -1,7 +0,0 @@ -Copyright (c) 2006,2007 David Crosby at Atomic Object, LLC - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/vendor/hardmock/README b/vendor/hardmock/README deleted file mode 100644 index 4650a2a3f..000000000 --- a/vendor/hardmock/README +++ /dev/null @@ -1,70 +0,0 @@ -== Hardmock - -Strict, ordered mock objects using very lightweight syntax in your tests. - -== How - -The basic procedure for using Hardmock in your tests is: - -* require 'hardmock' (this happens automatically when being used as a Rails plugin) -* Create some mocks -* Setup some expectations -* Execute the target code -* Verification of calls is automatic in =teardown= - -The expectations you set when using mocks are strict and ordered. -Expectations you declare by creating and using mocks are all considered together. - -* Hardmock::Mock#expects will show you more examples -* Hardmock::SimpleExpectation will teach you more about expectation methods - -== Example - - create_mocks :garage, :car - - # Set some expectations - @garage.expects.open_door - @car.expects.start(:choke) - @car.expects.drive(:reverse, 5.mph) - - # Execute the code (this code is usually, obviously, in your class under test) - @garage.open_door - @car.start :choke - @car.drive :reverse, 5.mph - - verify_mocks # OPTIONAL, teardown will do this for you - -Expects @garage.open_door, @car.start(:choke) and @car.drive(:reverse, 5.mph) to be called in that order, with those specific arguments. -* Violations of expectations, such as mis-ordered calls, calls on wrong objects, or incorrect methods result in Hardmock::ExpectationError -* verify_mocks will raise VerifyError if not all expectations have been met. - -== Download and Install - -* Homepage: http://hardmock.rubyforge.org -* GEM or TGZ or ZIP: http://rubyforge.org/frs/?group_id=2742 -* Rails plugin: script/plugin install -* SVN access: svn co svn://rubyforge.org/var/svn/hardmock/trunk -* Developer SVN access: svn co svn://developername@rubyforge.org/var/svn/hardmock/trunk - -== Setup for Test::Unit - - require 'hardmock' - require 'assert_error' # OPTIONAL: this adds the TestUnit extension 'assert_error' - -NOTE: If installed as a Rails plugin, init.rb does this for you... nothing else is needed. - -== Setup for RSpec - -Get this into your spec helper or environment or Rakefile or wherever you prefer: - - Spec::Runner.configure do |configuration| - configuration.include Hardmock - configuration.after(:each) {verify_mocks} - end - -This puts the implicit conveniences into your spec context, like "create_mocks" etc, and also provides for automatic -"verify_mocks" after each Example is run. - -== Author -* David Crosby crosby at http://atomicobject.com -* (c) 2006,2007 Atomic Object LLC diff --git a/vendor/hardmock/Rakefile b/vendor/hardmock/Rakefile deleted file mode 100644 index aff126c2c..000000000 --- a/vendor/hardmock/Rakefile +++ /dev/null @@ -1,8 +0,0 @@ -require 'rake' -require 'rubygems' - -HARDMOCK_VERSION = "1.3.7" - -Dir["rake_tasks/*.rake"].each { |f| load f } - -task :default => [ 'test:all' ] diff --git a/vendor/hardmock/config/environment.rb b/vendor/hardmock/config/environment.rb deleted file mode 100644 index a15e598a6..000000000 --- a/vendor/hardmock/config/environment.rb +++ /dev/null @@ -1,12 +0,0 @@ -# The path to the root directory of your application. -APP_ROOT = File.join(File.dirname(__FILE__), '..') - -ADDITIONAL_LOAD_PATHS = [] -ADDITIONAL_LOAD_PATHS.concat %w( - lib -).map { |dir| "#{APP_ROOT}/#{dir}" }.select { |dir| File.directory?(dir) } - -# Prepend to $LOAD_PATH -ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } - -# Require any additional libraries needed diff --git a/vendor/hardmock/lib/assert_error.rb b/vendor/hardmock/lib/assert_error.rb deleted file mode 100644 index 6da61de96..000000000 --- a/vendor/hardmock/lib/assert_error.rb +++ /dev/null @@ -1,23 +0,0 @@ -require 'test/unit/assertions' - -module Test::Unit #:nodoc:# - module Assertions #:nodoc:# - # A better 'assert_raise'. +patterns+ can be one or more Regexps, or a literal String that - # must match the entire error message. - def assert_error(err_type,*patterns,&block) - assert_not_nil block, "assert_error requires a block" - assert((err_type and err_type.kind_of?(Class)), "First argument to assert_error has to be an error type") - err = assert_raise(err_type) do - block.call - end - patterns.each do |pattern| - case pattern - when Regexp - assert_match(pattern, err.message) - else - assert_equal pattern, err.message - end - end - end - end -end diff --git a/vendor/hardmock/lib/extend_test_unit.rb b/vendor/hardmock/lib/extend_test_unit.rb deleted file mode 100644 index 3d7ef9d28..000000000 --- a/vendor/hardmock/lib/extend_test_unit.rb +++ /dev/null @@ -1,14 +0,0 @@ - -require 'test/unit/testcase' -class Test::Unit::TestCase - include Hardmock -end - -require 'test_unit_before_after' -Test::Unit::TestCase.before_setup do |test| - test.prepare_hardmock_control -end - -Test::Unit::TestCase.after_teardown do |test| - test.verify_mocks -end diff --git a/vendor/hardmock/lib/hardmock.rb b/vendor/hardmock/lib/hardmock.rb deleted file mode 100644 index 50f9a94b3..000000000 --- a/vendor/hardmock/lib/hardmock.rb +++ /dev/null @@ -1,86 +0,0 @@ -require 'hardmock/method_cleanout' -require 'hardmock/mock' -require 'hardmock/mock_control' -require 'hardmock/utils' -require 'hardmock/errors' -require 'hardmock/trapper' -require 'hardmock/expector' -require 'hardmock/expectation' -require 'hardmock/expectation_builder' -require 'hardmock/stubbing' - -module Hardmock - - # Create one or more new Mock instances in your test suite. - # Once created, the Mocks are accessible as instance variables in your test. - # Newly built Mocks are added to the full set of Mocks for this test, which will - # be verified when you call verify_mocks. - # - # create_mocks :donkey, :cat # Your test now has @donkey and @cat - # create_mock :dog # Test now has @donkey, @cat and @dog - # - # The first call returned a hash { :donkey => @donkey, :cat => @cat } - # and the second call returned { :dog => @dog } - # - # For more info on how to use your mocks, see Mock and Expectation - # - def create_mocks(*mock_names) - prepare_hardmock_control unless @main_mock_control - - mocks = {} - mock_names.each do |mock_name| - raise ArgumentError, "'nil' is not a valid name for a mock" if mock_name.nil? - mock_name = mock_name.to_s - mock_object = Mock.new(mock_name, @main_mock_control) - mocks[mock_name.to_sym] = mock_object - self.instance_variable_set "@#{mock_name}", mock_object - end - @all_mocks ||= {} - @all_mocks.merge! mocks - - return mocks.clone - end - - def prepare_hardmock_control - if @main_mock_control.nil? - @main_mock_control = MockControl.new - $main_mock_control = @main_mock_control - else - raise "@main_mock_control is already setup for this test!" - end - end - - alias :create_mock :create_mocks - - # Ensures that all expectations have been met. If not, VerifyException is - # raised. - # - # You normally won't need to call this yourself. Within Test::Unit::TestCase, this will be done automatically at teardown time. - # - # * +force+ -- if +false+, and a VerifyError or ExpectationError has already occurred, this method will not raise. This is to help you suppress repeated errors when if you're calling #verify_mocks in the teardown method of your test suite. BE WARNED - only use this if you're sure you aren't obscuring useful information. Eg, if your code handles exceptions internally, and an ExpectationError gets gobbled up by your +rescue+ block, the cause of failure for your test may be hidden from you. For this reason, #verify_mocks defaults to force=true as of Hardmock 1.0.1 - def verify_mocks(force=true) - return unless @main_mock_control - return if @main_mock_control.disappointed? and !force - @main_mock_control.verify - ensure - @main_mock_control.clear_expectations if @main_mock_control - $main_mock_control = nil - reset_stubs - end - - alias :verify_hardmocks :verify_mocks - - # Purge the main MockControl of all expectations, restore all concrete stubbed/mocked methods - def clear_expectations - @main_mock_control.clear_expectations if @main_mock_control - reset_stubs - $main_mock_control = nil - end - - def reset_stubs - Hardmock.restore_all_replaced_methods - end - -end - -require 'extend_test_unit' diff --git a/vendor/hardmock/lib/hardmock/errors.rb b/vendor/hardmock/lib/hardmock/errors.rb deleted file mode 100644 index 48698a641..000000000 --- a/vendor/hardmock/lib/hardmock/errors.rb +++ /dev/null @@ -1,22 +0,0 @@ -module Hardmock - # Raised when: - # * Unexpected method is called on a mock object - # * Bad arguments passed to an expected call - class ExpectationError < StandardError #:nodoc:# - end - - # Raised for methods that should no longer be called. Hopefully, the exception message contains helpful alternatives. - class DeprecationError < StandardError #:nodoc:# - end - - # Raised when stubbing fails - class StubbingError < StandardError #:nodoc:# - end - - # Raised when it is discovered that an expected method call was never made. - class VerifyError < StandardError #:nodoc:# - def initialize(msg,unmet_expectations) - super("#{msg}:" + unmet_expectations.map { |ex| "\n * #{ex.to_s}" }.join) - end - end -end diff --git a/vendor/hardmock/lib/hardmock/expectation.rb b/vendor/hardmock/lib/hardmock/expectation.rb deleted file mode 100644 index 4d1db92ed..000000000 --- a/vendor/hardmock/lib/hardmock/expectation.rb +++ /dev/null @@ -1,229 +0,0 @@ -require 'hardmock/utils' - -module Hardmock - class Expectation - include Utils - attr_reader :block_value - - def initialize(options) #:nodoc: - @options = options - end - - def apply_method_call(mock,mname,args,block) #:nodoc: - unless @options[:mock].equal?(mock) - raise anger("Wrong object", mock,mname,args) - end - unless @options[:method] == mname - raise anger("Wrong method",mock,mname,args) - end - - # Tester-defined block to invoke at method-call-time: - expectation_block = @options[:block] - - expected_args = @options[:arguments] - # if we have a block, we can skip the argument check if none were specified - unless (expected_args.nil? || expected_args.empty?) && expectation_block && !@options[:suppress_arguments_to_block] - unless expected_args == args - raise anger("Wrong arguments",mock,mname,args) - end - end - - relayed_args = args.dup - if block - if expectation_block.nil? - # Can't handle a runtime block without an expectation block - raise ExpectationError.new("Unexpected block provided to #{to_s}") - else - # Runtime blocks are passed as final argument to the expectation block - unless @options[:suppress_arguments_to_block] - relayed_args << block - else - # Arguments suppressed; send only the block - relayed_args = [block] - end - end - end - - # Run the expectation block: - @block_value = expectation_block.call(*relayed_args) if expectation_block - - raise @options[:raises] unless @options[:raises].nil? - - return_value = @options[:returns] - if return_value.nil? - return @block_value - else - return return_value - end - end - - # Set the return value for an expected method call. - # Eg, - # @cash_machine.expects.withdraw(20,:dollars).returns(20.00) - def returns(val) - @options[:returns] = val - self - end - alias_method :and_return, :returns - - # Set the arguments for an expected method call. - # Eg, - # @cash_machine.expects.deposit.with(20, "dollars").returns(:balance => "20") - def with(*args) - @options[:arguments] = args - self - end - - # Rig an expected method to raise an exception when the mock is invoked. - # - # Eg, - # @cash_machine.expects.withdraw(20,:dollars).raises "Insufficient funds" - # - # The argument can be: - # * an Exception -- will be used directly - # * a String -- will be used as the message for a RuntimeError - # * nothing -- RuntimeError.new("An Error") will be raised - def raises(err=nil) - case err - when Exception - @options[:raises] = err - when String - @options[:raises] = RuntimeError.new(err) - else - @options[:raises] = RuntimeError.new("An Error") - end - self - end - - # Convenience method: assumes +block_value+ is set, and is set to a Proc - # (or anything that responds to 'call') - # - # light_event = @traffic_light.trap.subscribe(:light_changes) - # - # # This code will meet the expectation: - # @traffic_light.subscribe :light_changes do |color| - # puts color - # end - # - # The color-handling block is now stored in light_event.block_value - # - # The block can be invoked like this: - # - # light_event.trigger :red - # - # See Mock#trap and Mock#expects for information on using expectation objects - # after they are set. - # - def trigger(*block_arguments) - unless block_value - raise ExpectationError.new("No block value is currently set for expectation #{to_s}") - end - unless block_value.respond_to?(:call) - raise ExpectationError.new("Can't apply trigger to #{block_value} for expectation #{to_s}") - end - block_value.call *block_arguments - end - - # Used when an expected method accepts a block at runtime. - # When the expected method is invoked, the block passed to - # that method will be invoked as well. - # - # NOTE: ExpectationError will be thrown upon running the expected method - # if the arguments you set up in +yields+ do not properly match up with - # the actual block that ends up getting passed. - # - # == Examples - # Single invocation: The block passed to +lock_down+ gets invoked - # once with no arguments: - # - # @safe_zone.expects.lock_down.yields - # - # # (works on code that looks like:) - # @safe_zone.lock_down do - # # ... this block invoked once - # end - # - # Multi-parameter blocks: The block passed to +each_item+ gets - # invoked twice, with :item1 the first time, and with - # :item2 the second time: - # - # @fruit_basket.expects.each_with_index.yields [:apple,1], [:orange,2] - # - # # (works on code that looks like:) - # @fruit_basket.each_with_index do |fruit,index| - # # ... this block invoked with fruit=:apple, index=1, - # # ... and then with fruit=:orange, index=2 - # end - # - # Arrays can be passed as arguments too... if the block - # takes a single argument and you want to pass a series of arrays into it, - # that will work as well: - # - # @list_provider.expects.each_list.yields [1,2,3], [4,5,6] - # - # # (works on code that looks like:) - # @list_provider.each_list do |list| - # # ... list is [1,2,3] the first time - # # ... list is [4,5,6] the second time - # end - # - # Return value: You can set the return value for the method that - # accepts the block like so: - # - # @cruncher.expects.do_things.yields(:bean1,:bean2).returns("The Results") - # - # Raising errors: You can set the raised exception for the method that - # accepts the block. NOTE: the error will be raised _after_ the block has - # been invoked. - # - # # :bean1 and :bean2 will be passed to the block, then an error is raised: - # @cruncher.expects.do_things.yields(:bean1,:bean2).raises("Too crunchy") - # - def yields(*items) - @options[:suppress_arguments_to_block] = true - if items.empty? - # Yield once - @options[:block] = lambda do |block| - if block.arity != 0 and block.arity != -1 - raise ExpectationError.new("The given block was expected to have no parameter count; instead, got #{block.arity} to <#{to_s}>") - end - block.call - end - else - # Yield one or more specific items - @options[:block] = lambda do |block| - items.each do |item| - if item.kind_of?(Array) - if block.arity == item.size - # Unfold the array into the block's arguments: - block.call *item - elsif block.arity == 1 - # Just pass the array in - block.call item - else - # Size mismatch - raise ExpectationError.new("Can't pass #{item.inspect} to block with arity #{block.arity} to <#{to_s}>") - end - else - if block.arity != 1 - # Size mismatch - raise ExpectationError.new("Can't pass #{item.inspect} to block with arity #{block.arity} to <#{to_s}>") - end - block.call item - end - end - end - end - self - end - - def to_s # :nodoc: - format_method_call_string(@options[:mock],@options[:method],@options[:arguments]) - end - - private - def anger(msg, mock,mname,args) - ExpectationError.new("#{msg}: expected call <#{to_s}> but was <#{format_method_call_string(mock,mname,args)}>") - end - end -end diff --git a/vendor/hardmock/lib/hardmock/expectation_builder.rb b/vendor/hardmock/lib/hardmock/expectation_builder.rb deleted file mode 100644 index 7445fb150..000000000 --- a/vendor/hardmock/lib/hardmock/expectation_builder.rb +++ /dev/null @@ -1,9 +0,0 @@ -require 'hardmock/expectation' - -module Hardmock - class ExpectationBuilder #:nodoc: - def build_expectation(options) - Expectation.new(options) - end - end -end diff --git a/vendor/hardmock/lib/hardmock/expector.rb b/vendor/hardmock/lib/hardmock/expector.rb deleted file mode 100644 index 8055460c5..000000000 --- a/vendor/hardmock/lib/hardmock/expector.rb +++ /dev/null @@ -1,26 +0,0 @@ -require 'hardmock/method_cleanout' -require 'hardmock/errors' - -module Hardmock - class Expector #:nodoc: - include MethodCleanout - - def initialize(mock,mock_control,expectation_builder) - @mock = mock - @mock_control = mock_control - @expectation_builder = expectation_builder - end - - def method_missing(mname, *args, &block) - expectation = @expectation_builder.build_expectation( - :mock => @mock, - :method => mname, - :arguments => args, - :block => block) - - @mock_control.add_expectation expectation - expectation - end - end - -end diff --git a/vendor/hardmock/lib/hardmock/method_cleanout.rb b/vendor/hardmock/lib/hardmock/method_cleanout.rb deleted file mode 100644 index 51797e6fe..000000000 --- a/vendor/hardmock/lib/hardmock/method_cleanout.rb +++ /dev/null @@ -1,33 +0,0 @@ - -module Hardmock #:nodoc: - module MethodCleanout #:nodoc: - SACRED_METHODS = %w{ - __id__ - __send__ - equal? - object_id - send - nil? - class - kind_of? - respond_to? - inspect - method - to_s - instance_variables - instance_eval - == - hm_metaclass - hm_meta_eval - hm_meta_def - } - - def self.included(base) #:nodoc: - base.class_eval do - instance_methods.each do |m| - undef_method m unless SACRED_METHODS.include?(m.to_s) - end - end - end - end -end diff --git a/vendor/hardmock/lib/hardmock/mock.rb b/vendor/hardmock/lib/hardmock/mock.rb deleted file mode 100644 index 928c432e8..000000000 --- a/vendor/hardmock/lib/hardmock/mock.rb +++ /dev/null @@ -1,180 +0,0 @@ - -module Hardmock - # Mock is used to set expectations in your test. Most of the time you'll use - # #expects to create expectations. - # - # Aside from the scant few control methods (like +expects+, +trap+ and +_verify+) - # all calls made on a Mock instance will be immediately applied to the internal - # expectation mechanism. - # - # * If the method call was expected and all the parameters match properly, execution continues - # * If the expectation was configured with an expectation block, the block is invoked - # * If the expectation was set up to raise an error, the error is raised now - # * If the expectation was set up to return a value, it is returned - # * If the method call was _not_ expected, or the parameter values are wrong, an ExpectationError is raised. - class Mock - include Hardmock::MethodCleanout - - # Create a new Mock instance with a name and a MockControl to support it. - # If not given, a MockControl is made implicitly for this Mock alone; this means - # expectations for this mock are not tied to other expectations in your test. - # - # It's not recommended to use a Mock directly; see Hardmock and - # Hardmock#create_mocks for the more wholistic approach. - def initialize(name, mock_control=nil) - @name = name - @control = mock_control || MockControl.new - @expectation_builder = ExpectationBuilder.new - end - - def inspect - "" - end - - # Begin declaring an expectation for this Mock. - # - # == Simple Examples - # Expect the +customer+ to be queried for +account+, and return "The - # Account": - # @customer.expects.account.returns "The Account" - # - # Expect the +withdraw+ method to be called, and raise an exception when it - # is (see Expectation#raises for more info): - # @cash_machine.expects.withdraw(20,:dollars).raises("not enough money") - # - # Expect +customer+ to have its +user_name+ set - # @customer.expects.user_name = 'Big Boss' - # - # Expect +customer+ to have its +user_name+ set, and raise a RuntimeException when - # that happens: - # @customer.expects('user_name=', "Big Boss").raises "lost connection" - # - # Expect +evaluate+ to be passed a block, and when that happens, pass a value - # to the block (see Expectation#yields for more info): - # @cruncher.expects.evaluate.yields("some data").returns("some results") - # - # - # == Expectation Blocks - # To do special handling of expected method calls when they occur, you - # may pass a block to your expectation, like: - # @page_scraper.expects.handle_content do |address,request,status| - # assert_not_nil address, "Can't abide nil addresses" - # assert_equal "http-get", request.method, "Can only handle GET" - # assert status > 200 and status < 300, status, "Failed status" - # "Simulated results #{request.content.downcase}" - # end - # In this example, when page_scraper.handle_content is called, its - # three arguments are passed to the expectation block and evaluated - # using the above assertions. The last value in the block will be used - # as the return value for +handle_content+ - # - # You may specify arguments to the expected method call, just like any normal - # expectation, and those arguments will be pre-validated before being passed - # to the expectation block. This is useful when you know all of the - # expected values but still need to do something programmatic. - # - # If the method being invoked on the mock accepts a block, that block will be - # passed to your expectation block as the last (or only) argument. Eg, the - # convenience method +yields+ can be replaced with the more explicit: - # @cruncher.expects.evaluate do |block| - # block.call "some data" - # "some results" - # end - # - # The result value of the expectation block becomes the return value for the - # expected method call. This can be overidden by using the +returns+ method: - # @cruncher.expects.evaluate do |block| - # block.call "some data" - # "some results" - # end.returns("the actual value") - # - # Additionally, the resulting value of the expectation block is stored - # in the +block_value+ field on the expectation. If you've saved a reference - # to your expectation, you may retrieve the block value once the expectation - # has been met. - # - # evaluation_event = @cruncher.expects.evaluate do |block| - # block.call "some data" - # "some results" - # end.returns("the actual value") - # - # result = @cruncher.evaluate do |input| - # puts input # => 'some data' - # end - # # result is 'the actual value' - # - # evaluation_event.block_value # => 'some results' - # - def expects(*args, &block) - expector = Expector.new(self,@control,@expectation_builder) - # If there are no args, we return the Expector - return expector if args.empty? - # If there ARE args, we set up the expectation right here and return it - expector.send(args.shift.to_sym, *args, &block) - end - alias_method :expect, :expects - alias_method :should_receive, :expects - - # Special-case convenience: #trap sets up an expectation for a method - # that will take a block. That block, when sent to the expected method, will - # be trapped and stored in the expectation's +block_value+ field. - # The Expectation#trigger method may then be used to invoke that block. - # - # Like +expects+, the +trap+ mechanism can be followed by +raises+ or +returns+. - # - # _Unlike_ +expects+, you may not use an expectation block with +trap+. If - # the expected method takes arguments in addition to the block, they must - # be specified in the arguments to the +trap+ call itself. - # - # == Example - # - # create_mocks :address_book, :editor_form - # - # # Expect a subscription on the :person_added event for @address_book: - # person_event = @address_book.trap.subscribe(:person_added) - # - # # The runtime code would look like: - # @address_book.subscribe :person_added do |person_name| - # @editor_form.name = person_name - # end - # - # # At this point, the expectation for 'subscribe' is met and the - # # block has been captured. But we're not done: - # @editor_form.expects.name = "David" - # - # # Now invoke the block we trapped earlier: - # person_event.trigger "David" - # - # verify_mocks - def trap(*args) - Trapper.new(self,@control,ExpectationBuilder.new) - end - - def method_missing(mname,*args) #:nodoc: - block = nil - block = Proc.new if block_given? - @control.apply_method_call(self,mname,args,block) - end - - - def _control #:nodoc: - @control - end - - def _name #:nodoc: - @name - end - - # Verify that all expectations are fulfilled. NOTE: this method triggers - # validation on the _control_ for this mock, so all Mocks that share the - # MockControl with this instance will be included in the verification. - # - # Only use this method if you are managing your own Mocks and their controls. - # - # Normal usage of Hardmock doesn't require you to call this; let - # Hardmock#verify_mocks do it for you. - def _verify - @control.verify - end - end -end diff --git a/vendor/hardmock/lib/hardmock/mock_control.rb b/vendor/hardmock/lib/hardmock/mock_control.rb deleted file mode 100644 index 302ebce77..000000000 --- a/vendor/hardmock/lib/hardmock/mock_control.rb +++ /dev/null @@ -1,53 +0,0 @@ -require 'hardmock/utils' - -module Hardmock - class MockControl #:nodoc: - include Utils - attr_accessor :name - - def initialize - clear_expectations - end - - def happy? - @expectations.empty? - end - - def disappointed? - @disappointed - end - - def add_expectation(expectation) -# puts "MockControl #{self.object_id.to_s(16)} adding expectation: #{expectation}" - @expectations << expectation - end - - def apply_method_call(mock,mname,args,block) - # Are we even expecting any sort of call? - if happy? - @disappointed = true - raise ExpectationError.new("Surprise call to #{format_method_call_string(mock,mname,args)}") - end - - begin - @expectations.shift.apply_method_call(mock,mname,args,block) - rescue Exception => ouch - @disappointed = true - raise ouch - end - end - - def verify -# puts "MockControl #{self.object_id.to_s(16)} verify: happy? #{happy?}" - @disappointed = !happy? - raise VerifyError.new("Unmet expectations", @expectations) unless happy? - end - - def clear_expectations - @expectations = [] - @disappointed = false - end - - end - -end diff --git a/vendor/hardmock/lib/hardmock/stubbing.rb b/vendor/hardmock/lib/hardmock/stubbing.rb deleted file mode 100644 index 0f8a293e6..000000000 --- a/vendor/hardmock/lib/hardmock/stubbing.rb +++ /dev/null @@ -1,210 +0,0 @@ - - -# Stubbing support -# -# Stubs methods on classes and instances -# - -# Why's "metaid.rb" stuff crunched down: -class Object #:nodoc:# - def hm_metaclass #:nodoc:# - class << self - self - end - end - - def hm_meta_eval(&blk) #:nodoc:# - hm_metaclass.instance_eval(&blk) - end - - def hm_meta_def(name, &blk) #:nodoc:# - hm_meta_eval { define_method name, &blk } - end -end - - - -module Hardmock - - # == Hardmock: Stubbing and Mocking Concrete Methods - # - # Hardmock lets you stub and/or mock methods on concrete classes or objects. - # - # * To "stub" a concrete method is to rig it to return the same thing always, disregarding any arguments. - # * To "mock" a concrete method is to surplant its funcionality by delegating to a mock object who will cover this behavior. - # - # Mocked methods have their expectations considered along with all other mock object expectations. - # - # If you use stubbing or concrete mocking in the absence (or before creation) of other mocks, you need to invoke prepare_hardmock_control. - # Once verify_mocks or clear_expectaions is called, the overriden behavior in the target objects is restored. - # - # == Examples - # - # River.stubs!(:sounds_like).returns("gurgle") - # - # River.expects!(:jump).returns("splash") - # - # rogue.stubs!(:sounds_like).returns("pshshsh") - # - # rogue.expects!(:rawhide_tanning_solvents).returns("giant snapping turtles") - # - module Stubbing - # Exists only for documentation - end - - class ReplacedMethod #:nodoc:# - attr_reader :target, :method_name - - def initialize(target, method_name) - @target = target - @method_name = method_name - - Hardmock.track_replaced_method self - end - end - - class StubbedMethod < ReplacedMethod #:nodoc:# - def invoke(args) - raise @raises if @raises - @return_value - end - - def returns(stubbed_return) - @return_value = stubbed_return - end - - def raises(err) - err = RuntimeError.new(err) unless err.kind_of?(Exception) - @raises = err - end - end - - class ::Object - def stubs!(method_name) - method_name = method_name.to_s - already_stubbed = Hardmock.has_replaced_method?(self, method_name) - - stubbed_method = Hardmock::StubbedMethod.new(self, method_name) - - - unless _is_mock? or already_stubbed - if methods.include?(method_name.to_s) - hm_meta_eval do - alias_method "_hardmock_original_#{method_name}".to_sym, method_name.to_sym - end - end - end - - hm_meta_def method_name do |*args| - stubbed_method.invoke(args) - end - - stubbed_method - end - - def expects!(method_name, *args, &block) - if self._is_mock? - raise Hardmock::StubbingError, "Cannot use 'expects!(:#{method_name})' on a Mock object; try 'expects' instead" - end - - method_name = method_name.to_s - - @_my_mock = Mock.new(_my_name, $main_mock_control) if @_my_mock.nil? - - unless Hardmock.has_replaced_method?(self, method_name) - # Track the method as replaced - Hardmock::ReplacedMethod.new(self, method_name) - - # Preserver original implementation of the method by aliasing it away - if methods.include?(method_name) - hm_meta_eval do - alias_method "_hardmock_original_#{method_name}".to_sym, method_name.to_sym - end - end - - # Re-define the method to utilize our patron mock instance. - # (This global-temp-var thing is hokey but I was having difficulty generating - # code for the meta class.) - begin - $method_text_temp = %{ - def #{method_name}(*args,&block) - @_my_mock.__send__(:#{method_name}, *args, &block) - end - } - class << self - eval $method_text_temp - end - ensure - $method_text_temp = nil - end - end - - return @_my_mock.expects(method_name, *args, &block) - end - - def _is_mock? - self.kind_of?(Mock) - end - - def _my_name - self.kind_of?(Class) ? self.name : self.class.name - end - - def _clear_mock - @_my_mock = nil - end - - end - - class ::NilClass - # Use this only if you really mean it - alias_method :intentionally_stubs!, :stubs! - - # Use this only if you really mean it - alias_method :intentionally_expects!, :expects! - - # Overridden to protect against accidental nil reference self delusion - def stubs!(mname) - raise StubbingError, "Cannot stub #{mname} method on nil. (If you really mean to, try 'intentionally_stubs!')" - end - - # Overridden to protect against accidental nil reference self delusion - def expects!(mname, *args) - raise StubbingError, "Cannot mock #{mname} method on nil. (If you really mean to, try 'intentionally_expects!')" - end - end - - class << self - def track_replaced_method(replaced_method) - all_replaced_methods << replaced_method - end - - def all_replaced_methods - $all_replaced_methods ||= [] - end - - def has_replaced_method?(obj, method_name) - hits = all_replaced_methods.select do |replaced| - (replaced.target.object_id == obj.object_id) and (replaced.method_name.to_s == method_name.to_s) - end - return !hits.empty? - end - - def restore_all_replaced_methods - all_replaced_methods.each do |replaced| - unless replaced.target._is_mock? - backed_up = "_hardmock_original_#{replaced.method_name}" - if replaced.target.methods.include?(backed_up) - replaced.target.hm_meta_eval do - alias_method replaced.method_name.to_sym, backed_up.to_sym - end - end - replaced.target._clear_mock - end - end - all_replaced_methods.clear - end - end - -end - diff --git a/vendor/hardmock/lib/hardmock/trapper.rb b/vendor/hardmock/lib/hardmock/trapper.rb deleted file mode 100644 index 6aab17603..000000000 --- a/vendor/hardmock/lib/hardmock/trapper.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'test/unit/assertions' -require 'hardmock/errors' - -module Hardmock - class Trapper #:nodoc: - include Hardmock::MethodCleanout - - def initialize(mock,mock_control,expectation_builder) - @mock = mock - @mock_control = mock_control - @expectation_builder = expectation_builder - end - - def method_missing(mname, *args) - if block_given? - raise ExpectationError.new("Don't pass blocks when using 'trap' (setting exepectations for '#{mname}')") - end - - the_block = lambda { |target_block| target_block } - expectation = @expectation_builder.build_expectation( - :mock => @mock, - :method => mname, - :arguments => args, - :suppress_arguments_to_block => true, - :block => the_block) - - @mock_control.add_expectation expectation - expectation - end - end -end diff --git a/vendor/hardmock/lib/hardmock/utils.rb b/vendor/hardmock/lib/hardmock/utils.rb deleted file mode 100644 index 1740577e1..000000000 --- a/vendor/hardmock/lib/hardmock/utils.rb +++ /dev/null @@ -1,9 +0,0 @@ - -module Hardmock - module Utils #:nodoc: - def format_method_call_string(mock,mname,args) - arg_string = args.map { |a| a.inspect }.join(', ') - call_text = "#{mock._name}.#{mname}(#{arg_string})" - end - end -end diff --git a/vendor/hardmock/lib/test_unit_before_after.rb b/vendor/hardmock/lib/test_unit_before_after.rb deleted file mode 100644 index 0499e3976..000000000 --- a/vendor/hardmock/lib/test_unit_before_after.rb +++ /dev/null @@ -1,169 +0,0 @@ -require 'test/unit' -require 'test/unit/testcase' -require 'test/unit/assertions' - -module Test #:nodoc:# - module Unit #:nodoc:# - - # == TestCase Modifications - # - # Monkey-patch to provide a formal mechanism for appending actions to be executed after teardown. - # Use after_teardown to define one or more actions to be executed after teardown for ALL tests. - # - # COMING SOON? - # * (maybe?) Hooks for before_teardown, after_setup, on_error - # * (maybe?) Options for positional control, eg, after_teardown :before_other_actions - # * (maybe?) Provide tagging/filtering so action execution can be controlled specifically? - # - # == Usage - # - # Invoke TestCase.after_teardown with optional parameter, which will be invoked with a reference - # to the test instance that has just been torn down. - # - # Example: - # - # Test::Unit::TestCase.after_teardown do |test| - # test.verify_mocks - # end - # - # == Justification - # - # There are a number of tools and libraries that play fast-n-loose with setup and teardown by - # wrapping them, and by overriding method_added as a means of upholding special setup/teardown - # behavior, usually by re-wrapping newly defined user-level setup/teardown methods. - # mocha and active_record/fixtures (and previously, hardmock) will fight for this - # territory with often unpredictable results. - # - # We wouldn't have to battle if Test::Unit provided a formal pre- and post- hook mechanism. - # - class TestCase - - class << self - - # Define an action to be run after teardown. Subsequent calls result in - # multiple actions. The block will be given a reference to the test - # being executed. - # - # Example: - # - # Test::Unit::TestCase.after_teardown do |test| - # test.verify_mocks - # end - def after_teardown(&block) - post_teardown_actions << block - end - - # Used internally. Access the list of post teardown actions for to be - # used by all tests. - def post_teardown_actions - @@post_teardown_actions ||= [] - end - - # Define an action to be run before setup. Subsequent calls result in - # multiple actions, EACH BEING PREPENDED TO THE PREVIOUS. - # The block will be given a reference to the test being executed. - # - # Example: - # - # Test::Unit::TestCase.before_setup do |test| - # test.prepare_hardmock_control - # end - def before_setup(&block) - pre_setup_actions.unshift block - end - - # Used internally. Access the list of post teardown actions for to be - # used by all tests. - def pre_setup_actions - @@pre_setup_actions ||= [] - end - end - - # OVERRIDE: This is a reimplementation of the default "run", updated to - # execute actions after teardown. - def run(result) - yield(STARTED, name) - @_result = result - begin - execute_pre_setup_actions(self) - setup - __send__(@method_name) - rescue Test::Unit::AssertionFailedError => e - add_failure(e.message, auxiliary_backtrace_filter(e.backtrace)) - rescue Exception - raise if should_passthru_exception($!) # See implementation; this is for pre-1.8.6 compat - add_error($!) - ensure - begin - teardown - rescue Test::Unit::AssertionFailedError => e - add_failure(e.message, auxiliary_backtrace_filter(e.backtrace)) - rescue Exception - raise if should_passthru_exception($!) # See implementation; this is for pre-1.8.6 compat - add_error($!) - ensure - execute_post_teardown_actions(self) - end - end - result.add_run - yield(FINISHED, name) - end - - private - - # Run through the after_teardown actions, treating failures and errors - # in the same way that "run" does: they are reported, and the remaining - # actions are executed. - def execute_post_teardown_actions(test_instance) - self.class.post_teardown_actions.each do |action| - begin - action.call test_instance - rescue Test::Unit::AssertionFailedError => e - add_failure(e.message, auxiliary_backtrace_filter(e.backtrace)) - rescue Exception - raise if should_passthru_exception($!) - add_error($!) - end - end - end - - # Run through the before_setup actions. - # Failures or errors cause execution to stop. - def execute_pre_setup_actions(test_instance) - self.class.pre_setup_actions.each do |action| -# begin - action.call test_instance -# rescue Test::Unit::AssertionFailedError => e -# add_failure(e.message, auxiliary_backtrace_filter(e.backtrace)) -# rescue Exception -# raise if should_passthru_exception($!) -# add_error($!) -# end - end - end - - # Make sure that this extension doesn't show up in failure backtraces - def auxiliary_backtrace_filter(trace) - trace.reject { |x| x =~ /test_unit_before_after/ } - end - - # Is the given error of the type that we allow to fly out (rather than catching it)? - def should_passthru_exception(ex) - return passthrough_exception_types.include?($!.class) - end - - # Provide a list of exception types that are to be allowed to explode out. - # Pre-ruby-1.8.6 doesn't use this functionality, so the PASSTHROUGH_EXCEPTIONS - # constant won't be defined. This methods defends against that and returns - # an empty list instead. - def passthrough_exception_types - begin - return PASSTHROUGH_EXCEPTIONS - rescue NameError - # older versions of test/unit do not have PASSTHROUGH_EXCEPTIONS constant - return [] - end - end - end - end -end diff --git a/vendor/hardmock/rake_tasks/rdoc.rake b/vendor/hardmock/rake_tasks/rdoc.rake deleted file mode 100644 index 6a6d79f5a..000000000 --- a/vendor/hardmock/rake_tasks/rdoc.rake +++ /dev/null @@ -1,19 +0,0 @@ -require 'rake/rdoctask' -require File.expand_path(File.dirname(__FILE__) + "/rdoc_options.rb") - -namespace :doc do - - desc "Generate RDoc documentation" - Rake::RDocTask.new { |rdoc| - rdoc.rdoc_dir = 'doc' - rdoc.title = "Hardmock: Strict expectation-based mock object library " - add_rdoc_options(rdoc.options) - rdoc.rdoc_files.include('lib/**/*.rb', 'README','CHANGES','LICENSE') - } - - task :show => [ 'doc:rerdoc' ] do - sh "open doc/index.html" - end - -end - diff --git a/vendor/hardmock/rake_tasks/rdoc_options.rb b/vendor/hardmock/rake_tasks/rdoc_options.rb deleted file mode 100644 index 85bf4ce7f..000000000 --- a/vendor/hardmock/rake_tasks/rdoc_options.rb +++ /dev/null @@ -1,4 +0,0 @@ - -def add_rdoc_options(options) - options << '--line-numbers' << '--inline-source' << '--main' << 'README' << '--title' << 'Hardmock' -end diff --git a/vendor/hardmock/rake_tasks/test.rake b/vendor/hardmock/rake_tasks/test.rake deleted file mode 100644 index 85a3753d0..000000000 --- a/vendor/hardmock/rake_tasks/test.rake +++ /dev/null @@ -1,22 +0,0 @@ -require 'rake/testtask' - -namespace :test do - - desc "Run unit tests" - Rake::TestTask.new("units") { |t| - t.libs << "test" - t.pattern = 'test/unit/*_test.rb' - t.verbose = true - } - - desc "Run functional tests" - Rake::TestTask.new("functional") { |t| - t.libs << "test" - t.pattern = 'test/functional/*_test.rb' - t.verbose = true - } - - desc "Run all the tests" - task :all => [ 'test:units', 'test:functional' ] - -end diff --git a/vendor/hardmock/test/functional/assert_error_test.rb b/vendor/hardmock/test/functional/assert_error_test.rb deleted file mode 100644 index e4b35cf3b..000000000 --- a/vendor/hardmock/test/functional/assert_error_test.rb +++ /dev/null @@ -1,52 +0,0 @@ -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'assert_error' - -class AssertErrorTest < Test::Unit::TestCase - - it "specfies an error type and message that should be raised" do - assert_error RuntimeError, "Too funky" do - raise RuntimeError.new("Too funky") - end - end - - it "flunks if the error message is wrong" do - err = assert_raise Test::Unit::AssertionFailedError do - assert_error RuntimeError, "not good" do - raise RuntimeError.new("Too funky") - end - end - assert_match(/not good/i, err.message) - assert_match(/too funky/i, err.message) - end - - it "flunks if the error type is wrong" do - err = assert_raise Test::Unit::AssertionFailedError do - assert_error StandardError, "Too funky" do - raise RuntimeError.new("Too funky") - end - end - assert_match(/StandardError/i, err.message) - assert_match(/RuntimeError/i, err.message) - end - - it "can match error message text using a series of Regexps" do - assert_error StandardError, /too/i, /funky/i do - raise StandardError.new("Too funky") - end - end - - it "flunks if the error message doesn't match all the Regexps" do - err = assert_raise Test::Unit::AssertionFailedError do - assert_error StandardError, /way/i, /too/i, /funky/i do - raise StandardError.new("Too funky") - end - end - assert_match(/way/i, err.message) - end - - it "can operate without any message specification" do - assert_error StandardError do - raise StandardError.new("ooof") - end - end -end diff --git a/vendor/hardmock/test/functional/auto_verify_test.rb b/vendor/hardmock/test/functional/auto_verify_test.rb deleted file mode 100644 index 1b005bdaa..000000000 --- a/vendor/hardmock/test/functional/auto_verify_test.rb +++ /dev/null @@ -1,178 +0,0 @@ -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'fileutils' - -class AutoVerifyTest < Test::Unit::TestCase - - def setup - @expect_unmet_expectations = true - end - - def teardown - remove_temp_test_file - end - - # - # TESTS - # - - it "auto-verifies all mocks in teardown" do - write_and_execute_test - end - - it "auto-verifies even if user defines own teardown" do - @teardown_code =<<-EOM - def teardown - # just in the way - end - EOM - write_and_execute_test - end - - should "not obscure normal failures when verification fails" do - @test_code =<<-EOM - def test_setup_doomed_expectation - create_mock :automobile - @automobile.expects.start - flunk "natural failure" - end - EOM - @expect_failures = 1 - write_and_execute_test - end - - should "not skip user-defined teardown when verification fails" do - @teardown_code =<<-EOM - def teardown - puts "User teardown" - end - EOM - write_and_execute_test - assert_output_contains(/User teardown/) - end - - it "is quiet when verification is ok" do - @test_code =<<-EOM - def test_ok - create_mock :automobile - @automobile.expects.start - @automobile.start - end - EOM - @teardown_code =<<-EOM - def teardown - puts "User teardown" - end - EOM - @expect_unmet_expectations = false - @expect_failures = 0 - @expect_errors = 0 - write_and_execute_test - assert_output_contains(/User teardown/) - end - - should "auto-verify even if user teardown explodes" do - @teardown_code =<<-EOM - def teardown - raise "self destruct" - end - EOM - @expect_errors = 2 - write_and_execute_test - assert_output_contains(/self destruct/) - end - - it "plays nice with inherited teardown methods" do - @full_code ||=<<-EOTEST - require File.expand_path(File.dirname(__FILE__) + "/../test_helper") - require 'hardmock' - class Test::Unit::TestCase - def teardown - puts "Test helper teardown" - end - end - class DummyTest < Test::Unit::TestCase - def test_prepare_to_die - create_mock :automobile - @automobile.expects.start - end - end - EOTEST - write_and_execute_test - assert_output_contains(/Test helper teardown/) - end - - # - # HELPERS - # - - def temp_test_file - File.expand_path(File.dirname(__FILE__) + "/tear_down_verification_test.rb") - end - - def run_test(tbody) - File.open(temp_test_file,"w") { |f| f.print(tbody) } - @test_output = `ruby #{temp_test_file} 2>&1` - end - - def formatted_test_output - if @test_output - @test_output.split(/\n/).map { |line| "> #{line}" }.join("\n") - else - "(NO TEST OUTPUT!)" - end - end - - def remove_temp_test_file - FileUtils::rm_f temp_test_file - end - - def assert_results(h) - if @test_output !~ /#{h[:tests]} tests, [0-9]+ assertions, #{h[:failures]} failures, #{h[:errors]} errors/ - flunk "Test results didn't match #{h.inspect}:\n#{formatted_test_output}" - end - end - - def assert_output_contains(*patterns) - patterns.each do |pattern| - if @test_output !~ pattern - flunk "Test output didn't match #{pattern.inspect}:\n#{formatted_test_output}" - end - end - end - - def assert_output_doesnt_contain(*patterns) - patterns.each do |pattern| - assert @test_output !~ pattern, "Output shouldn't match #{pattern.inspect} but it does." - end - end - - def write_and_execute_test - @test_code ||=<<-EOM - def test_setup_doomed_expectation - create_mock :automobile - @automobile.expects.start - end - EOM - @full_code ||=<<-EOTEST - require File.expand_path(File.dirname(__FILE__) + "/../test_helper") - require 'hardmock' - class DummyTest < Test::Unit::TestCase - #{@teardown_code} - #{@test_code} - end - EOTEST - run_test @full_code - - if @expect_unmet_expectations - assert_output_contains(/unmet expectations/i, /automobile/, /start/) - else - assert_output_doesnt_contain(/unmet expectations/i, /automobile/, /start/) - end - - @expect_tests ||= 1 - @expect_failures ||= 0 - @expect_errors ||= 1 - assert_results :tests => @expect_tests, :failures => @expect_failures, :errors => @expect_errors - end - -end diff --git a/vendor/hardmock/test/functional/direct_mock_usage_test.rb b/vendor/hardmock/test/functional/direct_mock_usage_test.rb deleted file mode 100644 index dcf2b2abe..000000000 --- a/vendor/hardmock/test/functional/direct_mock_usage_test.rb +++ /dev/null @@ -1,396 +0,0 @@ -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'hardmock' - -class DirectMockUsageTest < Test::Unit::TestCase - - def setup - @bird = Mock.new('bird') - end - - def teardown - end - - # - # TESTS - # - - it "raises VerifyError if expected method not called" do - @bird.expects.flap_flap - - err = assert_raise VerifyError do - @bird._verify - end - assert_match(/unmet expectations/i, err.message) - end - - should "not raise when expected calls are made in order" do - @bird.expects.flap_flap - @bird.expects.bang - @bird.expects.plop - - @bird.flap_flap - @bird.bang - @bird.plop - - @bird._verify - end - - it "raises ExpectationError when unexpected method are called" do - @bird.expects.flap_flap - - err = assert_raise ExpectationError do - @bird.shoot - end - assert_match(/wrong method/i, err.message) - end - - it "raises ExpectationError on bad arguments" do - @bird.expects.flap_flap(:swoosh) - - err = assert_raise ExpectationError do - @bird.flap_flap(:rip) - end - assert_match(/wrong arguments/i, err.message) - end - - it "raises VerifyError when not all expected methods are called" do - @bird.expects.flap_flap - @bird.expects.bang - @bird.expects.plop - - @bird.flap_flap - - err = assert_raise VerifyError do - @bird._verify - end - assert_match(/unmet expectations/i, err.message) - end - - it "raises ExpectationError when calls are made out of order" do - @bird.expects.flap_flap - @bird.expects.bang - @bird.expects.plop - - @bird.flap_flap - err = assert_raise ExpectationError do - @bird.plop - end - assert_match(/wrong method/i, err.message) - end - - it "returns the configured value" do - @bird.expects.plop.returns(':P') - assert_equal ':P', @bird.plop - @bird._verify - - @bird.expects.plop.returns(':x') - assert_equal ':x', @bird.plop - @bird._verify - end - - it "returns nil when no return is specified" do - @bird.expects.plop - assert_nil @bird.plop - @bird._verify - end - - it "raises the configured exception" do - err = RuntimeError.new('shaq') - @bird.expects.plop.raises(err) - actual_err = assert_raise RuntimeError do - @bird.plop - end - assert_same err, actual_err, 'should be the same error' - @bird._verify - end - - it "raises a RuntimeError when told to 'raise' a string" do - @bird.expects.plop.raises('shaq') - err = assert_raise RuntimeError do - @bird.plop - end - assert_match(/shaq/i, err.message) - @bird._verify - end - - it "raises a default RuntimeError" do - @bird.expects.plop.raises - err = assert_raise RuntimeError do - @bird.plop - end - assert_match(/error/i, err.message) - @bird._verify - end - - it "is quiet when correct arguments given" do - thing = Object.new - @bird.expects.plop(:big,'one',thing) - @bird.plop(:big,'one',thing) - @bird._verify - end - - it "raises ExpectationError when wrong number of arguments specified" do - thing = Object.new - @bird.expects.plop(:big,'one',thing) - err = assert_raise ExpectationError do - # more - @bird.plop(:big,'one',thing,:other) - end - assert_match(/wrong arguments/i, err.message) - @bird._verify - - @bird.expects.plop(:big,'one',thing) - err = assert_raise ExpectationError do - # less - @bird.plop(:big,'one') - end - assert_match(/wrong arguments/i, err.message) - @bird._verify - - @bird.expects.plop - err = assert_raise ExpectationError do - # less - @bird.plop(:big) - end - assert_match(/wrong arguments/i, err.message) - @bird._verify - end - - it "raises ExpectationError when arguments don't match" do - thing = Object.new - @bird.expects.plop(:big,'one',thing) - err = assert_raise ExpectationError do - @bird.plop(:big,'two',thing,:other) - end - assert_match(/wrong arguments/i, err.message) - @bird._verify - end - - it "can use a block for custom reactions" do - mitt = nil - @bird.expects.plop { mitt = :ball } - assert_nil mitt - @bird.plop - assert_equal :ball, mitt, 'didnt catch the ball' - @bird._verify - - @bird.expects.plop { raise 'ball' } - err = assert_raise RuntimeError do - @bird.plop - end - assert_match(/ball/i, err.message) - @bird._verify - end - - it "passes mock-call arguments to the expectation block" do - ball = nil - mitt = nil - @bird.expects.plop {|arg1,arg2| - ball = arg1 - mitt = arg2 - } - assert_nil ball - assert_nil mitt - @bird.plop(:ball,:mitt) - assert_equal :ball, ball - assert_equal :mitt, mitt - @bird._verify - end - - it "validates arguments if specified in addition to a block" do - ball = nil - mitt = nil - @bird.expects.plop(:ball,:mitt) {|arg1,arg2| - ball = arg1 - mitt = arg2 - } - assert_nil ball - assert_nil mitt - @bird.plop(:ball,:mitt) - assert_equal :ball, ball - assert_equal :mitt, mitt - @bird._verify - - ball = nil - mitt = nil - @bird.expects.plop(:bad,:stupid) {|arg1,arg2| - ball = arg1 - mitt = arg2 - } - assert_nil ball - assert_nil mitt - err = assert_raise ExpectationError do - @bird.plop(:ball,:mitt) - end - assert_match(/wrong arguments/i, err.message) - assert_nil ball - assert_nil mitt - @bird._verify - - ball = nil - mitt = nil - @bird.expects.plop(:ball,:mitt) {|arg1,arg2| - ball = arg1 - mitt = arg2 - } - assert_nil ball - assert_nil mitt - err = assert_raise ExpectationError do - @bird.plop(:ball) - end - assert_match(/wrong arguments/i, err.message) - assert_nil ball - assert_nil mitt - @bird._verify - end - - it "passes runtime blocks to the expectation block as the final argument" do - runtime_block_called = false - got_arg = nil - - # Eg, bird expects someone to subscribe to :tweet using the 'when' method - @bird.expects.when(:tweet) { |arg1, block| - got_arg = arg1 - block.call - } - - @bird.when(:tweet) do - runtime_block_called = true - end - - assert_equal :tweet, got_arg, "Wrong arg" - assert runtime_block_called, "The runtime block should have been invoked by the user block" - - @bird.expects.when(:warnk) { |e,blk| } - - err = assert_raise ExpectationError do - @bird.when(:honk) { } - end - assert_match(/wrong arguments/i, err.message) - - @bird._verify - end - - it "passes the runtime block to the expectation block as sole argument if no other args come into play" do - runtime_block_called = false - @bird.expects.subscribe { |block| block.call } - @bird.subscribe do - runtime_block_called = true - end - assert runtime_block_called, "The runtime block should have been invoked by the user block" - end - - it "provides nil as final argument if expectation block seems to want a block" do - invoked = false - @bird.expects.kablam(:scatter) { |shot,block| - assert_equal :scatter, shot, "Wrong shot" - assert_nil block, "The expectation block should get a nil block when user neglects to pass one" - invoked = true - } - @bird.kablam :scatter - assert invoked, "Expectation block not invoked" - - @bird._verify - end - - it "can set explicit return after an expectation block" do - got = nil - @bird.expects.kablam(:scatter) { |shot| - got = shot - }.returns(:death) - - val = @bird.kablam :scatter - assert_equal :death, val, "Wrong return value" - assert_equal :scatter, got, "Wrong argument" - @bird._verify - end - - it "can raise after an expectation block" do - got = nil - @bird.expects.kablam(:scatter) do |shot| - got = shot - end.raises "hell" - - err = assert_raise RuntimeError do - @bird.kablam :scatter - end - assert_match(/hell/i, err.message) - - @bird._verify - end - - it "stores the semantic value of the expectation block after it executes" do - expectation = @bird.expects.kablam(:slug) { |shot| - "The shot was #{shot}" - } - - assert_not_nil expectation, "Expectation nil" - assert_nil expectation.block_value, "Block value should start out nil" - - ret_val = @bird.kablam :slug - - assert_equal "The shot was slug", expectation.block_value - assert_equal "The shot was slug", ret_val, "Block value should also be used for return" - - @bird._verify - end - - - it "uses the value of the expectation block as the default return value" do - @bird.expects.kablam(:scatter) { |shot| - "The shot was #{shot}" - } - val = @bird.kablam :scatter - assert_equal "The shot was scatter", val, "Wrong return value" - @bird._verify - end - - it "returns the Expectation even if 'returns' is used" do - expectation = @bird.expects.kablam(:slug) { |shot| - "The shot was #{shot}" - }.returns :hosed - - assert_not_nil expectation, "Expectation nil" - assert_nil expectation.block_value, "Block value should start out nil" - - ret_val = @bird.kablam :slug - - assert_equal "The shot was slug", expectation.block_value - assert_equal :hosed, ret_val, "Block value should also be used for return" - - @bird._verify - end - - it "returns the Expectation even if 'raises' is used" do - expectation = @bird.expects.kablam(:slug) { |shot| - "The shot was #{shot}" - }.raises "aiee!" - - assert_not_nil expectation, "Expectation nil" - assert_nil expectation.block_value, "Block value should start out nil" - - err = assert_raise RuntimeError do - @bird.kablam :slug - end - assert_match(/aiee!/i, err.message) - assert_equal "The shot was slug", expectation.block_value - @bird._verify - end - - - it "supports assignment-style methods" do - @bird.expects.size = "large" - @bird.size = "large" - @bird._verify - end - - it "supports assignments and raising (using explicit-method syntax)" do - @bird.expects('size=','large').raises "boom" - - err = assert_raise RuntimeError do - @bird.size = "large" - end - assert_match(/boom/i, err.message) - end - -end diff --git a/vendor/hardmock/test/functional/hardmock_test.rb b/vendor/hardmock/test/functional/hardmock_test.rb deleted file mode 100644 index 159d36968..000000000 --- a/vendor/hardmock/test/functional/hardmock_test.rb +++ /dev/null @@ -1,434 +0,0 @@ -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'hardmock' -require 'assert_error' - -class HardmockTest < Test::Unit::TestCase - - # - # TESTS - # - - it "conveniently creates mocks using create_mock and create_mocks" do - - h = create_mock :donkey - assert_equal [ :donkey ], h.keys - - assert_mock_exists :donkey - assert_same @donkey, h[:donkey] - - assert_equal [ :donkey ], @all_mocks.keys, "Wrong keyset for @all_mocks" - - h2 = create_mocks :cat, 'dog' # symbol/string indifference at this level - assert_equal [:cat,:dog].to_set, h2.keys.to_set, "Wrong keyset for second hash" - assert_equal [:cat,:dog,:donkey].to_set, @all_mocks.keys.to_set, "@all_mocks wrong" - - assert_mock_exists :cat - assert_same @cat, h2[:cat] - assert_mock_exists :dog - assert_same @dog, h2[:dog] - - assert_mock_exists :donkey - end - - it "provides literal 'expects' syntax" do - assert_nil @order, "Should be no @order yet" - create_mock :order - assert_not_nil @order, "@order should be built" - - # Setup an expectation - @order.expects.update_stuff :key1 => 'val1', :key2 => 'val2' - - # Use the mock - @order.update_stuff :key1 => 'val1', :key2 => 'val2' - - # Verify - verify_mocks - - # See that it's ok to do it again - verify_mocks - end - - it "supports 'with' for specifying argument expectations" do - create_mocks :car - @car.expects(:fill).with('gas','booze') - @car.fill('gas', 'booze') - verify_mocks - end - - it "supports several mocks at once" do - create_mocks :order_builder, :order, :customer - - @order_builder.expects.create_new_order.returns @order - @customer.expects.account_number.returns(1234) - @order.expects.account_no = 1234 - @order.expects.save! - - # Run "the code" - o = @order_builder.create_new_order - o.account_no = @customer.account_number - o.save! - - verify_mocks - end - - it "enforces inter-mock call ordering" do - create_mocks :order_builder, :order, :customer - - @order_builder.expects.create_new_order.returns @order - @customer.expects.account_number.returns(1234) - @order.expects.account_no = 1234 - @order.expects.save! - - # Run "the code" - o = @order_builder.create_new_order - err = assert_raise ExpectationError do - o.save! - end - assert_match(/wrong object/i, err.message) - assert_match(/order.save!/i, err.message) - assert_match(/customer.account_number/i, err.message) - - assert_error VerifyError, /unmet expectations/i do - verify_mocks - end - end - - class UserPresenter - def initialize(args) - view = args[:view] - model = args[:model] - model.when :data_changes do - view.user_name = model.user_name - end - view.when :user_edited do - model.user_name = view.user_name - end - end - end - - it "makes MVP testing simple" do - mox = create_mocks :model, :view - - data_change = @model.expects.when(:data_changes) { |evt,block| block } - user_edit = @view.expects.when(:user_edited) { |evt,block| block } - - UserPresenter.new mox - - # Expect user name transfer from model to view - @model.expects.user_name.returns 'Da Croz' - @view.expects.user_name = 'Da Croz' - # Trigger data change event in model - data_change.block_value.call - - # Expect user name transfer from view to model - @view.expects.user_name.returns '6:8' - @model.expects.user_name = '6:8' - # Trigger edit event in view - user_edit.block_value.call - - verify_mocks - end - - it "continues to function after verify, if verification error is controlled" do - mox = create_mocks :model, :view - data_change = @model.expects.when(:data_changes) { |evt,block| block } - user_edit = @view.expects.when(:user_edited) { |evt,block| block } - UserPresenter.new mox - - # Expect user name transfer from model to view - @model.expects.user_name.returns 'Da Croz' - @view.expects.user_name = 'Da Croz' - - assert_error ExpectationError, /model.monkey_wrench/i do - @model.monkey_wrench - end - - # This should raise because of unmet expectations - assert_error VerifyError, /unmet expectations/i, /user_name/i do - verify_mocks - end - - # See that the non-forced verification remains quiet - assert_nothing_raised VerifyError do - verify_mocks(false) - end - - @model.expects.never_gonna_happen - - assert_error VerifyError, /unmet expectations/i, /never_gonna_happen/i do - verify_mocks - end - end - - class UserPresenterBroken - def initialize(args) - view = args[:view] - model = args[:model] - model.when :data_changes do - view.user_name = model.user_name - end - # no view stuff, will break appropriately - end - end - - it "flunks for typical Presenter constructor wiring failure" do - mox = create_mocks :model, :view - - data_change = @model.expects.when(:data_changes) { |evt,block| block } - user_edit = @view.expects.when(:user_edited) { |evt,block| block } - - UserPresenterBroken.new mox - - err = assert_raise VerifyError do - verify_mocks - end - assert_match(/unmet expectations/i, err.message) - assert_match(/view.when\(:user_edited\)/i, err.message) - - end - - it "provides convenient event-subscription trap syntax for MVP testing" do - mox = create_mocks :model, :view - - data_change = @model.trap.when(:data_changes) - user_edit = @view.trap.when(:user_edited) - - UserPresenter.new mox - - # Expect user name transfer from model to view - @model.expects.user_name.returns 'Da Croz' - @view.expects.user_name = 'Da Croz' - # Trigger data change event in model - data_change.trigger - - # Expect user name transfer from view to model - @view.expects.user_name.returns '6:8' - @model.expects.user_name = '6:8' - # Trigger edit event in view - user_edit.trigger - - verify_mocks - end - - it "raises if you try to pass an expectation block to 'trap'" do - create_mock :model - assert_error Hardmock::ExpectationError, /blocks/i, /trap/i do - @model.trap.when(:some_event) do raise "huh?" end - end - end - - class Grinder - def initialize(objects) - @chute = objects[:chute] - @bucket = objects[:bucket] - @blade = objects[:blade] - end - - def grind(slot) - @chute.each_bean(slot) do |bean| - @bucket << @blade.chop(bean) - end - end - end - - it "lets you write clear iteration-oriented expectations" do - grinder = Grinder.new create_mocks(:blade, :chute, :bucket) - - # Style 1: assertions on method args is done explicitly in block - @chute.expects.each_bean { |slot,block| - assert_equal :side_slot, slot, "Wrong slot" - block.call :bean1 - block.call :bean2 - } - - @blade.expects.chop(:bean1).returns(:grounds1) - @bucket.expects('<<', :grounds1) - - @blade.expects.chop(:bean2).returns(:grounds2) - @bucket.expects('<<', :grounds2) - - # Run "the code" - grinder.grind(:side_slot) - - verify_mocks - - # Style 2: assertions on method arguments done implicitly in the expectation code - @chute.expects.each_bean(:main_slot) { |slot,block| - block.call :bean3 - } - @blade.expects.chop(:bean3).returns(:grounds3) - @bucket.expects('<<', :grounds3) - grinder.grind :main_slot - verify_mocks - end - - it "further supports iteration testing using 'yield'" do - grinder = Grinder.new create_mocks(:blade, :chute, :bucket) - - @chute.expects.each_bean(:side_slot).yields :bean1, :bean2 - - @blade.expects.chop(:bean1).returns(:grounds1) - @bucket.expects('<<', :grounds1) - - @blade.expects.chop(:bean2).returns(:grounds2) - @bucket.expects('<<', :grounds2) - - grinder.grind :side_slot - - verify_mocks - end - - class HurtLocker - attr_reader :caught - def initialize(opts) - @locker = opts[:locker] - @store = opts[:store] - end - - def do_the_thing(area,data) - @locker.with_lock(area) do - @store.eat(data) - end - rescue => oops - @caught = oops - end - end - - it "makes mutex-style locking scenarios easy to test" do - hurt = HurtLocker.new create_mocks(:locker, :store) - - @locker.expects.with_lock(:main).yields - @store.expects.eat("some info") - - hurt.do_the_thing(:main, "some info") - - verify_mocks - end - - it "makes it easy to simulate error in mutex-style locking scenarios" do - hurt = HurtLocker.new create_mocks(:locker, :store) - err = StandardError.new('fmshooop') - @locker.expects.with_lock(:main).yields - @store.expects.eat("some info").raises(err) - - hurt.do_the_thing(:main, "some info") - - assert_same err, hurt.caught, "Expected that error to be handled internally" - verify_mocks - end - - it "actually returns 'false' instead of nil when mocking boolean return values" do - create_mock :car - @car.expects.ignition_on?.returns(true) - assert_equal true, @car.ignition_on?, "Should be true" - @car.expects.ignition_on?.returns(false) - assert_equal false, @car.ignition_on?, "Should be false" - end - - it "can mock most methods inherited from object using literal syntax" do - target_methods = %w|id clone display dup eql? ==| - create_mock :foo - target_methods.each do |m| - eval %{@foo.expects(m, "some stuff")} - eval %{@foo.#{m} "some stuff"} - end - end - - it "provides 'expect' as an alias for 'expects'" do - create_mock :foo - @foo.expect.boomboom - @foo.boomboom - verify_mocks - end - - it "provides 'should_receive' as an alias for 'expects'" do - create_mock :foo - @foo.should_receive.boomboom - @foo.boomboom - verify_mocks - end - - it "provides 'and_return' as an alias for 'returns'" do - create_mock :foo - @foo.expects(:boomboom).and_return :brick - assert_equal :brick, @foo.boomboom - verify_mocks - end - - it "does not interfere with a core subset of Object methods" do - create_mock :foo - @foo.method(:inspect) - @foo.inspect - @foo.to_s - @foo.instance_variables - @foo.instance_eval("") - verify_mocks - end - - it "can raise errors from within an expectation block" do - create_mock :cat - @cat.expects.meow do |arg| - assert_equal "mix", arg - raise 'HAIRBALL' - end - assert_error RuntimeError, 'HAIRBALL' do - @cat.meow("mix") - end - end - - it "can raise errors AFTER an expectation block" do - create_mock :cat - @cat.expects.meow do |arg| - assert_equal "mix", arg - end.raises('HAIRBALL') - assert_error RuntimeError, 'HAIRBALL' do - @cat.meow("mix") - end - end - - it "raises an immediate error if a mock is created with a nil name (common mistake: create_mock @cat)" do - # I make this mistake all the time: Typing in an instance var name instead of a symbol in create_mocks. - # When you do that, you're effectively passing nil(s) in as mock names. - assert_error ArgumentError, /'nil' is not a valid name for a mock/ do - create_mocks @apples, @oranges - end - end - - it "overrides 'inspect' to make nice output" do - create_mock :hay_bailer - assert_equal "", @hay_bailer.inspect, "Wrong output from 'inspect'" - end - - it "raises if prepare_hardmock_control is invoked after create_mocks, or more than once" do - create_mock :hi_there - create_mocks :another, :one - assert_error RuntimeError, /already setup/ do - prepare_hardmock_control - end - end - - should "support alias verify_hardmocks" do - create_mock :tree - @tree.expects(:grow) - assert_error VerifyError, /unmet/i do - verify_hardmocks - end - end - - # - # HELPERS - # - - def assert_mock_exists(name) - assert_not_nil @all_mocks, "@all_mocks not here yet" - mo = @all_mocks[name] - assert_not_nil mo, "Mock '#{name}' not in @all_mocks" - assert_kind_of Mock, mo, "Wrong type of object, wanted a Mock" - assert_equal name.to_s, mo._name, "Mock '#{name}' had wrong name" - ivar = self.instance_variable_get("@#{name}") - assert_not_nil ivar, "Mock '#{name}' not set as ivar" - assert_same mo, ivar, "Mock '#{name}' ivar not same as instance in @all_mocks" - assert_same @main_mock_control, mo._control, "Mock '#{name}' doesn't share the main mock control" - end -end - diff --git a/vendor/hardmock/test/functional/stubbing_test.rb b/vendor/hardmock/test/functional/stubbing_test.rb deleted file mode 100644 index f07a6708e..000000000 --- a/vendor/hardmock/test/functional/stubbing_test.rb +++ /dev/null @@ -1,479 +0,0 @@ -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'hardmock' -require 'assert_error' - -class StubbingTest < Test::Unit::TestCase - - # - # TESTS - # - - it "stubs a class method (and un-stubs after reset_stubs)" do - assert_equal "stones and gravel", Concrete.pour - assert_equal "glug glug", Jug.pour - - Concrete.stubs!(:pour).returns("dust and plaster") - - 3.times do - assert_equal "dust and plaster", Concrete.pour - end - - assert_equal "glug glug", Jug.pour, "Jug's 'pour' method broken" - assert_equal "stones and gravel", Concrete._hardmock_original_pour, "Original 'pour' method not aliased" - - assert_equal "For roads", Concrete.describe, "'describe' method broken" - - reset_stubs - - assert_equal "stones and gravel", Concrete.pour, "'pour' method not restored" - assert_equal "For roads", Concrete.describe, "'describe' method broken after verify" - - end - - it "stubs several class methods" do - Concrete.stubs!(:pour).returns("sludge") - Concrete.stubs!(:describe).returns("awful") - Jug.stubs!(:pour).returns("milk") - - assert_equal "sludge", Concrete.pour - assert_equal "awful", Concrete.describe - assert_equal "milk", Jug.pour - - reset_stubs - - assert_equal "stones and gravel", Concrete.pour - assert_equal "For roads", Concrete.describe - assert_equal "glug glug", Jug.pour - end - - it "stubs instance methods" do - slab = Concrete.new - assert_equal "bonk", slab.hit - - slab.stubs!(:hit).returns("slap") - assert_equal "slap", slab.hit, "'hit' not stubbed" - - reset_stubs - - assert_equal "bonk", slab.hit, "'hit' not restored" - end - - it "stubs instance methods without breaking class methods or other instances" do - slab = Concrete.new - scrape = Concrete.new - assert_equal "an instance", slab.describe - assert_equal "an instance", scrape.describe - assert_equal "For roads", Concrete.describe - - slab.stubs!(:describe).returns("new instance describe") - assert_equal "new instance describe", slab.describe, "'describe' on instance not stubbed" - assert_equal "an instance", scrape.describe, "'describe' on 'scrape' instance broken" - assert_equal "For roads", Concrete.describe, "'describe' class method broken" - - reset_stubs - - assert_equal "an instance", slab.describe, "'describe' instance method not restored" - assert_equal "an instance", scrape.describe, "'describe' on 'scrape' instance broken after restore" - assert_equal "For roads", Concrete.describe, "'describe' class method broken after restore" - end - - should "allow stubbing of nonexistant class methods" do - Concrete.stubs!(:funky).returns('juice') - assert_equal 'juice', Concrete.funky - end - - should "allow stubbing of nonexistant instance methods" do - chunk = Concrete.new - chunk.stubs!(:shark).returns('bite') - assert_equal 'bite', chunk.shark - end - - should "allow re-stubbing" do - Concrete.stubs!(:pour).returns("one") - assert_equal "one", Concrete.pour - - Concrete.stubs!(:pour).raises("hell") - assert_error RuntimeError, /hell/ do - Concrete.pour - end - - Concrete.stubs!(:pour).returns("two") - assert_equal "two", Concrete.pour - - reset_stubs - - assert_equal "stones and gravel", Concrete.pour - end - - it "does nothing with a runtime block when simply stubbing" do - slab = Concrete.new - slab.stubs!(:hit) do |nothing| - raise "BOOOMM!" - end - slab.hit - reset_stubs - end - - it "can raise errors from a stubbed method" do - Concrete.stubs!(:pour).raises(StandardError.new("no!")) - assert_error StandardError, /no!/ do - Concrete.pour - end - end - - it "provides string syntax for convenient raising of RuntimeErrors" do - Concrete.stubs!(:pour).raises("never!") - assert_error RuntimeError, /never!/ do - Concrete.pour - end - end - - - # - # Per-method mocking on classes or instances - # - - it "mocks specific methods on existing classes, and returns the class method to normal after verification" do - - assert_equal "stones and gravel", Concrete.pour, "Concrete.pour is already messed up" - - Concrete.expects!(:pour).returns("ALIGATORS") - assert_equal "ALIGATORS", Concrete.pour - - verify_mocks - assert_equal "stones and gravel", Concrete.pour, "Concrete.pour not restored" - end - - it "flunks if expected class method is not invoked" do - - Concrete.expects!(:pour).returns("ALIGATORS") - assert_error(Hardmock::VerifyError, /Concrete.pour/, /unmet expectations/i) do - verify_mocks - end - clear_expectations - end - - it "supports all normal mock functionality for class methods" do - - Concrete.expects!(:pour, "two tons").returns("mice") - Concrete.expects!(:pour, "three tons").returns("cats") - Concrete.expects!(:pour, "four tons").raises("Can't do it") - Concrete.expects!(:pour) do |some, args| - "==#{some}+#{args}==" - end - - assert_equal "mice", Concrete.pour("two tons") - assert_equal "cats", Concrete.pour("three tons") - assert_error(RuntimeError, /Can't do it/) do - Concrete.pour("four tons") - end - assert_equal "==first+second==", Concrete.pour("first","second") - end - - - it "enforces inter-mock ordering when mocking class methods" do - create_mocks :truck, :foreman - - @truck.expects.backup - Concrete.expects!(:pour, "something") - @foreman.expects.shout - - @truck.backup - assert_error Hardmock::ExpectationError, /wrong/i, /expected call/i, /Concrete.pour/ do - @foreman.shout - end - assert_error Hardmock::VerifyError, /unmet expectations/i, /foreman.shout/ do - verify_mocks - end - clear_expectations - end - - should "allow mocking non-existant class methods" do - Concrete.expects!(:something).returns("else") - assert_equal "else", Concrete.something - end - - it "mocks specific methods on existing instances, then restore them after verify" do - - slab = Concrete.new - assert_equal "bonk", slab.hit - - slab.expects!(:hit).returns("slap") - assert_equal "slap", slab.hit, "'hit' not stubbed" - - verify_mocks - assert_equal "bonk", slab.hit, "'hit' not restored" - end - - it "flunks if expected instance method is not invoked" do - - slab = Concrete.new - slab.expects!(:hit) - - assert_error Hardmock::VerifyError, /unmet expectations/i, /Concrete.hit/ do - verify_mocks - end - clear_expectations - end - - it "supports all normal mock functionality for instance methods" do - - slab = Concrete.new - - slab.expects!(:hit, "soft").returns("hey") - slab.expects!(:hit, "hard").returns("OOF") - slab.expects!(:hit).raises("stoppit") - slab.expects!(:hit) do |some, args| - "==#{some}+#{args}==" - end - - assert_equal "hey", slab.hit("soft") - assert_equal "OOF", slab.hit("hard") - assert_error(RuntimeError, /stoppit/) do - slab.hit - end - assert_equal "==first+second==", slab.hit("first","second") - - end - - it "enforces inter-mock ordering when mocking instance methods" do - create_mocks :truck, :foreman - slab1 = Concrete.new - slab2 = Concrete.new - - @truck.expects.backup - slab1.expects!(:hit) - @foreman.expects.shout - slab2.expects!(:hit) - @foreman.expects.whatever - - @truck.backup - slab1.hit - @foreman.shout - assert_error Hardmock::ExpectationError, /wrong/i, /expected call/i, /Concrete.hit/ do - @foreman.whatever - end - assert_error Hardmock::VerifyError, /unmet expectations/i, /foreman.whatever/ do - verify_mocks - end - clear_expectations - end - - should "allow mocking non-existant instance methods" do - slab = Concrete.new - slab.expects!(:wholly).returns('happy') - assert_equal 'happy', slab.wholly - end - - should "support concrete expectations that deal with runtime blocks" do - - Concrete.expects!(:pour, "a lot") do |how_much, block| - assert_equal "a lot", how_much, "Wrong how_much arg" - assert_not_nil block, "nil runtime block" - assert_equal "the block value", block.call, "Wrong runtime block value" - end - - Concrete.pour("a lot") do - "the block value" - end - - end - - it "can stub methods on mock objects" do - create_mock :horse - @horse.stubs!(:speak).returns("silence") - @horse.stubs!(:hello).returns("nothing") - @horse.expects(:canter).returns("clip clop") - - assert_equal "silence", @horse.speak - assert_equal "clip clop", @horse.canter - assert_equal "silence", @horse.speak - assert_equal "silence", @horse.speak - assert_equal "nothing", @horse.hello - assert_equal "nothing", @horse.hello - - verify_mocks - reset_stubs - end - - it "can stub the new method and return values" do - Concrete.stubs!(:new).returns("this value") - assert_equal "this value", Concrete.new, "did not properly stub new class method" - reset_stubs - end - - it "can mock the new method and return values" do - Concrete.expects!(:new).with("foo").returns("hello") - Concrete.expects!(:new).with("bar").returns("world") - - assert_equal "hello", Concrete.new("foo"), "did not properly mock out new class method" - assert_equal "world", Concrete.new("bar"), "did not properly mock out new class method" - - verify_mocks - reset_stubs - end - - it "can mock several different class methods at once" do - sim_code = lambda do |input| - record = Multitool.find_record(input) - report = Multitool.generate_report(record) - Multitool.format_output(report) - end - - @identifier = "the id" - @record = "the record" - @report = "the report" - @output = "the output" - - Multitool.expects!(:find_record).with(@identifier).returns(@record) - Multitool.expects!(:generate_report).with(@record).returns(@report) - Multitool.expects!(:format_output).with(@report).returns(@output) - - result = sim_code.call(@identifier) - assert_equal @output, result, "Wrong output" - end - - it "can handle a mix of different and repeat class method mock calls" do - prep = lambda { - Multitool.expects!(:find_record).with("A").returns("1") - Multitool.expects!(:generate_report).with("1") - Multitool.expects!(:find_record).with("B").returns("2") - Multitool.expects!(:generate_report).with("2") - } - - prep[] - Multitool.generate_report(Multitool.find_record("A")) - Multitool.generate_report(Multitool.find_record("B")) - - prep[] - Multitool.generate_report(Multitool.find_record("A")) - assert_error Hardmock::ExpectationError, /Wrong arguments/, /find_record\("B"\)/, /find_record\("C"\)/ do - Multitool.generate_report(Multitool.find_record("C")) - end - clear_expectations - end - - it "can mock several concrete instance methods at once" do - inst = OtherMultitool.new - sim_code = lambda do |input| - record = inst.find_record(input) - report = inst.generate_report(record) - inst.format_output(report) - end - - @identifier = "the id" - @record = "the record" - @report = "the report" - @output = "the output" - - inst.expects!(:find_record).with(@identifier).returns(@record) - inst.expects!(:generate_report).with(@record).returns(@report) - inst.expects!(:format_output).with(@report).returns(@output) - - result = sim_code.call(@identifier) - assert_equal @output, result, "Wrong output" - end - - it "verifies all concrete expects! from several different expectations" do - Multitool.expects!(:find_record) - Multitool.expects!(:generate_report) - Multitool.expects!(:format_output) - - Multitool.find_record - Multitool.generate_report - - assert_error Hardmock::VerifyError, /unmet expectations/i, /format_output/i do - verify_mocks - end - end - - it "will not allow expects! to be used on a mock object" do - create_mock :cow - assert_error Hardmock::StubbingError, /expects!/, /mock/i, /something/ do - @cow.expects!(:something) - end - end - - it "does not allow stubbing on nil objects" do - [ nil, @this_is_nil ].each do |nil_obj| - assert_error Hardmock::StubbingError, /cannot/i, /nil/i, /intentionally/ do - nil_obj.stubs!(:wont_work) - end - end - end - - it "does not allow concrete method mocking on nil objects" do - [ nil, @this_is_nil ].each do |nil_obj| - assert_error Hardmock::StubbingError, /cannot/i, /nil/i, /intentionally/ do - nil_obj.expects!(:wont_work) - end - end - end - - it "provides an alternate method for stubbing on nil objects" do - @this_is_nil.intentionally_stubs!(:bogus).returns('output') - assert_equal 'output', @this_is_nil.bogus - end - - it "provides an alternate method for mocking concreate methods on nil objects" do - @this_is_nil.intentionally_expects!(:bogus).returns('output') - assert_error Hardmock::VerifyError, /unmet expectations/i, /NilClass.bogus/ do - verify_mocks - end - end - - # - # HELPERS - # - - class Concrete - def initialize; end - def self.pour - "stones and gravel" - end - - def self.describe - "For roads" - end - - def hit - "bonk" - end - - def describe - "an instance" - end - end - - class Jug - def self.pour - "glug glug" - end - end - - class Multitool - def self.find_record(*a) - raise "The real Multitool.find_record was called with #{a.inspect}" - end - def self.generate_report(*a) - raise "The real Multitool.generate_report was called with #{a.inspect}" - end - def self.format_output(*a) - raise "The real Multitool.format_output was called with #{a.inspect}" - end - end - - class OtherMultitool - def find_record(*a) - raise "The real OtherMultitool#find_record was called with #{a.inspect}" - end - def generate_report(*a) - raise "The real OtherMultitool#generate_report was called with #{a.inspect}" - end - def format_output(*a) - raise "The real OtherMultitool#format_output was called with #{a.inspect}" - end - end - -end - diff --git a/vendor/hardmock/test/test_helper.rb b/vendor/hardmock/test/test_helper.rb deleted file mode 100644 index af159a46d..000000000 --- a/vendor/hardmock/test/test_helper.rb +++ /dev/null @@ -1,43 +0,0 @@ -here = File.expand_path(File.dirname(__FILE__)) -$: << here - -require "#{here}/../config/environment" -require 'test/unit' -require 'fileutils' -require 'logger' -require 'find' -require 'yaml' -require 'set' -require 'ostruct' - -class Test::Unit::TestCase - include FileUtils - - def poll(time_limit) - (time_limit * 10).to_i.times do - return true if yield - sleep 0.1 - end - return false - end - - def self.it(str, &block) - make_test_case "it", str, &block - end - - def self.should(str, &block) - make_test_case "should", str, &block - end - - def self.make_test_case(prefix, str, &block) - tname = self.name.sub(/Test$/,'') - if block - define_method "test #{prefix} #{str}" do - instance_eval &block - end - else - puts ">>> UNIMPLEMENTED CASE: #{tname}: #{str}" - end - end - -end diff --git a/vendor/hardmock/test/unit/expectation_builder_test.rb b/vendor/hardmock/test/unit/expectation_builder_test.rb deleted file mode 100644 index f689f983b..000000000 --- a/vendor/hardmock/test/unit/expectation_builder_test.rb +++ /dev/null @@ -1,19 +0,0 @@ -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'hardmock/expectation_builder' - -class ExpectationBuilderTest < Test::Unit::TestCase - include Hardmock - - def test_build_expectation - builder = ExpectationBuilder.new - - ex = builder.build_expectation( :stuff => 'inside' ) - assert_not_nil ex, "Didn't build an expectation" - assert_kind_of Expectation, ex, "Wrong type!" - - # Shhhh... fragile, yes, whatever. The functional tests do the - # real testing of this anyway - assert_equal({:stuff => 'inside'}, ex.instance_variable_get('@options'), "Hash not sent to SimpleExpectation constructor") - end - -end diff --git a/vendor/hardmock/test/unit/expectation_test.rb b/vendor/hardmock/test/unit/expectation_test.rb deleted file mode 100644 index 54bd20439..000000000 --- a/vendor/hardmock/test/unit/expectation_test.rb +++ /dev/null @@ -1,372 +0,0 @@ -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'hardmock/expectation' -require 'hardmock/errors' -require 'assert_error' - -class ExpectationTest < Test::Unit::TestCase - include Hardmock - - def setup - @mock = TheMock.new - end - # - # HELPERS - # - - class TheMock - def _name; 'the_mock'; end - end - class OtherMock - def _name; 'other_mock'; end - end - - # - # TESTS - # - - def test_to_s - ex = Expectation.new( :mock => @mock, :method => 'a_func', :arguments => [1, "two", :three, { :four => 4 }] ) - assert_equal %|the_mock.a_func(1, "two", :three, {:four=>4})|, ex.to_s - end - - def test_apply_method_call - se = Expectation.new(:mock => @mock, :method => 'some_func', - :arguments => [1,'two',:three] ) - - # Try it good: - assert_nothing_raised ExpectationError do - se.apply_method_call( @mock, 'some_func', [1,'two',:three], nil ) - end - - # Bad func name: - err = assert_raise ExpectationError do - se.apply_method_call( @mock, 'wrong_func', [1,'two',:three], nil ) - end - assert_match(/wrong method/i, err.message) - assert_match(/wrong_func/i, err.message) - assert_match(/[1, "two", :three]/i, err.message) - assert_match(/some_func/i, err.message) - assert_match(/the_mock/i, err.message) - - # Wrong mock - err = assert_raise ExpectationError do - se.apply_method_call( OtherMock.new, 'some_func', [1,'two',:three], nil ) - end - assert_match(/[1, "two", :three]/i, err.message) - assert_match(/some_func/i, err.message) - assert_match(/the_mock/i, err.message) - assert_match(/other_mock/i, err.message) - - # Wrong args - err = assert_raise ExpectationError do - se.apply_method_call( @mock, 'some_func', [1,'two',:four], nil) - end - assert_match(/[1, "two", :three]/i, err.message) - assert_match(/[1, "two", :four]/i, err.message) - assert_match(/wrong arguments/i, err.message) - assert_match(/some_func/i, err.message) - end - - def test_apply_method_call_should_call_proc_when_given - # now with a proc - thinger = nil - the_proc = Proc.new { thinger = :shaq } - se = Expectation.new(:mock => @mock, :method => 'some_func', - :block => the_proc) - - # Try it good: - assert_nil thinger - assert_nothing_raised ExpectationError do - se.apply_method_call(@mock, 'some_func', [], nil) - end - assert_equal :shaq, thinger, 'wheres shaq??' - end - - def test_apply_method_call_passes_runtime_block_as_last_argument_to_expectation_block - - passed_block = nil - exp_block_called = false - exp_block = Proc.new { |blk| - exp_block_called = true - passed_block = blk - } - - se = Expectation.new(:mock => @mock, :method => 'some_func', :block => exp_block, - :arguments => []) - - set_flag = false - runtime_block = Proc.new { set_flag = true } - - assert_nil passed_block, "Passed block should be nil" - assert !set_flag, "set_flag should be off" - - # Go - se.apply_method_call( @mock, 'some_func', [], runtime_block) - - # Examine the passed block - assert exp_block_called, "Expectation block not called" - assert_not_nil passed_block, "Should have been passed a block" - assert !set_flag, "set_flag should still be off" - passed_block.call - assert set_flag, "set_flag should be on" - end - - def test_apply_method_call_fails_when_theres_no_expectation_block_to_handle_the_runtime_block - se = Expectation.new(:mock => @mock, :method => 'some_func', :arguments => []) - runtime_block = Proc.new { set_flag = true } - err = assert_raise ExpectationError do - se.apply_method_call( @mock, 'some_func', [], runtime_block) - end - assert_match(/unexpected block/i, err.message) - assert_match(/the_mock.some_func()/i, err.message) - end - - def test_returns - se = Expectation.new(:mock => @mock, :method => 'some_func', - :arguments => [1,'two',:three]) - - se.returns "A value" - - assert_equal "A value", se.apply_method_call(@mock, 'some_func', [1,'two',:three], nil) - end - - def test_apply_method_call_captures_block_value - the_proc = lambda { "in the block" } - se = Expectation.new(:mock => @mock, :method => 'do_it', :arguments => [], :block => the_proc) - - assert_nil se.block_value, "Block value starts out nil" - - se.apply_method_call(@mock, 'do_it', [], nil) - - assert_equal "in the block", se.block_value, "Block value not captured" - end - - def test_trigger - # convenience method for block_value.call - target = false - inner_proc = lambda { target = true } - the_proc = lambda { inner_proc } - se = Expectation.new(:mock => @mock, :method => 'do_it', :arguments => [], :block => the_proc) - - assert_nil se.block_value, "Block value starts out nil" - se.apply_method_call(@mock, 'do_it', [], nil) - assert_not_nil se.block_value, "Block value not set" - - assert !target, "Target should still be false" - se.trigger - assert target, "Target not true!" - end - - def test_trigger_with_arguments - # convenience method for block_value.call - target = nil - inner_proc = lambda { |one,two| target = [one,two] } - the_proc = lambda { inner_proc } - se = Expectation.new(:mock => @mock, :method => 'do_it', :arguments => [], :block => the_proc) - - assert_nil se.block_value, "Block value starts out nil" - se.apply_method_call(@mock, 'do_it', [], nil) - assert_not_nil se.block_value, "Block value not set" - - assert_nil target, "target should still be nil" - se.trigger 'cat','dog' - assert_equal ['cat','dog'], target - end - - def test_trigger_nil_block_value - se = Expectation.new(:mock => @mock, :method => 'do_it', :arguments => []) - - assert_nil se.block_value, "Block value starts out nil" - se.apply_method_call(@mock, 'do_it', [], nil) - assert_nil se.block_value, "Block value should still be nil" - - err = assert_raise ExpectationError do - se.trigger - end - assert_match(/do_it/i, err.message) - assert_match(/block value/i, err.message) - end - - def test_trigger_non_proc_block_value - the_block = lambda { "woops" } - se = Expectation.new(:mock => @mock, :method => 'do_it', :arguments => [], :block => the_block) - - se.apply_method_call(@mock, 'do_it', [], nil) - assert_equal "woops", se.block_value - - err = assert_raise ExpectationError do - se.trigger - end - assert_match(/do_it/i, err.message) - assert_match(/trigger/i, err.message) - assert_match(/woops/i, err.message) - end - - - - def test_proc_used_for_return - the_proc = lambda { "in the block" } - se = Expectation.new(:mock => @mock, :method => 'do_it', :arguments => [], :block => the_proc) - - assert_equal "in the block", se.apply_method_call(@mock, 'do_it', [], nil) - assert_equal "in the block", se.block_value, "Captured block value affected wrongly" - end - - def test_explicit_return_overrides_proc_return - the_proc = lambda { "in the block" } - se = Expectation.new(:mock => @mock, :method => 'do_it', :arguments => [], :block => the_proc) - se.returns "the override" - assert_equal "the override", se.apply_method_call(@mock, 'do_it', [], nil) - assert_equal "in the block", se.block_value, "Captured block value affected wrongly" - end - - def test_yields - se = Expectation.new(:mock => @mock, :method => 'each_bean', :arguments => [:side_slot] ) - se.yields :bean1, :bean2 - - things = [] - a_block = lambda { |thinger| things << thinger } - - se.apply_method_call(@mock,'each_bean',[:side_slot],a_block) - assert_equal [:bean1,:bean2], things, "Wrong things" - end - - def test_yields_block_takes_no_arguments - se = Expectation.new(:mock => @mock, :method => 'each_bean', :arguments => [:side_slot] ) - se.yields - - things = [] - a_block = lambda { things << 'OOF' } - se.apply_method_call(@mock,'each_bean',[:side_slot],a_block) - assert_equal ['OOF'], things - end - - def test_yields_params_to_block_takes_no_arguments - se = Expectation.new(:mock => @mock, :method => 'each_bean', :arguments => [:side_slot] ) - se.yields :wont_fit - - things = [] - a_block = lambda { things << 'WUP' } - - err = assert_raise ExpectationError do - se.apply_method_call(@mock,'each_bean',[:side_slot],a_block) - end - assert_match(/wont_fit/i, err.message) - assert_match(/arity -1/i, err.message) - assert_equal [], things, "Wrong things" - end - - def test_yields_with_returns - se = Expectation.new(:mock => @mock, :method => 'each_bean', :arguments => [:side_slot] , - :returns => 'the results') - - exp = se.yields :bean1, :bean2 - assert_same se, exp, "'yields' needs to return a reference to the expectation" - things = [] - a_block = lambda { |thinger| things << thinger } - returned = se.apply_method_call(@mock,'each_bean',[:side_slot],a_block) - assert_equal [:bean1,:bean2], things, "Wrong things" - assert_equal 'the results', returned, "Wrong return value" - end - - def test_yields_with_raises - se = Expectation.new(:mock => @mock, :method => 'each_bean', :arguments => [:side_slot], - :raises => RuntimeError.new("kerboom")) - - exp = se.yields :bean1, :bean2 - assert_same se, exp, "'yields' needs to return a reference to the expectation" - things = [] - a_block = lambda { |thinger| things << thinger } - err = assert_raise RuntimeError do - se.apply_method_call(@mock,'each_bean',[:side_slot],a_block) - end - assert_match(/kerboom/i, err.message) - assert_equal [:bean1,:bean2], things, "Wrong things" - end - - def test_yields_and_inner_block_explodes - se = Expectation.new(:mock => @mock, :method => 'each_bean', :arguments => [:side_slot]) - - exp = se.yields :bean1, :bean2 - assert_same se, exp, "'yields' needs to return a reference to the expectation" - things = [] - a_block = lambda { |thinger| - things << thinger - raise "nasty" - } - err = assert_raise RuntimeError do - se.apply_method_call(@mock,'each_bean',[:side_slot],a_block) - end - assert_match(/nasty/i, err.message) - assert_equal [:bean1], things, "Wrong things" - end - - def test_yields_with_several_arrays - se = Expectation.new(:mock => @mock, :method => 'each_bean', :arguments => [:side_slot] ) - se.yields ['a','b'], ['c','d'] - - things = [] - a_block = lambda { |thinger| things << thinger } - - se.apply_method_call(@mock,'each_bean',[:side_slot],a_block) - assert_equal [ ['a','b'], ['c','d'] ], things, "Wrong things" - end - - def test_yields_tuples - se = Expectation.new(:mock => @mock, :method => 'each_bean', :arguments => [:side_slot] ) - se.yields ['a','b','c'], ['d','e','f'] - - things = [] - a_block = lambda { |left,mid,right| - things << { :left => left, :mid => mid, :right => right } - } - - se.apply_method_call(@mock,'each_bean',[:side_slot],a_block) - assert_equal [ - {:left => 'a', :mid => 'b', :right => 'c' }, - {:left => 'd', :mid => 'e', :right => 'f' }, - ], things, "Wrong things" - end - - def test_yields_tuples_size_mismatch - se = Expectation.new(:mock => @mock, :method => 'each_bean', :arguments => [:side_slot] ) - se.yields ['a','b','c'], ['d','e','f'] - - things = [] - a_block = lambda { |left,mid| - things << { :left => left, :mid => mid } - } - - err = assert_raise ExpectationError do - se.apply_method_call(@mock,'each_bean',[:side_slot],a_block) - end - assert_match(/arity/i, err.message) - assert_match(/the_mock.each_bean/i, err.message) - assert_match(/"a", "b", "c"/i, err.message) - assert_equal [], things, "Wrong things" - end - - def test_yields_bad_block_arity - se = Expectation.new(:mock => @mock, :method => 'do_later', :arguments => [] ) - se.yields - - assert_error Hardmock::ExpectationError, /block/i, /expected/i, /no param/i, /got 2/i do - se.apply_method_call(@mock,'do_later',[],lambda { |doesnt,match| raise "Surprise!" } ) - end - end - - def test_that_arguments_can_be_added_to_expectation - expectation = Expectation.new(:mock => @mock, :method => "each_bean") - assert_same expectation, expectation.with("jello", "for", "cosby"), "should have returned the same expectation" - - err = assert_raise ExpectationError do - expectation.apply_method_call(@mock, 'each_bean', [], nil) - end - assert_match(/wrong arguments/i, err.message) - - assert_nothing_raised(ExpectationError) do - expectation.apply_method_call(@mock, 'each_bean', ["jello", "for", "cosby"], nil) - end - end - -end diff --git a/vendor/hardmock/test/unit/expector_test.rb b/vendor/hardmock/test/unit/expector_test.rb deleted file mode 100644 index f420db24b..000000000 --- a/vendor/hardmock/test/unit/expector_test.rb +++ /dev/null @@ -1,57 +0,0 @@ -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'hardmock/expector' - -class ExpectorTest < Test::Unit::TestCase - include Hardmock - - class MyControl - attr_reader :added - def add_expectation(expectation) - @added ||= [] - @added << expectation - end - end - - class ExpBuilder - attr_reader :options - def build_expectation(options) - @options = options - "dummy expectation" - end - end - - def try_it_with(method_name) - mock = Object.new - mock_control = MyControl.new - builder = ExpBuilder.new - - exp = Expector.new(mock, mock_control, builder) - output = exp.send(method_name,:with, 1, 'sauce') - - assert_same mock, builder.options[:mock] - assert_equal method_name, builder.options[:method].to_s - assert_equal [:with,1,'sauce'], builder.options[:arguments] - assert_nil builder.options[:block] - assert_equal [ "dummy expectation" ], mock_control.added, - "Wrong expectation added to control" - - assert_equal "dummy expectation", output, "Expectation should have been returned" - end - - # - # TESTS - # - def test_method_missing - try_it_with 'wonder_bread' - try_it_with 'whatever' - end - - def test_methods_that_wont_trigger_method_missing - mock = Object.new - mock_control = MyControl.new - builder = ExpBuilder.new - - exp = Expector.new(mock, mock_control, builder) - assert_equal mock, exp.instance_eval("@mock") - end -end diff --git a/vendor/hardmock/test/unit/method_cleanout_test.rb b/vendor/hardmock/test/unit/method_cleanout_test.rb deleted file mode 100644 index 7aa629365..000000000 --- a/vendor/hardmock/test/unit/method_cleanout_test.rb +++ /dev/null @@ -1,36 +0,0 @@ -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'hardmock/method_cleanout' - -class MethodCleanoutTest < Test::Unit::TestCase - class Victim - OriginalMethods = instance_methods - include Hardmock::MethodCleanout - end - - def setup - @victim = Victim.new - end - - def test_should_remove_most_methods_from_a_class - expect_removed = Victim::OriginalMethods.reject { |m| - Hardmock::MethodCleanout::SACRED_METHODS.include?(m) - } - expect_removed.each do |m| - assert !@victim.respond_to?(m), "should not have method #{m}" - end - end - - def test_should_leave_the_sacred_methods_defined - Hardmock::MethodCleanout::SACRED_METHODS.each do |m| - next if m =~ /^hm_/ - assert @victim.respond_to?(m), "Sacred method '#{m}' was removed unexpectedly" - end - end - - def test_should_include_certain_important_methods_in_the_sacred_methods_list - %w|__id__ __send__ equal? object_id send nil? class kind_of? respond_to? inspect method to_s instance_variables instance_eval|.each do |m| - assert Hardmock::MethodCleanout::SACRED_METHODS.include?(m), "important method #{m} is not included in SACRED_METHODS" - end - end - -end diff --git a/vendor/hardmock/test/unit/mock_control_test.rb b/vendor/hardmock/test/unit/mock_control_test.rb deleted file mode 100644 index 3c52db677..000000000 --- a/vendor/hardmock/test/unit/mock_control_test.rb +++ /dev/null @@ -1,175 +0,0 @@ -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'hardmock/utils' -require 'hardmock/errors' -require 'hardmock/mock_control' - -class MockControlTest < Test::Unit::TestCase - include Hardmock - - def setup - @unmock = OpenStruct.new( :_name => 'fakemock' ) - - @control = MockControl.new - assert @control.happy?, "Control should start out happy" - end - - def teardown - end - - # - # HELPERS - # - - class MyExp - attr_reader :mock, :mname, :args, :block - def apply_method_call(mock, mname, args, block) - @mock = mock - @mname = mname - @args = args - @block = block - end - end - - class BoomExp < MyExp - def apply_method_call(mock, mname, args, block) - super - raise "BOOM" - end - end - - # - # TESTS - # - - def test_add_exepectation_and_apply_method_call - e1 = MyExp.new - - @control.add_expectation e1 - assert !@control.happy? - - @control.apply_method_call @unmock, 'some_func', [ 'the', :args ], nil - assert @control.happy? - - assert_same @unmock, e1.mock, "Wrong mock" - assert_equal 'some_func', e1.mname, "Wrong method" - assert_equal [ 'the', :args ], e1.args, "Wrong args" - - @control.verify - end - - def test_add_exepectation_and_apply_method_call_with_block - e1 = MyExp.new - - @control.add_expectation e1 - assert !@control.happy? - - runtime_block = Proc.new { "hello" } - @control.apply_method_call @unmock, 'some_func', [ 'the', :args ], runtime_block - assert @control.happy? - - assert_same @unmock, e1.mock, "Wrong mock" - assert_equal 'some_func', e1.mname, "Wrong method" - assert_equal [ 'the', :args ], e1.args, "Wrong args" - assert_equal "hello", e1.block.call, "Wrong block in expectation" - - @control.verify - end - - def test_add_expectation_then_verify - e1 = MyExp.new - - @control.add_expectation e1 - assert !@control.happy?, "Shoudn't be happy" - err = assert_raise VerifyError do - @control.verify - end - assert_match(/unmet expectations/i, err.message) - - @control.apply_method_call @unmock, 'some_func', [ 'the', :args ], nil - assert @control.happy? - - assert_same @unmock, e1.mock, "Wrong mock" - assert_equal 'some_func', e1.mname, "Wrong method" - assert_equal [ 'the', :args ], e1.args, "Wrong args" - - @control.verify - end - - def test_expectation_explosion - be1 = BoomExp.new - - @control.add_expectation be1 - - err = assert_raise RuntimeError do - @control.apply_method_call @unmock, 'a func', [:arg], nil - end - assert_match(/BOOM/i, err.message) - - assert_same @unmock, be1.mock - assert_equal 'a func', be1.mname - assert_equal [:arg], be1.args - end - - def test_disappointment_on_bad_verify - @control.add_expectation MyExp.new - assert !@control.happy?, "Shouldn't be happy" - assert !@control.disappointed?, "too early to be disappointed" - - # See verify fails - err = assert_raise VerifyError do - @control.verify - end - assert_match(/unmet expectations/i, err.message) - - assert !@control.happy?, "Still have unmet expectation" - assert @control.disappointed?, "We should be disappointed following that failure" - - @control.apply_method_call @unmock, 'something', [], nil - assert @control.happy?, "Should be happy" - assert @control.disappointed?, "We should be skeptical" - - @control.verify - - assert !@control.disappointed?, "Should be non-disappointed" - end - - def test_disappointment_from_surprise_calls - assert @control.happy?, "Should be happy" - assert !@control.disappointed?, "too early to be disappointed" - - # See verify fails - err = assert_raise ExpectationError do - @control.apply_method_call @unmock, "something", [], nil - end - assert_match(/surprise/i, err.message) - - assert @control.happy?, "Happiness is an empty list of expectations" - assert @control.disappointed?, "We should be disappointed following that failure" - - @control.verify - assert !@control.disappointed?, "Disappointment should be gone" - end - - def test_disappointment_from_bad_calls - be1 = BoomExp.new - assert !@control.disappointed?, "Shouldn't be disappointed" - @control.add_expectation be1 - assert !@control.disappointed?, "Shouldn't be disappointed" - - err = assert_raise RuntimeError do - @control.apply_method_call @unmock, 'a func', [:arg], nil - end - assert_match(/BOOM/i, err.message) - assert @control.disappointed?, "Should be disappointed" - - assert_same @unmock, be1.mock - assert_equal 'a func', be1.mname - assert_equal [:arg], be1.args - - assert @control.happy?, "Happiness is an empty list of expectations" - @control.verify - assert !@control.disappointed?, "Disappointment should be gone" - end - - -end diff --git a/vendor/hardmock/test/unit/mock_test.rb b/vendor/hardmock/test/unit/mock_test.rb deleted file mode 100644 index 2579bcc27..000000000 --- a/vendor/hardmock/test/unit/mock_test.rb +++ /dev/null @@ -1,279 +0,0 @@ -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'hardmock/method_cleanout' -require 'hardmock/mock' -require 'hardmock/mock_control' -require 'hardmock/expectation_builder' -require 'hardmock/expector' -require 'hardmock/trapper' - -class MockTest < Test::Unit::TestCase - include Hardmock - - def test_build_with_control - mc1 = MockControl.new - mock = Mock.new('hi', mc1) - assert_equal 'hi', mock._name, "Wrong name" - assert_same mc1, mock._control, "Wrong contol" - end - - def test_basics - mock = Mock.new('a name') - assert_equal 'a name', mock._name, "Wrong name for mock" - assert_not_nil mock._control, "Nil control in mock" - end - - def test_expects - mock = Mock.new('order') - control = mock._control - assert control.happy?, "Mock should start out satisfied" - - mock.expects.absorb_something(:location, 'garbage') - assert !control.happy?, "mock control should be unhappy" - - # Do the call - mock.absorb_something(:location, 'garbage') - assert control.happy?, "mock control should be happy again" - - # Verify - assert_nothing_raised Exception do - mock._verify - end - end - - def test_expects_using_arguments_for_method_and_arguments - mock = Mock.new('order') - mock.expects(:absorb_something, :location, 'garbage') - mock.absorb_something(:location, 'garbage') - mock._verify - end - - def test_expects_using_arguments_for_method_and_arguments_with_block - mock = Mock.new('order') - mock.expects(:absorb_something, :location, 'garbage') { |a,b,block| - assert_equal :location, a, "Wrong 'a' argument" - assert_equal 'garbage', b, "Wrong 'b' argument" - assert_equal 'innards', block.call, "Wrong block" - } - mock.absorb_something(:location, 'garbage') do "innards" end - mock._verify - end - - def test_expects_using_string_method_name - mock = Mock.new('order') - mock.expects('absorb_something', :location, 'garbage') - mock.absorb_something(:location, 'garbage') - mock._verify - end - - - def test_expects_assignment - mock = Mock.new('order') - mock.expects.account_number = 1234 - - mock.account_number = 1234 - - mock._verify - end - - def test_expects_assigment_using_arguments_for_method_and_arguments - mock = Mock.new('order') - mock.expects(:account_number=, 1234) - mock.account_number = 1234 - mock._verify - end - - def test_expects_assigment_using_string_method_name - mock = Mock.new('order') - mock.expects('account_number=', 1234) - mock.account_number = 1234 - mock._verify - end - - def test_expects_assignment_and_return_is_overruled_by_ruby_syntax - # Prove that we can set up a return but that it doesn't mean much, - # because ruby's parser will 'do the right thing' as regards semantic - # values for assignment. (That is, the rvalue of the assignment) - mock = Mock.new('order') - mock.expects(:account_number=, 1234).returns "gold" - got = mock.account_number = 1234 - mock._verify - assert_equal 1234, got, "Expected rvalue" - end - - def test_expects_assignment_and_raise - mock = Mock.new('order') - mock.expects(:account_number=, 1234).raises StandardError.new("kaboom") - err = assert_raise StandardError do - mock.account_number = 1234 - end - assert_match(/kaboom/i, err.message) - mock._verify - end - - - def test_expects_multiple - mock = Mock.new('order') - control = mock._control - - assert control.happy? - - mock.expects.one_thing :hi, { :goose => 'neck' } - mock.expects.another 5,6,7 - assert !control.happy? - - mock.one_thing :hi, { :goose => 'neck' } - assert !control.happy? - - mock.another 5,6,7 - assert control.happy? - end - - def test_surprise_call - mock = Mock.new('order') - err = assert_raise ExpectationError do - mock.uh_oh - end - assert_match(/surprise/i, err.message) - assert_match(/uh_oh/i, err.message) - - err = assert_raise ExpectationError do - mock.whoa :horse - end - assert_match(/surprise/i, err.message) - assert_match(/order\.whoa\(:horse\)/i, err.message) - end - - def test_wrong_call - mock = Mock.new('order') - mock.expects.pig 'arse' - err = assert_raise ExpectationError do - mock.whoa :horse - end - assert_match(/wrong method/i, err.message) - assert_match(/order\.whoa\(:horse\)/i, err.message) - assert_match(/order\.pig\("arse"\)/i, err.message) - end - - def test_wrong_arguments - mock = Mock.new('order') - mock.expects.go_fast(:a, 1, 'three') - - err = assert_raise ExpectationError do - mock.go_fast :a, 1, 'not right' - end - assert_match(/wrong argument/i, err.message) - assert_match(/order\.go_fast\(:a, 1, "three"\)/i, err.message) - assert_match(/order\.go_fast\(:a, 1, "not right"\)/i, err.message) - end - - def test_expects_and_return - mock = Mock.new('order') - mock.expects.delivery_date.returns Date.today - assert_equal Date.today, mock.delivery_date - mock._verify - end - - def test_expects_and_return_with_arguments - mock = Mock.new('order') - mock.expects.delivery_date(:arf,14).returns(Date.today) - assert_equal Date.today, mock.delivery_date(:arf,14) - mock._verify - end - - def test_expects_and_raise - mock = Mock.new('order') - mock.expects.delivery_date.raises StandardError.new("bloof") - - err = assert_raise StandardError do - mock.delivery_date - end - assert_match(/bloof/i, err.message) - - mock._verify - - # Try convenience argument String - mock.expects.pow.raises "hell" - err = assert_raise RuntimeError do - mock.pow - end - assert_match(/hell/i, err.message) - - mock._verify - - # Try convenience argument nothing - mock.expects.pow.raises - err = assert_raise RuntimeError do - mock.pow - end - assert_match(/an error/i, err.message) - - mock._verify - end - - def test_expects_a_runtime_block - mock = Mock.new('order') - got_val = nil - - mock.expects.when(:something) { |e,block| - got_val = block.call - } - - mock.when :something do "hi there" end - - assert_equal "hi there", got_val, "Expectation block not invoked" - mock._verify - end - - def test_trap_block - mock = Mock.new('order') - exp = mock.trap.observe - - # use it - mock.observe { "burp" } - - assert_equal "burp", exp.block_value.call - end - - def test_trap_arguments_and_block - mock = Mock.new('order') - exp = mock.trap.subscribe(:data_changed) - - # use it - mock.subscribe(:data_changed) { "burp" } - assert_equal "burp", exp.block_value.call - mock._verify - end - - def test_trap_arguments_and_block_wrong_num_args - mock = Mock.new('order') - exp = mock.trap.subscribe(:data_changed) - - assert_raise ExpectationError do - mock.subscribe(:data_changed,1) { "burp" } - end - mock._verify - end - - def test_trap_arguments_and_block_wrong_args - mock = Mock.new('order') - exp = mock.trap.subscribe(:data_changed) - - assert_raise ExpectationError do - mock.subscribe("no good") { "burp" } - end - - mock._verify - end - - def test_trap_is_not_leniant_about_arguments - mock = Mock.new('order') - exp = mock.trap.subscribe - - assert_raise ExpectationError do - mock.subscribe("no good") { "burp" } - end - - mock._verify - end - -end diff --git a/vendor/hardmock/test/unit/test_unit_before_after_test.rb b/vendor/hardmock/test/unit/test_unit_before_after_test.rb deleted file mode 100644 index 172f52709..000000000 --- a/vendor/hardmock/test/unit/test_unit_before_after_test.rb +++ /dev/null @@ -1,452 +0,0 @@ -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") - -class TestUnitBeforeAfter < Test::Unit::TestCase - - # - # after_teardown - # - - it "adds TestCase.after_teardown hook for appending post-teardown actions" do - write_and_run_test :use_after_teardown => true - - see_in_order "Loaded suite", - "THE SETUP", - "A TEST", - "THE TEARDOWN", - "1st after_teardown", - "2nd after_teardown", - "Finished in" - see_results :tests => 1, :assertions => 0, :failures => 0, :errors => 0 - end - - should "execute all after_teardowns, even if the main teardown flunks" do - write_and_run_test :use_after_teardown => true, :flunk_in_teardown => true - see_in_order "Loaded suite", - "THE SETUP", - "A TEST", - "F", - "1st after_teardown", - "2nd after_teardown", - "Finished in", - "1) Failure:", - "test_something(MyExampleTest) [_test_file_temp.rb:20]:", - "FLUNK IN TEARDOWN" - see_results :tests => 1, :assertions => 1, :failures => 1, :errors => 0 - end - - should "execute all after_teardowns, even if the main teardown explodes" do - write_and_run_test :use_after_teardown => true, :raise_in_teardown => true - see_in_order "Loaded suite", - "THE SETUP", - "A TEST", - "E", - "1st after_teardown", - "2nd after_teardown", - "Finished in", - "RuntimeError: ERROR IN TEARDOWN" - see_results :tests => 1, :assertions => 0, :failures => 0, :errors => 1 - end - - should "execute all after_teardowns, even if some of them flunk" do - write_and_run_test :use_after_teardown => true, :flunk_in_after_teardown => true - see_in_order "Loaded suite", - "THE SETUP", - "A TEST", - "THE TEARDOWN", - "1st after_teardown", - "F", - "2nd after_teardown", - "Finished in", - "1) Failure:", - "test_something(MyExampleTest) [_test_file_temp.rb:7]:", - "Flunk in first after_teardown", - "2) Failure:", - "test_something(MyExampleTest) [_test_file_temp.rb:10]:", - "Flunk in second after_teardown" - see_results :tests => 1, :assertions => 2, :failures => 2, :errors => 0 - end - - should "execute all after_teardowns, even if some of them explode" do - write_and_run_test :use_after_teardown => true, :raise_in_after_teardown => true - see_in_order "Loaded suite", - "THE SETUP", - "A TEST", - "THE TEARDOWN", - "1st after_teardown", - "E", - "2nd after_teardown", - "Finished in", - "RuntimeError: Error in first after_teardown", - "RuntimeError: Error in second after_teardown" - see_results :tests => 1, :assertions => 0, :failures => 0, :errors => 2 - end - - it "will run after_teardowns in the absence of a regular teardown" do - write_and_run_test :omit_teardown => true, :use_after_teardown => true - see_in_order "Loaded suite", - "THE SETUP", - "A TEST", - "1st after_teardown", - "2nd after_teardown", - "Finished in" - see_results :tests => 1, :assertions => 0, :failures => 0, :errors => 0 - end - - should "not interfere with normal test writing" do - write_and_run_test - see_in_order "Loaded suite", - "THE SETUP", - "A TEST", - "THE TEARDOWN", - "Finished in" - see_results :tests => 1, :assertions => 0, :failures => 0, :errors => 0 - end - - it "provides a cleaned-up backtrace" do - write_and_run_test :with_failure => true - see_in_order "Loaded suite", - "THE SETUP", - "A FAILING TEST", - "F", "THE TEARDOWN", - "Finished in", - "1) Failure:", - "test_something(MyExampleTest) [_test_file_temp.rb:17]:", - "Instrumented failure.", - " is not true." - see_results :tests => 1, :assertions => 1, :failures => 1, :errors => 0 - end - - it "provides a cleaned-up backtrace, but not TOO cleaned up" do - write_and_run_test :with_failure => true, :use_helpers => true - see_in_order "Loaded suite", - "THE SETUP", - "A FAILING TEST", - "F", "THE TEARDOWN", - "Finished in", - "1) Failure:", - "test_something(MyExampleTest)\n", - "[_test_file_temp.rb:25:in `tripwire'", - "_test_file_temp.rb:21:in `my_helper'", - "_test_file_temp.rb:17:in `test_something']:", - "Instrumented failure.", - " is not true." - see_results :tests => 1, :assertions => 1, :failures => 1, :errors => 0 - end - - should "not interfere with passthrough exception types" do - if is_modern_test_unit? - write_and_run_test :raise_nasty_in_test => true - see_in_no_particular_order "Loaded suite", - "THE TEARDOWN", - "_test_file_temp.rb:16:in `test_something': NASTY ERROR (NoMemoryError)" - see_no_results - end - end - - # - # before_setup - # - - it "adds TestCase.before_setup hook for prepending pre-setup actions" do - write_and_run_test :use_before_setup => true - see_in_order "Loaded suite", - "3rd before_setup", - "2nd before_setup", - "1st before_setup", - "THE SETUP", - "A TEST", - "THE TEARDOWN", - "Finished in" - see_results :tests => 1, :assertions => 0, :failures => 0, :errors => 0 - end - - should "stop executing the test on the first failure withing a before_setup action" do - write_and_run_test :use_before_setup => true, :flunk_in_before_setup => true - see_in_order "Loaded suite", - "3rd before_setup", - "2nd before_setup", - "FTHE TEARDOWN", - "1) Failure:", - "test_something(MyExampleTest) [_test_file_temp.rb:10]:", - "Flunk in 2nd before_setup." - see_results :tests => 1, :assertions => 1, :failures => 1, :errors => 0 - end - - should "stop executing the test on the first error within a before_setup action" do - write_and_run_test :use_before_setup => true, :raise_in_before_setup => true - see_in_order "Loaded suite", - "3rd before_setup", - "2nd before_setup", - "ETHE TEARDOWN", - "Finished in", - "test_something(MyExampleTest):", - "RuntimeError: Error in 2nd before_setup", - "_test_file_temp.rb:10", - "/hardmock/lib/test_unit_before_after.rb:", ":in `call'" - see_results :tests => 1, :assertions => 0, :failures => 0, :errors => 1 - end - - it "will run before_setup actions in the absence of a regular setup" do - write_and_run_test :omit_setup => true, :use_before_setup => true - see_in_order "Loaded suite", - "3rd before_setup", - "2nd before_setup", - "1st before_setup", - "A TEST", - "THE TEARDOWN", - "Finished in" - see_results :tests => 1, :assertions => 0, :failures => 0, :errors => 0 - end - - it "allows before_setup and after_teardown to be used at the same time" do - write_and_run_test :use_before_setup => true, :use_after_teardown => true - see_in_order "Loaded suite", - "3rd before_setup", - "2nd before_setup", - "1st before_setup", - "A TEST", - "THE TEARDOWN", - "1st after_teardown", - "2nd after_teardown", - "Finished in" - see_results :tests => 1, :assertions => 0, :failures => 0, :errors => 0 - end - - # - # HELPERS - # - - def teardown - remove_test - end - - def test_filename - "_test_file_temp.rb" - end - - def remove_test - rm_f test_filename - end - - def write_and_run_test(opts={}) - write(test_filename, generate_test_code(opts)) - run_test - end - - def run_test - @output = `ruby #{test_filename} 2>&1` - end - - - def write(fname, code) - File.open(fname,"w") do |f| - f.print code - end - end - - def show_output - puts "-- BEGIN TEST OUTPUT" - puts @output - puts "-- END TEST OUTPUT" - end - - def see_in_order(*phrases) - idx = 0 - phrases.each do |txt| - idx = @output.index(txt, idx) - if idx.nil? - if @output.index(txt) - flunk "Phrase '#{txt}' is out-of-order in test output:\n#{@output}" - else - flunk "Phrase '#{txt}' not found in test output:\n#{@output}" - end - end - end - end - - def see_in_no_particular_order(*phrases) - phrases.each do |txt| - assert_not_nil @output.index(txt), "Didn't see '#{txt}' in test output:\n#{@output}" - end - end - - def see_results(opts) - if @output =~ /(\d+) tests, (\d+) assertions, (\d+) failures, (\d+) errors/ - tests, assertions, failures, errors = [ $1, $2, $3, $4 ] - [:tests, :assertions, :failures, :errors].each do |key| - eval %{assert_equal(opts[:#{key}].to_s, #{key}, "Wrong number of #{key} in report") if opts[:#{key}]} - end - else - flunk "Didn't see the test results report line" - end - end - - def see_no_results - if @output =~ /\d+ tests, \d+ assertions, \d+ failures, \d+ errors/ - flunk "Should not have had a results line:\n#{@output}" - end - end - - def lib_dir - File.expand_path(File.dirname(__FILE__) + "/../../lib") - end - - def generate_test_code(opts={}) - - if opts[:with_failure] or opts[:raise_nasty_in_test] - test_method_code = generate_failing_test("test_something", opts) - else - test_method_code = generate_passing_test("test_something") - end - - - requires_for_ext = '' - if opts[:use_before_setup] or opts[:use_after_teardown] - requires_for_ext =<<-RFE - $: << "#{lib_dir}" - require 'test_unit_before_after' - RFE - end - - before_setups = '' - if opts[:use_before_setup] - add_on_two = "" - if opts[:flunk_in_before_setup] - add_on_two = %{; test.flunk "Flunk in 2nd before_setup"} - elsif opts[:raise_in_before_setup] - add_on_two = %{; raise "Error in 2nd before_setup"} - end - before_setups =<<-BSTS - Test::Unit::TestCase.before_setup do |test| - puts "1st before_setup" - end - Test::Unit::TestCase.before_setup do |test| - puts "2nd before_setup" #{add_on_two} - end - Test::Unit::TestCase.before_setup do |test| - puts "3rd before_setup" - end - - BSTS - end - - - setup_code =<<-SC - def setup - puts "THE SETUP" - end - SC - if opts[:omit_setup] - setup_code = "" - end - - after_teardowns = '' - if opts[:use_after_teardown] - add_on_one = "" - add_on_two = "" - if opts[:flunk_in_after_teardown] - add_on_one = %{; test.flunk "Flunk in first after_teardown"} - add_on_two = %{; test.flunk "Flunk in second after_teardown"} - elsif opts[:raise_in_after_teardown] - add_on_one = %{; raise "Error in first after_teardown"} - add_on_two = %{; raise "Error in second after_teardown"} - end - after_teardowns =<<-ATDS - Test::Unit::TestCase.after_teardown do |test| - puts "1st after_teardown" #{add_on_one} - end - Test::Unit::TestCase.after_teardown do |test| - puts "2nd after_teardown" #{add_on_two} - end - ATDS - end - - teardown_code =<<-TDC - def teardown - puts "THE TEARDOWN" - end - TDC - if opts[:flunk_in_teardown] - teardown_code =<<-TDC - def teardown - flunk "FLUNK IN TEARDOWN" - end - TDC - elsif opts[:raise_in_teardown] - teardown_code =<<-TDC - def teardown - raise "ERROR IN TEARDOWN" - end - TDC - end - if opts[:omit_teardown] - teardown_code = "" - end - - str = <<-TCODE - require 'test/unit' - #{requires_for_ext} - - #{before_setups} #{after_teardowns} - - class MyExampleTest < Test::Unit::TestCase - #{setup_code} - #{teardown_code} - #{test_method_code} - end - TCODE - end - - def generate_passing_test(tname) - str = <<-TMETH - def #{tname} - puts "A TEST" - end - TMETH - end - - def generate_failing_test(tname, opts={}) - str = "NOT DEFINED?" - if opts[:raise_nasty_in_test] - str = <<-TMETH - def #{tname} - raise NoMemoryError, "NASTY ERROR" - end - TMETH - - elsif opts[:use_helpers] - str = <<-TMETH - def #{tname} - puts "A FAILING TEST" - my_helper - end - - def my_helper - tripwire - end - - def tripwire - assert false, "Instrumented failure" - end - TMETH - else - str = <<-TMETH - def #{tname} - puts "A FAILING TEST" - assert false, "Instrumented failure" - end - TMETH - end - return str - end - - def is_modern_test_unit? - begin - Test::Unit::TestCase::PASSTHROUGH_EXCEPTIONS - return true - rescue NameError - return false - end - end - -end diff --git a/vendor/hardmock/test/unit/trapper_test.rb b/vendor/hardmock/test/unit/trapper_test.rb deleted file mode 100644 index f7d4114f6..000000000 --- a/vendor/hardmock/test/unit/trapper_test.rb +++ /dev/null @@ -1,62 +0,0 @@ -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'hardmock/method_cleanout' -require 'hardmock/trapper' - -class TrapperTest < Test::Unit::TestCase - include Hardmock - - def setup - @mock = Object.new - @mock_control = MyControl.new - @builder = ExpBuilder.new - @trapper = Trapper.new(@mock, @mock_control, @builder) - end - - # - # HELPERS - # - - class MyControl - attr_reader :added - def add_expectation(expectation) - @added ||= [] - @added << expectation - end - end - - class ExpBuilder - attr_reader :options - def build_expectation(options) - @options = options - "dummy expectation" - end - end - - # - # TESTS - # - - def test_method_missing - - output = @trapper.change(:less) - - assert_same @mock, @builder.options[:mock] - assert_equal :change, @builder.options[:method] - assert_equal [:less], @builder.options[:arguments] - assert_not_nil @builder.options[:block] - assert @builder.options[:suppress_arguments_to_block], ":suppress_arguments_to_block should be set" - assert_equal [ "dummy expectation" ], @mock_control.added, - "Wrong expectation added to control" - - assert_equal "dummy expectation", output, "Expectation should have been returned" - - # Examine the block. It should take one argument and simply return - # that argument. because of the 'suppress arguments to block' - # setting, the argument can only end up being a block, in practice. - trapper_block = @builder.options[:block] - assert_equal "the argument", trapper_block.call("the argument"), - "The block should merely return the passed argument" - end - - -end diff --git a/vendor/hardmock/test/unit/verify_error_test.rb b/vendor/hardmock/test/unit/verify_error_test.rb deleted file mode 100644 index ecd23fd23..000000000 --- a/vendor/hardmock/test/unit/verify_error_test.rb +++ /dev/null @@ -1,40 +0,0 @@ -require File.expand_path(File.dirname(__FILE__) + "/../test_helper") -require 'hardmock/method_cleanout' -require 'hardmock/mock_control' -require 'hardmock/errors' -require 'hardmock/expectation_builder' -require 'hardmock/expectation' -require 'hardmock/mock' - -class VerifyErrorTest < Test::Unit::TestCase - include Hardmock - - # - # TESTS - # - - def test_formatted_list_of_unmet_expectations - mock1 = Mock.new('mock1') - mock2 = Mock.new('mock2') - exp1 = Expectation.new( :mock => mock1, :method => 'send_parts', :arguments => [1,2,:a] ) - exp2 = Expectation.new( :mock => mock2, :method => 'grind_it', :arguments => [] ) - - exp_list = [ exp1, exp2 ] - - err = VerifyError.new("This is the error", exp_list) - assert_equal "This is the error:\n * #{exp1.to_s}\n * #{exp2.to_s}", err.message - end - - def test_empty_list_of_expectations - # this is not a normal case; not spending a lot of time to make this better - exp_list = [] - err = VerifyError.new("This is the error:\n", exp_list) - end - - def test_nil_expectation_list - # this is not a normal case; not spending a lot of time to make this better - exp_list = [] - err = VerifyError.new("This is the error:\n", exp_list) - end - -end diff --git a/vendor/unity b/vendor/unity index 5a36b197f..73237c5d2 160000 --- a/vendor/unity +++ b/vendor/unity @@ -1 +1 @@ -Subproject commit 5a36b197fb34c0a77ac891c355596cb5c25aaf5b +Subproject commit 73237c5d224169c7b4d2ec8321f9ac92e8071708