ceedling 1.1.1 → 1.1.3

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/GIT_COMMIT_SHA +1 -1
  3. data/README.md +3 -3
  4. data/assets/broken_uncovered_file.c +13 -0
  5. data/assets/test_example_file_2.c +22 -0
  6. data/assets/test_example_file_crash_sigsegv_with_param.c +32 -0
  7. data/assets/tests_with_partials_coverage/src/blanks.c +10 -0
  8. data/assets/tests_with_partials_coverage/src/blanks.h +1 -0
  9. data/assets/tests_with_partials_coverage/src/counter.c +12 -0
  10. data/assets/tests_with_partials_coverage/src/counter.h +1 -0
  11. data/assets/tests_with_partials_coverage/src/decorators.c +11 -0
  12. data/assets/tests_with_partials_coverage/src/decorators.h +4 -0
  13. data/assets/tests_with_partials_coverage/test/test_blanks.c +8 -0
  14. data/assets/tests_with_partials_coverage/test/test_counter.c +9 -0
  15. data/assets/tests_with_partials_coverage/test/test_decorators.c +8 -0
  16. data/bin/actions_wrapper.rb +7 -6
  17. data/docs/Changelog.md +25 -3
  18. data/lib/ceedling/c_extractor/c_extractor_declarations.rb +3 -1
  19. data/lib/ceedling/c_extractor/c_extractor_preprocessing.rb +20 -7
  20. data/lib/ceedling/config/configurator.rb +7 -1
  21. data/lib/ceedling/defaults.rb +8 -2
  22. data/lib/ceedling/generators/generator_partials.rb +13 -3
  23. data/lib/ceedling/generators/generator_test_results_backtrace.rb +136 -68
  24. data/lib/ceedling/generators/generator_test_runner.rb +20 -2
  25. data/lib/ceedling/objects.yml +1 -0
  26. data/lib/ceedling/preprocess/preprocessinator_file_assembler.rb +10 -2
  27. data/lib/ceedling/preprocess/preprocessinator_reconstructor.rb +23 -15
  28. data/lib/version.rb +1 -1
  29. data/plugins/gcov/lib/gcov.rb +2 -2
  30. data/site-local/sitemap.xml.gz +0 -0
  31. data/spec/support/system/system_context.rb +15 -0
  32. data/spec/system/deployment_as_gem_spec.rb +2 -0
  33. data/spec/system/deployment_as_vendor_spec.rb +2 -0
  34. data/spec/system/gcov_deployment_spec.rb +20 -6
  35. data/spec/system/support/common_test_cases.rb +90 -0
  36. data/spec/system/support/gcov_common_test_cases.rb +31 -45
  37. data/spec/system/support/gcov_partials_test_cases.rb +116 -0
  38. data/spec/units/c_extractor/c_extractor_declarations_spec.rb +128 -0
  39. data/spec/units/c_extractor/c_extractor_definitions_spec.rb +7 -0
  40. data/spec/units/c_extractor/c_extractor_integration_spec.rb +17 -0
  41. data/spec/units/c_extractor/c_extractor_preprocessing_spec.rb +46 -0
  42. data/spec/units/configurator_spec.rb +45 -0
  43. data/spec/units/generators/generator_partials_spec.rb +32 -6
  44. data/spec/units/generators/generator_test_results_backtrace_spec.rb +49 -5
  45. data/spec/units/generators/generator_test_runner_spec.rb +26 -2
  46. data/spec/units/preprocess/preprocessinator_file_assembler_spec.rb +96 -2
  47. data/spec/units/preprocess/preprocessinator_reconstructor_spec.rb +151 -1
  48. data/spec/units/test_context_extractor_spec.rb +3 -3
  49. metadata +16 -2
@@ -9,6 +9,7 @@ require 'spec_helper'
9
9
  require 'ceedling/config/configurator'
10
10
  require 'ceedling/ruby_expandinator'
11
11
  require 'ceedling/exceptions'
12
+ require 'ceedling/constants'
12
13
 
13
14
  describe Configurator do
14
15
 
@@ -184,4 +185,48 @@ describe Configurator do
184
185
 
185
186
  end
186
187
 
188
+ describe "#set_partials_derived_config" do
189
+
190
+ def partials_config
191
+ { project: { use_partials: true }, defines: { test: ['TEST'] }, cmock: {} }
192
+ end
193
+
194
+ it "does nothing when :use_partials is disabled" do
195
+ config = partials_config
196
+ config[:project][:use_partials] = false
197
+
198
+ @configurator.set_partials_derived_config( config )
199
+
200
+ expect( config[:defines][:test] ).to eq( ['TEST'] )
201
+ expect( config[:defines] ).to_not have_key( :preprocess )
202
+ end
203
+
204
+ it "appends the partials prefix symbol to a simple :test: defines list" do
205
+ config = partials_config
206
+
207
+ @configurator.set_partials_derived_config( config )
208
+
209
+ expect( config[:defines][:test] ).to include( "CEEDLING_PARTIALS_PREFIX=#{PARTIAL_FILENAME_PREFIX}" )
210
+ end
211
+
212
+ it "leaves :preprocess: defines untouched when the project does not define that section" do
213
+ config = partials_config
214
+
215
+ @configurator.set_partials_derived_config( config )
216
+
217
+ expect( config[:defines] ).to_not have_key( :preprocess )
218
+ end
219
+
220
+ it "appends the partials prefix symbol under the :* matcher when the project defines its own :preprocess: matcher hash" do
221
+ config = partials_config
222
+ config[:defines][:preprocess] = { 'TestSoilMoisture.c' => ['CEEDLING_DELTA_PROBE'] }
223
+
224
+ @configurator.set_partials_derived_config( config )
225
+
226
+ expect( config[:defines][:preprocess][:*] ).to include( "CEEDLING_PARTIALS_PREFIX=#{PARTIAL_FILENAME_PREFIX}" )
227
+ expect( config[:defines][:preprocess]['TestSoilMoisture.c'] ).to eq( ['CEEDLING_DELTA_PROBE'] )
228
+ end
229
+
230
+ end
231
+
187
232
  end
@@ -84,12 +84,14 @@ describe GeneratorPartials do
84
84
  header_file_handle = double('header_file_handle')
85
85
  source_file_handle = double('source_file_handle')
86
86
 
87
+ # Binary mode ('wb'): generated files carry embedded content whose line
88
+ # endings must reach disk unchanged, which text mode would not guarantee.
87
89
  allow(@file_wrapper).to receive(:open)
88
- .with(expected_header_filepath, 'w')
90
+ .with(expected_header_filepath, 'wb')
89
91
  .and_yield(header_file_handle)
90
92
 
91
93
  allow(@file_wrapper).to receive(:open)
92
- .with(expected_source_filepath, 'w')
94
+ .with(expected_source_filepath, 'wb')
93
95
  .and_yield(source_file_handle)
94
96
 
95
97
  # Spy on generate_header and generate_source -- allow them to be called but track the calls
@@ -145,8 +147,8 @@ describe GeneratorPartials do
145
147
  expect(@file_path_utils).to have_received(:form_partial_implementation_header_filename).with(name)
146
148
 
147
149
  # Verify file operations
148
- expect(@file_wrapper).to have_received(:open).with(expected_header_filepath, 'w')
149
- expect(@file_wrapper).to have_received(:open).with(expected_source_filepath, 'w')
150
+ expect(@file_wrapper).to have_received(:open).with(expected_header_filepath, 'wb')
151
+ expect(@file_wrapper).to have_received(:open).with(expected_source_filepath, 'wb')
150
152
 
151
153
  # Verify return value is the source filepath
152
154
  expect(result).to eq(expected_source_filepath)
@@ -167,9 +169,11 @@ describe GeneratorPartials do
167
169
  .and_return(header_filename)
168
170
 
169
171
  # Mock FileWrapper.open to yield a file handle
172
+ # Binary mode ('wb'): generated files carry embedded content whose line
173
+ # endings must reach disk unchanged, which text mode would not guarantee.
170
174
  file_handle = double('file_handle')
171
175
  allow(@file_wrapper).to receive(:open)
172
- .with(expected_filepath, 'w')
176
+ .with(expected_filepath, 'wb')
173
177
  .and_yield(file_handle)
174
178
 
175
179
  # Spy on generate_header -- allow it to be called but track the call
@@ -211,7 +215,7 @@ describe GeneratorPartials do
211
215
  expect(@file_path_utils).to have_received(:form_partial_interface_header_filename).with(name)
212
216
 
213
217
  # Verify file operations
214
- expect(@file_wrapper).to have_received(:open).with(expected_filepath, 'w')
218
+ expect(@file_wrapper).to have_received(:open).with(expected_filepath, 'wb')
215
219
 
216
220
  # Verify return value is the header filepath
217
221
  expect(result).to eq(expected_filepath)
@@ -649,6 +653,28 @@ describe GeneratorPartials do
649
653
  expect( buf.string.strip() ).to eq file_contents.strip()
650
654
  end
651
655
 
656
+ it "should pass a code_block's line endings through unchanged, including embedded CRLF" do
657
+ # code_block content is written to the IO handle exactly as given, byte for
658
+ # byte, since the file it ends up in is opened by its caller in binary mode
659
+ # specifically so nothing here alters a line ending already present in it.
660
+ code_block_with_crlf = "void helloBlanks(void) {\r\n\r\n return;\r\n}"
661
+
662
+ defn = Partials.manufacture_function_definition(
663
+ line_num: 2,
664
+ source_filepath: 'src/blanks.c',
665
+ name: 'helloBlanks',
666
+ signature: 'void helloBlanks(void)',
667
+ code_block: code_block_with_crlf
668
+ )
669
+ c_module = make_module(
670
+ CExtractorTypes::CFunctionDefinition.new(name: 'helloBlanks')
671
+ )
672
+
673
+ @generator.send(:generate_source, buf, [], [defn], c_module)
674
+
675
+ expect( buf.string ).to include( code_block_with_crlf )
676
+ end
677
+
652
678
  it "should emit only variable declarations and function definitions from all four element_sequence categories" do
653
679
  # Source generation emits two categories and silently skips two others:
654
680
  # CVariableDeclaration → emitted as-is
@@ -65,6 +65,19 @@ GDB_NO_SIGNAL_OUTPUT = <<~GDB.freeze
65
65
  Inferior 1 (process 12345) exited with code 0139.
66
66
  GDB
67
67
 
68
+ # Parameterized test crash (issue #1185): the crashing frame names the generated
69
+ # wrapper function (`runner_args1_test_value_out_of_range_good`), not the human-facing
70
+ # test name (`test_value_out_of_range_good(101, 1)`) that Unity reports at runtime.
71
+ GDB_SIGSEGV_PARAM_OUTPUT = <<~GDB.freeze
72
+ [Thread debugging using libthread_db enabled]
73
+
74
+ Program received signal SIGSEGV, Segmentation fault.
75
+ 0x00005618066ea1fb in runner_args1_test_value_out_of_range_good () at test/test_module_d.c:157
76
+ 157 int invalid = 1 / 0;
77
+ #0 0x00005618066ea1fb in runner_args1_test_value_out_of_range_good () at test/test_module_d.c:157
78
+ #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
79
+ GDB
80
+
68
81
  # Windows SIGSEGV — Thread N prefix, space-padded source line, DLL frames
69
82
  # Single-quoted heredoc: backslashes in Windows paths are literal, not escape sequences
70
83
  GDB_WINDOWS_SIGSEGV_OUTPUT = <<~'GDB'.freeze
@@ -153,7 +166,7 @@ describe GeneratorTestResultsBacktrace do
153
166
  let(:filename) { 'test_lib.c' }
154
167
  let(:executable) { 'build/test/out/test_lib/test_lib.out' }
155
168
  let(:shell_result){ { exit_code: 1, output: '', time: 0.0 } }
156
- let(:test_cases) { [{ test: 'test_asserting', line_number: 8 }] }
169
+ let(:test_cases) { [{ test: 'test_asserting', symbol: 'test_asserting', line_number: 8 }] }
157
170
 
158
171
  before(:each) do
159
172
  allow(@configurator).to receive(:project_build_tests_root).and_return('build/test')
@@ -194,7 +207,7 @@ describe GeneratorTestResultsBacktrace do
194
207
  end
195
208
 
196
209
  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 }]
210
+ test_cases_sigsegv = [{ test: 'testCrash', symbol: 'testCrash', line_number: 37 }]
198
211
  filename_sigsegv = 'TestUsartModel.c'
199
212
 
200
213
  allow(@tool_executor).to receive(:exec)
@@ -219,8 +232,39 @@ describe GeneratorTestResultsBacktrace do
219
232
  expect(crash_line).to include('(/build/logs/test/TestUsartModel/testCrash.gdb.log)')
220
233
  end
221
234
 
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 }]
235
+ it 'attributes a crash in a parameterized test to its wrapper symbol, not its display name' do
236
+ test_cases_param = [{
237
+ test: 'test_value_out_of_range_good(101, 1)',
238
+ symbol: 'runner_args1_test_value_out_of_range_good',
239
+ line_number: 157
240
+ }]
241
+ filename_param = 'test_module_d.c'
242
+
243
+ allow(@tool_executor).to receive(:exec)
244
+ .and_return({ output: GDB_SIGSEGV_PARAM_OUTPUT, time: 0.5, exit_code: 139 })
245
+
246
+ expected_output_lines = []
247
+ allow(@generator_test_results).to receive(:regenerate_test_executable_stdout) do |**kwargs|
248
+ expected_output_lines = kwargs[:output]
249
+ 'regenerated'
250
+ end
251
+
252
+ expect(@file_wrapper).to receive(:write)
253
+ .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')
254
+
255
+ @backtrace.do_gdb( filename_param, executable, shell_result, test_cases_param, context: :test )
256
+
257
+ crash_line = expected_output_lines.first
258
+ # The FAIL line reports the human-facing display name (with args)...
259
+ expect(crash_line).to start_with('test_module_d.c:157:test_value_out_of_range_good(101, 1):FAIL: Test case crashed')
260
+ # ...but the crash frame was still correctly located via the wrapper symbol.
261
+ expect(crash_line).to include('>> [SIGSEGV] Segmentation fault')
262
+ expect(crash_line).to include("#{NEWLINE_TOKEN}`int invalid = 1 / 0;`")
263
+ expect(crash_line).not_to include('failed to extract')
264
+ end
265
+
266
+ it 'handles a SIGABRT crash from assert() — shows assertion text, no source line' do
267
+ test_cases_assert = [{ test: 'test_asserting', symbol: 'test_asserting', line_number: 8 }]
224
268
 
225
269
  allow(@tool_executor).to receive(:exec)
226
270
  .and_return({ output: GDB_SIGABRT_ASSERT_OUTPUT, time: 0.3, exit_code: 134 })
@@ -460,7 +504,7 @@ describe GeneratorTestResultsBacktrace do
460
504
 
461
505
  describe '#do_gdb with brief Windows assert (no crash frame)' do
462
506
  let(:filename_assert) { 'test_example_file_crash_assert.c' }
463
- let(:test_cases_assert) { [{ test: 'test_add_numbers_triggers_assert', line_number: 20 }] }
507
+ let(:test_cases_assert) { [{ test: 'test_add_numbers_triggers_assert', symbol: 'test_add_numbers_triggers_assert', line_number: 20 }] }
464
508
  let(:executable) { 'build/test/out/test_example_file_crash_assert/test_example_file_crash_assert.out' }
465
509
  let(:shell_result) { { exit_code: 1, output: '', time: 0.0 } }
466
510
 
@@ -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
@@ -17,7 +17,7 @@ RSpec.describe PreprocessinatorFileAssembler do
17
17
  # Use real ParsingParcels for _filter_conditionals and collect_file_contents_fallback tests
18
18
  let(:real_parsing_parcels) { ParsingParcels.new }
19
19
  let(:real_reconstructor) do
20
- PreprocessinatorReconstructor.new({ parsing_parcels: real_parsing_parcels })
20
+ PreprocessinatorReconstructor.new({ parsing_parcels: real_parsing_parcels, file_wrapper: @file_wrapper })
21
21
  end
22
22
 
23
23
  before :each do
@@ -359,9 +359,12 @@ RSpec.describe PreprocessinatorFileAssembler do
359
359
 
360
360
  let(:preprocessed_filepath) { '/build/mocks/test_module/module.h' }
361
361
 
362
+ # Binary mode ('wb'): `contents` lines assembled into this file come from
363
+ # an upstream preprocessed file and must reach disk with their line
364
+ # endings unaltered, which text mode would not guarantee.
362
365
  def stub_file_write(filepath)
363
366
  output = StringIO.new
364
- allow(@file_wrapper).to receive(:open).with(filepath, 'w').and_yield(output)
367
+ allow(@file_wrapper).to receive(:open).with(filepath, 'wb').and_yield(output)
365
368
  output
366
369
  end
367
370
 
@@ -413,6 +416,97 @@ RSpec.describe PreprocessinatorFileAssembler do
413
416
  expect(output.string).to include('void foo(void);')
414
417
  end
415
418
 
419
+ it 'opens its output file in binary mode' do
420
+ stub_file_write(preprocessed_filepath)
421
+
422
+ subject.assemble_preprocessed_header_file(
423
+ filename: 'module.h',
424
+ preprocessed_filepath: preprocessed_filepath,
425
+ contents: [],
426
+ extras: [],
427
+ includes: []
428
+ )
429
+
430
+ expect(@file_wrapper).to have_received(:open).with(preprocessed_filepath, 'wb')
431
+ end
432
+
433
+ it "passes content lines through to the output file unchanged, including embedded CRLF" do
434
+ output = stub_file_write(preprocessed_filepath)
435
+ content_line_with_crlf = "void foo(void) {\r\n return;\r\n}"
436
+
437
+ subject.assemble_preprocessed_header_file(
438
+ filename: 'module.h',
439
+ preprocessed_filepath: preprocessed_filepath,
440
+ contents: [content_line_with_crlf],
441
+ extras: [],
442
+ includes: []
443
+ )
444
+
445
+ expect(output.string).to include(content_line_with_crlf)
446
+ end
447
+
448
+ end
449
+
450
+
451
+ # ===========================================================================
452
+ describe '#assemble_preprocessed_code_file' do
453
+ # ===========================================================================
454
+
455
+ let(:preprocessed_filepath) { '/build/test/preprocess/module.c' }
456
+
457
+ # Binary mode ('wb'): `contents` lines assembled into this file come from
458
+ # an upstream preprocessed file and must reach disk with their line
459
+ # endings unaltered, which text mode would not guarantee.
460
+ def stub_file_write(filepath)
461
+ output = StringIO.new
462
+ allow(@file_wrapper).to receive(:open).with(filepath, 'wb').and_yield(output)
463
+ output
464
+ end
465
+
466
+ it 'writes provided includes and contents to the output file' do
467
+ output = stub_file_write(preprocessed_filepath)
468
+
469
+ subject.assemble_preprocessed_code_file(
470
+ filename: 'module.c',
471
+ preprocessed_filepath: preprocessed_filepath,
472
+ contents: ['void foo(void) {}'],
473
+ extras: [],
474
+ includes: ['#include "bar.h"']
475
+ )
476
+
477
+ expect(output.string).to include('#include "bar.h"')
478
+ expect(output.string).to include('void foo(void) {}')
479
+ end
480
+
481
+ it 'opens its output file in binary mode' do
482
+ stub_file_write(preprocessed_filepath)
483
+
484
+ subject.assemble_preprocessed_code_file(
485
+ filename: 'module.c',
486
+ preprocessed_filepath: preprocessed_filepath,
487
+ contents: [],
488
+ extras: [],
489
+ includes: []
490
+ )
491
+
492
+ expect(@file_wrapper).to have_received(:open).with(preprocessed_filepath, 'wb')
493
+ end
494
+
495
+ it "passes content lines through to the output file unchanged, including embedded CRLF" do
496
+ output = stub_file_write(preprocessed_filepath)
497
+ content_line_with_crlf = "void foo(void) {\r\n return;\r\n}"
498
+
499
+ subject.assemble_preprocessed_code_file(
500
+ filename: 'module.c',
501
+ preprocessed_filepath: preprocessed_filepath,
502
+ contents: [content_line_with_crlf],
503
+ extras: [],
504
+ includes: []
505
+ )
506
+
507
+ expect(output.string).to include(content_line_with_crlf)
508
+ end
509
+
416
510
  end
417
511
 
418
512
  end
@@ -12,9 +12,11 @@ require 'ceedling/parsing_parcels'
12
12
  describe PreprocessinatorReconstructor do
13
13
  before(:each) do
14
14
  @parsing_parcels = ParsingParcels.new()
15
+ @file_wrapper = double( "FileWrapper" )
15
16
  @extractor = described_class.new(
16
17
  {
17
- :parsing_parcels => @parsing_parcels
18
+ :parsing_parcels => @parsing_parcels,
19
+ :file_wrapper => @file_wrapper,
18
20
  }
19
21
  )
20
22
  end
@@ -75,6 +77,105 @@ describe PreprocessinatorReconstructor do
75
77
  expect( @extractor.extract_file_as_array_from_expansion( input, filepath ) ).to eq expected
76
78
  end
77
79
 
80
+ it "should preserve a run of multiple consecutive blank lines rather than collapsing to one" do
81
+ filepath = "this/path/MY_FILE.C"
82
+
83
+ file_contents = [
84
+ '# 1 "./this/path/MY_FILE.C" 99999',
85
+ 'void some_function(void) {',
86
+ '',
87
+ '',
88
+ '',
89
+ ' return 0;',
90
+ '}'
91
+ ]
92
+
93
+ # All three blank lines must survive -- e.g. a stripped multi-line comment
94
+ # or literal blank lines in the source, either way nothing gets dropped.
95
+ expected = [
96
+ 'void some_function(void) {',
97
+ '',
98
+ '',
99
+ '',
100
+ ' return 0;',
101
+ '}'
102
+ ]
103
+
104
+ input = StringIO.new( file_contents.join( "\n" ) )
105
+
106
+ expect( @extractor.extract_file_as_array_from_expansion( input, filepath ) ).to eq expected
107
+ end
108
+
109
+ it "should preserve independent runs of differing blank-line counts between separate code lines" do
110
+ filepath = "path/multi.c"
111
+
112
+ file_contents = [
113
+ '# 1 "path/multi.c" 1',
114
+ 'int a;',
115
+ '',
116
+ '',
117
+ 'int b;',
118
+ '',
119
+ 'int c;',
120
+ '',
121
+ '',
122
+ '',
123
+ 'int d;'
124
+ ]
125
+
126
+ # Each run's count must stay attached to its own gap -- not merged with a
127
+ # neighboring run, not truncated to a shared count.
128
+ expected = [
129
+ 'int a;',
130
+ '',
131
+ '',
132
+ 'int b;',
133
+ '',
134
+ 'int c;',
135
+ '',
136
+ '',
137
+ '',
138
+ 'int d;'
139
+ ]
140
+
141
+ input = StringIO.new( file_contents.join( "\n" ) )
142
+
143
+ expect( @extractor.extract_file_as_array_from_expansion( input, filepath ) ).to eq expected
144
+ end
145
+
146
+ it "should preserve a following blank-line run after a marker-continuation (dangling semicolon) fragment" do
147
+ filepath = "sys/types_wrapper.c"
148
+
149
+ file_contents = [
150
+ '# 1 "sys/types_wrapper.c" 1',
151
+ 'typedef unsigned long uint32_t', # System typedef expansion, unterminated
152
+ '# 1 "sys/types_wrapper.c" 2', # GCC re-marks the SAME source line...
153
+ ';', # ...to attach a dangling ';' as a continuation, not a new line
154
+ '',
155
+ '',
156
+ 'void some_function(void) {',
157
+ ' return;',
158
+ '}',
159
+ '# 1 "some/useless/file.c"'
160
+ ]
161
+
162
+ # The typedef and its dangling ';' must aggregate onto a single output
163
+ # line (existing behavior), and the two blank lines that follow -- e.g.
164
+ # from a stripped comment after the typedef -- must both still survive.
165
+ expected = [
166
+ 'typedef unsigned long uint32_t;',
167
+ '',
168
+ '',
169
+ 'void some_function(void) {',
170
+ ' return;',
171
+ '}'
172
+ ]
173
+
174
+ input = StringIO.new( file_contents.join( "\n" ) )
175
+
176
+ expect( @extractor.extract_file_as_array_from_expansion( input, filepath ) ).to eq expected
177
+ end
178
+
78
179
  it "should extract text of original file from preprocessed expansion with complex preprocessor line marker sequence" do
79
180
  filepath = "dir/our_file.c"
80
181
 
@@ -207,6 +308,8 @@ describe PreprocessinatorReconstructor do
207
308
  '',
208
309
  'static _Bool ecs_foo__init_structure(void);',
209
310
  '',
311
+ '',
312
+ '',
210
313
  'void ecs_foo_init(void) {',
211
314
  ' var1 = ecs_foo__init_structure();',
212
315
  '}',
@@ -697,4 +800,51 @@ describe PreprocessinatorReconstructor do
697
800
 
698
801
  end
699
802
 
803
+ context "#compact_file_from_expansion" do
804
+ it "should open its input in binary mode and its output in binary mode" do
805
+ # Binary mode on both ends: this content is read again downstream by code
806
+ # that depends on its line count being exact, so nothing here should be
807
+ # subject to platform-specific line ending translation.
808
+ input_handle = StringIO.new( '# 1 "WANT.c" 5' + "\n" + 'text_we_want();' + "\n" )
809
+ output_handle = StringIO.new
810
+
811
+ expect(@file_wrapper).to receive(:open).with( 'input.c', 'rb' ).and_yield( input_handle )
812
+ expect(@file_wrapper).to receive(:open).with( 'output.c', 'wb' ).and_yield( output_handle )
813
+
814
+ @extractor.compact_file_from_expansion(
815
+ input_filepath: 'input.c',
816
+ source_filepath: 'WANT.c',
817
+ output_filepath: 'output.c'
818
+ )
819
+ end
820
+
821
+ it "should extract the same content from CRLF-terminated input as from LF-terminated input" do
822
+ # GCC's preprocessor output on Windows is CRLF-terminated. Extraction must
823
+ # produce identical results regardless of the input's line endings, since
824
+ # the exact line count of what's extracted matters to code downstream.
825
+ lf_input = StringIO.new(
826
+ '# 1 "WANT.c" 5' + "\n" + "line_one();\n" + "\n" + "line_two();\n"
827
+ )
828
+ crlf_input = StringIO.new(
829
+ '# 1 "WANT.c" 5' + "\r\n" + "line_one();\r\n" + "\r\n" + "line_two();\r\n"
830
+ )
831
+ lf_output = StringIO.new
832
+ crlf_output = StringIO.new
833
+
834
+ allow(@file_wrapper).to receive(:open).with( 'lf.c', 'rb' ).and_yield( lf_input )
835
+ allow(@file_wrapper).to receive(:open).with( 'lf_out.c', 'wb' ).and_yield( lf_output )
836
+ allow(@file_wrapper).to receive(:open).with( 'crlf.c', 'rb' ).and_yield( crlf_input )
837
+ allow(@file_wrapper).to receive(:open).with( 'crlf_out.c', 'wb' ).and_yield( crlf_output )
838
+
839
+ @extractor.compact_file_from_expansion(
840
+ input_filepath: 'lf.c', source_filepath: 'WANT.c', output_filepath: 'lf_out.c'
841
+ )
842
+ @extractor.compact_file_from_expansion(
843
+ input_filepath: 'crlf.c', source_filepath: 'WANT.c', output_filepath: 'crlf_out.c'
844
+ )
845
+
846
+ expect( crlf_output.string ).to eq( lf_output.string )
847
+ end
848
+ end
849
+
700
850
  end
@@ -391,8 +391,8 @@ describe TestContextExtractor do
391
391
  @extractor.collect_context( filepath, input, TestContextExtractor::Context::TEST_RUNNER_DETAILS )
392
392
 
393
393
  expected = [
394
- {:line_number => 2, :test => 'test_this_function'},
395
- {:line_number => 12, :test => 'test_another_function'},
394
+ {:line_number => 2, :test => 'test_this_function', :symbol => 'test_this_function'},
395
+ {:line_number => 12, :test => 'test_another_function', :symbol => 'test_another_function'},
396
396
  ]
397
397
 
398
398
  expect( @extractor.lookup_test_cases( filepath ) ).to eq expected
@@ -411,7 +411,7 @@ describe TestContextExtractor do
411
411
  @extractor.collect_context( filepath, input, TestContextExtractor::Context::TEST_RUNNER_DETAILS )
412
412
  }.not_to raise_error
413
413
 
414
- expected = [{ :line_number => 2, :test => 'test_this_function' }]
414
+ expected = [{ :line_number => 2, :test => 'test_this_function', :symbol => 'test_this_function' }]
415
415
  expect( @extractor.lookup_test_cases( filepath ) ).to eq expected
416
416
  end
417
417
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ceedling
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mark VanderVoord
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2026-07-23 00:00:00.000000000 Z
13
+ date: 2026-08-06 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rake
@@ -167,6 +167,7 @@ files:
167
167
  - Gemfile.lock
168
168
  - README.md
169
169
  - Rakefile
170
+ - assets/broken_uncovered_file.c
170
171
  - assets/ceedling
171
172
  - assets/ceedling.cmd
172
173
  - assets/default_gitignore
@@ -179,9 +180,11 @@ files:
179
180
  - assets/example_file_with_stdio.h
180
181
  - assets/project.yml
181
182
  - assets/test_example_file.c
183
+ - assets/test_example_file_2.c
182
184
  - assets/test_example_file_boom.c
183
185
  - assets/test_example_file_crash_assert.c
184
186
  - assets/test_example_file_crash_sigsegv.c
187
+ - assets/test_example_file_crash_sigsegv_with_param.c
185
188
  - assets/test_example_file_source_include.c
186
189
  - assets/test_example_file_success.c
187
190
  - assets/test_example_file_unity_printf.c
@@ -203,6 +206,15 @@ files:
203
206
  - assets/tests_with_fallback_conditionals/src/conditional_module.h
204
207
  - assets/tests_with_fallback_conditionals/src/optional_dep.h
205
208
  - assets/tests_with_fallback_conditionals/test/test_conditionals.c
209
+ - assets/tests_with_partials_coverage/src/blanks.c
210
+ - assets/tests_with_partials_coverage/src/blanks.h
211
+ - assets/tests_with_partials_coverage/src/counter.c
212
+ - assets/tests_with_partials_coverage/src/counter.h
213
+ - assets/tests_with_partials_coverage/src/decorators.c
214
+ - assets/tests_with_partials_coverage/src/decorators.h
215
+ - assets/tests_with_partials_coverage/test/test_blanks.c
216
+ - assets/tests_with_partials_coverage/test/test_counter.c
217
+ - assets/tests_with_partials_coverage/test/test_decorators.c
206
218
  - assets/tests_with_preprocessing/src/adc_hardwareA.c
207
219
  - assets/tests_with_preprocessing/src/adc_hardwareA.h
208
220
  - assets/tests_with_preprocessing/src/adc_hardwareB.c
@@ -917,6 +929,7 @@ files:
917
929
  - spec/system/support/cppcheck_helpers.rb
918
930
  - spec/system/support/gcov_common_test_cases.rb
919
931
  - spec/system/support/gcov_helpers.rb
932
+ - spec/system/support/gcov_partials_test_cases.rb
920
933
  - spec/system/support/valgrind_common_test_cases.rb
921
934
  - spec/system/support/valgrind_helpers.rb
922
935
  - spec/system/upgrade_as_vendor_spec.rb
@@ -1762,6 +1775,7 @@ test_files:
1762
1775
  - spec/system/support/cppcheck_helpers.rb
1763
1776
  - spec/system/support/gcov_common_test_cases.rb
1764
1777
  - spec/system/support/gcov_helpers.rb
1778
+ - spec/system/support/gcov_partials_test_cases.rb
1765
1779
  - spec/system/support/valgrind_common_test_cases.rb
1766
1780
  - spec/system/support/valgrind_helpers.rb
1767
1781
  - spec/system/upgrade_as_vendor_spec.rb