ceedling 1.1.1 → 1.1.2
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/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/docs/Changelog.md +17 -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/generators/generator_partials.rb +13 -3
- 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/site-local/sitemap.xml.gz +0 -0
- data/spec/system/gcov_deployment_spec.rb +20 -6
- 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/generators/generator_partials_spec.rb +32 -6
- data/spec/units/preprocess/preprocessinator_file_assembler_spec.rb +96 -2
- data/spec/units/preprocess/preprocessinator_reconstructor_spec.rb +151 -1
- metadata +15 -2
|
@@ -18,8 +18,8 @@ module GcovCommonTestCases
|
|
|
18
18
|
FileUtils.cp test_asset_path("example_file.c"), 'src/'
|
|
19
19
|
FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/'
|
|
20
20
|
|
|
21
|
-
output =
|
|
22
|
-
expect(
|
|
21
|
+
output = @c.ceedling_build_exec("gcov:all")
|
|
22
|
+
expect(@c.last_exit_status).to eq(0) # Since test cases either pass or are ignored, Ceedling exits with success
|
|
23
23
|
expect(output).to match(/TESTED:\s+\d/)
|
|
24
24
|
expect(output).to match(/PASSED:\s+\d/)
|
|
25
25
|
expect(output).to match(/FAILED:\s+\d/)
|
|
@@ -36,8 +36,8 @@ module GcovCommonTestCases
|
|
|
36
36
|
FileUtils.cp test_asset_path("example_file.c"), 'src/'
|
|
37
37
|
FileUtils.cp test_asset_path("test_example_file.c"), 'test/'
|
|
38
38
|
|
|
39
|
-
output =
|
|
40
|
-
expect(
|
|
39
|
+
output = @c.ceedling_build_exec("gcov:all")
|
|
40
|
+
expect(@c.last_exit_status).to eq(1) # Unit test failures => Ceedling exit code 1
|
|
41
41
|
expect(output).to match(/TESTED:\s+\d/)
|
|
42
42
|
expect(output).to match(/PASSED:\s+\d/)
|
|
43
43
|
expect(output).to match(/FAILED:\s+\d/)
|
|
@@ -122,8 +122,8 @@ module GcovCommonTestCases
|
|
|
122
122
|
FileUtils.cp test_asset_path("example_file.c"), 'src/'
|
|
123
123
|
FileUtils.cp test_asset_path("test_example_file_boom.c"), 'test/'
|
|
124
124
|
|
|
125
|
-
output =
|
|
126
|
-
expect(
|
|
125
|
+
output = @c.ceedling_build_exec("gcov:all")
|
|
126
|
+
expect(@c.last_exit_status).to eq(1) # Since a test explodes, Ceedling exits with error
|
|
127
127
|
expect(output).to match(/(?:ERROR: Ceedling Failed)|(?:Ceedling could not complete operations because of errors)/)
|
|
128
128
|
end
|
|
129
129
|
end
|
|
@@ -133,8 +133,8 @@ module GcovCommonTestCases
|
|
|
133
133
|
@c.with_context do
|
|
134
134
|
Dir.chdir @proj_name do
|
|
135
135
|
prep_project_yml_for_coverage
|
|
136
|
-
output =
|
|
137
|
-
expect(
|
|
136
|
+
output = @c.ceedling_appcmd_exec("help")
|
|
137
|
+
expect(@c.last_exit_status).to eq(0)
|
|
138
138
|
expect(output).to match(/ceedling gcov:\*/i)
|
|
139
139
|
expect(output).to match(/ceedling gcov:all/i)
|
|
140
140
|
end
|
|
@@ -149,7 +149,7 @@ module GcovCommonTestCases
|
|
|
149
149
|
FileUtils.cp test_asset_path("example_file.c"), 'src/'
|
|
150
150
|
FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/'
|
|
151
151
|
|
|
152
|
-
output =
|
|
152
|
+
output = @c.ceedling_build_exec("gcov:all")
|
|
153
153
|
if @gcov_reports.include? :gcovr
|
|
154
154
|
expect(output).to match(/Generating HTML coverage report in 'build\/artifacts\/gcov\/gcovr\/'\.\.\./)
|
|
155
155
|
expect(File.exist?('build/artifacts/gcov/gcovr/GcovCoverageResults.html')).to eq true
|
|
@@ -170,8 +170,8 @@ module GcovCommonTestCases
|
|
|
170
170
|
FileUtils.cp test_asset_path("example_file_with_stdio.c"), 'src/'
|
|
171
171
|
FileUtils.cp test_asset_path("test_example_file_with_stdio.c"), 'test/'
|
|
172
172
|
|
|
173
|
-
output =
|
|
174
|
-
expect(
|
|
173
|
+
output = @c.ceedling_build_exec("gcov:all")
|
|
174
|
+
expect(@c.last_exit_status).to eq(0)
|
|
175
175
|
# Console summary must report coverage for the source file, not a warning.
|
|
176
176
|
# When <stdio.h> is included, gcov may list a system header (e.g. _stdio.h)
|
|
177
177
|
# as the first File entry — the fix ensures the correct source is found.
|
|
@@ -194,8 +194,8 @@ module GcovCommonTestCases
|
|
|
194
194
|
|
|
195
195
|
@c.merge_project_yml_for_test({ :project => { :use_partials => true } })
|
|
196
196
|
|
|
197
|
-
output =
|
|
198
|
-
expect(
|
|
197
|
+
output = @c.ceedling_build_exec("gcov:all")
|
|
198
|
+
expect(@c.last_exit_status).to eq(0)
|
|
199
199
|
# Coverage must be reported for the Partial (gcov references the original
|
|
200
200
|
# module via #line remapping); no spurious "Found no coverage results" warning.
|
|
201
201
|
expect(output).to match(/Lines executed:/)
|
|
@@ -216,18 +216,7 @@ module GcovCommonTestCases
|
|
|
216
216
|
# coverage entries: Ceedling builds one executable per test file, and both
|
|
217
217
|
# link against example_file.c, producing two sets of .gcda coverage data
|
|
218
218
|
# for the same functions.
|
|
219
|
-
|
|
220
|
-
#include "unity.h"
|
|
221
|
-
#include "example_file.h"
|
|
222
|
-
|
|
223
|
-
void setUp(void) {}
|
|
224
|
-
void tearDown(void) {}
|
|
225
|
-
|
|
226
|
-
void test_difference_between_two_numbers(void)
|
|
227
|
-
{
|
|
228
|
-
TEST_ASSERT_EQUAL_INT(0, difference_between_numbers(1, 1));
|
|
229
|
-
}
|
|
230
|
-
C
|
|
219
|
+
FileUtils.cp test_asset_path("test_example_file_2.c"), 'test/'
|
|
231
220
|
|
|
232
221
|
# 'strict' causes gcovr to exit non-zero when the same function appears in
|
|
233
222
|
# multiple test executables' coverage data. Ceedling's default is
|
|
@@ -246,11 +235,11 @@ module GcovCommonTestCases
|
|
|
246
235
|
}
|
|
247
236
|
})
|
|
248
237
|
|
|
249
|
-
output =
|
|
238
|
+
output = @c.ceedling_build_exec("gcov:all --verbosity=obnoxious")
|
|
250
239
|
if @gcov_reports.include? :gcovr
|
|
251
240
|
# Config file honored (fix applied): strict mode exits non-zero on duplicate functions.
|
|
252
241
|
# Config file overridden by Ceedling CLI (bug): merge-use-line-max exits 0.
|
|
253
|
-
expect(
|
|
242
|
+
expect(@c.last_exit_status).not_to eq(0)
|
|
254
243
|
|
|
255
244
|
# No `--exclude` should appear on the gcovr command line when a config file is in use.
|
|
256
245
|
expect(output).not_to match(/--exclude/)
|
|
@@ -269,8 +258,8 @@ module GcovCommonTestCases
|
|
|
269
258
|
|
|
270
259
|
@c.merge_project_yml_for_test({:project => { :use_backtrace => :gdb }})
|
|
271
260
|
|
|
272
|
-
output =
|
|
273
|
-
expect(
|
|
261
|
+
output = @c.ceedling_build_exec("gcov:all")
|
|
262
|
+
expect(@c.last_exit_status).to eq(1) # Ceedling should exit with error because of failed test due to crash
|
|
274
263
|
expect(output).to match(/crashed/i)
|
|
275
264
|
expect(output).to match(/Unit test failures/)
|
|
276
265
|
expect(File.exist?('./build/gcov/results/test_example_file_crash_sigsegv.fail'))
|
|
@@ -303,8 +292,8 @@ module GcovCommonTestCases
|
|
|
303
292
|
|
|
304
293
|
@c.merge_project_yml_for_test({:project => { :use_backtrace => :gdb }})
|
|
305
294
|
|
|
306
|
-
output =
|
|
307
|
-
expect(
|
|
295
|
+
output = @c.ceedling_build_exec("gcov:all --exclude_test_case=test_add_numbers_adds_numbers")
|
|
296
|
+
expect(@c.last_exit_status).to eq(1) # Ceedling should exit with error because of failed test due to crash
|
|
308
297
|
expect(output).to match(/Test Case Crashed/i)
|
|
309
298
|
expect(output).to match(/Unit test failures/)
|
|
310
299
|
expect(File.exist?('./build/gcov/results/test_example_file_crash_sigsegv.fail'))
|
|
@@ -345,8 +334,8 @@ module GcovCommonTestCases
|
|
|
345
334
|
updated_test_file.insert(updated_test_file.length(), add_test_case)
|
|
346
335
|
File.write('test/test_example_file_crash_sigsegv.c', updated_test_file.join("\n"), mode: 'w')
|
|
347
336
|
|
|
348
|
-
output =
|
|
349
|
-
expect(
|
|
337
|
+
output = @c.ceedling_build_exec("gcov:all --exclude_test_case=test_add_numbers_will_fail")
|
|
338
|
+
expect(@c.last_exit_status).to eq(0)
|
|
350
339
|
expect(File.exist?('./build/gcov/results/test_example_file_crash_sigsegv.pass'))
|
|
351
340
|
expect(output).to match(/TESTED:\s+2/)
|
|
352
341
|
expect(output).to match(/PASSED:\s+2/)
|
|
@@ -377,8 +366,8 @@ module GcovCommonTestCases
|
|
|
377
366
|
# Untested — no test references this source file.
|
|
378
367
|
FileUtils.cp test_asset_path("uncovered_example_file.c"), 'src/'
|
|
379
368
|
|
|
380
|
-
output =
|
|
381
|
-
expect(
|
|
369
|
+
output = @c.ceedling_build_exec("gcov:all")
|
|
370
|
+
expect(@c.last_exit_status).to eq(0)
|
|
382
371
|
expect(output).not_to match(/Untested Source Files/)
|
|
383
372
|
expect(output).not_to match(/Processing Untested Sources/)
|
|
384
373
|
expect(output).not_to match(/uncovered_example_file/)
|
|
@@ -397,8 +386,8 @@ module GcovCommonTestCases
|
|
|
397
386
|
# Untested — no test references this source file.
|
|
398
387
|
FileUtils.cp test_asset_path("uncovered_example_file.c"), 'src/'
|
|
399
388
|
|
|
400
|
-
output =
|
|
401
|
-
expect(
|
|
389
|
+
output = @c.ceedling_build_exec("gcov:all")
|
|
390
|
+
expect(@c.last_exit_status).to eq(0)
|
|
402
391
|
# Immediate warning-level filepath listing (no compilation attempted).
|
|
403
392
|
expect(output).to match(/Untested.+not.+coverage report/)
|
|
404
393
|
expect(output).to match(/uncovered_example_file\.c/)
|
|
@@ -421,13 +410,10 @@ module GcovCommonTestCases
|
|
|
421
410
|
|
|
422
411
|
# Untested source with a syntax error — fails to compile regardless of
|
|
423
412
|
# any :defines/:flags, deliberately triggering the guidance notice.
|
|
424
|
-
|
|
425
|
-
int broken_uncovered_function(int a, int b) {
|
|
426
|
-
return a + b // Missing semicolon and closing brace: guaranteed compile error.
|
|
427
|
-
C
|
|
413
|
+
FileUtils.cp test_asset_path("broken_uncovered_file.c"), 'src/'
|
|
428
414
|
|
|
429
|
-
output =
|
|
430
|
-
expect(
|
|
415
|
+
output = @c.ceedling_build_exec("gcov:all")
|
|
416
|
+
expect(@c.last_exit_status).not_to eq(0) # Fail-fast: compile failure fails the build
|
|
431
417
|
expect(output).to match(/Compiling.+with coverage failed/)
|
|
432
418
|
expect(output).to match(/:untested_sources.*:compile/)
|
|
433
419
|
# Avoid unicode character matching in log matching, since some shells may not support them.
|
|
@@ -450,8 +436,8 @@ module GcovCommonTestCases
|
|
|
450
436
|
|
|
451
437
|
# Run the standalone task directly — no prior `gcov:all` in this invocation,
|
|
452
438
|
# so no test fixture is ever built or executed.
|
|
453
|
-
output =
|
|
454
|
-
expect(
|
|
439
|
+
output = @c.ceedling_build_exec("gcov:untested_sources")
|
|
440
|
+
expect(@c.last_exit_status).to eq(0)
|
|
455
441
|
expect(output).not_to match(/TESTED:\s+\d/) # No test build/run occurred
|
|
456
442
|
expect(File.exist?('build/gcov/out/uncovered_example_file.o')).to eq true
|
|
457
443
|
end
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# =========================================================================
|
|
2
|
+
# Ceedling - Test-Centered Build System for C
|
|
3
|
+
# ThrowTheSwitch.org
|
|
4
|
+
# Copyright (c) 2010-26 Mike Karlesky, Mark VanderVoord, & Greg Williams
|
|
5
|
+
# SPDX-License-Identifier: MIT
|
|
6
|
+
# =========================================================================
|
|
7
|
+
|
|
8
|
+
require 'rexml/document'
|
|
9
|
+
require_relative 'gcov_helpers'
|
|
10
|
+
|
|
11
|
+
module GcovPartialsTestCases
|
|
12
|
+
include GcovHelpers
|
|
13
|
+
|
|
14
|
+
# Runs `ceedling gcov:all` with Partials + Cobertura XML reporting enabled, then
|
|
15
|
+
# returns { line_number => hits } for `source_relpath` (e.g. 'src/blanks.c').
|
|
16
|
+
def partials_cobertura_hits_by_line(source_relpath)
|
|
17
|
+
@c.merge_project_yml_for_test({
|
|
18
|
+
:project => { :use_partials => true },
|
|
19
|
+
:gcov => { :reports => ['Cobertura'] }
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
output = @c.ceedling_build_exec("gcov:all")
|
|
23
|
+
expect(@c.last_exit_status).to eq(0)
|
|
24
|
+
|
|
25
|
+
cobertura_path = File.join('build', 'artifacts', 'gcov', 'gcovr', 'GcovCoverageCobertura.xml')
|
|
26
|
+
doc = REXML::Document.new(File.read(cobertura_path))
|
|
27
|
+
|
|
28
|
+
doc.elements.to_a("//class[@filename='#{source_relpath}']/lines/line").each_with_object({}) do |el, h|
|
|
29
|
+
h[el.attributes['number'].to_i] = el.attributes['hits'].to_i
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def gcov_partials_coverage_blank_lines_in_function_body
|
|
34
|
+
@c.with_context do
|
|
35
|
+
Dir.chdir @proj_name do
|
|
36
|
+
prep_project_yml_for_coverage
|
|
37
|
+
|
|
38
|
+
# Multiple consecutive blank lines inside a function body -- the same
|
|
39
|
+
# shape a stripped multi-line comment leaves behind -- sit between the
|
|
40
|
+
# opening brace and the one executed statement. Each function in a
|
|
41
|
+
# generated Partial gets exactly one #line directive, at its own first
|
|
42
|
+
# line; every line after that is trusted to match the original source
|
|
43
|
+
# 1:1. If any lines partway through the body go missing during
|
|
44
|
+
# reconstruction, gcov's line attribution for everything after that
|
|
45
|
+
# point in the function shifts up by the deficit.
|
|
46
|
+
asset_base = test_asset_path("tests_with_partials_coverage")
|
|
47
|
+
FileUtils.cp "#{asset_base}/src/blanks.h", 'src/'
|
|
48
|
+
FileUtils.cp "#{asset_base}/src/blanks.c", 'src/'
|
|
49
|
+
FileUtils.cp "#{asset_base}/test/test_blanks.c", 'test/'
|
|
50
|
+
|
|
51
|
+
hits = partials_cobertura_hits_by_line('src/blanks.c')
|
|
52
|
+
|
|
53
|
+
# `return;` is on source line 5 -- it must show as hit.
|
|
54
|
+
expect(hits[5]).to be > 0
|
|
55
|
+
# The blank lines (3-4) must not appear as executable/hit lines at all.
|
|
56
|
+
expect(hits[3]).to be_nil
|
|
57
|
+
expect(hits[4]).to be_nil
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def gcov_partials_coverage_decorators_on_own_lines
|
|
63
|
+
@c.with_context do
|
|
64
|
+
Dir.chdir @proj_name do
|
|
65
|
+
prep_project_yml_for_coverage
|
|
66
|
+
|
|
67
|
+
# `static` and `inline` each occupy their own line ahead of the return
|
|
68
|
+
# type. Partials strips both keywords from the front of the extracted
|
|
69
|
+
# function text (per the docs, functions are stripped of `static` and
|
|
70
|
+
# `inline`) and must bump the emitted #line directive forward by
|
|
71
|
+
# exactly the number of lines that stripping removed -- otherwise the
|
|
72
|
+
# whole body, including the blank line below, reports coverage against
|
|
73
|
+
# the wrong original source lines.
|
|
74
|
+
asset_base = test_asset_path("tests_with_partials_coverage")
|
|
75
|
+
FileUtils.cp "#{asset_base}/src/decorators.h", 'src/'
|
|
76
|
+
FileUtils.cp "#{asset_base}/src/decorators.c", 'src/'
|
|
77
|
+
FileUtils.cp "#{asset_base}/test/test_decorators.c", 'test/'
|
|
78
|
+
|
|
79
|
+
hits = partials_cobertura_hits_by_line('src/decorators.c')
|
|
80
|
+
|
|
81
|
+
# `return;` is on source line 7 -- it must show as hit.
|
|
82
|
+
expect(hits[7]).to be > 0
|
|
83
|
+
# The blank line (6) must not appear as an executable/hit line.
|
|
84
|
+
expect(hits[6]).to be_nil
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def gcov_partials_coverage_function_scope_static_promotion
|
|
90
|
+
@c.with_context do
|
|
91
|
+
Dir.chdir @proj_name do
|
|
92
|
+
prep_project_yml_for_coverage
|
|
93
|
+
|
|
94
|
+
# A function-scope `static` variable is replaced with a no-op and
|
|
95
|
+
# promoted to module scope so the Partial's testable copy of the
|
|
96
|
+
# function keeps the original's persistence semantics (per the docs'
|
|
97
|
+
# Partials creation steps). Confirms that substitution doesn't shift
|
|
98
|
+
# the lines after it out of alignment with their original source line
|
|
99
|
+
# numbers.
|
|
100
|
+
asset_base = test_asset_path("tests_with_partials_coverage")
|
|
101
|
+
FileUtils.cp "#{asset_base}/src/counter.h", 'src/'
|
|
102
|
+
FileUtils.cp "#{asset_base}/src/counter.c", 'src/'
|
|
103
|
+
FileUtils.cp "#{asset_base}/test/test_counter.c", 'test/'
|
|
104
|
+
|
|
105
|
+
hits = partials_cobertura_hits_by_line('src/counter.c')
|
|
106
|
+
|
|
107
|
+
# `count++;` (line 5) and `return count;` (line 7) must both show as
|
|
108
|
+
# hit, correctly separated by the blank lines around them (4 and 6).
|
|
109
|
+
expect(hits[5]).to be > 0
|
|
110
|
+
expect(hits[7]).to be > 0
|
|
111
|
+
expect(hits[4]).to be_nil
|
|
112
|
+
expect(hits[6]).to be_nil
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -445,6 +445,58 @@ describe CExtractorDeclarations do
|
|
|
445
445
|
end
|
|
446
446
|
end
|
|
447
447
|
|
|
448
|
+
context "escape sequences in string literal initializers" do
|
|
449
|
+
it "extracts pointer variable with escaped quote in string literal initializer" do
|
|
450
|
+
content = 'char* s = "say \"hi\"";'
|
|
451
|
+
success, variable, pos, rest = extract_variable.call(content)
|
|
452
|
+
|
|
453
|
+
expect(success).to be true
|
|
454
|
+
check_single(variable, name: 's', type: 'char*', text: 'char* s = "say \"hi\"";')
|
|
455
|
+
expect(pos).to eq(content.length)
|
|
456
|
+
expect(rest).to eq("")
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
it "extracts pointer variable with escaped backslash in string literal initializer" do
|
|
460
|
+
content = 'char* s = "C:\\\\dir";'
|
|
461
|
+
success, variable, pos, rest = extract_variable.call(content)
|
|
462
|
+
|
|
463
|
+
expect(success).to be true
|
|
464
|
+
check_single(variable, name: 's', type: 'char*', text: 'char* s = "C:\\\\dir";')
|
|
465
|
+
expect(pos).to eq(content.length)
|
|
466
|
+
expect(rest).to eq("")
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
it "extracts pointer variable with escaped newline in string literal initializer" do
|
|
470
|
+
content = 'char* s = "line1\\nline2";'
|
|
471
|
+
success, variable, pos, rest = extract_variable.call(content)
|
|
472
|
+
|
|
473
|
+
expect(success).to be true
|
|
474
|
+
check_single(variable, name: 's', type: 'char*', text: 'char* s = "line1\\nline2";')
|
|
475
|
+
expect(pos).to eq(content.length)
|
|
476
|
+
expect(rest).to eq("")
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
it "extracts pointer variable with hex escape in string literal initializer" do
|
|
480
|
+
content = 'char* s = "\\x41\\x42";'
|
|
481
|
+
success, variable, pos, rest = extract_variable.call(content)
|
|
482
|
+
|
|
483
|
+
expect(success).to be true
|
|
484
|
+
check_single(variable, name: 's', type: 'char*', text: 'char* s = "\\x41\\x42";')
|
|
485
|
+
expect(pos).to eq(content.length)
|
|
486
|
+
expect(rest).to eq("")
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
it "extracts pointer variable with octal escape in string literal initializer" do
|
|
490
|
+
content = 'char* s = "\\101\\102";'
|
|
491
|
+
success, variable, pos, rest = extract_variable.call(content)
|
|
492
|
+
|
|
493
|
+
expect(success).to be true
|
|
494
|
+
check_single(variable, name: 's', type: 'char*', text: 'char* s = "\\101\\102";')
|
|
495
|
+
expect(pos).to eq(content.length)
|
|
496
|
+
expect(rest).to eq("")
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
|
|
448
500
|
context "qualified type declarations" do
|
|
449
501
|
it "extracts const variable" do
|
|
450
502
|
content = "const int value;"
|
|
@@ -685,6 +737,82 @@ describe CExtractorDeclarations do
|
|
|
685
737
|
end
|
|
686
738
|
end
|
|
687
739
|
|
|
740
|
+
context "multiline declarations" do
|
|
741
|
+
it "extracts array declaration whose closing brace and semicolon fall on the final line after multiline data (GH #1182)" do
|
|
742
|
+
content = "static uint8_t arr[10] = {\n 0x00, 0x01, 0x02, 0x03, 0x04,\n 0x05, 0x06, 0x07, 0x08, 0x09\n};"
|
|
743
|
+
success, variable, pos, rest = extract_variable.call(content)
|
|
744
|
+
|
|
745
|
+
expect(success).to be true
|
|
746
|
+
check_single(variable, name: 'arr', type: 'uint8_t', decorators: ['static'],
|
|
747
|
+
text: 'uint8_t arr[10] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09 };',
|
|
748
|
+
array_suffix: '[10]')
|
|
749
|
+
expect(pos).to eq(content.length)
|
|
750
|
+
expect(rest).to eq("")
|
|
751
|
+
end
|
|
752
|
+
|
|
753
|
+
it "extracts nested multi-dimensional array initializer whose closing braces and semicolon fall on the final line" do
|
|
754
|
+
content = "static int matrix[2][3] = {\n {1, 2, 3},\n {4, 5, 6}\n};"
|
|
755
|
+
success, variable, pos, rest = extract_variable.call(content)
|
|
756
|
+
|
|
757
|
+
expect(success).to be true
|
|
758
|
+
check_single(variable, name: 'matrix', type: 'int', decorators: ['static'],
|
|
759
|
+
text: 'int matrix[2][3] = { {1, 2, 3}, {4, 5, 6} };',
|
|
760
|
+
array_suffix: '[2][3]')
|
|
761
|
+
expect(pos).to eq(content.length)
|
|
762
|
+
expect(rest).to eq("")
|
|
763
|
+
end
|
|
764
|
+
|
|
765
|
+
it "expands a compound declaration whose second declarator has a multiline brace initializer" do
|
|
766
|
+
content = "static int a, arr[3] = {\n 1,\n 2,\n 3\n};"
|
|
767
|
+
success, variable, pos, rest = extract_variable.call(content)
|
|
768
|
+
|
|
769
|
+
expect(success).to be true
|
|
770
|
+
expect(variable.length).to eq 2
|
|
771
|
+
|
|
772
|
+
expect(variable[0].name).to eq 'a'
|
|
773
|
+
expect(variable[0].decorators).to eq ['static']
|
|
774
|
+
expect(variable[0].text).to eq 'int a;'
|
|
775
|
+
|
|
776
|
+
expect(variable[1].name).to eq 'arr'
|
|
777
|
+
expect(variable[1].decorators).to eq ['static']
|
|
778
|
+
expect(variable[1].array_suffix).to eq '[3]'
|
|
779
|
+
expect(variable[1].text).to eq 'int arr[3] = { 1, 2, 3 };'
|
|
780
|
+
|
|
781
|
+
expect(pos).to eq(content.length)
|
|
782
|
+
expect(rest).to eq("")
|
|
783
|
+
end
|
|
784
|
+
|
|
785
|
+
it "extracts a declaration with the type and name split across lines and no braces involved" do
|
|
786
|
+
content = "int\n x;"
|
|
787
|
+
success, variable, pos, rest = extract_variable.call(content)
|
|
788
|
+
|
|
789
|
+
expect(success).to be true
|
|
790
|
+
check_single(variable, name: 'x', type: 'int', text: 'int x;')
|
|
791
|
+
expect(pos).to eq(content.length)
|
|
792
|
+
expect(rest).to eq("")
|
|
793
|
+
end
|
|
794
|
+
|
|
795
|
+
it "extracts a declaration whose initializer value falls on the line after the assignment" do
|
|
796
|
+
content = "int x =\n 5;"
|
|
797
|
+
success, variable, pos, rest = extract_variable.call(content)
|
|
798
|
+
|
|
799
|
+
expect(success).to be true
|
|
800
|
+
check_single(variable, name: 'x', type: 'int', text: 'int x = 5;')
|
|
801
|
+
expect(pos).to eq(content.length)
|
|
802
|
+
expect(rest).to eq("")
|
|
803
|
+
end
|
|
804
|
+
|
|
805
|
+
it "extracts a declaration whose terminating semicolon is alone on the line following a bracketed declarator" do
|
|
806
|
+
content = "int arr[10]\n;"
|
|
807
|
+
success, variable, pos, rest = extract_variable.call(content)
|
|
808
|
+
|
|
809
|
+
expect(success).to be true
|
|
810
|
+
check_single(variable, name: 'arr', type: 'int', text: 'int arr[10] ;', array_suffix: '[10]')
|
|
811
|
+
expect(pos).to eq(content.length)
|
|
812
|
+
expect(rest).to eq("")
|
|
813
|
+
end
|
|
814
|
+
end
|
|
815
|
+
|
|
688
816
|
context "array_suffix field" do
|
|
689
817
|
it "returns empty string for a scalar variable" do
|
|
690
818
|
content = "static uint8 s_count;"
|
|
@@ -295,6 +295,13 @@ describe CExtractorDefinitions do
|
|
|
295
295
|
expect(pos).to eq input.length
|
|
296
296
|
end
|
|
297
297
|
|
|
298
|
+
it "extracts a named struct when the closing '}' and terminating ';' fall on separate lines" do
|
|
299
|
+
input = "struct Point {\n int x;\n}\n;\n"
|
|
300
|
+
result, pos = try_aggregate(input)
|
|
301
|
+
expect(result).to eq [true, input.chomp]
|
|
302
|
+
expect(pos).to eq input.length
|
|
303
|
+
end
|
|
304
|
+
|
|
298
305
|
it "extracts an anonymous struct (no tag name)" do
|
|
299
306
|
input = "struct { int x; int y; };\n"
|
|
300
307
|
result, pos = try_aggregate(input)
|
|
@@ -561,6 +561,23 @@ describe CExtractor do
|
|
|
561
561
|
expect( contents.variable_declarations.length ).to eq 0
|
|
562
562
|
end
|
|
563
563
|
|
|
564
|
+
it "should extract a function definition following a #define with an escaped character in a string literal (GH #1184)" do
|
|
565
|
+
file_contents = <<~'CONTENTS'
|
|
566
|
+
#include "world.h"
|
|
567
|
+
#define SZ1 sizeof("\n")
|
|
568
|
+
void helloWorld() { return; }
|
|
569
|
+
CONTENTS
|
|
570
|
+
|
|
571
|
+
contents = extract_from.call(file_contents)
|
|
572
|
+
|
|
573
|
+
expect( contents.macro_definitions.length ).to eq 1
|
|
574
|
+
expect( contents.macro_definitions[0].text ).to eq '#define SZ1 sizeof("\n")'
|
|
575
|
+
|
|
576
|
+
expect( contents.function_definitions.length ).to eq 1
|
|
577
|
+
expect( contents.function_definitions[0].name ).to eq 'helloWorld'
|
|
578
|
+
expect( contents.function_definitions[0].body ).to eq '{ return; }'
|
|
579
|
+
end
|
|
580
|
+
|
|
564
581
|
it "should consume #pragma and #include directives without storing them" do
|
|
565
582
|
file_contents = <<~CONTENTS
|
|
566
583
|
#pragma once
|
|
@@ -290,6 +290,45 @@ describe CExtractorPreprocessing do
|
|
|
290
290
|
expect(pos).to eq input.length
|
|
291
291
|
end
|
|
292
292
|
|
|
293
|
+
# --- Regression: GH #1184 — escape sequences inside a #define string literal ---
|
|
294
|
+
# A lone '\' preceding a string-literal escape (e.g. \n, \", \\) is not a line
|
|
295
|
+
# continuation and must not be mistaken for one, truncating the directive.
|
|
296
|
+
|
|
297
|
+
it "extracts a #define whose string literal contains an escaped newline" do
|
|
298
|
+
input = "#define SZ1 sizeof(\"\\n\")\n"
|
|
299
|
+
result, pos = try_directive(input)
|
|
300
|
+
expect(result).to eq [true, input.rstrip]
|
|
301
|
+
expect(pos).to eq input.length
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
it "extracts a #define whose string literal contains an escaped quote" do
|
|
305
|
+
input = "#define MSG \"say \\\"hi\\\"\"\n"
|
|
306
|
+
result, pos = try_directive(input)
|
|
307
|
+
expect(result).to eq [true, input.rstrip]
|
|
308
|
+
expect(pos).to eq input.length
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
it "extracts a #define whose string literal contains an escaped backslash" do
|
|
312
|
+
input = "#define PATH \"C:\\\\dir\"\n"
|
|
313
|
+
result, pos = try_directive(input)
|
|
314
|
+
expect(result).to eq [true, input.rstrip]
|
|
315
|
+
expect(pos).to eq input.length
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
it "extracts a #define with an escaped character and no trailing newline (EOS)" do
|
|
319
|
+
input = "#define SZ1 sizeof(\"\\n\")"
|
|
320
|
+
result, pos = try_directive(input)
|
|
321
|
+
expect(result).to eq [true, input]
|
|
322
|
+
expect(pos).to eq input.length
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
it "does not consume a following function definition after a #define with an escaped character" do
|
|
326
|
+
input = "#define SZ1 sizeof(\"\\n\")\nvoid helloWorld() { return; }\n"
|
|
327
|
+
result, pos = try_directive(input)
|
|
328
|
+
expect(result).to eq [true, "#define SZ1 sizeof(\"\\n\")"]
|
|
329
|
+
expect(pos).to eq "#define SZ1 sizeof(\"\\n\")\n".length
|
|
330
|
+
end
|
|
331
|
+
|
|
293
332
|
it "stops at end of directive and does not consume following code" do
|
|
294
333
|
input = "#define FOO 1\nint x = 0;"
|
|
295
334
|
result, pos = try_directive(input)
|
|
@@ -483,6 +522,13 @@ describe CExtractorPreprocessing do
|
|
|
483
522
|
expect(pos).to eq input.length
|
|
484
523
|
end
|
|
485
524
|
|
|
525
|
+
it "handles a static assert whose terminating ';' falls on its own line after the closing ')'" do
|
|
526
|
+
input = "static_assert(sizeof(int) == 4, \"msg\")\n;\n"
|
|
527
|
+
result, pos = try_static_assert(input)
|
|
528
|
+
expect(result[0]).to be true
|
|
529
|
+
expect(pos).to eq input.length
|
|
530
|
+
end
|
|
531
|
+
|
|
486
532
|
# --- Boundary behaviour ---
|
|
487
533
|
|
|
488
534
|
it "stops at the ';' and does not consume following code" do
|
|
@@ -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
|