ceedling 1.1.7 → 1.1.8
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 +1 -1
- data/docs/Changelog.md +30 -0
- data/docs/KnownIssues.md +2 -0
- data/docs/mkdocs/plugins/gcov/gcovr.md +9 -4
- data/docs/mkdocs/reference/gcov-plugin.md +9 -4
- data/lib/ceedling/c_extractor/c_extractor_declarations.rb +11 -10
- data/lib/ceedling/c_extractor/c_extractor_preprocessing.rb +39 -2
- data/lib/ceedling/generators/generator_test_results_backtrace.rb +5 -3
- data/lib/ceedling/generators/generator_test_runner.rb +5 -1
- data/lib/ceedling/preprocess/preprocessinator_line_marker_includes_extractor.rb +6 -1
- data/lib/ceedling/preprocess/preprocessinator_reconstructor.rb +18 -5
- data/lib/version.rb +1 -1
- data/plugins/gcov/lib/console_reportinator.rb +7 -0
- data/plugins/gcov/lib/gcov.rb +29 -10
- data/plugins/gcov/lib/gcovr_reportinator.rb +62 -48
- data/plugins/gcov/lib/reportgenerator_reportinator.rb +10 -6
- data/site-local/plugins/gcov/gcovr.html +9 -4
- data/site-local/reference/gcov-plugin.html +9 -4
- data/site-local/sitemap.xml.gz +0 -0
- data/spec/system/gcov_deployment_spec.rb +1 -0
- data/spec/system/support/gcov_common_test_cases.rb +18 -0
- data/spec/units/c_extractor/c_extractor_declarations_spec.rb +16 -0
- data/spec/units/c_extractor/c_extractor_integration_spec.rb +53 -0
- data/spec/units/c_extractor/c_extractor_preprocessing_spec.rb +45 -0
- data/spec/units/generators/generator_partials_spec.rb +98 -0
- data/spec/units/generators/generator_test_results_backtrace_spec.rb +26 -0
- data/spec/units/generators/generator_test_runner_spec.rb +23 -0
- data/spec/units/preprocess/preprocessinator_line_marker_includes_extractor_spec.rb +64 -0
- data/spec/units/preprocess/preprocessinator_reconstructor_spec.rb +74 -0
- metadata +4 -2
|
@@ -128,7 +128,9 @@ class ReportGeneratorReportinator < GcovReportinator
|
|
|
128
128
|
|
|
129
129
|
# Get the ReportGenerator options from the project options.
|
|
130
130
|
def collect_reportgenerator_opts(opts)
|
|
131
|
-
|
|
131
|
+
# dup prevents repeated calls from accumulating mutations on the shared opts hash --
|
|
132
|
+
# mirrors GcovrReportinator#collect_gcovr_opts' identical guard and rationale.
|
|
133
|
+
_opts = opts[REPORT_GENERATOR_SETTING_PREFIX.to_sym].dup
|
|
132
134
|
|
|
133
135
|
# Auto-generate -filefilters: exclusion patterns for test paths and the build root.
|
|
134
136
|
# These exclude test files and all generated/framework files from coverage reports.
|
|
@@ -275,17 +277,19 @@ class ReportGeneratorReportinator < GcovReportinator
|
|
|
275
277
|
# Build filename-fragment patterns for the .gcno scan exclusion regex.
|
|
276
278
|
# Excludes test files, generated mocks, and test runners from gcov processing.
|
|
277
279
|
# The '.*' suffix handles source extensions embedded in .gcno filenames (e.g. test_foo.c.gcno).
|
|
280
|
+
# test_prefix/mock_prefix are config-driven, so they're Regexp-escaped -- unescaped, a
|
|
281
|
+
# metacharacter in either (e.g. a literal '.') would silently over- or under-match.
|
|
278
282
|
def build_gcno_exclusions
|
|
279
283
|
data = build_exclusion_data
|
|
280
284
|
# Use [^\/\\]* (no path separators) rather than .* to match filenames only.
|
|
281
285
|
# Ceedling names build output dirs after test files (e.g. build/gcov/out/test_foo/),
|
|
282
286
|
# so .* would greedily match test_foo/bar.c and exclude ALL production sources.
|
|
283
287
|
[
|
|
284
|
-
"#{data[:test_prefix]}[^\\/\\\\]*", # test_foo.gcno
|
|
285
|
-
"#{data[:mock_prefix]}[^\\/\\\\]*", # MockBar.gcno
|
|
286
|
-
"[^\\/\\\\]*_runner[^\\/\\\\]*",
|
|
287
|
-
File.basename(UNITY_C_FILE, '.*'),
|
|
288
|
-
File.basename(CMOCK_C_FILE, '.*')
|
|
288
|
+
"#{Regexp.escape(data[:test_prefix].to_s)}[^\\/\\\\]*", # test_foo.gcno
|
|
289
|
+
"#{Regexp.escape(data[:mock_prefix].to_s)}[^\\/\\\\]*", # MockBar.gcno
|
|
290
|
+
"[^\\/\\\\]*_runner[^\\/\\\\]*", # test_foo_runner.gcno
|
|
291
|
+
Regexp.escape(File.basename(UNITY_C_FILE, '.*')), # unity.gcno
|
|
292
|
+
Regexp.escape(File.basename(CMOCK_C_FILE, '.*')) # cmock.gcno
|
|
289
293
|
]
|
|
290
294
|
end
|
|
291
295
|
|
|
@@ -5367,7 +5367,7 @@ it's never silently non-functional.</p>
|
|
|
5367
5367
|
<p>Report the decision coverage. For HTML, JSON, and the summary report.
|
|
5368
5368
|
(<code>gcovr --decisions</code>)</p>
|
|
5369
5369
|
<p>Implied automatically whenever <code>:fail_under_decision</code> is set.</p>
|
|
5370
|
-
<p><strong>Requires:</strong> <code>gcovr</code>
|
|
5370
|
+
<p><strong>Requires:</strong> <code>gcovr</code> 5.1 or higher</p>
|
|
5371
5371
|
<hr />
|
|
5372
5372
|
<h3 id="fail_under_function"><code>:fail_under_function</code></h3>
|
|
5373
5373
|
<p>Exit with a status of 16 if the total function coverage is less than this minimum
|
|
@@ -5387,15 +5387,20 @@ logs a warning without breaking the build.</p>
|
|
|
5387
5387
|
<hr />
|
|
5388
5388
|
<h3 id="branches"><code>:branches</code></h3>
|
|
5389
5389
|
<p>Report the branch coverage instead of the line coverage. Applies to the text
|
|
5390
|
-
report only.
|
|
5390
|
+
report only. Uses <code>gcovr --txt-metric branch</code> on <code>gcovr</code> 7.0+ (<code>--branches</code> is
|
|
5391
|
+
deprecated as of 7.0, though still functional); <code>gcovr --branches</code> below it.</p>
|
|
5391
5392
|
<hr />
|
|
5392
5393
|
<h3 id="sort_uncovered"><code>:sort_uncovered</code></h3>
|
|
5393
5394
|
<p>Sort entries by increasing number of uncovered lines. Applies to text and HTML
|
|
5394
|
-
reports.
|
|
5395
|
+
reports. Uses <code>gcovr --sort uncovered-number</code> on <code>gcovr</code> 7.0+
|
|
5396
|
+
(<code>--sort-uncovered</code> is deprecated as of 7.0, though still functional);
|
|
5397
|
+
<code>gcovr --sort-uncovered</code> below it.</p>
|
|
5395
5398
|
<hr />
|
|
5396
5399
|
<h3 id="sort_percentage"><code>:sort_percentage</code></h3>
|
|
5397
5400
|
<p>Sort entries by increasing percentage of uncovered lines. Applies to text and
|
|
5398
|
-
HTML reports.
|
|
5401
|
+
HTML reports. Uses <code>gcovr --sort uncovered-percent</code> on <code>gcovr</code> 7.0+
|
|
5402
|
+
(<code>--sort-percentage</code> is deprecated as of 7.0, though still functional);
|
|
5403
|
+
<code>gcovr --sort-percentage</code> below it.</p>
|
|
5399
5404
|
<hr />
|
|
5400
5405
|
<h3 id="print_summary"><code>:print_summary</code></h3>
|
|
5401
5406
|
<p>Print a small report to stdout with line and branch percentage coverage. This
|
|
@@ -5668,7 +5668,7 @@ present — setting this option implies <code>:decisions</code> (below) automati
|
|
|
5668
5668
|
<p>Report the decision coverage. For HTML, JSON, and the summary report.
|
|
5669
5669
|
(<code>gcovr --decisions</code>)</p>
|
|
5670
5670
|
<p>Implied automatically whenever <code>:fail_under_decision</code> is set.</p>
|
|
5671
|
-
<p><strong>Requires:</strong> <code>gcovr</code>
|
|
5671
|
+
<p><strong>Requires:</strong> <code>gcovr</code> 5.1 or higher</p>
|
|
5672
5672
|
<hr />
|
|
5673
5673
|
<h3 id="fail_under_function"><code>:fail_under_function</code></h3>
|
|
5674
5674
|
<p>Exit with status 16 if total function coverage is below this percentage.
|
|
@@ -5686,15 +5686,20 @@ instead of only logging a warning.</p>
|
|
|
5686
5686
|
<hr />
|
|
5687
5687
|
<h3 id="branches"><code>:branches</code></h3>
|
|
5688
5688
|
<p>Report branch coverage instead of line coverage. Applies to text reports only.
|
|
5689
|
-
|
|
5689
|
+
Uses <code>gcovr --txt-metric branch</code> on <code>gcovr</code> 7.0+ (<code>--branches</code> is deprecated as
|
|
5690
|
+
of 7.0, though still functional); <code>gcovr --branches</code> below it.</p>
|
|
5690
5691
|
<hr />
|
|
5691
5692
|
<h3 id="sort_uncovered"><code>:sort_uncovered</code></h3>
|
|
5692
5693
|
<p>Sort report entries by increasing number of uncovered lines. Applies to text
|
|
5693
|
-
and HTML reports.
|
|
5694
|
+
and HTML reports. Uses <code>gcovr --sort uncovered-number</code> on <code>gcovr</code> 7.0+
|
|
5695
|
+
(<code>--sort-uncovered</code> is deprecated as of 7.0, though still functional);
|
|
5696
|
+
<code>gcovr --sort-uncovered</code> below it.</p>
|
|
5694
5697
|
<hr />
|
|
5695
5698
|
<h3 id="sort_percentage"><code>:sort_percentage</code></h3>
|
|
5696
5699
|
<p>Sort report entries by increasing percentage of uncovered lines. Applies to
|
|
5697
|
-
text and HTML reports.
|
|
5700
|
+
text and HTML reports. Uses <code>gcovr --sort uncovered-percent</code> on <code>gcovr</code> 7.0+
|
|
5701
|
+
(<code>--sort-percentage</code> is deprecated as of 7.0, though still functional);
|
|
5702
|
+
<code>gcovr --sort-percentage</code> below it.</p>
|
|
5698
5703
|
<hr />
|
|
5699
5704
|
<h3 id="print_summary"><code>:print_summary</code></h3>
|
|
5700
5705
|
<p>Print a brief line- and branch-coverage summary to stdout in addition to any
|
data/site-local/sitemap.xml.gz
CHANGED
|
Binary file
|
|
@@ -37,6 +37,7 @@ ceedling_system_tests do
|
|
|
37
37
|
test_case :project_with_gcov_success
|
|
38
38
|
test_case :project_with_gcov_fail
|
|
39
39
|
test_case :project_with_gcov_fail_under_decision
|
|
40
|
+
test_case :project_with_gcov_fail_under_line_rejects_zero
|
|
40
41
|
test_case :gcov_console_report_with_system_header
|
|
41
42
|
test_case :gcov_console_report_with_partial
|
|
42
43
|
# TODO: Restore these tests when the :abort_on_uncovered option is restored in the Gcov plugin
|
|
@@ -98,6 +98,24 @@ module GcovCommonTestCases
|
|
|
98
98
|
end
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
+
# :fail_under_* documents a 1-100 range; 0 was previously silently accepted as a
|
|
102
|
+
# threshold that could never actually fail (0% coverage is always >= 0).
|
|
103
|
+
def project_with_gcov_fail_under_line_rejects_zero
|
|
104
|
+
@c.with_context do
|
|
105
|
+
Dir.chdir @proj_name do
|
|
106
|
+
prep_project_yml_for_coverage
|
|
107
|
+
@c.merge_project_yml_for_test( { :gcov => { :gcovr => { :fail_under_line => 0 } } } )
|
|
108
|
+
FileUtils.cp test_asset_path("example_file.h"), 'src/'
|
|
109
|
+
FileUtils.cp test_asset_path("example_file.c"), 'src/'
|
|
110
|
+
FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/'
|
|
111
|
+
|
|
112
|
+
output = @c.ceedling_build_exec("gcov:all")
|
|
113
|
+
expect(@c.last_exit_status).to_not eq(0)
|
|
114
|
+
expect(output).to match(/:fail_under_line.*must be an integer percentage/)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
101
119
|
# TODO: Restore this test when the :abort_on_uncovered option is restored in the Gcov plugin
|
|
102
120
|
# def project_with_gcov_fail_because_of_uncovered_files
|
|
103
121
|
# @c.with_context do
|
|
@@ -52,6 +52,22 @@ describe CExtractorDeclarations do
|
|
|
52
52
|
expect(rest).to eq("")
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
+
# #1266 follow-on: same unanchored-substring risk class as #1262/#1266. The type-side
|
|
56
|
+
# identifier "counter_t" itself contains "count" as a substring; a plain rindex(name)
|
|
57
|
+
# would still find the real trailing occurrence here (rindex searches right-to-left),
|
|
58
|
+
# but this pins down that the \b-anchored match extract_type now uses doesn't regress
|
|
59
|
+
# this ordinary case, where the type name happens to share a substring with the
|
|
60
|
+
# declared name.
|
|
61
|
+
it "extracts a variable whose type name contains the variable's own name as a substring" do
|
|
62
|
+
content = "counter_t count;"
|
|
63
|
+
success, variable, pos, rest = extract_variable.call(content)
|
|
64
|
+
|
|
65
|
+
expect(success).to be true
|
|
66
|
+
check_single(variable, name: 'count', type: 'counter_t', text: 'counter_t count;')
|
|
67
|
+
expect(pos).to eq(content.length)
|
|
68
|
+
expect(rest).to eq("")
|
|
69
|
+
end
|
|
70
|
+
|
|
55
71
|
it "extracts simple char variable" do
|
|
56
72
|
content = "char c;"
|
|
57
73
|
success, variable, pos, rest = extract_variable.call(content)
|
|
@@ -565,6 +565,59 @@ describe CExtractor do
|
|
|
565
565
|
expect( contents.variable_declarations.length ).to eq 0
|
|
566
566
|
end
|
|
567
567
|
|
|
568
|
+
# #1262: Partials-generated headers occasionally concatenated two adjacent
|
|
569
|
+
# #define lines onto one physical line. Reconstructing the joined text
|
|
570
|
+
# from element_sequence (as generator_partials.rb does) rules the plain,
|
|
571
|
+
# comment-free case in or out formally, rather than by inspection of
|
|
572
|
+
# #_collect_directive alone. This shape -- three plain register-offset
|
|
573
|
+
# macros in a row, no comments -- is expected to reconstruct cleanly.
|
|
574
|
+
it "reconstructs three adjacent plain #define macros joined by newlines with no text lost between them" do
|
|
575
|
+
file_contents = <<~'CONTENTS'
|
|
576
|
+
#define START_ADDRESS 0x00
|
|
577
|
+
#define IDX_DATARATE (ADS124S08_REG_ADDR_DATARATE - START_ADDRESS)
|
|
578
|
+
#define IDX_REF (ADS124S08_REG_ADDR_REF - START_ADDRESS)
|
|
579
|
+
CONTENTS
|
|
580
|
+
|
|
581
|
+
contents = extract_from.call(file_contents)
|
|
582
|
+
|
|
583
|
+
expect( contents.macro_definitions.length ).to eq 3
|
|
584
|
+
joined = contents.element_sequence.map(&:text).join("\n")
|
|
585
|
+
expect( joined ).to eq(
|
|
586
|
+
"#define START_ADDRESS 0x00\n" +
|
|
587
|
+
"#define IDX_DATARATE (ADS124S08_REG_ADDR_DATARATE - START_ADDRESS)\n" +
|
|
588
|
+
"#define IDX_REF (ADS124S08_REG_ADDR_REF - START_ADDRESS)"
|
|
589
|
+
)
|
|
590
|
+
end
|
|
591
|
+
|
|
592
|
+
# #1262's actual reported trigger: a trailing // comment on one #define containing
|
|
593
|
+
# an ordinary English contraction. The stray apostrophe used to be scanned as the
|
|
594
|
+
# start of a char literal, sending the directive scanner hunting for a closing
|
|
595
|
+
# match straight through this macro's own newline and into the next two #defines
|
|
596
|
+
# -- merging all three into one macro_definitions entry with no separating newline,
|
|
597
|
+
# which is exactly what later caused Partials-generated headers to concatenate two
|
|
598
|
+
# #define lines onto one physical line ("stray '#' in program").
|
|
599
|
+
it "reconstructs three adjacent #define macros when one has a trailing comment with an apostrophe" do
|
|
600
|
+
file_contents = <<~'CONTENTS'
|
|
601
|
+
#define START_ADDRESS 0x00 // don't change this
|
|
602
|
+
#define IDX_DATARATE (ADS124S08_REG_ADDR_DATARATE - START_ADDRESS)
|
|
603
|
+
#define IDX_REF (ADS124S08_REG_ADDR_REF - START_ADDRESS)
|
|
604
|
+
CONTENTS
|
|
605
|
+
|
|
606
|
+
contents = extract_from.call(file_contents)
|
|
607
|
+
|
|
608
|
+
expect( contents.macro_definitions.length ).to eq 3
|
|
609
|
+
expect( contents.macro_definitions[0].text ).to eq "#define START_ADDRESS 0x00 // don't change this"
|
|
610
|
+
expect( contents.macro_definitions[1].text ).to eq "#define IDX_DATARATE (ADS124S08_REG_ADDR_DATARATE - START_ADDRESS)"
|
|
611
|
+
expect( contents.macro_definitions[2].text ).to eq "#define IDX_REF (ADS124S08_REG_ADDR_REF - START_ADDRESS)"
|
|
612
|
+
|
|
613
|
+
joined = contents.element_sequence.map(&:text).join("\n")
|
|
614
|
+
expect( joined ).to eq(
|
|
615
|
+
"#define START_ADDRESS 0x00 // don't change this\n" +
|
|
616
|
+
"#define IDX_DATARATE (ADS124S08_REG_ADDR_DATARATE - START_ADDRESS)\n" +
|
|
617
|
+
"#define IDX_REF (ADS124S08_REG_ADDR_REF - START_ADDRESS)"
|
|
618
|
+
)
|
|
619
|
+
end
|
|
620
|
+
|
|
568
621
|
it "should extract a function definition following a #define with an escaped character in a string literal (GH #1184)" do
|
|
569
622
|
file_contents = <<~'CONTENTS'
|
|
570
623
|
#include "world.h"
|
|
@@ -336,6 +336,51 @@ describe CExtractorPreprocessing do
|
|
|
336
336
|
expect(pos).to eq "#define FOO 1\n".length
|
|
337
337
|
end
|
|
338
338
|
|
|
339
|
+
# --- Regression: GH #1262 — a trailing // comment's apostrophe/quote merges
|
|
340
|
+
# the next directive into this one ---
|
|
341
|
+
# A '\'' or '"' inside an ordinary // comment (e.g. an English contraction
|
|
342
|
+
# like "don't") is not a string/char literal delimiter. Scanned as one
|
|
343
|
+
# anyway, it sends skip_c_string hunting for a closing match straight through
|
|
344
|
+
# this directive's own newline and into whatever follows, silently merging
|
|
345
|
+
# the next #define's text (and losing the newline between them) into this one.
|
|
346
|
+
|
|
347
|
+
it "extracts a #define with a trailing // comment containing an apostrophe" do
|
|
348
|
+
input = "#define START_ADDRESS 0x00 // don't change this\n"
|
|
349
|
+
result, pos = try_directive(input)
|
|
350
|
+
expect(result).to eq [true, input.rstrip]
|
|
351
|
+
expect(pos).to eq input.length
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
it "does not consume a following #define after a // comment containing an apostrophe" do
|
|
355
|
+
input = "#define START_ADDRESS 0x00 // don't change this\n#define IDX 1\n"
|
|
356
|
+
result, pos = try_directive(input)
|
|
357
|
+
expect(result).to eq [true, "#define START_ADDRESS 0x00 // don't change this"]
|
|
358
|
+
expect(pos).to eq "#define START_ADDRESS 0x00 // don't change this\n".length
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
it "does not consume a following #define after a // comment containing a double quote" do
|
|
362
|
+
input = %(#define LABEL "unbalanced) + %( quote in a comment: "\n#define IDX 1\n)
|
|
363
|
+
result, pos = try_directive(input)
|
|
364
|
+
expect(result[0]).to eq true
|
|
365
|
+
expect(result[1]).to start_with('#define LABEL')
|
|
366
|
+
expect(pos).to be < input.length
|
|
367
|
+
expect(input[pos..]).to eq "#define IDX 1\n"
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
it "extracts a #define with a trailing /* */ comment containing an apostrophe" do
|
|
371
|
+
input = "#define START_ADDRESS 0x00 /* don't change this */\n"
|
|
372
|
+
result, pos = try_directive(input)
|
|
373
|
+
expect(result).to eq [true, input.rstrip]
|
|
374
|
+
expect(pos).to eq input.length
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
it "carries a directive across physical lines when a // comment's own line ends in a backslash" do
|
|
378
|
+
input = "#define MACRO(x) \\\n // comment continues \\\n do_thing(x);\n"
|
|
379
|
+
result, pos = try_directive(input)
|
|
380
|
+
expect(result).to eq [true, input.rstrip]
|
|
381
|
+
expect(pos).to eq input.length
|
|
382
|
+
end
|
|
383
|
+
|
|
339
384
|
it "leaves scanner position unchanged on failure" do
|
|
340
385
|
scanner = StringScanner.new("int x;")
|
|
341
386
|
scanner.pos = 0
|
|
@@ -397,6 +397,56 @@ describe GeneratorPartials do
|
|
|
397
397
|
|
|
398
398
|
expect( buf.string ).to_not include("#define FOO 1")
|
|
399
399
|
end
|
|
400
|
+
|
|
401
|
+
# #1262: two consecutive typedefs (no macro carry-forward involved at
|
|
402
|
+
# all) had no byte-exact adjacency coverage -- only the macro-before-
|
|
403
|
+
# typedef pending_macros path did.
|
|
404
|
+
it "emits two consecutive typedefs each on their own line" do
|
|
405
|
+
output_path = '/path/to/output'
|
|
406
|
+
name = 'my_module'
|
|
407
|
+
header_filename = 'my_module_types.h'
|
|
408
|
+
|
|
409
|
+
allow(@file_path_utils).to receive(:form_partial_types_header_filename).and_return(header_filename)
|
|
410
|
+
|
|
411
|
+
buf = StringIO.new()
|
|
412
|
+
allow(@file_wrapper).to receive(:open).and_yield(buf)
|
|
413
|
+
|
|
414
|
+
typedef_a = CExtractorTypes::CStatement.new(text: "typedef uint8_t Byte;", line_num: 1)
|
|
415
|
+
typedef_b = CExtractorTypes::CStatement.new(text: "typedef uint16_t Word;", line_num: 2)
|
|
416
|
+
|
|
417
|
+
c_module = CExtractorTypes::CModule.new(
|
|
418
|
+
type_definitions: [typedef_a, typedef_b],
|
|
419
|
+
element_sequence: [typedef_a, typedef_b]
|
|
420
|
+
)
|
|
421
|
+
|
|
422
|
+
@generator.generate_types(name: name, c_module: c_module, output_path: output_path)
|
|
423
|
+
|
|
424
|
+
expect( buf.string ).to include( "typedef uint8_t Byte;\ntypedef uint16_t Word;\n" )
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
# #1262: same gap for two consecutive aggregate definitions.
|
|
428
|
+
it "emits two consecutive aggregate definitions each on their own line" do
|
|
429
|
+
output_path = '/path/to/output'
|
|
430
|
+
name = 'my_module'
|
|
431
|
+
header_filename = 'my_module_types.h'
|
|
432
|
+
|
|
433
|
+
allow(@file_path_utils).to receive(:form_partial_types_header_filename).and_return(header_filename)
|
|
434
|
+
|
|
435
|
+
buf = StringIO.new()
|
|
436
|
+
allow(@file_wrapper).to receive(:open).and_yield(buf)
|
|
437
|
+
|
|
438
|
+
aggregate_a = CExtractorTypes::CStatement.new(text: "struct Point { int x; int y; };", line_num: 1)
|
|
439
|
+
aggregate_b = CExtractorTypes::CStatement.new(text: "struct Color { int r; int g; int b; };", line_num: 2)
|
|
440
|
+
|
|
441
|
+
c_module = CExtractorTypes::CModule.new(
|
|
442
|
+
aggregate_definitions: [aggregate_a, aggregate_b],
|
|
443
|
+
element_sequence: [aggregate_a, aggregate_b]
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
@generator.generate_types(name: name, c_module: c_module, output_path: output_path)
|
|
447
|
+
|
|
448
|
+
expect( buf.string ).to include( "struct Point { int x; int y; };\nstruct Color { int r; int g; int b; };\n" )
|
|
449
|
+
end
|
|
400
450
|
end
|
|
401
451
|
|
|
402
452
|
context "#generate_header (private method)" do
|
|
@@ -555,6 +605,54 @@ describe GeneratorPartials do
|
|
|
555
605
|
expect( buf.string.strip() ).to eq file_contents.strip()
|
|
556
606
|
end
|
|
557
607
|
|
|
608
|
+
# #1262: a header with several single-line macros in a row and nothing
|
|
609
|
+
# type-defining after them (so generate_types never runs at all, per its
|
|
610
|
+
# own empty-module guard) is the ordinary, ungoverned case -- every macro
|
|
611
|
+
# here goes through this inline CStatement branch, not generate_types'
|
|
612
|
+
# own carry-forward logic, which already had its own adjacency coverage.
|
|
613
|
+
it "emits three consecutive macros each on their own line, with nothing following them" do
|
|
614
|
+
file_contents = <<~CONTENTS
|
|
615
|
+
#ifndef __CEEDLING_GENERATED_REGS_H__
|
|
616
|
+
#define __CEEDLING_GENERATED_REGS_H__
|
|
617
|
+
|
|
618
|
+
#define START_ADDRESS 0x00
|
|
619
|
+
#define IDX_DATARATE (ADS124S08_REG_ADDR_DATARATE - START_ADDRESS)
|
|
620
|
+
#define IDX_REF (ADS124S08_REG_ADDR_REF - START_ADDRESS)
|
|
621
|
+
|
|
622
|
+
#endif // __CEEDLING_GENERATED_REGS_H__
|
|
623
|
+
|
|
624
|
+
CONTENTS
|
|
625
|
+
|
|
626
|
+
c_module = make_module(
|
|
627
|
+
make_stmt(text: "#define START_ADDRESS 0x00", line_num: 1),
|
|
628
|
+
make_stmt(text: "#define IDX_DATARATE (ADS124S08_REG_ADDR_DATARATE - START_ADDRESS)", line_num: 2),
|
|
629
|
+
make_stmt(text: "#define IDX_REF (ADS124S08_REG_ADDR_REF - START_ADDRESS)", line_num: 3)
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
@generator.send(:generate_header, buf, 'regs', [], [], c_module, false)
|
|
633
|
+
expect( buf.string.strip() ).to eq file_contents.strip()
|
|
634
|
+
end
|
|
635
|
+
|
|
636
|
+
# #1262 regression: the actual reported shape -- one macro's trailing // comment
|
|
637
|
+
# includes an apostrophe. Extraction is what previously merged the macros (see
|
|
638
|
+
# c_extractor specs); this confirms generate_header still emits each item's text,
|
|
639
|
+
# comment included, on its own line once extraction is correct.
|
|
640
|
+
it "emits each macro on its own line even when one has a trailing comment with an apostrophe (GH #1262)" do
|
|
641
|
+
c_module = make_module(
|
|
642
|
+
make_stmt(text: "#define START_ADDRESS 0x00 // don't change this", line_num: 1),
|
|
643
|
+
make_stmt(text: "#define IDX_DATARATE (ADS124S08_REG_ADDR_DATARATE - START_ADDRESS)", line_num: 2),
|
|
644
|
+
make_stmt(text: "#define IDX_REF (ADS124S08_REG_ADDR_REF - START_ADDRESS)", line_num: 3)
|
|
645
|
+
)
|
|
646
|
+
|
|
647
|
+
@generator.send(:generate_header, buf, 'regs', [], [], c_module, false)
|
|
648
|
+
|
|
649
|
+
expect( buf.string ).to include(
|
|
650
|
+
"#define START_ADDRESS 0x00 // don't change this\n" +
|
|
651
|
+
"#define IDX_DATARATE (ADS124S08_REG_ADDR_DATARATE - START_ADDRESS)\n" +
|
|
652
|
+
"#define IDX_REF (ADS124S08_REG_ADDR_REF - START_ADDRESS)\n"
|
|
653
|
+
)
|
|
654
|
+
end
|
|
655
|
+
|
|
558
656
|
it "should emit macro and variable statements inline while routing typedefs and aggregates to the shared types header" do
|
|
559
657
|
# One item of each category that can appear in a generated Partial header:
|
|
560
658
|
# macro_definitions → CStatement emitted as-is, inline
|
|
@@ -379,6 +379,32 @@ describe GeneratorTestResultsBacktrace do
|
|
|
379
379
|
expect(crash_line).not_to include('failed to extract')
|
|
380
380
|
end
|
|
381
381
|
|
|
382
|
+
# #1266 follow-on: the same unanchored-substring risk class as #1262/#1266, here in the
|
|
383
|
+
# "prefer whichever unresolved member's own symbol is named in the backtrace" search --
|
|
384
|
+
# no \b before the escaped symbol, so a shorter unresolved symbol that's a suffix of a
|
|
385
|
+
# longer one actually named in the crash frame can false-match and steal the attribution.
|
|
386
|
+
it 'attributes a crash to the member whose symbol actually appears in the backtrace, not a shorter symbol that is merely its suffix' do
|
|
387
|
+
test_cases_suffix = [
|
|
388
|
+
{ test: 'test_x(1)', symbol: 'foo', line_number: 10 },
|
|
389
|
+
{ test: 'test_x(2)', symbol: 'my_foo', line_number: 20 }
|
|
390
|
+
]
|
|
391
|
+
filename_suffix = 'test_module.c'
|
|
392
|
+
|
|
393
|
+
crash_output = <<~GDB
|
|
394
|
+
Program received signal SIGSEGV, Segmentation fault.
|
|
395
|
+
0x00005618066ea1fb in my_foo () at test/test_module.c:42
|
|
396
|
+
#0 0x00005618066ea1fb in my_foo () at test/test_module.c:42
|
|
397
|
+
GDB
|
|
398
|
+
|
|
399
|
+
allow(@tool_executor).to receive(:exec)
|
|
400
|
+
.and_return({ output: crash_output, time: 0.5, exit_code: 139, stderr: '', status: @ok_status })
|
|
401
|
+
|
|
402
|
+
expect(@file_wrapper).to receive(:write)
|
|
403
|
+
.with('/build/logs/test/test_module/test_x(2).gdb.log', /=== test_x\(2\) ===/, 'a')
|
|
404
|
+
|
|
405
|
+
@backtrace.do_gdb( filename_suffix, executable, shell_result, test_cases_suffix, context: :test )
|
|
406
|
+
end
|
|
407
|
+
|
|
382
408
|
it 'handles a SIGABRT crash from assert() — shows assertion text, no source line' do
|
|
383
409
|
test_cases_assert = [{ test: 'test_asserting', symbol: 'test_asserting', line_number: 8 }]
|
|
384
410
|
|
|
@@ -86,6 +86,29 @@ describe GeneratorTestRunner do
|
|
|
86
86
|
|
|
87
87
|
expect( test_cases.first[:line_number] ).to eq( 5 )
|
|
88
88
|
end
|
|
89
|
+
|
|
90
|
+
# #1266 follow-on: the same unanchored-substring risk class as #1262/#1266. A static
|
|
91
|
+
# helper function between two test cases whose name merely contains a later test's
|
|
92
|
+
# name as a substring (here "test_ab" inside "reset_test_ab_state") must not steal
|
|
93
|
+
# that test's line number, and an unescaped test name must not be treated as a regex.
|
|
94
|
+
it 'does not false-match a test name against an unrelated line that merely contains it as a substring' do
|
|
95
|
+
source = <<~SOURCE
|
|
96
|
+
void test_a(void) {}
|
|
97
|
+
void reset_test_ab_state(void) {}
|
|
98
|
+
void test_ab(void) {}
|
|
99
|
+
SOURCE
|
|
100
|
+
|
|
101
|
+
runner = build_runner( test_file_contents: source, preprocessed_file_contents: source )
|
|
102
|
+
test_cases = [
|
|
103
|
+
{ test: 'test_a', line_number: 0 },
|
|
104
|
+
{ test: 'test_ab', line_number: 0 }
|
|
105
|
+
]
|
|
106
|
+
|
|
107
|
+
runner.send( :remap_line_numbers!, test_cases, source )
|
|
108
|
+
|
|
109
|
+
expect( test_cases[0][:line_number] ).to eq( 1 )
|
|
110
|
+
expect( test_cases[1][:line_number] ).to eq( 3 )
|
|
111
|
+
end
|
|
89
112
|
end
|
|
90
113
|
|
|
91
114
|
describe '#initialize / #test_cases' do
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# =========================================================================
|
|
2
|
+
# Ceedling - Test-Centered Build System for C
|
|
3
|
+
# ThrowTheSwitch.org
|
|
4
|
+
# Copyright (c) 2010-26 Mike Karlesky, Mark VanderVoord, & Greg Williams
|
|
5
|
+
# SPDX-License-Identifier: MIT
|
|
6
|
+
# =========================================================================
|
|
7
|
+
|
|
8
|
+
require 'spec_helper'
|
|
9
|
+
require 'ceedling/preprocess/preprocessinator_line_marker_includes_extractor'
|
|
10
|
+
require 'ceedling/includes/includes'
|
|
11
|
+
|
|
12
|
+
# Regression coverage for GH #1268 only -- this class otherwise has no unit spec on this
|
|
13
|
+
# branch. Built on #extract_includes_from_string, a StringIO wrapper around the same
|
|
14
|
+
# private #extract_includes every production call eventually reaches.
|
|
15
|
+
describe PreprocessinatorLineMarkerIncludesExtractor do
|
|
16
|
+
before(:each) do
|
|
17
|
+
@include_factory = double('include_factory')
|
|
18
|
+
|
|
19
|
+
allow(@include_factory).to receive(:user_include_from_filepath) do |filepath|
|
|
20
|
+
UserInclude.new(filepath)
|
|
21
|
+
end
|
|
22
|
+
allow(@include_factory).to receive(:system_include_from_filepath) do |filepath|
|
|
23
|
+
SystemInclude.new(filepath)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
@extractor = described_class.new(
|
|
27
|
+
:include_factory => @include_factory
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def paths_of(includes)
|
|
32
|
+
includes.map(&:filepath)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe '#extract_includes_from_string' do
|
|
36
|
+
# #1268: GCC's -fdirectives-only output preserves the ORIGINAL indentation of a
|
|
37
|
+
# top-level #include when it replaces that directive with a line marker entering
|
|
38
|
+
# the included file -- an indented ` #include "widget.h"` produces an indented
|
|
39
|
+
# ` # 1 "widget.h" 1`, not the flush-left marker every other marker GCC
|
|
40
|
+
# generates uses. LINE_MARKER_REGEX must recognize that marker too, or the include
|
|
41
|
+
# is silently missing from the extracted list entirely.
|
|
42
|
+
it 'recognizes a line marker that is itself indented (e.g. from an indented #include)' do
|
|
43
|
+
content = <<~OUTPUT
|
|
44
|
+
# 1 "test.c"
|
|
45
|
+
# 1 "widget.h" 1
|
|
46
|
+
OUTPUT
|
|
47
|
+
|
|
48
|
+
includes = @extractor.extract_includes_from_string( content, 'test.c', described_class::USER )
|
|
49
|
+
|
|
50
|
+
expect( paths_of(includes) ).to eq( ['widget.h'] )
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'still recognizes an ordinary, flush-left line marker' do
|
|
54
|
+
content = <<~OUTPUT
|
|
55
|
+
# 1 "test.c"
|
|
56
|
+
# 1 "widget.h" 1
|
|
57
|
+
OUTPUT
|
|
58
|
+
|
|
59
|
+
includes = @extractor.extract_includes_from_string( content, 'test.c', described_class::USER )
|
|
60
|
+
|
|
61
|
+
expect( paths_of(includes) ).to eq( ['widget.h'] )
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -211,6 +211,41 @@ describe PreprocessinatorReconstructor do
|
|
|
211
211
|
expect( @extractor.extract_file_as_array_from_expansion(input, filepath) ).to eq expected
|
|
212
212
|
end
|
|
213
213
|
|
|
214
|
+
# #1268: GCC's -fdirectives-only output preserves the ORIGINAL indentation of a
|
|
215
|
+
# top-level #include when it replaces that directive with a line marker entering
|
|
216
|
+
# the included file -- e.g. a source line " #include \"Module3.h\"" produces
|
|
217
|
+
# " # 1 \"Module3.h\" 1", not the flush-left "# 1 ..." this method's marker
|
|
218
|
+
# regexes assumed every marker would be. An unrecognized entering-marker leaves
|
|
219
|
+
# `extract` stuck at whatever it already was instead of turning off, so the
|
|
220
|
+
# marker line itself (and everything up to the NEXT recognized marker, e.g. one
|
|
221
|
+
# from a nested system include triggered from inside the ignored file) leaks into
|
|
222
|
+
# the extracted output before the ping-pong finally self-corrects.
|
|
223
|
+
it "does not leak file content when the marker entering an included file is itself indented" do
|
|
224
|
+
filepath = "dir/our_file.c"
|
|
225
|
+
|
|
226
|
+
file_contents = [
|
|
227
|
+
'# 1 "dir/our_file.c"',
|
|
228
|
+
'void wanted_before(void);',
|
|
229
|
+
' # 1 "dir/included.h" 1', # indented entering-marker (leading whitespace from source)
|
|
230
|
+
'int leaked_from_included_h;',
|
|
231
|
+
'# 1 "/usr/include/nested.h" 1 3 4', # a normal, flush-left marker from within included.h
|
|
232
|
+
'int also_should_not_leak;',
|
|
233
|
+
'# 2 "dir/included.h" 2', # returning from nested.h back into included.h (still not our file)
|
|
234
|
+
'int still_should_not_leak;',
|
|
235
|
+
'# 3 "dir/our_file.c" 2', # returning to our file
|
|
236
|
+
'void wanted_after(void);',
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
expected = [
|
|
240
|
+
'void wanted_before(void);',
|
|
241
|
+
'void wanted_after(void);',
|
|
242
|
+
]
|
|
243
|
+
|
|
244
|
+
input = StringIO.new( file_contents.join( "\n" ) )
|
|
245
|
+
|
|
246
|
+
expect( @extractor.extract_file_as_array_from_expansion( input, filepath ) ).to eq expected
|
|
247
|
+
end
|
|
248
|
+
|
|
214
249
|
it "should extract text of original file from preprocessed expansion ignoring embedded expansions having similar names" do
|
|
215
250
|
filepath = "dir1/dir2/our_file.c"
|
|
216
251
|
|
|
@@ -798,6 +833,45 @@ describe PreprocessinatorReconstructor do
|
|
|
798
833
|
expect( @extractor.extract_macro_defs( file_text, '_INCLUDE_GUARD_' ) ).to eq expected
|
|
799
834
|
end
|
|
800
835
|
|
|
836
|
+
# #1266: a plain substring test against the include guard rejected any macro whose
|
|
837
|
+
# name OR value merely contained the guard string, not only the guard macro itself.
|
|
838
|
+
# RTC_HOUR_SECONDS' own name contains the guard "RTC_H", and RTC_DAY_SECONDS'
|
|
839
|
+
# *value* references RTC_HOUR_SECONDS, so its captured text also contains "RTC_H" --
|
|
840
|
+
# both were silently dropped from the reconstructed header, breaking compilation.
|
|
841
|
+
it "does not reject an ordinary macro whose name or value merely contains the include guard as a substring (GH #1266)" do
|
|
842
|
+
file_text = <<~FILE_TEXT
|
|
843
|
+
#ifndef RTC_H
|
|
844
|
+
#define RTC_H
|
|
845
|
+
|
|
846
|
+
#define RTC_MINUTE_SECONDS 60u
|
|
847
|
+
#define RTC_HOUR_SECONDS (60u * RTC_MINUTE_SECONDS)
|
|
848
|
+
#define RTC_DAY_SECONDS (24u * RTC_HOUR_SECONDS)
|
|
849
|
+
|
|
850
|
+
#endif
|
|
851
|
+
FILE_TEXT
|
|
852
|
+
|
|
853
|
+
expected = [
|
|
854
|
+
"#define RTC_MINUTE_SECONDS 60u",
|
|
855
|
+
"#define RTC_HOUR_SECONDS (60u * RTC_MINUTE_SECONDS)",
|
|
856
|
+
"#define RTC_DAY_SECONDS (24u * RTC_HOUR_SECONDS)"
|
|
857
|
+
]
|
|
858
|
+
|
|
859
|
+
expect( @extractor.extract_macro_defs( file_text, 'RTC_H' ) ).to eq expected
|
|
860
|
+
end
|
|
861
|
+
|
|
862
|
+
it "still rejects the include guard when it appears with trailing whitespace instead of a value" do
|
|
863
|
+
file_text = <<~FILE_TEXT
|
|
864
|
+
#ifndef RTC_H
|
|
865
|
+
#define RTC_H
|
|
866
|
+
|
|
867
|
+
#define RTC_MINUTE_SECONDS 60u
|
|
868
|
+
FILE_TEXT
|
|
869
|
+
|
|
870
|
+
expected = [ "#define RTC_MINUTE_SECONDS 60u" ]
|
|
871
|
+
|
|
872
|
+
expect( @extractor.extract_macro_defs( file_text, 'RTC_H' ) ).to eq expected
|
|
873
|
+
end
|
|
874
|
+
|
|
801
875
|
end
|
|
802
876
|
|
|
803
877
|
context "#compact_file_from_expansion" do
|
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.8
|
|
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-09-
|
|
13
|
+
date: 2026-09-10 00:00:00.000000000 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: rake
|
|
@@ -1004,6 +1004,7 @@ files:
|
|
|
1004
1004
|
- spec/units/preprocess/preprocessinator_comment_stripper_spec.rb
|
|
1005
1005
|
- spec/units/preprocess/preprocessinator_file_assembler_spec.rb
|
|
1006
1006
|
- spec/units/preprocess/preprocessinator_includes_handler_spec.rb
|
|
1007
|
+
- spec/units/preprocess/preprocessinator_line_marker_includes_extractor_spec.rb
|
|
1007
1008
|
- spec/units/preprocess/preprocessinator_reconstructor_spec.rb
|
|
1008
1009
|
- spec/units/preprocess/preprocessinator_spec.rb
|
|
1009
1010
|
- spec/units/reportinator_spec.rb
|
|
@@ -1856,6 +1857,7 @@ test_files:
|
|
|
1856
1857
|
- spec/units/preprocess/preprocessinator_comment_stripper_spec.rb
|
|
1857
1858
|
- spec/units/preprocess/preprocessinator_file_assembler_spec.rb
|
|
1858
1859
|
- spec/units/preprocess/preprocessinator_includes_handler_spec.rb
|
|
1860
|
+
- spec/units/preprocess/preprocessinator_line_marker_includes_extractor_spec.rb
|
|
1859
1861
|
- spec/units/preprocess/preprocessinator_reconstructor_spec.rb
|
|
1860
1862
|
- spec/units/preprocess/preprocessinator_spec.rb
|
|
1861
1863
|
- spec/units/reportinator_spec.rb
|