ceedling 1.1.5 → 1.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/GIT_COMMIT_SHA +1 -1
  3. data/Gemfile +4 -0
  4. data/Gemfile.lock +2 -0
  5. data/README.md +4 -1
  6. data/assets/tests_with_conditional_includes/src/feature_config.h +13 -0
  7. data/assets/tests_with_conditional_includes/src/feature_extra.h +13 -0
  8. data/assets/tests_with_conditional_includes/src/local_flag_extra.h +13 -0
  9. data/assets/tests_with_conditional_includes/src/nested_extra.h +13 -0
  10. data/assets/tests_with_conditional_includes/src/nested_wrapper.h +16 -0
  11. data/assets/tests_with_conditional_includes/src/project_flag_extra.h +13 -0
  12. data/assets/tests_with_conditional_includes/src/widget.c +48 -0
  13. data/assets/tests_with_conditional_includes/src/widget.h +15 -0
  14. data/assets/tests_with_conditional_includes/src/widget_feature.c +29 -0
  15. data/assets/tests_with_conditional_includes/src/widget_feature.h +13 -0
  16. data/assets/tests_with_conditional_includes/test/test_widget.c +44 -0
  17. data/assets/tests_with_conditional_includes/test/test_widget_feature.c +31 -0
  18. data/docs/Changelog.md +31 -1
  19. data/docs/KnownIssues.md +1 -0
  20. data/docs/mkdocs/configuration/project-file.md +25 -7
  21. data/docs/mkdocs/configuration/reference/cexception.md +1 -1
  22. data/docs/mkdocs/configuration/reference/cmock.md +1 -1
  23. data/docs/mkdocs/configuration/reference/unity.md +1 -1
  24. data/docs/mkdocs/getting-started/quick-start.md +11 -8
  25. data/docs/mkdocs/plugins/gcov/gcovr.md +324 -189
  26. data/docs/mkdocs/plugins/gcov/reportgenerator.md +113 -31
  27. data/docs/mkdocs/reference/gcov-plugin.md +21 -3
  28. data/docs/mkdocs/snapshot/plugins/gcov/config/defaults.yml +0 -1
  29. data/examples/wondrous_forest/src/SensorHal.h +13 -0
  30. data/examples/wondrous_forest/src/TemperatureSensor.c +12 -5
  31. data/examples/wondrous_forest/src/TemperatureSensor.h +5 -4
  32. data/examples/wondrous_forest/test/TestTemperatureSensor.c +32 -3
  33. data/lib/ceedling/generators/generator_test_results_backtrace.rb +16 -8
  34. data/lib/ceedling/generators/generator_test_runner.rb +25 -1
  35. data/lib/ceedling/partials/partializer.rb +8 -1
  36. data/lib/ceedling/plugins/plugin_reportinator.rb +9 -0
  37. data/lib/ceedling/plugins/report_tests_stdout_plugin.rb +1 -1
  38. data/lib/ceedling/preprocess/preprocessinator.rb +22 -0
  39. data/lib/ceedling/preprocess/preprocessinator_includes_handler.rb +33 -0
  40. data/lib/version.rb +1 -1
  41. data/plugins/gcov/config/defaults.yml +0 -1
  42. data/plugins/gcov/lib/gcov.rb +35 -16
  43. data/plugins/gcov/lib/gcovr_reportinator.rb +31 -1
  44. data/plugins/valgrind/lib/valgrind.rb +1 -1
  45. data/site-local/configuration/project-file.html +78 -8
  46. data/site-local/configuration/reference/cexception.html +5 -5
  47. data/site-local/configuration/reference/cmock.html +5 -5
  48. data/site-local/configuration/reference/unity.html +5 -5
  49. data/site-local/getting-started/quick-start.html +13 -9
  50. data/site-local/plugins/gcov/gcovr.html +1215 -255
  51. data/site-local/plugins/gcov/reportgenerator.html +404 -46
  52. data/site-local/reference/gcov-plugin.html +40 -7
  53. data/site-local/sitemap.xml.gz +0 -0
  54. data/site-local/snapshot/plugins/gcov/config/defaults.yml +0 -1
  55. data/spec/support/system/spec_system_helper.rb +11 -0
  56. data/spec/support/system/system_context.rb +25 -12
  57. data/spec/system/conditional_include_macro_visibility_spec.rb +152 -0
  58. data/spec/system/deployment_as_gem_spec.rb +2 -0
  59. data/spec/system/deployment_as_vendor_spec.rb +2 -0
  60. data/spec/system/encoding_stress_spec.rb +4 -4
  61. data/spec/system/example_wondrous_forest_spec.rb +2 -2
  62. data/spec/system/gcov_deployment_spec.rb +2 -0
  63. data/spec/system/support/common_test_cases.rb +47 -1
  64. data/spec/system/support/gcov_common_test_cases.rb +52 -0
  65. data/spec/system/test_runner_system_include_spec.rb +90 -0
  66. data/spec/units/generators/generator_test_results_backtrace_spec.rb +51 -0
  67. data/spec/units/generators/generator_test_runner_spec.rb +69 -0
  68. data/spec/units/partials/partializer_spec.rb +69 -0
  69. data/spec/units/plugin_reportinator_spec.rb +38 -0
  70. data/spec/units/preprocess/preprocessinator_includes_handler_spec.rb +61 -0
  71. data/spec/units/preprocess/preprocessinator_spec.rb +136 -0
  72. data/vendor/c_exception/lib/CException.h +1 -1
  73. data/vendor/c_exception/project.yml +2 -2
  74. data/vendor/c_exception/test/TestException.c +1 -1
  75. data/vendor/c_exception/test/support/CExceptionConfig.h +5 -11
  76. data/vendor/cmock/docs/CMockChangeLog.md +23 -22
  77. data/vendor/cmock/scripts/create_makefile.rb +4 -3
  78. data/vendor/cmock/src/cmock.h +1 -1
  79. data/vendor/cmock/vendor/c_exception/lib/CException.h +1 -1
  80. data/vendor/cmock/vendor/c_exception/project.yml +2 -2
  81. data/vendor/cmock/vendor/c_exception/test/TestException.c +1 -1
  82. data/vendor/cmock/vendor/c_exception/test/support/CExceptionConfig.h +5 -11
  83. data/vendor/cmock/vendor/unity/auto/generate_test_runner.rb +85 -29
  84. data/vendor/cmock/vendor/unity/docs/UnityChangeLog.md +1 -0
  85. data/vendor/cmock/vendor/unity/examples/unity_config.h +8 -0
  86. data/vendor/cmock/vendor/unity/src/unity.c +9 -1
  87. data/vendor/cmock/vendor/unity/src/unity.h +1 -1
  88. data/vendor/cmock/vendor/unity/test/rakefile +2 -1
  89. data/vendor/cmock/vendor/unity/test/testdata/testRunnerGeneratorEmpty.c +13 -0
  90. data/vendor/cmock/vendor/unity/test/tests/test_generate_test_runner.rb +31 -0
  91. data/vendor/unity/auto/generate_test_runner.rb +63 -57
  92. data/vendor/unity/docs/UnityChangeLog.md +1 -0
  93. data/vendor/unity/examples/unity_config.h +8 -0
  94. data/vendor/unity/src/unity.c +9 -1
  95. data/vendor/unity/src/unity.h +1 -1
  96. data/vendor/unity/test/rakefile +2 -1
  97. data/vendor/unity/test/tests/test_generate_test_runner.rb +20 -0
  98. metadata +23 -3
  99. data/vendor/cmock/vendor/unity/auto/run_test.erb +0 -37
@@ -6,6 +6,7 @@
6
6
  # =========================================================================
7
7
 
8
8
  require 'fileutils'
9
+ require 'bundler'
9
10
  require 'ceedling/encodinator'
10
11
  require_relative 'gem_dir_layout'
11
12
 
@@ -44,16 +45,26 @@ class SystemContext
44
45
  )
45
46
 
46
47
  Dir.chdir(shared_dir) do
47
- saved = ENV.to_hash
48
48
  begin
49
- %w{BUNDLE_GEMFILE BUNDLE_BIN_PATH RUBYOPT}.each { |k| ENV.delete(k) }
50
- deploy_output = `bundle config set --local path '#{shared_gem.install_dir}' 2>&1`
51
- # --prefer-local: Without it, Bundler resolves gems (e.g. `erb`) fresh from
52
- # rubygems repository even when Ruby's default-gem copy satisfies the Gemfile constraint.
53
- deploy_output += `bundle install --prefer-local 2>&1`
49
+ # Bundler.with_unbundled_env restores the pre-`bundle exec` environment for this
50
+ # block -- clearing not just BUNDLE_GEMFILE/BUNDLE_BIN_PATH/RUBYOPT (the previous,
51
+ # incomplete hand-rolled list here) but also GEM_HOME/GEM_PATH/RUBYLIB/PATH and every
52
+ # other var Bundler sets when this spec suite itself runs under `bundle exec` against
53
+ # a non-default GEM_HOME (e.g. `bundle install --path vendor/bundle`). Any one of
54
+ # those left in place is enough on its own for the `bundle exec ruby -S ceedling
55
+ # version` verification below to load the *outer* project's bundler/gems instead of
56
+ # this shared gem's own `--local path` install, and fail to find this Gemfile's exact
57
+ # pinned versions there.
58
+ deploy_output = Bundler.with_unbundled_env do
59
+ output = `bundle config set --local path '#{shared_gem.install_dir}' 2>&1`
60
+ # --prefer-local: Without it, Bundler resolves gems (e.g. `erb`) fresh from
61
+ # rubygems repository even when Ruby's default-gem copy satisfies the Gemfile constraint.
62
+ output += `bundle install --prefer-local 2>&1`
63
+ output
64
+ end
54
65
  raise VerificationFailed, "bundle install failed:\n#{deploy_output}" unless $?.success?
55
66
 
56
- verify = `bundle exec ruby -S ceedling version 2>&1`
67
+ verify = Bundler.with_unbundled_env { `bundle exec ruby -S ceedling version 2>&1` }
57
68
  unless $?.success?
58
69
  raise VerificationFailed,
59
70
  "Ceedling does not appear to be installed or ready for use.\n" \
@@ -62,8 +73,6 @@ class SystemContext
62
73
  rescue
63
74
  FileUtils.rm_rf(shared_dir)
64
75
  raise
65
- ensure
66
- ENV.replace(saved)
67
76
  end
68
77
  end
69
78
 
@@ -226,8 +235,11 @@ class SystemContext
226
235
  # encoding regardless of their actual content, so any project source content the
227
236
  # subprocess echoes (verbose logging, compiler output, etc.) can make later regex
228
237
  # matching against this string raise. Sanitize once here, the same way Ceedling itself
229
- # sanitizes file content it scans.
230
- @raw_output = (stdout + stderr).clean_encoding
238
+ # sanitizes file content it scans -- reused for both the raw output and the failure
239
+ # report below, since a real compiler's own diagnostic text (e.g. clang's curly-quoted
240
+ # identifiers) is exactly the kind of non-ASCII content this needs to survive too.
241
+ stdout, stderr = stdout.clean_encoding, stderr.clean_encoding
242
+ @raw_output = stdout + stderr
231
243
  @console_summary = compose_failure_report(stdout, stderr)
232
244
 
233
245
  SystemTestOutput.new(@raw_output)
@@ -241,7 +253,8 @@ class SystemContext
241
253
 
242
254
  @last_cmd = cmd
243
255
  @last_exit_status = status.exitstatus
244
- @raw_output = (stdout + stderr).clean_encoding
256
+ stdout, stderr = stdout.clean_encoding, stderr.clean_encoding
257
+ @raw_output = stdout + stderr
245
258
  @console_summary = compose_failure_report(stdout, stderr)
246
259
 
247
260
  SystemTestOutput.new(@raw_output)
@@ -0,0 +1,152 @@
1
+ # =========================================================================
2
+ # Ceedling - Test-Centered Build System for C
3
+ # ThrowTheSwitch.org
4
+ # Copyright (c) 2010-26 Mike Karlesky, Mark VanderVoord, & Greg Williams
5
+ # SPDX-License-Identifier: MIT
6
+ # =========================================================================
7
+
8
+ require 'spec_system_helper'
9
+
10
+ ##
11
+ ## Conditional-#include Macro-Visibility Tests (directives-only preprocessing)
12
+ ## =============================================================================
13
+ ##
14
+ ## Ceedling discovers a file's #includes via two preprocessor passes reconciled
15
+ ## into one list: a "bare" pass and an accurate pass (gcc -E -fdirectives-only
16
+ ## against the real file with real search paths -- genuinely opens every
17
+ ## header). Reconciliation keeps an accurate-pass entry only if bare also found
18
+ ## it, to filter out headers reached only via a deeper, nested path.
19
+ ##
20
+ ## "Bare" is itself two things unioned together: a gcc -M -MG -MP pass against
21
+ ## an isolated, sibling-free copy of the file (so it never actually opens any
22
+ ## header the file #includes, but can still resolve an #include whose own
23
+ ## target is a macro, since command-line -D defines are visible even in
24
+ ## isolation), and a plain literal text scan of the file's own #include lines
25
+ ## (no conditional evaluation at all, so it sees past a guard the isolated gcc
26
+ ## pass can't evaluate -- but also can't resolve a macro-computed #include
27
+ ## target, since there's no literal filename in the source text to find).
28
+ ## Neither replaces the other; each catches what the other structurally can't.
29
+ ##
30
+ ## These tests characterize that reconciliation's behavior for #include
31
+ ## directives guarded by an #if/#ifdef, across the different places the
32
+ ## guarding macro can come from -- some already handled correctly, one
33
+ ## (issue #1223) fixed by adding the text-scan half of "bare" above.
34
+ ##
35
+ ## Test assets: assets/tests_with_conditional_includes/
36
+ ## - widget.c/.h: three scenarios that already work correctly:
37
+ ## (a) conditional include gated on a project :defines macro
38
+ ## (b) conditional include gated on a same-file #define
39
+ ## (c) a header reached only transitively (nested_wrapper.h's own
40
+ ## #include, never itself directly #include'd by widget.c) --
41
+ ## must not appear duplicated in as a false top-level entry
42
+ ## - widget_feature.c/.h + feature_config.h + feature_extra.h: issue #1223
43
+ ## itself -- a conditional include gated on a macro defined by an
44
+ ## *earlier #include in the same file* (feature_config.h's FEATURE_LEVEL),
45
+ ## which the isolated bare pass can never see, silently dropping
46
+ ## feature_extra.h and leaving FEATURE_EXTRA_MACRO undeclared.
47
+ ##
48
+
49
+ ceedling_system_tests do
50
+
51
+ before :all do
52
+ @c = SystemContext.new
53
+ @c.deploy_gem
54
+ end
55
+
56
+ after :all do
57
+ @c.done!
58
+ end
59
+
60
+ before { @proj_name = unique_proj_name("cond_inc") }
61
+
62
+ describe "Deployed as a gem" do
63
+ before do
64
+ @c.with_context do
65
+ @c.ceedling_appcmd_exec("new #{@proj_name}")
66
+ end
67
+ end
68
+
69
+ # =========================================================================
70
+ describe "Conditional includes that already resolve correctly today" do
71
+ # =========================================================================
72
+
73
+ before do
74
+ @c.with_context do
75
+ Dir.chdir @proj_name do
76
+ FileUtils.cp test_asset_path("tests_with_conditional_includes/src/widget.h"), 'src/'
77
+ FileUtils.cp test_asset_path("tests_with_conditional_includes/src/widget.c"), 'src/'
78
+ FileUtils.cp test_asset_path("tests_with_conditional_includes/src/project_flag_extra.h"), 'src/'
79
+ FileUtils.cp test_asset_path("tests_with_conditional_includes/src/local_flag_extra.h"), 'src/'
80
+ FileUtils.cp test_asset_path("tests_with_conditional_includes/src/nested_wrapper.h"), 'src/'
81
+ FileUtils.cp test_asset_path("tests_with_conditional_includes/src/nested_extra.h"), 'src/'
82
+ FileUtils.cp test_asset_path("tests_with_conditional_includes/test/test_widget.c"), 'test/'
83
+
84
+ settings = {
85
+ :project => { :use_partials => true },
86
+ :defines => { :test => ['PROJECT_FLAG'] }
87
+ }
88
+ @c.merge_project_yml_for_test(settings)
89
+ end
90
+ end
91
+ end
92
+
93
+ it "resolves a project-:defines-gated include, a same-file-#define-gated include, and correctly excludes a transitively-nested header from duplication" do
94
+ @c.with_context do
95
+ Dir.chdir @proj_name do
96
+ output = @c.ceedling_build_exec("test:widget")
97
+ expect(@c.last_exit_status).to eq(0)
98
+ expect(output).to match(/TESTED:\s+3/)
99
+ expect(output).to match(/PASSED:\s+3/)
100
+ expect(output).to match(/FAILED:\s+0/)
101
+
102
+ # (c) nested_extra.h is reached only transitively, through
103
+ # nested_wrapper.h's own #include -- confirm it is not promoted
104
+ # into a spurious, duplicated top-level #include in the generated
105
+ # Partial implementation (the exact filtering property the #1223
106
+ # fix must not break).
107
+ generated = Dir.glob('build/test/partials/**/*_impl.c').first
108
+ expect(generated).not_to be_nil
109
+ contents = File.read(generated)
110
+ expect(contents.scan(/#include\s+"nested_extra\.h"/).length).to eq(0)
111
+ end
112
+ end
113
+ end
114
+
115
+ end
116
+
117
+ # =========================================================================
118
+ describe "Conditional include gated on a macro from an earlier #include in the same file (issue #1223)" do
119
+ # =========================================================================
120
+
121
+ before do
122
+ @c.with_context do
123
+ Dir.chdir @proj_name do
124
+ FileUtils.cp test_asset_path("tests_with_conditional_includes/src/widget_feature.h"), 'src/'
125
+ FileUtils.cp test_asset_path("tests_with_conditional_includes/src/widget_feature.c"), 'src/'
126
+ FileUtils.cp test_asset_path("tests_with_conditional_includes/src/feature_config.h"), 'src/'
127
+ FileUtils.cp test_asset_path("tests_with_conditional_includes/src/feature_extra.h"), 'src/'
128
+ FileUtils.cp test_asset_path("tests_with_conditional_includes/test/test_widget_feature.c"), 'test/'
129
+
130
+ settings = { :project => { :use_partials => true } }
131
+ @c.merge_project_yml_for_test(settings)
132
+ end
133
+ end
134
+ end
135
+
136
+ it "keeps FEATURE_EXTRA_MACRO visible in the generated Partial so the build compiles" do
137
+ @c.with_context do
138
+ Dir.chdir @proj_name do
139
+ output = @c.ceedling_build_exec("test:widget_feature")
140
+ expect(@c.last_exit_status).to eq(0)
141
+ expect(output).to match(/TESTED:\s+1/)
142
+ expect(output).to match(/PASSED:\s+1/)
143
+ expect(output).to match(/FAILED:\s+0/)
144
+ end
145
+ end
146
+ end
147
+
148
+ end
149
+
150
+ end
151
+
152
+ end
@@ -87,6 +87,8 @@ ceedling_system_tests do
87
87
 
88
88
  describe "Verbosity and output" do
89
89
  test_case :test_project_with_named_verbosity
90
+ test_case :test_project_with_warnings_verbosity_prints_overall_summary
91
+ test_case :test_project_with_errors_verbosity_prints_overall_summary_on_passing_run
90
92
  test_case :test_project_with_numerical_verbosity
91
93
  test_case :report_tests_raw_output_log_plugin
92
94
  end
@@ -90,6 +90,8 @@ ceedling_system_tests do
90
90
 
91
91
  describe "Verbosity and output" do
92
92
  test_case :test_project_with_named_verbosity
93
+ test_case :test_project_with_warnings_verbosity_prints_overall_summary
94
+ test_case :test_project_with_errors_verbosity_prints_overall_summary_on_passing_run
93
95
  test_case :test_project_with_numerical_verbosity
94
96
  test_case :report_tests_raw_output_log_plugin
95
97
  end
@@ -131,8 +131,8 @@ ceedling_system_tests do
131
131
  @c.merge_project_yml_for_test({ :test_build => { :preprocess_force_fallback => false } })
132
132
  output = @c.ceedling_build_exec("test:all")
133
133
  expect(@c.last_exit_status).to eq(0)
134
- expect(output).to match(/TESTED:\s+67/)
135
- expect(output).to match(/PASSED:\s+67/)
134
+ expect(output).to match(/TESTED:\s+68/)
135
+ expect(output).to match(/PASSED:\s+68/)
136
136
  end
137
137
  end
138
138
  end
@@ -144,8 +144,8 @@ ceedling_system_tests do
144
144
  @c.merge_project_yml_for_test({ :test_build => { :preprocess_force_fallback => true } })
145
145
  output = @c.ceedling_build_exec("test:all")
146
146
  expect(@c.last_exit_status).to eq(0)
147
- expect(output).to match(/TESTED:\s+67/)
148
- expect(output).to match(/PASSED:\s+67/)
147
+ expect(output).to match(/TESTED:\s+68/)
148
+ expect(output).to match(/PASSED:\s+68/)
149
149
  end
150
150
  end
151
151
  end
@@ -46,8 +46,8 @@ ceedling_system_tests do
46
46
  @c.with_context do
47
47
  Dir.chdir "wondrous_forest" do
48
48
  @output = @c.ceedling_build_exec("test:all")
49
- expect(@output).to match(/TESTED:\s+67/)
50
- expect(@output).to match(/PASSED:\s+67/)
49
+ expect(@output).to match(/TESTED:\s+68/)
50
+ expect(@output).to match(/PASSED:\s+68/)
51
51
  expect(@output).to match(/FAILED:\s+0/)
52
52
  end
53
53
  end
@@ -33,8 +33,10 @@ ceedling_system_tests do
33
33
  end
34
34
  end
35
35
 
36
+ test_case :project_with_gcov_enabled_test_all_does_not_require_gcovr
36
37
  test_case :project_with_gcov_success
37
38
  test_case :project_with_gcov_fail
39
+ test_case :project_with_gcov_fail_under_decision
38
40
  test_case :gcov_console_report_with_system_header
39
41
  test_case :gcov_console_report_with_partial
40
42
  # TODO: Restore these tests when the :abort_on_uncovered option is restored in the Gcov plugin
@@ -114,7 +114,11 @@ module CommonSystemTestCases
114
114
  @c.with_context do
115
115
  Dir.chdir @proj_name do
116
116
  all_docs = Dir["docs/*"]
117
- expect(all_docs).to contain_exactly('docs/ceedling', 'docs/unity', 'docs/cmock', 'docs/c_exception', 'docs/license.txt')
117
+ # docs/ceedling only exists when this repo's own local HTML docs bundle
118
+ # (site-local/) was available to copy in -- see html_docs_bundle_available?.
119
+ expected = ['docs/unity', 'docs/cmock', 'docs/c_exception', 'docs/license.txt']
120
+ expected << 'docs/ceedling' if html_docs_bundle_available?
121
+ expect(all_docs).to contain_exactly(*expected)
118
122
  end
119
123
  end
120
124
  end
@@ -197,6 +201,48 @@ module CommonSystemTestCases
197
201
  end
198
202
  end
199
203
 
204
+ # The Overall Test Summary must still print at :warnings verbosity
205
+ # (`--verbosity=warnings`, Verbosity::COMPLAIN) on an all-passing run, not just
206
+ # when verbosity is :normal or higher, or when a test fails.
207
+ def test_project_with_warnings_verbosity_prints_overall_summary
208
+ @c.with_context do
209
+ Dir.chdir @proj_name do
210
+ FileUtils.cp test_asset_path("example_file.h"), 'src/'
211
+ FileUtils.cp test_asset_path("example_file.c"), 'src/'
212
+ FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/'
213
+
214
+ output = @c.ceedling_build_exec("--verbosity=warnings")
215
+ expect(@c.last_exit_status).to eq(0)
216
+ expect(output).to match(/OVERALL TEST SUMMARY/)
217
+ expect(output).to match(/TESTED:\s+\d/)
218
+ expect(output).to match(/PASSED:\s+\d/)
219
+ expect(output).to match(/FAILED:\s+\d/)
220
+ expect(output).to match(/IGNORED:\s+\d/)
221
+ end
222
+ end
223
+ end
224
+
225
+ # ERRORS is the true floor for the Overall Test Summary -- a build's own
226
+ # test results are core information, visible even at the most restrictive
227
+ # verbosity short of :silent, whether the run passed or failed.
228
+ def test_project_with_errors_verbosity_prints_overall_summary_on_passing_run
229
+ @c.with_context do
230
+ Dir.chdir @proj_name do
231
+ FileUtils.cp test_asset_path("example_file.h"), 'src/'
232
+ FileUtils.cp test_asset_path("example_file.c"), 'src/'
233
+ FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/'
234
+
235
+ output = @c.ceedling_build_exec("--verbosity=errors")
236
+ expect(@c.last_exit_status).to eq(0)
237
+ expect(output).to match(/OVERALL TEST SUMMARY/)
238
+ expect(output).to match(/TESTED:\s+\d/)
239
+ expect(output).to match(/PASSED:\s+\d/)
240
+ expect(output).to match(/FAILED:\s+\d/)
241
+ expect(output).to match(/IGNORED:\s+\d/)
242
+ end
243
+ end
244
+ end
245
+
200
246
  def test_project_with_numerical_verbosity
201
247
  @c.with_context do
202
248
  Dir.chdir @proj_name do
@@ -10,6 +10,38 @@ require_relative 'gcov_helpers'
10
10
  module GcovCommonTestCases
11
11
  include GcovHelpers
12
12
 
13
+ # #1252 -- Enabling the gcov plugin must not make gcovr a hard dependency of every
14
+ # build. Deliberately does NOT call prep_project_yml_for_coverage (which conditionally
15
+ # strips :utilities ➡️ gcovr when the host lacks a real gcovr) -- the point here is
16
+ # gcovr enabled in config but genuinely unreachable, reproducing #1252 regardless of
17
+ # what's actually installed on the machine running this spec.
18
+ def project_with_gcov_enabled_test_all_does_not_require_gcovr
19
+ @c.with_context do
20
+ Dir.chdir @proj_name do
21
+ @c.uncomment_project_yml_option_for_test("- gcov")
22
+ # Default :gcov ↳ :utilities already includes gcovr and :reports already
23
+ # includes HtmlBasic -- exactly the enabled-but-unused shape #1252 reported.
24
+ @c.merge_project_yml_for_test(
25
+ { :tools => { :gcov_gcovr_report => { :executable => 'nonexistent_gcovr_binary_for_1252_test' } } }
26
+ )
27
+ FileUtils.cp test_asset_path("example_file.h"), 'src/'
28
+ FileUtils.cp test_asset_path("example_file.c"), 'src/'
29
+ FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/'
30
+
31
+ # An ordinary test:all run must succeed -- it never needs gcovr at all.
32
+ output = @c.ceedling_build_exec("test:all")
33
+ expect(@c.last_exit_status).to eq(0)
34
+ expect(output).to_not match(/nonexistent_gcovr_binary_for_1252_test/)
35
+
36
+ # A real gcov: build still needs gcovr and must still fail loudly when it's missing --
37
+ # the fix defers gcovr's tool validation, it doesn't remove it.
38
+ output = @c.ceedling_build_exec("gcov:all")
39
+ expect(@c.last_exit_status).to_not eq(0)
40
+ expect(output).to match(/nonexistent_gcovr_binary_for_1252_test/)
41
+ end
42
+ end
43
+ end
44
+
13
45
  def project_with_gcov_success
14
46
  @c.with_context do
15
47
  Dir.chdir @proj_name do
@@ -46,6 +78,26 @@ module GcovCommonTestCases
46
78
  end
47
79
  end
48
80
 
81
+ # gcovr's own --fail-under-decision is a no-op without --decisions also present --
82
+ # Ceedling supplies --decisions automatically whenever :fail_under_decision is
83
+ # configured, so a passing run reports success instead of gcovr's own opaque
84
+ # "need also option --decision" CLI error.
85
+ def project_with_gcov_fail_under_decision
86
+ @c.with_context do
87
+ Dir.chdir @proj_name do
88
+ prep_project_yml_for_coverage
89
+ @c.merge_project_yml_for_test( { :gcov => { :gcovr => { :fail_under_decision => 1 } } } )
90
+ FileUtils.cp test_asset_path("example_file.h"), 'src/'
91
+ FileUtils.cp test_asset_path("example_file.c"), 'src/'
92
+ FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/'
93
+
94
+ output = @c.ceedling_build_exec("gcov:all")
95
+ expect(@c.last_exit_status).to eq(0)
96
+ expect(output).to_not match(/need also option --decision/)
97
+ end
98
+ end
99
+ end
100
+
49
101
  # TODO: Restore this test when the :abort_on_uncovered option is restored in the Gcov plugin
50
102
  # def project_with_gcov_fail_because_of_uncovered_files
51
103
  # @c.with_context do
@@ -0,0 +1,90 @@
1
+ # =========================================================================
2
+ # Ceedling - Test-Centered Build System for C
3
+ # ThrowTheSwitch.org
4
+ # Copyright (c) 2010-26 Mike Karlesky, Mark VanderVoord, & Greg Williams
5
+ # SPDX-License-Identifier: MIT
6
+ # =========================================================================
7
+
8
+ require 'spec_system_helper'
9
+
10
+ ##
11
+ ## Generated Runner Preserves System #include Directory/Brackets (issue #1236)
12
+ ## =============================================================================
13
+ ##
14
+ ## A test file directly #include-ing a system header with a directory component
15
+ ## (e.g. `#include <sys/stat.h>`) previously produced a generated runner containing
16
+ ## `#include "stat.h"` -- the directory component and the system-include `<>`
17
+ ## delimiters were both lost, since GeneratorTestRunner#generate rendered every
18
+ ## include (mocks aside) down to its bare basename with no `<>` for system headers.
19
+ ## The test file itself compiled fine; only the separately generated runner file
20
+ ## failed, with a "No such file or directory" error on the mangled include.
21
+ ##
22
+
23
+ TEST_RUNNER_SYSTEM_INCLUDE_C = <<~C
24
+ #include <sys/stat.h>
25
+
26
+ #include "unity.h"
27
+
28
+ void setUp(void) {}
29
+ void tearDown(void) {}
30
+
31
+ void test_runner_preserves_system_include_path(void)
32
+ {
33
+ struct stat value;
34
+ TEST_ASSERT_EQUAL_UINT(sizeof(value), sizeof(struct stat));
35
+ }
36
+ C
37
+
38
+ ceedling_system_tests do
39
+
40
+ before :all do
41
+ @c = SystemContext.new
42
+ @c.deploy_gem
43
+ end
44
+
45
+ after :all do
46
+ @c.done!
47
+ end
48
+
49
+ before { @proj_name = unique_proj_name("runner_sys_inc") }
50
+
51
+ describe "Deployed as a gem" do
52
+ before do
53
+ @c.with_context do
54
+ @c.ceedling_appcmd_exec("new #{@proj_name}")
55
+ end
56
+ end
57
+
58
+ before do
59
+ @c.with_context do
60
+ Dir.chdir @proj_name do
61
+ File.write('test/test_runner_system_include.c', TEST_RUNNER_SYSTEM_INCLUDE_C)
62
+ end
63
+ end
64
+ end
65
+
66
+ it "preserves a system include's directory component and angle brackets in the generated runner" do
67
+ @c.with_context do
68
+ Dir.chdir @proj_name do
69
+ output = @c.ceedling_build_exec("test:all")
70
+
71
+ expect(@c.last_exit_status).to eq(0)
72
+ expect(output).to match(/TESTED:\s+1/)
73
+ expect(output).to match(/PASSED:\s+1/)
74
+ expect(output).to match(/FAILED:\s+0/)
75
+
76
+ # The generated runner is what actually failed to compile in the reported
77
+ # issue -- assert on its content directly rather than just overall build
78
+ # success, since only the runner (not the test file itself) was affected.
79
+ runner_path = Dir.glob('build/test/runners/*_runner.c').first
80
+ expect(runner_path).not_to be_nil
81
+
82
+ runner_contents = File.read(runner_path)
83
+ expect(runner_contents).to include('#include <sys/stat.h>')
84
+ expect(runner_contents).not_to include('#include "stat.h"')
85
+ end
86
+ end
87
+ end
88
+ end
89
+
90
+ end
@@ -130,6 +130,7 @@ SIMPLE_ASSERT_CRASH_OUTPUT =
130
130
  SIMPLE_PASS_OUTPUT = "test_lib.c:3:test_empty:PASS\n---------\n1 Tests 0 Failures 0 Ignored\nOK\n"
131
131
  SIMPLE_FAIL_OUTPUT = "test_lib.c:8:test_bad:FAIL: Expected 1 Was 2\n---------\n1 Tests 1 Failures 0 Ignored\nFAIL\n"
132
132
  SIMPLE_IGNORE_OUTPUT = "test_lib.c:12:test_skip:IGNORE\n---------\n1 Tests 0 Failures 1 Ignored\nOK\n"
133
+ SIMPLE_IGNORE_WITH_MESSAGE_OUTPUT = "test_lib.c:12:test_skip:IGNORE: Not yet implemented\n---------\n1 Tests 0 Failures 1 Ignored\nOK\n"
133
134
 
134
135
 
135
136
  describe GeneratorTestResultsBacktrace do
@@ -255,6 +256,31 @@ describe GeneratorTestResultsBacktrace do
255
256
  expect(expected_output_lines).to include('test_lib.c:9:test_asserting:FAIL: Expected 1 Was 2')
256
257
  end
257
258
 
259
+ # Ignored-crash false positive: a TEST_IGNORE_MESSAGE(...) test case carries a
260
+ # trailing message, same as FAIL. Before this fix, only FAIL tolerated one --
261
+ # a message-carrying IGNORE fell through to "unresolved" and was misreported
262
+ # as crash evidence (a second @file_wrapper.write, for a companion_case that
263
+ # never actually crashed).
264
+ it 'handles an IGNORE test case with a trailing message and does not write a log' do
265
+ allow(@tool_executor).to receive(:exec).and_return(
266
+ { output: GDB_NO_SIGNAL_OUTPUT, time: 0.1, exit_code: 1, stderr: '', status: @ok_status },
267
+ { output: "test_lib.c:9:test_asserting:IGNORE: Not yet implemented\n---------\n1 Tests 0 Failures 1 Ignored\nOK\n",
268
+ time: 0.1, exit_code: 0, stderr: '', status: @ok_status }
269
+ )
270
+
271
+ expect(@file_wrapper).to receive(:write).once.with(%r{test_crashes_elsewhere}, anything, anything)
272
+
273
+ expected_output_lines = []
274
+ allow(@generator_test_results).to receive(:regenerate_test_executable_stdout) do |**kwargs|
275
+ expected_output_lines = kwargs[:output]
276
+ 'regenerated'
277
+ end
278
+
279
+ @backtrace.do_gdb( filename, executable, shell_result, [companion_case] + test_cases, context: :test )
280
+
281
+ expect(expected_output_lines).to include('test_lib.c:9:test_asserting:IGNORE: Not yet implemented')
282
+ end
283
+
258
284
  it "does not trust a matched PASS line when the retry's own real status contradicts it" do
259
285
  # Regression test for issue #1198: a diagnostic retry can print a clean Unity
260
286
  # result line while its own real process outcome says otherwise.
@@ -471,6 +497,31 @@ describe GeneratorTestResultsBacktrace do
471
497
  expect(expected_output_lines).to include('test_lib.c:12:test_skip:IGNORE')
472
498
  end
473
499
 
500
+ # Ignored-crash false positive: a TEST_IGNORE_MESSAGE(...) test case carries a
501
+ # trailing message, same as FAIL (handled just above). Before this fix, only FAIL
502
+ # tolerated one -- a message-carrying IGNORE fell through to the "no result line"
503
+ # branch and was misreported as a second crashed test case (test_skip, alongside
504
+ # the real crash attributed to test_crashes_elsewhere).
505
+ it 'handles an IGNORE test case with a trailing message' do
506
+ test_cases_ignore = [{ test: 'test_crashes_elsewhere', line_number: 20 }, { test: 'test_skip', line_number: 12 }]
507
+
508
+ allow(@tool_executor).to receive(:exec).and_return(
509
+ { output: SIMPLE_ASSERT_CRASH_OUTPUT, time: 0.1, exit_code: 134, stderr: '', status: @ok_status },
510
+ { output: SIMPLE_IGNORE_WITH_MESSAGE_OUTPUT, time: 0.02, exit_code: 0, stderr: '', status: @ok_status }
511
+ )
512
+
513
+ expected_output_lines = []
514
+ allow(@generator_test_results).to receive(:regenerate_test_executable_stdout) do |**kwargs|
515
+ expected_output_lines = kwargs[:output]
516
+ 'regenerated'
517
+ end
518
+
519
+ @backtrace.do_simple( filename, executable, shell_result, test_cases_ignore, context: :test )
520
+
521
+ expect(expected_output_lines).to include('test_lib.c:12:test_skip:IGNORE: Not yet implemented')
522
+ expect(expected_output_lines).to_not include(a_string_matching(/test_skip:FAIL/))
523
+ end
524
+
474
525
  it "does not trust a matched PASS line when the retry's own real status contradicts it" do
475
526
  # Regression test for issue #1198: a diagnostic retry can print a clean Unity
476
527
  # result line while its own real process outcome says otherwise.
@@ -8,6 +8,7 @@
8
8
  require 'spec_helper'
9
9
  require 'ceedling/generators/generator_test_runner'
10
10
  require 'ceedling/parsing_parcels'
11
+ require 'ceedling/includes/includes'
11
12
 
12
13
  describe GeneratorTestRunner do
13
14
  before(:each) do
@@ -141,4 +142,72 @@ describe GeneratorTestRunner do
141
142
  )
142
143
  end
143
144
  end
145
+
146
+ # ===========================================================================
147
+ describe '#generate' do
148
+ # ===========================================================================
149
+ # Regression coverage for https://github.com/ThrowTheSwitch/Ceedling/issues/1236:
150
+ # a system #include with a directory component (e.g. <sys/stat.h>) was rendered
151
+ # into the generated runner as a bare, unbracketed basename ("stat.h"), losing
152
+ # both the directory and the user/system distinction, breaking runner compilation.
153
+ #
154
+ # @unity_runner_generator is a real UnityTestRunnerGenerator, constructed directly
155
+ # inside #initialize rather than injected -- swapped out for a double here so these
156
+ # specs assert on exactly what GeneratorTestRunner itself hands off, independent of
157
+ # Unity's own generated file formatting.
158
+ def build_and_capture_includes(includes:, mocks: [])
159
+ runner = build_runner( test_file_contents: "void test_Thing(void) {}\n" )
160
+
161
+ captured = nil
162
+ fake_generator = double('unity_runner_generator')
163
+ allow(fake_generator).to receive(:generate) do |*args|
164
+ captured = args[4]
165
+ end
166
+ runner.instance_variable_set(:@unity_runner_generator, fake_generator)
167
+
168
+ runner.generate(
169
+ module_name: 'test_thing',
170
+ runner_filepath: '/build/test/runners/test_thing_runner.c',
171
+ mocks: mocks,
172
+ includes: includes
173
+ )
174
+
175
+ return captured
176
+ end
177
+
178
+ it 'renders a system include with a directory component in angle brackets, directory intact' do
179
+ captured = build_and_capture_includes( includes: [ SystemInclude.new('sys/stat.h') ] )
180
+
181
+ expect( captured ).to eq( ['<sys/stat.h>'] )
182
+ end
183
+
184
+ it 'prefers a reconciled system include\'s include_path over its resolved filepath' do
185
+ # Mirrors what Includes.reconcile actually produces: filepath is the real,
186
+ # resolved (possibly absolute) location; include_path is the original,
187
+ # as-written directive text that must be what's rendered.
188
+ reconciled = SystemInclude.new('/usr/include/sys/stat.h', include_path: 'sys/stat.h')
189
+
190
+ captured = build_and_capture_includes( includes: [ reconciled ] )
191
+
192
+ expect( captured ).to eq( ['<sys/stat.h>'] )
193
+ end
194
+
195
+ it 'renders a user include with a directory component down to its bare filename, unchanged from prior behavior' do
196
+ # Unlike a system header, a project's own directories are exposed to the
197
+ # compiler as a flat list of -I search paths -- rendering a user include's
198
+ # own directory component here breaks resolution rather than fixing anything,
199
+ # so this behavior is intentionally unchanged by the issue #1236 fix.
200
+ captured = build_and_capture_includes( includes: [ UserInclude.new('sub/header.h') ] )
201
+
202
+ expect( captured ).to eq( ['header.h'] )
203
+ end
204
+
205
+ it 'renders a mixed list of system and user includes each in their own correct form' do
206
+ captured = build_and_capture_includes(
207
+ includes: [ SystemInclude.new('sys/stat.h'), UserInclude.new('sub/header.h') ]
208
+ )
209
+
210
+ expect( captured ).to eq( ['<sys/stat.h>', 'header.h'] )
211
+ end
212
+ end
144
213
  end