From 248c4d4b8866582757c9ef17e6e59f2f7ca8e41d Mon Sep 17 00:00:00 2001 From: Mike Karlesky Date: Wed, 28 Feb 2024 15:49:46 -0500 Subject: [PATCH 1/5] Added HTML tests report from main branch --- docs/BreakingChanges.md | 2 +- docs/ReleaseNotes.md | 20 +- plugins/report_tests_log_factory/README.md | 33 +++- .../lib/html_tests_reporter.rb | 174 ++++++++++++++++++ .../sample_html_report.png | Bin 0 -> 46135 bytes 5 files changed, 222 insertions(+), 7 deletions(-) create mode 100644 plugins/report_tests_log_factory/lib/html_tests_reporter.rb create mode 100644 plugins/report_tests_log_factory/sample_html_report.png diff --git a/docs/BreakingChanges.md b/docs/BreakingChanges.md index b2b4293b..65fa9003 100644 --- a/docs/BreakingChanges.md +++ b/docs/BreakingChanges.md @@ -85,7 +85,7 @@ This plugin (renamed -- see next section) no longer generates empty log files an # Consolidation of plugins: `json_tests_report`, `xml_tests_report` & `junit_tests_report` ➡️ `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 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). +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. diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index 4563c747..342d1cb1 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -2,7 +2,7 @@ **Version:** 0.32 pre-release incremental build -**Date:** February 26, 2024 +**Date:** February 28, 2024
@@ -171,6 +171,22 @@ Each test executable is now built as a mini project. Using improved `:defines` h 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. +### `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 output of these prior plugins are now simply configuration options for this new 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). +
## 💪 Improvements and 🪲 Bug Fixes @@ -242,7 +258,7 @@ The three previously discrete plugins listed below have been consolidated into a 1. `json_tests_report` 1. `xml_tests_report` -`report_tests_log_factory` is able to generate all 3 reports of the plugins it replaces as well as generate 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. +`report_tests_log_factory` is able to generate all 3 reports of the plugins it replaces as well as 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. diff --git a/plugins/report_tests_log_factory/README.md b/plugins/report_tests_log_factory/README.md index 7f5f5c39..9f10ff13 100644 --- a/plugins/report_tests_log_factory/README.md +++ b/plugins/report_tests_log_factory/README.md @@ -1,16 +1,17 @@ # Ceedling Plugin: Test Suite Report Log Factory -Generate one or more built-in test suite reports — JSON, JUnit XML, or CppUnit XML — or create your own. +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 three available test suite report formats: +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. @@ -43,11 +44,12 @@ Enable the reports you wish to generate — `json`, `junit`, and/or `cppunit` ```yaml :report_tests_log_factory: - # Any one or all three of the following... + # Any one or all four of the following... :reports: - json - junit - cppunit + - html ``` Each report is written to a default filename within `/artifacts/`: @@ -55,6 +57,7 @@ Each report is written to a default filename within `/artifacts/ - ``` ## CppUnit XML Format @@ -274,9 +276,32 @@ In mapping a Ceedling test suite to CppUnit convetions, a CppUnit test name is t 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: diff --git a/plugins/report_tests_log_factory/lib/html_tests_reporter.rb b/plugins/report_tests_log_factory/lib/html_tests_reporter.rb new file mode 100644 index 00000000..b4df98e0 --- /dev/null +++ b/plugins/report_tests_log_factory/lib/html_tests_reporter.rb @@ -0,0 +1,174 @@ +require 'tests_reporter' + +class HtmlTestsReporter < TestsReporter + + def setup() + super( default_filename: 'tests_report.html' ) + end + + # HTML header + def header(results:, stream:) + stream.puts "" + stream.puts '' + stream.puts '' + stream.puts '' + stream.puts '' + stream.puts '' + stream.puts 'Test Overview' + stream.puts '' + stream.puts '' + 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 '' + stream.puts '' + stream.puts '' + stream.puts "" + stream.puts "" + stream.puts "" + stream.puts "" + stream.puts "" + stream.puts "" + stream.puts "" + stream.puts "
TotalPassedIgnoredFailed
#{counts[:total]}#{counts[:total] - counts[:ignored] - counts[:failed]}#{counts[:ignored]}#{counts[:failed]}
" + end + + def write_failures(results, stream) + return if results.size.zero? + + stream.puts '

Failed Tests

' + stream.puts '' + stream.puts '' + stream.puts '' + + results.each do |result| + filename = result[:source][:file] + @first_row = true + + result[:collection].each do |item| + + stream.puts "" + + if @first_row + stream.puts "" + @first_row = false + end + + stream.puts "" + if item[:message].empty? + stream.puts "" + else + if item[:message].size > 150 + stream.puts "" + else + stream.puts "" + end + end + stream.puts "" + end + end + + stream.puts "" + stream.puts "
FileLocationMessage
#{filename}#{item[:test]}::#{item[:line]}
Message hidden due to long length.#{item[:message]}
#{item[:message]}
" + end + + def write_tests(results, stream, title, style) + return if results.size.zero? + + stream.puts "

#{title}

" + stream.puts "" + stream.puts '' + stream.puts '' + + results.each do |result| + filename = result[:source][:file] + @first_row = true + + result[:collection].each do |item| + stream.puts "" + + if @first_row + stream.puts "" + @first_row = false + end + + stream.puts "" + if item[:message].empty? + stream.puts "" + else + if item[:message].size > 150 + stream.puts "" + else + stream.puts "" + end + end + stream.puts "" + end + end + + stream.puts "" + stream.puts "
FileNameMessage
#{filename}#{item[:test]}
Message hidden due to long length.#{item[:message]}
#{item[:message]}
" + end + +end 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 0000000000000000000000000000000000000000..2cc6468cc59d58a0b8fe95ed48f6be44b15fe0ef GIT binary patch literal 46135 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 literal 0 HcmV?d00001 From 08209433aa093a9c5e03349c2a122fb9f752111e Mon Sep 17 00:00:00 2001 From: Mike Karlesky Date: Wed, 28 Feb 2024 15:54:52 -0500 Subject: [PATCH 2/5] Documented :test_runner in project file --- docs/CeedlingPacket.md | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/docs/CeedlingPacket.md b/docs/CeedlingPacket.md index ef5614fb..2afceade 100644 --- a/docs/CeedlingPacket.md +++ b/docs/CeedlingPacket.md @@ -1839,7 +1839,7 @@ migrated to the `:test_build` and `:release_build` sections. ``` yaml :test_runner: - :cmdline_args: true + :cmdline_args: true ``` If a test segfaults when `cmdline_args` has be set to `true`, the debugger will execute @@ -3270,7 +3270,38 @@ project configuration file. ## `:test_runner` Configure test runner generation -TODO: ... +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 are easily +forgotten. + +So, Unity provides a script able to generate a test runner in C for you. It +relies on [conventions] used in 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. A YAML hash beneath `:test_runner` is provided directly to that script. + +[Test runner configuration options are documented in the Unity project][unity-runner-options]. + +Example configuration: + +```yaml +:test_runner: + # Insert additional #include statements in a generated runner + :includes: + - Foo.h + - Bar.h +``` + +[ceedling-conventions]: #important-conventions--behaviors +[unity-runner-options]: https://github.com/ThrowTheSwitch/Unity/blob/master/docs/UnityHelperScriptsGuide.md#options-accepted-by-generate_test_runnerrb ## `:tools` Configuring command line tools used for build steps From 183fabeac4040434079adeff4d820fa49289c790 Mon Sep 17 00:00:00 2001 From: Mike Karlesky Date: Wed, 28 Feb 2024 15:55:07 -0500 Subject: [PATCH 3/5] Documentation format and typo fixes --- docs/BreakingChanges.md | 2 +- docs/ReleaseNotes.md | 2 +- plugins/gcov/README.md | 8 +++++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/BreakingChanges.md b/docs/BreakingChanges.md index 65fa9003..cdddc5dc 100644 --- a/docs/BreakingChanges.md +++ b/docs/BreakingChanges.md @@ -83,7 +83,7 @@ See the [official documentation](CeedlingPacket.md) on global constants & access 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 plugins: `json_tests_report`, `xml_tests_report` & `junit_tests_report` ➡️ `report_tests_log_factory` +# 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). diff --git a/docs/ReleaseNotes.md b/docs/ReleaseNotes.md index 342d1cb1..a436ce9b 100644 --- a/docs/ReleaseNotes.md +++ b/docs/ReleaseNotes.md @@ -126,7 +126,7 @@ The gcov plugin has been updated and improved, but its proprietary counterpart, #### 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 `gcvor`-only configuration was maintained. This support for the deprecated `gcvor` configuration format has been 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. diff --git a/plugins/gcov/README.md b/plugins/gcov/README.md index d2b955b0..079ed9dc 100644 --- a/plugins/gcov/README.md +++ b/plugins/gcov/README.md @@ -15,7 +15,9 @@ 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 help][troubleshooting]. + +[troubleshooting]: #advanced-configuration--troubleshooting # Simple Coverage Summaries @@ -752,10 +754,10 @@ 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). -## “gcvor not found” +## “gcovr not found” `gcovr` is a Python-based application. Depending on the particulars of its -installation and your platform, you may encounter a “gcvor not found” error. +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. From d4ed241e76384e8fbd27fee0b76b802ce87948c3 Mon Sep 17 00:00:00 2001 From: Mike Karlesky Date: Wed, 28 Feb 2024 16:35:19 -0500 Subject: [PATCH 4/5] Beginning of duration measurment documentation --- docs/CeedlingPacket.md | 24 ++++++++++++++++++++++ plugins/report_tests_log_factory/README.md | 4 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/docs/CeedlingPacket.md b/docs/CeedlingPacket.md index 2afceade..b33fae21 100644 --- a/docs/CeedlingPacket.md +++ b/docs/CeedlingPacket.md @@ -1256,6 +1256,30 @@ A test case function signature must have these elements: In other words, a test function signature should look like this: `void test(void)`. +### Execution time (duration) in Ceedling operations & test suites + +#### Ceedling's logged run times + +Ceedling logs two execution times for every project run. + +#### Ceedling test suite and Unity test executable run durations + +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. + +#### 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 by 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). Unity test case durations default to 0 if this Unity 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, 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 in the microsecond range. + ## The Magic of Dependency Tracking Previous versions of Ceedling used features of Rake to offer diff --git a/plugins/report_tests_log_factory/README.md b/plugins/report_tests_log_factory/README.md index 9f10ff13..ee5440a0 100644 --- a/plugins/report_tests_log_factory/README.md +++ b/plugins/report_tests_log_factory/README.md @@ -79,7 +79,7 @@ Some test reporting formats include the execution time (duration) for aspects of 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 in your Ceedling project file (below). This plugin and the core of Ceedling take care of the rest. +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 in reports default to 0 if this Unity compilation option is not configured. ```yaml :unity: @@ -87,7 +87,7 @@ To enable test case duration measurements, they must be enabled as a Unity compi - UNITY_INCLUDE_EXEC_TIME ``` -_Note:_ Most test cases are quite short, and most computers are quite fast. As such, 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. +_Note:_ Most test cases are quite short, and most computers are quite fast. As such, 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 in the microsecond range. [Unity]: https://github.com/ThrowTheSwitch/Unity From 2b2a92982e3f580e3fca3e9635050303949713bf Mon Sep 17 00:00:00 2001 From: Mike Karlesky Date: Thu, 29 Feb 2024 20:47:09 -0500 Subject: [PATCH 5/5] Time (duration) tracking documentation --- docs/CeedlingPacket.md | 111 +++++++++++++++------ plugins/report_tests_log_factory/README.md | 4 +- 2 files changed, 82 insertions(+), 33 deletions(-) diff --git a/docs/CeedlingPacket.md b/docs/CeedlingPacket.md index b33fae21..97af08e7 100644 --- a/docs/CeedlingPacket.md +++ b/docs/CeedlingPacket.md @@ -70,7 +70,7 @@ Once you have Ceedling installed and a project file, Ceedling tasks go like this 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 +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. @@ -106,7 +106,7 @@ It's just all mixed together. 1. **[Now What? How Do I Make It _GO_?][packet-section-7]** - Ceedling's many command line tasks and some of the rules about using them. + Ceedling’s many command line tasks and some of the rules about using them. 1. **[Important Conventions & Behaviors][packet-section-8]** @@ -121,13 +121,13 @@ It's just all mixed together. 1. **[The Almighty Ceedling Project Configuration File (in Glorious YAML)][packet-section-10]** - This is the exhaustive documentation for all of Ceedling's project file + 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. **[Build Directive Macros][packet-section-11]** - These code macros can help you accomplish your build goals When Ceedling's + These code macros can help you accomplish your build goals When Ceedling’s conventions aren't enough. 1. **[Ceedling Plugins][packet-section-12]** @@ -208,7 +208,7 @@ and directory structure – all by way of the configuration file. Further, because Ceedling piggybacks 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 +extend Ceedling’s capabilities for needs such as custom code metrics reporting and coverage testing. ## What’s with This Name? @@ -584,7 +584,7 @@ those header files. It's kinda magical. 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’s [README][/README.md]. Ceedling, Unity, and CMock rely on a variety of [conventions to make your life easier][conventions-and-behaviors]. @@ -1105,7 +1105,7 @@ holds all that stuff if you want). ## Directory Structure, Filenames & Extensions -Much of Ceedling's functionality is driven by collecting files +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 @@ -1256,21 +1256,66 @@ A test case function signature must have these elements: In other words, a test function signature should look like this: `void test(void)`. -### Execution time (duration) in Ceedling operations & test suites +### Execution time (duration) reporting in Ceedling operations & test suites -#### Ceedling's logged run times +#### 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 -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. +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 by reports. +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 (below). Unity test case durations default to 0 if this Unity compilation option is not set. +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: @@ -1278,7 +1323,11 @@ To enable test case duration measurements, they must be enabled as a Unity compi - UNITY_INCLUDE_EXEC_TIME ``` -_Note:_ Most test cases are quite short, and most computers are quite fast. As such, 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 in the microsecond range. +_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 @@ -1375,7 +1424,7 @@ with an exit code of 0 even upon test case failures. ``` 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 +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. @@ -1418,7 +1467,7 @@ both enable it and configure it. 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 +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 @@ -1431,7 +1480,7 @@ documentation. 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. +in Ceedling’s `:unity` project settings. ### Example Unity configurations @@ -1523,11 +1572,11 @@ 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 +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. +section within Ceedling’s project file. [cmock-yaml-config]: #cmock-configure-cmocks-code-generation--compilation @@ -1562,7 +1611,7 @@ compilation with symbols managed in your Ceedling project file's 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 +These can be configured in Ceedling’s `:cexception` ↳ `:defines` project settings. Unlike Unity which is always available in test builds and CMock that @@ -1665,7 +1714,7 @@ for this. A few highlights from that reference page: of Ceedling itself is skewed towards supporting testing, though Ceedling can, of course, build your binary release artifact as well. Note that some complex binary release builds are beyond - Ceedling's abilities. See the Ceedling plugin [subprojects] for + Ceedling’s abilities. See the Ceedling plugin [subprojects] for extending release build abilities. [MinGW]: http://www.mingw.org/ @@ -2246,7 +2295,7 @@ 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 +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 @@ -2492,7 +2541,7 @@ Ceedling uses path lists and wildcard matching against filename extensions to co ## `:defines` Command line symbols used in compilation -Ceedling's internal, default compiler tool configurations (see later `:tools` section) +Ceedling’s internal, default compiler tool configurations (see later `:tools` section) execute compilation of test and source C files. These default tool configurations are a one-size-fits-all approach. If you need to add to @@ -2592,7 +2641,7 @@ matchers and the simpler list format cannot be mixed for `:defines` ↳ `:test`. 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' + 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 @@ -2889,7 +2938,7 @@ few levels of support. ## `:flags` Configure preprocessing, compilation & linking command line flags -Ceedling's internal, default tool configurations (see later `:tools` section) execute +Ceedling’s internal, default tool configurations (see later `:tools` section) execute compilation and linking of test and source files among other needs. These default tool configurations are a one-size-fits-all approach. If you need to add to @@ -2997,7 +3046,7 @@ flags list format cannot be mixed for `:flags` ↳ `:test`. 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' + 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 @@ -3223,7 +3272,7 @@ See [CMock] documentation. * `:verbosity`: - If not set, defaults to Ceedling's verbosity level + If not set, defaults to Ceedling’s verbosity level * `:defines`: @@ -3628,7 +3677,7 @@ 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 +dedicated to Ceedling’s plugins. Each built-in plugin subdirectory includes thorough documentation covering its capabilities and configuration options. _Note_: Many users find that the handy-dandy [Command Hooks plugin][command-hooks] @@ -3718,7 +3767,7 @@ enabled for test builds. #include "unity.h" #include "somefile.h" -// There is no file.h in this project to trigger Ceedling's convention. +// There is no file.h in this project to trigger Ceedling’s convention. // Compile file.c and link into test_mycode executable. TEST_SOURCE_FILE("foo/bar/file.c") @@ -3745,7 +3794,7 @@ Unless you have a pretty funky C project, at least one search path entry — however formed — is necessary for every test executable. Please see [Configuring Your Header File Search Paths][header-file-search-paths] -for an overview of Ceedling's conventions on header file search paths. +for an overview of Ceedling’s conventions on header file search paths. [header-file-search-paths]: #configuring-your-header-file-search-paths @@ -3789,7 +3838,7 @@ for how to create custom plugins. [//]: # (Links in this section already defined above) -## Ceedling's built-in plugins, a directory +## Ceedling’s built-in plugins, a directory ### Ceedling plugin `report_tests_pretty_stdout` @@ -3864,7 +3913,7 @@ https://subscription.packtpub.com/book/programming/9781800208988/11/ch11lvl1sec3 ### Ceedling plugin `command_hooks` -[This plugin][command-hooks] provides a simple means for connecting Ceedling's build events to +[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. diff --git a/plugins/report_tests_log_factory/README.md b/plugins/report_tests_log_factory/README.md index ee5440a0..95dcd385 100644 --- a/plugins/report_tests_log_factory/README.md +++ b/plugins/report_tests_log_factory/README.md @@ -79,7 +79,7 @@ Some test reporting formats include the execution time (duration) for aspects of 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 in reports default to 0 if this Unity compilation option is not configured. +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: @@ -87,7 +87,7 @@ To enable test case duration measurements, they must be enabled as a Unity compi - UNITY_INCLUDE_EXEC_TIME ``` -_Note:_ Most test cases are quite short, and most computers are quite fast. As such, 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 in the microsecond range. +_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