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.
- checksums.yaml +4 -4
- data/GIT_COMMIT_SHA +1 -1
- data/README.md +3 -3
- data/assets/broken_uncovered_file.c +13 -0
- data/assets/test_example_file_2.c +22 -0
- data/assets/test_example_file_crash_sigsegv_with_param.c +32 -0
- data/assets/tests_with_partials_coverage/src/blanks.c +10 -0
- data/assets/tests_with_partials_coverage/src/blanks.h +1 -0
- data/assets/tests_with_partials_coverage/src/counter.c +12 -0
- data/assets/tests_with_partials_coverage/src/counter.h +1 -0
- data/assets/tests_with_partials_coverage/src/decorators.c +11 -0
- data/assets/tests_with_partials_coverage/src/decorators.h +4 -0
- data/assets/tests_with_partials_coverage/test/test_blanks.c +8 -0
- data/assets/tests_with_partials_coverage/test/test_counter.c +9 -0
- data/assets/tests_with_partials_coverage/test/test_decorators.c +8 -0
- data/bin/actions_wrapper.rb +7 -6
- data/docs/Changelog.md +25 -3
- data/lib/ceedling/c_extractor/c_extractor_declarations.rb +3 -1
- data/lib/ceedling/c_extractor/c_extractor_preprocessing.rb +20 -7
- data/lib/ceedling/config/configurator.rb +7 -1
- data/lib/ceedling/defaults.rb +8 -2
- data/lib/ceedling/generators/generator_partials.rb +13 -3
- data/lib/ceedling/generators/generator_test_results_backtrace.rb +136 -68
- data/lib/ceedling/generators/generator_test_runner.rb +20 -2
- data/lib/ceedling/objects.yml +1 -0
- data/lib/ceedling/preprocess/preprocessinator_file_assembler.rb +10 -2
- data/lib/ceedling/preprocess/preprocessinator_reconstructor.rb +23 -15
- data/lib/version.rb +1 -1
- data/plugins/gcov/lib/gcov.rb +2 -2
- data/site-local/sitemap.xml.gz +0 -0
- data/spec/support/system/system_context.rb +15 -0
- data/spec/system/deployment_as_gem_spec.rb +2 -0
- data/spec/system/deployment_as_vendor_spec.rb +2 -0
- data/spec/system/gcov_deployment_spec.rb +20 -6
- data/spec/system/support/common_test_cases.rb +90 -0
- data/spec/system/support/gcov_common_test_cases.rb +31 -45
- data/spec/system/support/gcov_partials_test_cases.rb +116 -0
- data/spec/units/c_extractor/c_extractor_declarations_spec.rb +128 -0
- data/spec/units/c_extractor/c_extractor_definitions_spec.rb +7 -0
- data/spec/units/c_extractor/c_extractor_integration_spec.rb +17 -0
- data/spec/units/c_extractor/c_extractor_preprocessing_spec.rb +46 -0
- data/spec/units/configurator_spec.rb +45 -0
- data/spec/units/generators/generator_partials_spec.rb +32 -6
- data/spec/units/generators/generator_test_results_backtrace_spec.rb +49 -5
- data/spec/units/generators/generator_test_runner_spec.rb +26 -2
- data/spec/units/preprocess/preprocessinator_file_assembler_spec.rb +96 -2
- data/spec/units/preprocess/preprocessinator_reconstructor_spec.rb +151 -1
- data/spec/units/test_context_extractor_spec.rb +3 -3
- 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, '
|
|
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, '
|
|
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, '
|
|
149
|
-
expect(@file_wrapper).to have_received(:open).with(expected_source_filepath, '
|
|
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, '
|
|
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, '
|
|
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 '
|
|
223
|
-
|
|
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, '
|
|
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.
|
|
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-
|
|
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
|