ceedling 1.1.2 → 1.1.4

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 (173) hide show
  1. checksums.yaml +4 -4
  2. data/GIT_COMMIT_SHA +1 -1
  3. data/README.md +1 -1
  4. data/assets/fake_crash_test_fixture.rb +14 -0
  5. data/assets/partial_all_module.h +11 -0
  6. data/assets/run_with_ubsan.rb +15 -0
  7. data/assets/same_dir_pairing_module.c +4 -0
  8. data/assets/same_dir_pairing_module.h +8 -0
  9. data/assets/same_dir_pairing_module_func.h +6 -0
  10. data/assets/same_dir_pairing_other.c +4 -0
  11. data/assets/same_dir_pairing_other.h +8 -0
  12. data/assets/test_example_file_crash_sigsegv_with_param.c +32 -0
  13. data/assets/test_example_file_ub_shift_overflow.c +22 -0
  14. data/assets/test_partial_all_module.c +16 -0
  15. data/assets/test_same_dir_pairing.c +23 -0
  16. data/bin/actions_wrapper.rb +7 -6
  17. data/docs/Changelog.md +29 -1
  18. data/docs/mkdocs/configuration/reference/index.md +8 -0
  19. data/docs/mkdocs/configuration/reference/partials.md +38 -0
  20. data/examples/wondrous_forest/src/LightSensor.c +21 -4
  21. data/examples/wondrous_forest/src/LightSensor.h +12 -4
  22. data/examples/wondrous_forest/test/TestLightSensor.c +34 -4
  23. data/lib/ceedling/c_extractor/c_extractor.rb +37 -11
  24. data/lib/ceedling/c_extractor/c_extractor_constants.rb +0 -3
  25. data/lib/ceedling/c_extractor/c_extractor_declarations.rb +26 -25
  26. data/lib/ceedling/c_extractor/c_extractor_functions.rb +5 -12
  27. data/lib/ceedling/config/configurator.rb +8 -1
  28. data/lib/ceedling/config/configurator_setup.rb +26 -0
  29. data/lib/ceedling/defaults.rb +16 -2
  30. data/lib/ceedling/file_path_utils.rb +7 -0
  31. data/lib/ceedling/file_wrapper.rb +8 -0
  32. data/lib/ceedling/generators/generator.rb +15 -0
  33. data/lib/ceedling/generators/generator_helper.rb +25 -6
  34. data/lib/ceedling/generators/generator_partials.rb +84 -3
  35. data/lib/ceedling/generators/generator_test_results_backtrace.rb +268 -100
  36. data/lib/ceedling/generators/generator_test_runner.rb +20 -2
  37. data/lib/ceedling/includes/includes.rb +66 -11
  38. data/lib/ceedling/objects.yml +5 -0
  39. data/lib/ceedling/partials/partializer.rb +18 -3
  40. data/lib/ceedling/partials/partializer_utils.rb +6 -1
  41. data/lib/ceedling/preprocess/preprocessinator_includes_handler.rb +43 -16
  42. data/lib/ceedling/test_invoker/test_build_executor.rb +19 -8
  43. data/lib/version.rb +1 -1
  44. data/mkdocs.yml +1 -0
  45. data/plugins/gcov/lib/gcov.rb +2 -2
  46. data/site-local/404.html +30 -0
  47. data/site-local/configuration/environment-vars.html +30 -0
  48. data/site-local/configuration/global-collections.html +30 -0
  49. data/site-local/configuration/index.html +30 -0
  50. data/site-local/configuration/loading.html +30 -0
  51. data/site-local/configuration/mixins.html +30 -0
  52. data/site-local/configuration/parallel-builds.html +30 -0
  53. data/site-local/configuration/project-file.html +30 -0
  54. data/site-local/configuration/reference/cexception.html +30 -0
  55. data/site-local/configuration/reference/cmock.html +30 -0
  56. data/site-local/configuration/reference/defines.html +30 -0
  57. data/site-local/configuration/reference/environment.html +30 -0
  58. data/site-local/configuration/reference/extension.html +30 -0
  59. data/site-local/configuration/reference/files.html +30 -0
  60. data/site-local/configuration/reference/flags.html +30 -0
  61. data/site-local/configuration/reference/index.html +36 -0
  62. data/site-local/configuration/reference/libraries.html +30 -0
  63. data/site-local/configuration/reference/mixins.html +30 -0
  64. data/site-local/configuration/reference/partials.html +4257 -0
  65. data/site-local/configuration/reference/paths.html +30 -0
  66. data/site-local/configuration/reference/plugins.html +30 -0
  67. data/site-local/configuration/reference/project.html +30 -0
  68. data/site-local/configuration/reference/release-build.html +31 -1
  69. data/site-local/configuration/reference/test-build.html +31 -1
  70. data/site-local/configuration/reference/test-runner.html +30 -0
  71. data/site-local/configuration/reference/tools.html +30 -0
  72. data/site-local/configuration/reference/unity.html +30 -0
  73. data/site-local/configuration/which-ceedling.html +30 -0
  74. data/site-local/development/index.html +30 -0
  75. data/site-local/development/plugins/configuration.html +30 -0
  76. data/site-local/development/plugins/index.html +30 -0
  77. data/site-local/development/plugins/plugin-subclass.html +30 -0
  78. data/site-local/development/plugins/rake-tasks.html +30 -0
  79. data/site-local/development/workflow.html +30 -0
  80. data/site-local/getting-started/command-line.html +30 -0
  81. data/site-local/getting-started/example-projects/cipher-quest.html +30 -0
  82. data/site-local/getting-started/example-projects/index.html +30 -0
  83. data/site-local/getting-started/example-projects/temp-sensor.html +30 -0
  84. data/site-local/getting-started/example-projects/wondrous-forest.html +30 -0
  85. data/site-local/getting-started/index.html +30 -0
  86. data/site-local/getting-started/installation.html +30 -0
  87. data/site-local/getting-started/quick-start.html +30 -0
  88. data/site-local/help.html +30 -0
  89. data/site-local/index.html +30 -0
  90. data/site-local/overview/build-system.html +30 -0
  91. data/site-local/overview/index.html +30 -0
  92. data/site-local/overview/test-environments.html +30 -0
  93. data/site-local/overview/tools-and-frameworks.html +30 -0
  94. data/site-local/plugins/beep.html +30 -0
  95. data/site-local/plugins/bullseye.html +30 -0
  96. data/site-local/plugins/command-hooks.html +30 -0
  97. data/site-local/plugins/compile-commands-json-db.html +30 -0
  98. data/site-local/plugins/cppcheck.html +30 -0
  99. data/site-local/plugins/dependencies.html +30 -0
  100. data/site-local/plugins/fff.html +30 -0
  101. data/site-local/plugins/gcov/examples.html +30 -0
  102. data/site-local/plugins/gcov/gcovr.html +30 -0
  103. data/site-local/plugins/gcov/index.html +30 -0
  104. data/site-local/plugins/gcov/overview.html +30 -0
  105. data/site-local/plugins/gcov/reportgenerator.html +30 -0
  106. data/site-local/plugins/gcov/reporting.html +30 -0
  107. data/site-local/plugins/gcov/setup.html +30 -0
  108. data/site-local/plugins/gcov/tool-versions.html +30 -0
  109. data/site-local/plugins/gcov/troubleshooting.html +30 -0
  110. data/site-local/plugins/index.html +30 -0
  111. data/site-local/plugins/module-generator.html +30 -0
  112. data/site-local/plugins/report-build-warnings-log.html +30 -0
  113. data/site-local/plugins/report-tests-gtestlike-stdout.html +30 -0
  114. data/site-local/plugins/report-tests-ide-stdout.html +30 -0
  115. data/site-local/plugins/report-tests-log-factory.html +30 -0
  116. data/site-local/plugins/report-tests-pretty-stdout.html +30 -0
  117. data/site-local/plugins/report-tests-raw-output-log.html +30 -0
  118. data/site-local/plugins/report-tests-teamcity-stdout.html +30 -0
  119. data/site-local/plugins/valgrind.html +30 -0
  120. data/site-local/project/release-history.html +30 -0
  121. data/site-local/project/upgrade.html +30 -0
  122. data/site-local/reference/build-directives.html +30 -0
  123. data/site-local/reference/command-line.html +30 -0
  124. data/site-local/reference/environment-vars.html +30 -0
  125. data/site-local/reference/gcov-plugin.html +30 -0
  126. data/site-local/reference/global-collections.html +30 -0
  127. data/site-local/reference/index.html +30 -0
  128. data/site-local/reference/partials-macros.html +30 -0
  129. data/site-local/reference/project-configuration.html +30 -0
  130. data/site-local/sitemap.xml.gz +0 -0
  131. data/site-local/snapshot/examples/cipher_quest/index.html +30 -0
  132. data/site-local/snapshot/examples/temp_sensor/index.html +30 -0
  133. data/site-local/snapshot/examples/wondrous_forest/index.html +30 -0
  134. data/site-local/testing-guide/build-directives.html +30 -0
  135. data/site-local/testing-guide/conventions.html +30 -0
  136. data/site-local/testing-guide/frameworks.html +30 -0
  137. data/site-local/testing-guide/index.html +30 -0
  138. data/site-local/testing-guide/partials/configuration.html +30 -0
  139. data/site-local/testing-guide/partials/conventions.html +30 -0
  140. data/site-local/testing-guide/partials/directives.html +30 -0
  141. data/site-local/testing-guide/partials/example.html +30 -0
  142. data/site-local/testing-guide/partials/index.html +30 -0
  143. data/site-local/testing-guide/partials/overview.html +30 -0
  144. data/site-local/testing-guide/partials/variables.html +30 -0
  145. data/site-local/testing-guide/test-cases.html +30 -0
  146. data/site-local/testing-guide/test-sample.html +30 -0
  147. data/site-local/testing-guide/test-suite-anatomy.html +30 -0
  148. data/spec/support/system/spec_system_helper.rb +25 -0
  149. data/spec/support/system/system_context.rb +15 -0
  150. data/spec/system/deployment_as_gem_spec.rb +14 -0
  151. data/spec/system/deployment_as_vendor_spec.rb +19 -0
  152. data/spec/system/encoding_stress_spec.rb +4 -4
  153. data/spec/system/example_wondrous_forest_spec.rb +2 -2
  154. data/spec/system/support/common_test_cases.rb +227 -0
  155. data/spec/units/c_extractor/c_extractor_declarations_spec.rb +27 -4
  156. data/spec/units/c_extractor/c_extractor_functions_spec.rb +1 -11
  157. data/spec/units/c_extractor/c_extractor_integration_spec.rb +18 -11
  158. data/spec/units/c_extractor/c_extractor_spec.rb +57 -1
  159. data/spec/units/config/configurator_setup_spec.rb +61 -0
  160. data/spec/units/configurator_spec.rb +45 -0
  161. data/spec/units/file_path_utils_spec.rb +17 -0
  162. data/spec/units/generators/generator_helper_spec.rb +34 -7
  163. data/spec/units/generators/generator_partials_spec.rb +120 -12
  164. data/spec/units/generators/generator_test_results_backtrace_spec.rb +248 -41
  165. data/spec/units/generators/generator_test_runner_spec.rb +26 -2
  166. data/spec/units/includes/include_spec.rb +30 -18
  167. data/spec/units/includes/includes_spec.rb +100 -10
  168. data/spec/units/partials/partializer_spec.rb +113 -1
  169. data/spec/units/partials/partializer_utils_spec.rb +11 -0
  170. data/spec/units/preprocess/preprocessinator_includes_handler_spec.rb +96 -7
  171. data/spec/units/system_wrapper_spec.rb +72 -0
  172. data/spec/units/test_context_extractor_spec.rb +3 -3
  173. metadata +20 -2
@@ -7,7 +7,9 @@
7
7
 
8
8
  require 'spec_helper'
9
9
  require 'ceedling/generators/generator_test_results_backtrace'
10
+ require 'ceedling/generators/generator_helper'
10
11
  require 'ceedling/constants'
12
+ require 'ceedling/exceptions'
11
13
 
12
14
  # ── Representative gdb output constants ──────────────────────────────────────
13
15
 
@@ -65,6 +67,19 @@ GDB_NO_SIGNAL_OUTPUT = <<~GDB.freeze
65
67
  Inferior 1 (process 12345) exited with code 0139.
66
68
  GDB
67
69
 
70
+ # Parameterized test crash (issue #1185): the crashing frame names the generated
71
+ # wrapper function (`runner_args1_test_value_out_of_range_good`), not the human-facing
72
+ # test name (`test_value_out_of_range_good(101, 1)`) that Unity reports at runtime.
73
+ GDB_SIGSEGV_PARAM_OUTPUT = <<~GDB.freeze
74
+ [Thread debugging using libthread_db enabled]
75
+
76
+ Program received signal SIGSEGV, Segmentation fault.
77
+ 0x00005618066ea1fb in runner_args1_test_value_out_of_range_good () at test/test_module_d.c:157
78
+ 157 int invalid = 1 / 0;
79
+ #0 0x00005618066ea1fb in runner_args1_test_value_out_of_range_good () at test/test_module_d.c:157
80
+ #1 0x00005618066eb4de in run_test (func=0x5618066ea1e7 <runner_args1_test_value_out_of_range_good>, name=0x5618066eb2e0 "test_value_out_of_range_good(101, 1)", line_num=157) at build/test/runners/test_module_d_runner.c:76
81
+ GDB
82
+
68
83
  # Windows SIGSEGV — Thread N prefix, space-padded source line, DLL frames
69
84
  # Single-quoted heredoc: backslashes in Windows paths are literal, not escape sequences
70
85
  GDB_WINDOWS_SIGSEGV_OUTPUT = <<~'GDB'.freeze
@@ -112,9 +127,9 @@ SIMPLE_ASSERT_CRASH_OUTPUT =
112
127
  "test_lib.out: src/lib.c:5: asserting: Assertion `0' failed.\n" \
113
128
  "Aborted (core dumped)\n"
114
129
 
115
- SIMPLE_PASS_OUTPUT = "test_lib.c:3:test_empty:PASS\n"
116
- SIMPLE_FAIL_OUTPUT = "test_lib.c:8:test_bad:FAIL: Expected 1 Was 2\n"
117
- SIMPLE_IGNORE_OUTPUT = "test_lib.c:12:test_skip:IGNORE\n"
130
+ SIMPLE_PASS_OUTPUT = "test_lib.c:3:test_empty:PASS\n---------\n1 Tests 0 Failures 0 Ignored\nOK\n"
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
+ SIMPLE_IGNORE_OUTPUT = "test_lib.c:12:test_skip:IGNORE\n---------\n1 Tests 0 Failures 1 Ignored\nOK\n"
118
133
 
119
134
 
120
135
  describe GeneratorTestResultsBacktrace do
@@ -127,16 +142,28 @@ describe GeneratorTestResultsBacktrace do
127
142
  @generator_test_results = instance_double('GeneratorTestResults')
128
143
  @file_path_utils = instance_double('FilePathUtils')
129
144
  @file_wrapper = instance_double('FileWrapper')
145
+ @loginator = double('loginator').as_null_object
146
+ # A real GeneratorHelper, not a double -- these tests exercise the actual crash
147
+ # detection logic a retry's status is checked against, not a stubbed stand-in for it.
148
+ @generator_helper = GeneratorHelper.new({ :loginator => @loginator })
130
149
 
131
150
  @backtrace = described_class.new({
132
151
  :configurator => @configurator,
133
152
  :tool_executor => @tool_executor,
134
153
  :generator_test_results => @generator_test_results,
154
+ :generator_helper => @generator_helper,
135
155
  :file_path_utils => @file_path_utils,
136
- :file_wrapper => @file_wrapper
156
+ :file_wrapper => @file_wrapper,
157
+ :loginator => @loginator
137
158
  })
138
159
  @backtrace.setup()
139
160
 
161
+ # A healthy retry sub-process status -- the common case for most stubs below. Crash
162
+ # scenarios that already resolve via a missing result-line match (the pre-existing
163
+ # "unresolved" path) don't depend on this value at all; it's included on every stub
164
+ # anyway so `crash_result[:status]` is never unexpectedly missing.
165
+ @ok_status = double('status', signaled?: false, success?: true, exitstatus: 0, termsig: nil)
166
+
140
167
  # Standard stubs used by most tests
141
168
  allow(@tool_executor).to receive(:build_command_line).and_return({ options: {} })
142
169
  allow(@file_path_utils).to receive(:form_test_gdb_log) do |test, context:, name:|
@@ -153,52 +180,128 @@ describe GeneratorTestResultsBacktrace do
153
180
  let(:filename) { 'test_lib.c' }
154
181
  let(:executable) { 'build/test/out/test_lib/test_lib.out' }
155
182
  let(:shell_result){ { exit_code: 1, output: '', time: 0.0 } }
156
- let(:test_cases) { [{ test: 'test_asserting', line_number: 8 }] }
183
+ let(:test_cases) { [{ test: 'test_asserting', symbol: 'test_asserting', line_number: 8 }] }
157
184
 
158
185
  before(:each) do
159
186
  allow(@configurator).to receive(:project_build_tests_root).and_return('build/test')
160
187
  allow(@configurator).to receive(:tools_test_backtrace_gdb).and_return({})
161
188
  end
162
189
 
190
+ # Companion helper: a genuinely-crashing group paired alongside the group under
191
+ # test, matching realistic multi-test-case usage -- do_gdb only ever runs because
192
+ # the main run already crashed, so an isolated group with nothing but a clean match
193
+ # is indistinguishable from the #1198 bug pattern (every retry legitimately clean,
194
+ # contradicting the crash that triggered this method) and correctly falls back on
195
+ # its own, tested separately below.
196
+ let(:companion_case) { { test: 'test_crashes_elsewhere', symbol: 'test_crashes_elsewhere', line_number: 20 } }
197
+
163
198
  it 'handles a PASS test case and does not write a log' do
164
- allow(@tool_executor).to receive(:exec)
165
- .and_return({ output: "test_lib.c:9:test_asserting:PASS\n", time: 0.1, exit_code: 0 })
199
+ allow(@tool_executor).to receive(:exec).and_return(
200
+ { output: GDB_NO_SIGNAL_OUTPUT, time: 0.1, exit_code: 1, stderr: '', status: @ok_status },
201
+ { output: "test_lib.c:9:test_asserting:PASS\n---------\n1 Tests 0 Failures 0 Ignored\nOK\n",
202
+ time: 0.1, exit_code: 0, stderr: '', status: @ok_status }
203
+ )
166
204
 
167
- expect(@file_wrapper).not_to receive(:write)
205
+ expect(@file_wrapper).to receive(:write).once.with(%r{test_crashes_elsewhere}, anything, anything)
168
206
 
169
- result = @backtrace.do_gdb( filename, executable, shell_result, test_cases, context: :test )
207
+ expected_output_lines = []
208
+ allow(@generator_test_results).to receive(:regenerate_test_executable_stdout) do |**kwargs|
209
+ expected_output_lines = kwargs[:output]
210
+ 'regenerated'
211
+ end
170
212
 
171
- expect(result[:exit_code]).to eq(0)
213
+ @backtrace.do_gdb( filename, executable, shell_result, [companion_case] + test_cases, context: :test )
214
+
215
+ expect(expected_output_lines).to include('test_lib.c:9:test_asserting:PASS')
172
216
  end
173
217
 
174
218
  it 'handles an IGNORE test case and does not write a log' do
175
- allow(@tool_executor).to receive(:exec)
176
- .and_return({ output: "test_lib.c:9:test_asserting:IGNORE\n", time: 0.1, exit_code: 0 })
219
+ allow(@tool_executor).to receive(:exec).and_return(
220
+ { output: GDB_NO_SIGNAL_OUTPUT, time: 0.1, exit_code: 1, stderr: '', status: @ok_status },
221
+ { output: "test_lib.c:9:test_asserting:IGNORE\n---------\n1 Tests 0 Failures 1 Ignored\nOK\n",
222
+ time: 0.1, exit_code: 0, stderr: '', status: @ok_status }
223
+ )
177
224
 
178
- expect(@file_wrapper).not_to receive(:write)
225
+ expect(@file_wrapper).to receive(:write).once.with(%r{test_crashes_elsewhere}, anything, anything)
179
226
 
180
- result = @backtrace.do_gdb( filename, executable, shell_result, test_cases, context: :test )
227
+ expected_output_lines = []
228
+ allow(@generator_test_results).to receive(:regenerate_test_executable_stdout) do |**kwargs|
229
+ expected_output_lines = kwargs[:output]
230
+ 'regenerated'
231
+ end
232
+
233
+ @backtrace.do_gdb( filename, executable, shell_result, [companion_case] + test_cases, context: :test )
181
234
 
182
- expect(result[:exit_code]).to eq(0)
235
+ expect(expected_output_lines).to include('test_lib.c:9:test_asserting:IGNORE')
183
236
  end
184
237
 
185
238
  it 'handles a FAIL test case and does not write a log' do
186
- allow(@tool_executor).to receive(:exec)
187
- .and_return({ output: "test_lib.c:9:test_asserting:FAIL: Expected 1 Was 2\n", time: 0.1, exit_code: 1 })
239
+ allow(@tool_executor).to receive(:exec).and_return(
240
+ { output: GDB_NO_SIGNAL_OUTPUT, time: 0.1, exit_code: 1, stderr: '', status: @ok_status },
241
+ { output: "test_lib.c:9:test_asserting:FAIL: Expected 1 Was 2\n---------\n1 Tests 1 Failures 0 Ignored\nFAIL\n",
242
+ time: 0.1, exit_code: 1, stderr: '', status: @ok_status }
243
+ )
244
+
245
+ expect(@file_wrapper).to receive(:write).once.with(%r{test_crashes_elsewhere}, anything, anything)
246
+
247
+ expected_output_lines = []
248
+ allow(@generator_test_results).to receive(:regenerate_test_executable_stdout) do |**kwargs|
249
+ expected_output_lines = kwargs[:output]
250
+ 'regenerated'
251
+ end
252
+
253
+ @backtrace.do_gdb( filename, executable, shell_result, [companion_case] + test_cases, context: :test )
254
+
255
+ expect(expected_output_lines).to include('test_lib.c:9:test_asserting:FAIL: Expected 1 Was 2')
256
+ end
188
257
 
189
- expect(@file_wrapper).not_to receive(:write)
258
+ it "does not trust a matched PASS line when the retry's own real status contradicts it" do
259
+ # Regression test for issue #1198: a diagnostic retry can print a clean Unity
260
+ # result line while its own real process outcome says otherwise.
261
+ bad_status = double('status', signaled?: false, success?: false, exitstatus: 1, termsig: nil)
262
+
263
+ allow(@tool_executor).to receive(:exec).and_return({
264
+ output: "test_lib.c:9:test_asserting:PASS\n---------\n1 Tests 0 Failures 0 Ignored\nOK\n",
265
+ time: 0.1, exit_code: 0, stderr: '', status: bad_status
266
+ })
267
+
268
+ expected_output_lines = []
269
+ allow(@generator_test_results).to receive(:regenerate_test_executable_stdout) do |**kwargs|
270
+ expected_output_lines = kwargs[:output]
271
+ 'regenerated'
272
+ end
190
273
 
191
274
  result = @backtrace.do_gdb( filename, executable, shell_result, test_cases, context: :test )
192
275
 
193
276
  expect(result[:exit_code]).to eq(1)
277
+ expect(expected_output_lines.first).to include(':FAIL:')
278
+ end
279
+
280
+ it 'falls back to a whole-file crash failure when no retry ever reproduces the crash the main run detected' do
281
+ # Regression test for issue #1198: every retry can legitimately pass in its own,
282
+ # differently-configured environment even though the main run genuinely crashed.
283
+ # An all-clean diagnostic must not be allowed to overrule that.
284
+ allow(@tool_executor).to receive(:exec).and_return({
285
+ output: "test_lib.c:9:test_asserting:PASS\n---------\n1 Tests 0 Failures 0 Ignored\nOK\n",
286
+ time: 0.1, exit_code: 0, stderr: '', status: @ok_status
287
+ })
288
+
289
+ fallback_result = { output: 'crash-failure-output', exit_code: 1 }
290
+ expect(@generator_test_results).to receive(:create_crash_failure)
291
+ .with(filename, shell_result, test_cases)
292
+ .and_return(fallback_result)
293
+
294
+ result = @backtrace.do_gdb( filename, executable, shell_result, test_cases, context: :test )
295
+
296
+ expect(result).to eq(fallback_result)
194
297
  end
195
298
 
196
299
  it 'handles a SIGSEGV crash — writes log, includes signal label, backtick source line, and log path' do
197
- test_cases_sigsegv = [{ test: 'testCrash', line_number: 37 }]
300
+ test_cases_sigsegv = [{ test: 'testCrash', symbol: 'testCrash', line_number: 37 }]
198
301
  filename_sigsegv = 'TestUsartModel.c'
199
302
 
200
303
  allow(@tool_executor).to receive(:exec)
201
- .and_return({ output: GDB_SIGSEGV_OUTPUT, time: 0.5, exit_code: 139 })
304
+ .and_return({ output: GDB_SIGSEGV_OUTPUT, time: 0.5, exit_code: 139, stderr: '', status: @ok_status })
202
305
 
203
306
  expected_output_lines = []
204
307
  allow(@generator_test_results).to receive(:regenerate_test_executable_stdout) do |**kwargs|
@@ -219,11 +322,42 @@ describe GeneratorTestResultsBacktrace do
219
322
  expect(crash_line).to include('(/build/logs/test/TestUsartModel/testCrash.gdb.log)')
220
323
  end
221
324
 
222
- it 'handles a SIGABRT crash from assert() shows assertion text, no source line (issue #1038)' do
223
- test_cases_assert = [{ test: 'test_asserting', line_number: 8 }]
325
+ it 'attributes a crash in a parameterized test to its wrapper symbol, not its display name' do
326
+ test_cases_param = [{
327
+ test: 'test_value_out_of_range_good(101, 1)',
328
+ symbol: 'runner_args1_test_value_out_of_range_good',
329
+ line_number: 157
330
+ }]
331
+ filename_param = 'test_module_d.c'
332
+
333
+ allow(@tool_executor).to receive(:exec)
334
+ .and_return({ output: GDB_SIGSEGV_PARAM_OUTPUT, time: 0.5, exit_code: 139, stderr: '', status: @ok_status })
335
+
336
+ expected_output_lines = []
337
+ allow(@generator_test_results).to receive(:regenerate_test_executable_stdout) do |**kwargs|
338
+ expected_output_lines = kwargs[:output]
339
+ 'regenerated'
340
+ end
341
+
342
+ expect(@file_wrapper).to receive(:write)
343
+ .with('/build/logs/test/test_module_d/test_value_out_of_range_good(101, 1).gdb.log', /=== test_value_out_of_range_good\(101, 1\) ===/, 'a')
344
+
345
+ @backtrace.do_gdb( filename_param, executable, shell_result, test_cases_param, context: :test )
346
+
347
+ crash_line = expected_output_lines.first
348
+ # The FAIL line reports the human-facing display name (with args)...
349
+ expect(crash_line).to start_with('test_module_d.c:157:test_value_out_of_range_good(101, 1):FAIL: Test case crashed')
350
+ # ...but the crash frame was still correctly located via the wrapper symbol.
351
+ expect(crash_line).to include('>> [SIGSEGV] Segmentation fault')
352
+ expect(crash_line).to include("#{NEWLINE_TOKEN}`int invalid = 1 / 0;`")
353
+ expect(crash_line).not_to include('failed to extract')
354
+ end
355
+
356
+ it 'handles a SIGABRT crash from assert() — shows assertion text, no source line' do
357
+ test_cases_assert = [{ test: 'test_asserting', symbol: 'test_asserting', line_number: 8 }]
224
358
 
225
359
  allow(@tool_executor).to receive(:exec)
226
- .and_return({ output: GDB_SIGABRT_ASSERT_OUTPUT, time: 0.3, exit_code: 134 })
360
+ .and_return({ output: GDB_SIGABRT_ASSERT_OUTPUT, time: 0.3, exit_code: 134, stderr: '', status: @ok_status })
227
361
 
228
362
  expected_output_lines = []
229
363
  allow(@generator_test_results).to receive(:regenerate_test_executable_stdout) do |**kwargs|
@@ -244,7 +378,7 @@ describe GeneratorTestResultsBacktrace do
244
378
 
245
379
  it 'handles a crash with no identifiable signal — fallback message, log path without "log: " prefix' do
246
380
  allow(@tool_executor).to receive(:exec)
247
- .and_return({ output: GDB_NO_SIGNAL_OUTPUT, time: 0.1, exit_code: 1 })
381
+ .and_return({ output: GDB_NO_SIGNAL_OUTPUT, time: 0.1, exit_code: 1, stderr: '', status: @ok_status })
248
382
 
249
383
  expected_output_lines = []
250
384
  allow(@generator_test_results).to receive(:regenerate_test_executable_stdout) do |**kwargs|
@@ -273,42 +407,115 @@ describe GeneratorTestResultsBacktrace do
273
407
  allow(@configurator).to receive(:tools_test_fixture_simple_backtrace).and_return({})
274
408
  end
275
409
 
410
+ # Each of the three cases below is paired with a companion group that genuinely
411
+ # crashes -- a realistic multi-test-case file, where the group under test is only
412
+ # one of several. Without a companion, an isolated group with nothing but a clean
413
+ # match is indistinguishable from the #1198 bug pattern (every retry legitimately
414
+ # clean, contradicting the crash do_simple only ever runs because of) and correctly
415
+ # triggers the whole-file fallback tested below in its own right.
416
+
276
417
  it 'handles a PASS test case' do
277
- allow(@tool_executor).to receive(:exec)
278
- .and_return({ output: SIMPLE_PASS_OUTPUT, time: 0.05, exit_code: 0 })
418
+ test_cases_multi = [{ test: 'test_crashes_elsewhere', line_number: 20 }] + test_cases
279
419
 
280
- result = @backtrace.do_simple( filename, executable, shell_result, test_cases, context: :test )
420
+ allow(@tool_executor).to receive(:exec).and_return(
421
+ { output: SIMPLE_ASSERT_CRASH_OUTPUT, time: 0.1, exit_code: 134, stderr: '', status: @ok_status },
422
+ { output: SIMPLE_PASS_OUTPUT, time: 0.05, exit_code: 0, stderr: '', status: @ok_status }
423
+ )
281
424
 
282
- expect(result[:exit_code]).to eq(0)
425
+ expected_output_lines = []
426
+ allow(@generator_test_results).to receive(:regenerate_test_executable_stdout) do |**kwargs|
427
+ expected_output_lines = kwargs[:output]
428
+ 'regenerated'
429
+ end
430
+
431
+ @backtrace.do_simple( filename, executable, shell_result, test_cases_multi, context: :test )
432
+
433
+ expect(expected_output_lines).to include('test_lib.c:3:test_empty:PASS')
283
434
  end
284
435
 
285
436
  it 'handles a FAIL test case with a message' do
286
- test_cases_fail = [{ test: 'test_bad', line_number: 8 }]
437
+ test_cases_fail = [{ test: 'test_crashes_elsewhere', line_number: 20 }, { test: 'test_bad', line_number: 8 }]
287
438
 
288
- allow(@tool_executor).to receive(:exec)
289
- .and_return({ output: SIMPLE_FAIL_OUTPUT, time: 0.05, exit_code: 1 })
439
+ allow(@tool_executor).to receive(:exec).and_return(
440
+ { output: SIMPLE_ASSERT_CRASH_OUTPUT, time: 0.1, exit_code: 134, stderr: '', status: @ok_status },
441
+ { output: SIMPLE_FAIL_OUTPUT, time: 0.05, exit_code: 1, stderr: '', status: @ok_status }
442
+ )
290
443
 
291
- result = @backtrace.do_simple( filename, executable, shell_result, test_cases_fail, context: :test )
444
+ expected_output_lines = []
445
+ allow(@generator_test_results).to receive(:regenerate_test_executable_stdout) do |**kwargs|
446
+ expected_output_lines = kwargs[:output]
447
+ 'regenerated'
448
+ end
292
449
 
293
- expect(result[:exit_code]).to eq(1)
450
+ @backtrace.do_simple( filename, executable, shell_result, test_cases_fail, context: :test )
451
+
452
+ expect(expected_output_lines).to include('test_lib.c:8:test_bad:FAIL: Expected 1 Was 2')
294
453
  end
295
454
 
296
455
  it 'handles an IGNORE test case' do
297
- test_cases_ignore = [{ test: 'test_skip', line_number: 12 }]
456
+ test_cases_ignore = [{ test: 'test_crashes_elsewhere', line_number: 20 }, { test: 'test_skip', line_number: 12 }]
457
+
458
+ allow(@tool_executor).to receive(:exec).and_return(
459
+ { output: SIMPLE_ASSERT_CRASH_OUTPUT, time: 0.1, exit_code: 134, stderr: '', status: @ok_status },
460
+ { output: SIMPLE_IGNORE_OUTPUT, time: 0.02, exit_code: 0, stderr: '', status: @ok_status }
461
+ )
462
+
463
+ expected_output_lines = []
464
+ allow(@generator_test_results).to receive(:regenerate_test_executable_stdout) do |**kwargs|
465
+ expected_output_lines = kwargs[:output]
466
+ 'regenerated'
467
+ end
468
+
469
+ @backtrace.do_simple( filename, executable, shell_result, test_cases_ignore, context: :test )
470
+
471
+ expect(expected_output_lines).to include('test_lib.c:12:test_skip:IGNORE')
472
+ end
473
+
474
+ it "does not trust a matched PASS line when the retry's own real status contradicts it" do
475
+ # Regression test for issue #1198: a diagnostic retry can print a clean Unity
476
+ # result line while its own real process outcome says otherwise.
477
+ bad_status = double('status', signaled?: false, success?: false, exitstatus: 1, termsig: nil)
298
478
 
299
479
  allow(@tool_executor).to receive(:exec)
300
- .and_return({ output: SIMPLE_IGNORE_OUTPUT, time: 0.02, exit_code: 0 })
480
+ .and_return({ output: SIMPLE_PASS_OUTPUT, time: 0.05, exit_code: 0, stderr: '', status: bad_status })
301
481
 
302
- result = @backtrace.do_simple( filename, executable, shell_result, test_cases_ignore, context: :test )
482
+ expected_output_lines = []
483
+ allow(@generator_test_results).to receive(:regenerate_test_executable_stdout) do |**kwargs|
484
+ expected_output_lines = kwargs[:output]
485
+ 'regenerated'
486
+ end
487
+
488
+ result = @backtrace.do_simple( filename, executable, shell_result, test_cases, context: :test )
489
+
490
+ expect(result[:exit_code]).to eq(1)
491
+ expect(expected_output_lines.first).to include(':FAIL:')
492
+ end
493
+
494
+ it 'falls back to a whole-file crash failure when no retry ever reproduces the crash the main run detected' do
495
+ # Regression test for issue #1198: the exact reported scenario -- a custom
496
+ # :test_fixture wrapper enables sanitizer halt-on-error and the main run crashes,
497
+ # but the unrelated, independently-configured test_fixture_simple_backtrace tool
498
+ # runs the same executable directly, without the wrapper, and it legitimately
499
+ # passes. Every retry can be entirely clean and still must not overrule a crash
500
+ # Ceedling already established.
501
+ allow(@tool_executor).to receive(:exec)
502
+ .and_return({ output: SIMPLE_PASS_OUTPUT, time: 0.05, exit_code: 0, stderr: '', status: @ok_status })
503
+
504
+ fallback_result = { output: 'crash-failure-output', exit_code: 1 }
505
+ expect(@generator_test_results).to receive(:create_crash_failure)
506
+ .with(filename, shell_result, test_cases)
507
+ .and_return(fallback_result)
508
+
509
+ result = @backtrace.do_simple( filename, executable, shell_result, test_cases, context: :test )
303
510
 
304
- expect(result[:exit_code]).to eq(0)
511
+ expect(result).to eq(fallback_result)
305
512
  end
306
513
 
307
514
  it 'handles a crash and includes extra executable output in the failure message' do
308
515
  test_cases_crash = [{ test: 'test_asserting', line_number: 8 }]
309
516
 
310
517
  allow(@tool_executor).to receive(:exec)
311
- .and_return({ output: SIMPLE_ASSERT_CRASH_OUTPUT, time: 0.1, exit_code: 134 })
518
+ .and_return({ output: SIMPLE_ASSERT_CRASH_OUTPUT, time: 0.1, exit_code: 134, stderr: '', status: @ok_status })
312
519
 
313
520
  expected_output_lines = []
314
521
  allow(@generator_test_results).to receive(:regenerate_test_executable_stdout) do |**kwargs|
@@ -328,7 +535,7 @@ describe GeneratorTestResultsBacktrace do
328
535
  test_cases_crash = [{ test: 'test_asserting', line_number: 8 }]
329
536
 
330
537
  allow(@tool_executor).to receive(:exec)
331
- .and_return({ output: "\n\n\n", time: 0.1, exit_code: 134 })
538
+ .and_return({ output: "\n\n\n", time: 0.1, exit_code: 134, stderr: '', status: @ok_status })
332
539
 
333
540
  expected_output_lines = []
334
541
  allow(@generator_test_results).to receive(:regenerate_test_executable_stdout) do |**kwargs|
@@ -460,7 +667,7 @@ describe GeneratorTestResultsBacktrace do
460
667
 
461
668
  describe '#do_gdb with brief Windows assert (no crash frame)' do
462
669
  let(:filename_assert) { 'test_example_file_crash_assert.c' }
463
- let(:test_cases_assert) { [{ test: 'test_add_numbers_triggers_assert', line_number: 20 }] }
670
+ let(:test_cases_assert) { [{ test: 'test_add_numbers_triggers_assert', symbol: 'test_add_numbers_triggers_assert', line_number: 20 }] }
464
671
  let(:executable) { 'build/test/out/test_example_file_crash_assert/test_example_file_crash_assert.out' }
465
672
  let(:shell_result) { { exit_code: 1, output: '', time: 0.0 } }
466
673
 
@@ -471,7 +678,7 @@ describe GeneratorTestResultsBacktrace do
471
678
 
472
679
  it 'uses assertion label and metadata line number when no crash location frame is found' do
473
680
  allow(@tool_executor).to receive(:exec)
474
- .and_return({ output: GDB_WINDOWS_ASSERT_BRIEF_OUTPUT, time: 0.1, exit_code: 3 })
681
+ .and_return({ output: GDB_WINDOWS_ASSERT_BRIEF_OUTPUT, time: 0.1, exit_code: 3, stderr: '', status: @ok_status })
475
682
 
476
683
  expected_output_lines = []
477
684
  allow(@generator_test_results).to receive(:regenerate_test_executable_stdout) do |**kwargs|
@@ -96,7 +96,7 @@ describe GeneratorTestRunner do
96
96
 
97
97
  runner = build_runner( test_file_contents: source )
98
98
 
99
- expect( runner.test_cases ).to eq( [ { test: 'test_ShouldDoSomething', line_number: 2 } ] )
99
+ expect( runner.test_cases ).to eq( [ { test: 'test_ShouldDoSomething', symbol: 'test_ShouldDoSomething', line_number: 2 } ] )
100
100
  end
101
101
 
102
102
  it 'remaps line numbers back to the original file when preprocessed content is given' do
@@ -114,7 +114,31 @@ describe GeneratorTestRunner do
114
114
 
115
115
  runner = build_runner( test_file_contents: original, preprocessed_file_contents: preprocessed )
116
116
 
117
- expect( runner.test_cases ).to eq( [ { test: 'test_ShouldDoSomething', line_number: 3 } ] )
117
+ expect( runner.test_cases ).to eq( [ { test: 'test_ShouldDoSomething', symbol: 'test_ShouldDoSomething', line_number: 3 } ] )
118
+ end
119
+
120
+ it 'expands a parameterized test into one entry per TEST_CASE, carrying the runtime name and wrapper symbol' do
121
+ source = <<~SOURCE
122
+ void setUp(void) {}
123
+ void tearDown(void) {}
124
+
125
+ TEST_CASE(101, 1)
126
+ TEST_CASE(-1, 1)
127
+ void test_value_out_of_range_good(int a, int expected) {}
128
+ SOURCE
129
+
130
+ runner = described_class.new(
131
+ config: { use_param_tests: true },
132
+ test_file_contents: source,
133
+ parsing_parcels: @parsing_parcels
134
+ )
135
+
136
+ expect( runner.test_cases ).to eq(
137
+ [
138
+ { test: 'test_value_out_of_range_good(101, 1)', symbol: 'runner_args1_test_value_out_of_range_good', line_number: 6 },
139
+ { test: 'test_value_out_of_range_good(-1, 1)', symbol: 'runner_args2_test_value_out_of_range_good', line_number: 6 }
140
+ ]
141
+ )
118
142
  end
119
143
  end
120
144
  end
@@ -30,9 +30,9 @@ describe UserInclude do
30
30
  expect(include_obj).to eq('header.h')
31
31
  end
32
32
 
33
- it "creates a UserInclude with a path and used path in string expansion" do
34
- include_obj = UserInclude.new("path/to/header.h", use_path: true)
35
-
33
+ it "creates a UserInclude with an include_path override used in string expansion" do
34
+ include_obj = UserInclude.new("path/to/header.h", include_path: "path/to/header.h")
35
+
36
36
  expect(include_obj.filename).to eq("header.h")
37
37
  expect(include_obj.filepath).to eq("path/to/header.h")
38
38
  expect("#{include_obj}").to eq('#include "path/to/header.h"')
@@ -110,14 +110,26 @@ describe SystemInclude do
110
110
  expect(include_obj).to eq('header.h')
111
111
  end
112
112
 
113
- it "creates a SystemInclude with a path and used path in string expansion" do
114
- include_obj = SystemInclude.new("path/to/header.h", use_path: true)
115
-
113
+ it "creates a SystemInclude with an include_path override used in string expansion" do
114
+ include_obj = SystemInclude.new("path/to/header.h", include_path: "path/to/header.h")
115
+
116
116
  expect(include_obj.filename).to eq("header.h")
117
117
  expect(include_obj.filepath).to eq("path/to/header.h")
118
118
  expect("#{include_obj}").to eq('#include <path/to/header.h>')
119
119
  expect(include_obj).to eq('path/to/header.h')
120
120
  end
121
+
122
+ it "renders include_path instead of the bare filename, while filepath/filename keep the resolved identity" do
123
+ include_obj = SystemInclude.new(
124
+ "/usr/include/x86_64-linux-gnu/sys/stat.h", include_path: "sys/stat.h"
125
+ )
126
+
127
+ expect(include_obj.filepath).to eq("/usr/include/x86_64-linux-gnu/sys/stat.h")
128
+ expect(include_obj.filename).to eq("stat.h")
129
+ expect(include_obj.include_path).to eq("sys/stat.h")
130
+ expect("#{include_obj}").to eq('#include <sys/stat.h>')
131
+ expect(include_obj).to eq('sys/stat.h')
132
+ end
121
133
  end
122
134
 
123
135
  describe "Graceful handling of input" do
@@ -189,9 +201,9 @@ describe "Include equality" do
189
201
  end
190
202
 
191
203
  it "compares not equal to another UserInclude with same filename but different paths" do
192
- include1 = UserInclude.new("path1/header.h", use_path: true)
193
- include2 = UserInclude.new("path2/header.h", use_path: true)
194
-
204
+ include1 = UserInclude.new("path1/header.h", include_path: "path1/header.h")
205
+ include2 = UserInclude.new("path2/header.h", include_path: "path2/header.h")
206
+
195
207
  expect(include1).not_to eq(include2)
196
208
  expect(include2).not_to eq(include1)
197
209
  end
@@ -266,9 +278,9 @@ describe "Include equality" do
266
278
  end
267
279
 
268
280
  it "compares not equal to another SystemInclude with same filename but different paths" do
269
- include1 = SystemInclude.new("sys/stdio.h", use_path: true)
270
- include2 = SystemInclude.new("other/stdio.h", use_path: true)
271
-
281
+ include1 = SystemInclude.new("sys/stdio.h", include_path: "sys/stdio.h")
282
+ include2 = SystemInclude.new("other/stdio.h", include_path: "other/stdio.h")
283
+
272
284
  expect(include1).not_to eq(include2)
273
285
  expect(include2).not_to eq(include1)
274
286
  end
@@ -342,9 +354,9 @@ describe "Include equality" do
342
354
  end
343
355
 
344
356
  it "compares not equal to another MockInclude with same filename but different paths" do
345
- include1 = MockInclude.new("mocks/mock_module.h", use_path: true)
346
- include2 = MockInclude.new("test/mocks/mock_module.h", use_path: true)
347
-
357
+ include1 = MockInclude.new("mocks/mock_module.h", include_path: "mocks/mock_module.h")
358
+ include2 = MockInclude.new("test/mocks/mock_module.h", include_path: "test/mocks/mock_module.h")
359
+
348
360
  expect(include1).not_to eq(include2)
349
361
  expect(include2).not_to eq(include1)
350
362
  end
@@ -547,9 +559,9 @@ describe "Include string coercion (to_str)" do
547
559
  expect(include_obj.to_str).to eq("header.h")
548
560
  end
549
561
 
550
- it "returns the full filepath when constructed with use_path: true" do
551
- include_obj = UserInclude.new("sub/dir/header.h", use_path: true)
552
- # to_str always returns @filename (the basename) regardless of use_path
562
+ it "returns the basename even when constructed with an include_path override" do
563
+ include_obj = UserInclude.new("sub/dir/header.h", include_path: "sub/dir/header.h")
564
+ # to_str always returns @filename (the basename) regardless of include_path
553
565
  expect(include_obj.to_str).to eq("header.h")
554
566
  end
555
567
  end