ceedling 1.1.6 → 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/Gemfile +4 -0
- data/Gemfile.lock +2 -0
- data/README.md +4 -1
- data/docs/Changelog.md +41 -0
- data/docs/KnownIssues.md +3 -0
- data/docs/mkdocs/configuration/project-file.md +25 -7
- data/docs/mkdocs/configuration/reference/cexception.md +1 -1
- data/docs/mkdocs/configuration/reference/cmock.md +1 -1
- data/docs/mkdocs/configuration/reference/unity.md +1 -1
- data/docs/mkdocs/getting-started/quick-start.md +11 -8
- data/docs/mkdocs/plugins/gcov/gcovr.md +329 -189
- data/docs/mkdocs/plugins/gcov/reportgenerator.md +113 -31
- data/docs/mkdocs/reference/gcov-plugin.md +29 -6
- data/docs/mkdocs/snapshot/plugins/gcov/config/defaults.yml +0 -1
- 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 +21 -11
- data/lib/ceedling/generators/generator_test_runner.rb +5 -1
- data/lib/ceedling/plugins/plugin_reportinator.rb +9 -0
- data/lib/ceedling/plugins/report_tests_stdout_plugin.rb +1 -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/config/defaults.yml +0 -1
- data/plugins/gcov/lib/console_reportinator.rb +7 -0
- data/plugins/gcov/lib/gcov.rb +64 -26
- data/plugins/gcov/lib/gcovr_reportinator.rb +91 -47
- data/plugins/gcov/lib/reportgenerator_reportinator.rb +10 -6
- data/plugins/valgrind/lib/valgrind.rb +1 -1
- data/site-local/configuration/project-file.html +78 -8
- data/site-local/configuration/reference/cexception.html +5 -5
- data/site-local/configuration/reference/cmock.html +5 -5
- data/site-local/configuration/reference/unity.html +5 -5
- data/site-local/getting-started/quick-start.html +13 -9
- data/site-local/plugins/gcov/gcovr.html +1220 -255
- data/site-local/plugins/gcov/reportgenerator.html +404 -46
- data/site-local/reference/gcov-plugin.html +48 -10
- data/site-local/sitemap.xml.gz +0 -0
- data/site-local/snapshot/plugins/gcov/config/defaults.yml +0 -1
- data/spec/support/system/system_context.rb +7 -3
- 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 +3 -0
- data/spec/system/support/common_test_cases.rb +42 -0
- data/spec/system/support/gcov_common_test_cases.rb +70 -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 +77 -0
- data/spec/units/generators/generator_test_runner_spec.rb +23 -0
- data/spec/units/plugin_reportinator_spec.rb +38 -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 +6 -2
|
@@ -235,8 +235,11 @@ class SystemContext
|
|
|
235
235
|
# encoding regardless of their actual content, so any project source content the
|
|
236
236
|
# subprocess echoes (verbose logging, compiler output, etc.) can make later regex
|
|
237
237
|
# matching against this string raise. Sanitize once here, the same way Ceedling itself
|
|
238
|
-
# sanitizes file content it scans
|
|
239
|
-
|
|
238
|
+
# sanitizes file content it scans -- reused for both the raw output and the failure
|
|
239
|
+
# report below, since a real compiler's own diagnostic text (e.g. clang's curly-quoted
|
|
240
|
+
# identifiers) is exactly the kind of non-ASCII content this needs to survive too.
|
|
241
|
+
stdout, stderr = stdout.clean_encoding, stderr.clean_encoding
|
|
242
|
+
@raw_output = stdout + stderr
|
|
240
243
|
@console_summary = compose_failure_report(stdout, stderr)
|
|
241
244
|
|
|
242
245
|
SystemTestOutput.new(@raw_output)
|
|
@@ -250,7 +253,8 @@ class SystemContext
|
|
|
250
253
|
|
|
251
254
|
@last_cmd = cmd
|
|
252
255
|
@last_exit_status = status.exitstatus
|
|
253
|
-
|
|
256
|
+
stdout, stderr = stdout.clean_encoding, stderr.clean_encoding
|
|
257
|
+
@raw_output = stdout + stderr
|
|
254
258
|
@console_summary = compose_failure_report(stdout, stderr)
|
|
255
259
|
|
|
256
260
|
SystemTestOutput.new(@raw_output)
|
|
@@ -87,6 +87,8 @@ ceedling_system_tests do
|
|
|
87
87
|
|
|
88
88
|
describe "Verbosity and output" do
|
|
89
89
|
test_case :test_project_with_named_verbosity
|
|
90
|
+
test_case :test_project_with_warnings_verbosity_prints_overall_summary
|
|
91
|
+
test_case :test_project_with_errors_verbosity_prints_overall_summary_on_passing_run
|
|
90
92
|
test_case :test_project_with_numerical_verbosity
|
|
91
93
|
test_case :report_tests_raw_output_log_plugin
|
|
92
94
|
end
|
|
@@ -90,6 +90,8 @@ ceedling_system_tests do
|
|
|
90
90
|
|
|
91
91
|
describe "Verbosity and output" do
|
|
92
92
|
test_case :test_project_with_named_verbosity
|
|
93
|
+
test_case :test_project_with_warnings_verbosity_prints_overall_summary
|
|
94
|
+
test_case :test_project_with_errors_verbosity_prints_overall_summary_on_passing_run
|
|
93
95
|
test_case :test_project_with_numerical_verbosity
|
|
94
96
|
test_case :report_tests_raw_output_log_plugin
|
|
95
97
|
end
|
|
@@ -33,8 +33,11 @@ ceedling_system_tests do
|
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
test_case :project_with_gcov_enabled_test_all_does_not_require_gcovr
|
|
36
37
|
test_case :project_with_gcov_success
|
|
37
38
|
test_case :project_with_gcov_fail
|
|
39
|
+
test_case :project_with_gcov_fail_under_decision
|
|
40
|
+
test_case :project_with_gcov_fail_under_line_rejects_zero
|
|
38
41
|
test_case :gcov_console_report_with_system_header
|
|
39
42
|
test_case :gcov_console_report_with_partial
|
|
40
43
|
# TODO: Restore these tests when the :abort_on_uncovered option is restored in the Gcov plugin
|
|
@@ -201,6 +201,48 @@ module CommonSystemTestCases
|
|
|
201
201
|
end
|
|
202
202
|
end
|
|
203
203
|
|
|
204
|
+
# The Overall Test Summary must still print at :warnings verbosity
|
|
205
|
+
# (`--verbosity=warnings`, Verbosity::COMPLAIN) on an all-passing run, not just
|
|
206
|
+
# when verbosity is :normal or higher, or when a test fails.
|
|
207
|
+
def test_project_with_warnings_verbosity_prints_overall_summary
|
|
208
|
+
@c.with_context do
|
|
209
|
+
Dir.chdir @proj_name do
|
|
210
|
+
FileUtils.cp test_asset_path("example_file.h"), 'src/'
|
|
211
|
+
FileUtils.cp test_asset_path("example_file.c"), 'src/'
|
|
212
|
+
FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/'
|
|
213
|
+
|
|
214
|
+
output = @c.ceedling_build_exec("--verbosity=warnings")
|
|
215
|
+
expect(@c.last_exit_status).to eq(0)
|
|
216
|
+
expect(output).to match(/OVERALL TEST SUMMARY/)
|
|
217
|
+
expect(output).to match(/TESTED:\s+\d/)
|
|
218
|
+
expect(output).to match(/PASSED:\s+\d/)
|
|
219
|
+
expect(output).to match(/FAILED:\s+\d/)
|
|
220
|
+
expect(output).to match(/IGNORED:\s+\d/)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# ERRORS is the true floor for the Overall Test Summary -- a build's own
|
|
226
|
+
# test results are core information, visible even at the most restrictive
|
|
227
|
+
# verbosity short of :silent, whether the run passed or failed.
|
|
228
|
+
def test_project_with_errors_verbosity_prints_overall_summary_on_passing_run
|
|
229
|
+
@c.with_context do
|
|
230
|
+
Dir.chdir @proj_name do
|
|
231
|
+
FileUtils.cp test_asset_path("example_file.h"), 'src/'
|
|
232
|
+
FileUtils.cp test_asset_path("example_file.c"), 'src/'
|
|
233
|
+
FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/'
|
|
234
|
+
|
|
235
|
+
output = @c.ceedling_build_exec("--verbosity=errors")
|
|
236
|
+
expect(@c.last_exit_status).to eq(0)
|
|
237
|
+
expect(output).to match(/OVERALL TEST SUMMARY/)
|
|
238
|
+
expect(output).to match(/TESTED:\s+\d/)
|
|
239
|
+
expect(output).to match(/PASSED:\s+\d/)
|
|
240
|
+
expect(output).to match(/FAILED:\s+\d/)
|
|
241
|
+
expect(output).to match(/IGNORED:\s+\d/)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
204
246
|
def test_project_with_numerical_verbosity
|
|
205
247
|
@c.with_context do
|
|
206
248
|
Dir.chdir @proj_name do
|
|
@@ -10,6 +10,38 @@ require_relative 'gcov_helpers'
|
|
|
10
10
|
module GcovCommonTestCases
|
|
11
11
|
include GcovHelpers
|
|
12
12
|
|
|
13
|
+
# #1252 -- Enabling the gcov plugin must not make gcovr a hard dependency of every
|
|
14
|
+
# build. Deliberately does NOT call prep_project_yml_for_coverage (which conditionally
|
|
15
|
+
# strips :utilities ➡️ gcovr when the host lacks a real gcovr) -- the point here is
|
|
16
|
+
# gcovr enabled in config but genuinely unreachable, reproducing #1252 regardless of
|
|
17
|
+
# what's actually installed on the machine running this spec.
|
|
18
|
+
def project_with_gcov_enabled_test_all_does_not_require_gcovr
|
|
19
|
+
@c.with_context do
|
|
20
|
+
Dir.chdir @proj_name do
|
|
21
|
+
@c.uncomment_project_yml_option_for_test("- gcov")
|
|
22
|
+
# Default :gcov ↳ :utilities already includes gcovr and :reports already
|
|
23
|
+
# includes HtmlBasic -- exactly the enabled-but-unused shape #1252 reported.
|
|
24
|
+
@c.merge_project_yml_for_test(
|
|
25
|
+
{ :tools => { :gcov_gcovr_report => { :executable => 'nonexistent_gcovr_binary_for_1252_test' } } }
|
|
26
|
+
)
|
|
27
|
+
FileUtils.cp test_asset_path("example_file.h"), 'src/'
|
|
28
|
+
FileUtils.cp test_asset_path("example_file.c"), 'src/'
|
|
29
|
+
FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/'
|
|
30
|
+
|
|
31
|
+
# An ordinary test:all run must succeed -- it never needs gcovr at all.
|
|
32
|
+
output = @c.ceedling_build_exec("test:all")
|
|
33
|
+
expect(@c.last_exit_status).to eq(0)
|
|
34
|
+
expect(output).to_not match(/nonexistent_gcovr_binary_for_1252_test/)
|
|
35
|
+
|
|
36
|
+
# A real gcov: build still needs gcovr and must still fail loudly when it's missing --
|
|
37
|
+
# the fix defers gcovr's tool validation, it doesn't remove it.
|
|
38
|
+
output = @c.ceedling_build_exec("gcov:all")
|
|
39
|
+
expect(@c.last_exit_status).to_not eq(0)
|
|
40
|
+
expect(output).to match(/nonexistent_gcovr_binary_for_1252_test/)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
13
45
|
def project_with_gcov_success
|
|
14
46
|
@c.with_context do
|
|
15
47
|
Dir.chdir @proj_name do
|
|
@@ -46,6 +78,44 @@ module GcovCommonTestCases
|
|
|
46
78
|
end
|
|
47
79
|
end
|
|
48
80
|
|
|
81
|
+
# gcovr's own --fail-under-decision is a no-op without --decisions also present --
|
|
82
|
+
# Ceedling supplies --decisions automatically whenever :fail_under_decision is
|
|
83
|
+
# configured, so a passing run reports success instead of gcovr's own opaque
|
|
84
|
+
# "need also option --decision" CLI error.
|
|
85
|
+
def project_with_gcov_fail_under_decision
|
|
86
|
+
@c.with_context do
|
|
87
|
+
Dir.chdir @proj_name do
|
|
88
|
+
prep_project_yml_for_coverage
|
|
89
|
+
@c.merge_project_yml_for_test( { :gcov => { :gcovr => { :fail_under_decision => 1 } } } )
|
|
90
|
+
FileUtils.cp test_asset_path("example_file.h"), 'src/'
|
|
91
|
+
FileUtils.cp test_asset_path("example_file.c"), 'src/'
|
|
92
|
+
FileUtils.cp test_asset_path("test_example_file_success.c"), 'test/'
|
|
93
|
+
|
|
94
|
+
output = @c.ceedling_build_exec("gcov:all")
|
|
95
|
+
expect(@c.last_exit_status).to eq(0)
|
|
96
|
+
expect(output).to_not match(/need also option --decision/)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
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
|
+
|
|
49
119
|
# TODO: Restore this test when the :abort_on_uncovered option is restored in the Gcov plugin
|
|
50
120
|
# def project_with_gcov_fail_because_of_uncovered_files
|
|
51
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
|
|
@@ -130,6 +130,7 @@ SIMPLE_ASSERT_CRASH_OUTPUT =
|
|
|
130
130
|
SIMPLE_PASS_OUTPUT = "test_lib.c:3:test_empty:PASS\n---------\n1 Tests 0 Failures 0 Ignored\nOK\n"
|
|
131
131
|
SIMPLE_FAIL_OUTPUT = "test_lib.c:8:test_bad:FAIL: Expected 1 Was 2\n---------\n1 Tests 1 Failures 0 Ignored\nFAIL\n"
|
|
132
132
|
SIMPLE_IGNORE_OUTPUT = "test_lib.c:12:test_skip:IGNORE\n---------\n1 Tests 0 Failures 1 Ignored\nOK\n"
|
|
133
|
+
SIMPLE_IGNORE_WITH_MESSAGE_OUTPUT = "test_lib.c:12:test_skip:IGNORE: Not yet implemented\n---------\n1 Tests 0 Failures 1 Ignored\nOK\n"
|
|
133
134
|
|
|
134
135
|
|
|
135
136
|
describe GeneratorTestResultsBacktrace do
|
|
@@ -255,6 +256,31 @@ describe GeneratorTestResultsBacktrace do
|
|
|
255
256
|
expect(expected_output_lines).to include('test_lib.c:9:test_asserting:FAIL: Expected 1 Was 2')
|
|
256
257
|
end
|
|
257
258
|
|
|
259
|
+
# Ignored-crash false positive: a TEST_IGNORE_MESSAGE(...) test case carries a
|
|
260
|
+
# trailing message, same as FAIL. Before this fix, only FAIL tolerated one --
|
|
261
|
+
# a message-carrying IGNORE fell through to "unresolved" and was misreported
|
|
262
|
+
# as crash evidence (a second @file_wrapper.write, for a companion_case that
|
|
263
|
+
# never actually crashed).
|
|
264
|
+
it 'handles an IGNORE test case with a trailing message and does not write a log' do
|
|
265
|
+
allow(@tool_executor).to receive(:exec).and_return(
|
|
266
|
+
{ output: GDB_NO_SIGNAL_OUTPUT, time: 0.1, exit_code: 1, stderr: '', status: @ok_status },
|
|
267
|
+
{ output: "test_lib.c:9:test_asserting:IGNORE: Not yet implemented\n---------\n1 Tests 0 Failures 1 Ignored\nOK\n",
|
|
268
|
+
time: 0.1, exit_code: 0, stderr: '', status: @ok_status }
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
expect(@file_wrapper).to receive(:write).once.with(%r{test_crashes_elsewhere}, anything, anything)
|
|
272
|
+
|
|
273
|
+
expected_output_lines = []
|
|
274
|
+
allow(@generator_test_results).to receive(:regenerate_test_executable_stdout) do |**kwargs|
|
|
275
|
+
expected_output_lines = kwargs[:output]
|
|
276
|
+
'regenerated'
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
@backtrace.do_gdb( filename, executable, shell_result, [companion_case] + test_cases, context: :test )
|
|
280
|
+
|
|
281
|
+
expect(expected_output_lines).to include('test_lib.c:9:test_asserting:IGNORE: Not yet implemented')
|
|
282
|
+
end
|
|
283
|
+
|
|
258
284
|
it "does not trust a matched PASS line when the retry's own real status contradicts it" do
|
|
259
285
|
# Regression test for issue #1198: a diagnostic retry can print a clean Unity
|
|
260
286
|
# result line while its own real process outcome says otherwise.
|
|
@@ -353,6 +379,32 @@ describe GeneratorTestResultsBacktrace do
|
|
|
353
379
|
expect(crash_line).not_to include('failed to extract')
|
|
354
380
|
end
|
|
355
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
|
+
|
|
356
408
|
it 'handles a SIGABRT crash from assert() — shows assertion text, no source line' do
|
|
357
409
|
test_cases_assert = [{ test: 'test_asserting', symbol: 'test_asserting', line_number: 8 }]
|
|
358
410
|
|
|
@@ -471,6 +523,31 @@ describe GeneratorTestResultsBacktrace do
|
|
|
471
523
|
expect(expected_output_lines).to include('test_lib.c:12:test_skip:IGNORE')
|
|
472
524
|
end
|
|
473
525
|
|
|
526
|
+
# Ignored-crash false positive: a TEST_IGNORE_MESSAGE(...) test case carries a
|
|
527
|
+
# trailing message, same as FAIL (handled just above). Before this fix, only FAIL
|
|
528
|
+
# tolerated one -- a message-carrying IGNORE fell through to the "no result line"
|
|
529
|
+
# branch and was misreported as a second crashed test case (test_skip, alongside
|
|
530
|
+
# the real crash attributed to test_crashes_elsewhere).
|
|
531
|
+
it 'handles an IGNORE test case with a trailing message' do
|
|
532
|
+
test_cases_ignore = [{ test: 'test_crashes_elsewhere', line_number: 20 }, { test: 'test_skip', line_number: 12 }]
|
|
533
|
+
|
|
534
|
+
allow(@tool_executor).to receive(:exec).and_return(
|
|
535
|
+
{ output: SIMPLE_ASSERT_CRASH_OUTPUT, time: 0.1, exit_code: 134, stderr: '', status: @ok_status },
|
|
536
|
+
{ output: SIMPLE_IGNORE_WITH_MESSAGE_OUTPUT, time: 0.02, exit_code: 0, stderr: '', status: @ok_status }
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
expected_output_lines = []
|
|
540
|
+
allow(@generator_test_results).to receive(:regenerate_test_executable_stdout) do |**kwargs|
|
|
541
|
+
expected_output_lines = kwargs[:output]
|
|
542
|
+
'regenerated'
|
|
543
|
+
end
|
|
544
|
+
|
|
545
|
+
@backtrace.do_simple( filename, executable, shell_result, test_cases_ignore, context: :test )
|
|
546
|
+
|
|
547
|
+
expect(expected_output_lines).to include('test_lib.c:12:test_skip:IGNORE: Not yet implemented')
|
|
548
|
+
expect(expected_output_lines).to_not include(a_string_matching(/test_skip:FAIL/))
|
|
549
|
+
end
|
|
550
|
+
|
|
474
551
|
it "does not trust a matched PASS line when the retry's own real status contradicts it" do
|
|
475
552
|
# Regression test for issue #1198: a diagnostic retry can print a clean Unity
|
|
476
553
|
# result line while its own real process outcome says otherwise.
|
|
@@ -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,38 @@
|
|
|
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/constants'
|
|
10
|
+
require 'ceedling/plugins/plugin_reportinator'
|
|
11
|
+
|
|
12
|
+
describe PluginReportinator do
|
|
13
|
+
before(:each) do
|
|
14
|
+
@plugin_reportinator_helper = double('plugin_reportinator_helper')
|
|
15
|
+
@plugin_manager = double('plugin_manager')
|
|
16
|
+
@reportinator = double('reportinator')
|
|
17
|
+
@loginator = double('loginator')
|
|
18
|
+
|
|
19
|
+
@reportinator_plugin = described_class.new(
|
|
20
|
+
{
|
|
21
|
+
:plugin_reportinator_helper => @plugin_reportinator_helper,
|
|
22
|
+
:plugin_manager => @plugin_manager,
|
|
23
|
+
:reportinator => @reportinator,
|
|
24
|
+
:loginator => @loginator
|
|
25
|
+
}
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# A build's own test results -- pass or fail -- are core information a user
|
|
30
|
+
# silencing routine build chatter still wants to see, so every plugin that
|
|
31
|
+
# prints post-build test results (report_tests_stdout_plugin, gcov, valgrind)
|
|
32
|
+
# shares this one answer instead of each deciding independently.
|
|
33
|
+
describe '#test_results_floor_verbosity' do
|
|
34
|
+
it 'is ERRORS' do
|
|
35
|
+
expect( @reportinator_plugin.test_results_floor_verbosity ).to eq( Verbosity::ERRORS )
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|