ceedling 1.1.7 → 1.1.9
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 +48 -0
- data/docs/KnownIssues.md +3 -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 +67 -2
- data/lib/ceedling/file_path_utils.rb +4 -0
- data/lib/ceedling/generators/generator_test_results_backtrace.rb +5 -3
- data/lib/ceedling/generators/generator_test_runner.rb +5 -1
- data/lib/ceedling/includes/includes.rb +8 -1
- data/lib/ceedling/partials/partializer.rb +69 -24
- 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 +71 -0
- data/spec/units/c_extractor/c_extractor_preprocessing_spec.rb +122 -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/includes/includes_spec.rb +21 -3
- data/spec/units/partials/partializer_spec.rb +62 -0
- data/spec/units/preprocess/preprocessinator_line_marker_includes_extractor_spec.rb +64 -0
- data/spec/units/preprocess/preprocessinator_reconstructor_spec.rb +74 -0
- data/vendor/unity/auto/generate_test_runner.rb +3 -1
- data/vendor/unity/auto/unity_test_summary.rb +2 -2
- data/vendor/unity/docs/UnityChangeLog.md +7 -0
- data/vendor/unity/docs/UnityConfigurationGuide.md +4 -0
- data/vendor/unity/src/unity_internals.h +21 -7
- data/vendor/unity/test/Makefile +9 -2
- data/vendor/unity/test/rakefile_helper.rb +1 -0
- data/vendor/unity/test/tests/test_generate_test_runner.rb +49 -0
- data/vendor/unity/test/tests/test_unity_test_summary.rb +58 -0
- metadata +5 -2
|
@@ -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
|
|
@@ -412,18 +412,36 @@ describe "Includes sorting" do
|
|
|
412
412
|
MockInclude.new("mock_module.h"),
|
|
413
413
|
UserInclude.new("config.h")
|
|
414
414
|
]
|
|
415
|
-
|
|
415
|
+
|
|
416
416
|
Includes.sort!(includes)
|
|
417
|
-
|
|
417
|
+
|
|
418
418
|
expect(includes.length).to eq(3)
|
|
419
419
|
expect(includes[0]).to be_a(UserInclude)
|
|
420
420
|
expect(includes[1]).to be_a(UserInclude)
|
|
421
421
|
expect(includes[2]).to be_a(UserInclude)
|
|
422
422
|
end
|
|
423
423
|
|
|
424
|
+
# Ruby's sort/sort_by family does not guarantee a stable sort -- every UserInclude
|
|
425
|
+
# here ties on the same comparator value, so a comparator-sort-based implementation
|
|
426
|
+
# is free to reorder them unpredictably across Ruby versions/platforms even with no
|
|
427
|
+
# actual duplicates present. This is the shape that exposed that gap in practice.
|
|
428
|
+
it "preserves each group's own original relative order exactly, not just which group it ends up in" do
|
|
429
|
+
header1 = UserInclude.new("header1.h")
|
|
430
|
+
header2 = UserInclude.new("header2.h")
|
|
431
|
+
header3 = UserInclude.new("header3.h")
|
|
432
|
+
stdio = SystemInclude.new("stdio.h")
|
|
433
|
+
stdlib = SystemInclude.new("stdlib.h")
|
|
434
|
+
|
|
435
|
+
includes = [header1, stdio, header2, stdlib, header3]
|
|
436
|
+
|
|
437
|
+
result = Includes.sort!(includes)
|
|
438
|
+
|
|
439
|
+
expect(result).to eq([stdio, stdlib, header1, header2, header3])
|
|
440
|
+
end
|
|
441
|
+
|
|
424
442
|
it "handles empty array" do
|
|
425
443
|
includes = []
|
|
426
|
-
|
|
444
|
+
|
|
427
445
|
result = Includes.sort!(includes)
|
|
428
446
|
|
|
429
447
|
expect(result).to eq([])
|
|
@@ -547,6 +547,46 @@ describe Partializer do
|
|
|
547
547
|
|
|
548
548
|
expect(result).to match_array([UserInclude.new('header1.h')])
|
|
549
549
|
end
|
|
550
|
+
|
|
551
|
+
# The shared types header's own top-of-file guard-spoofing macro must
|
|
552
|
+
# be defined before anything later in the list gets a chance to transitively
|
|
553
|
+
# re-include this module's real header a second, unguarded way -- splicing the
|
|
554
|
+
# types header in at the module's own original position (rather than appending it
|
|
555
|
+
# after everything else) is what guarantees that ordering.
|
|
556
|
+
it "splices the shared types header in at the module's own original list position, not at the end" do
|
|
557
|
+
includes = [
|
|
558
|
+
UserInclude.new('header1.h'),
|
|
559
|
+
UserInclude.new('module.h'),
|
|
560
|
+
UserInclude.new('header2.h')
|
|
561
|
+
]
|
|
562
|
+
result = @partializer.remap_implementation_header_includes(
|
|
563
|
+
name: 'module',
|
|
564
|
+
includes: includes,
|
|
565
|
+
partials: {},
|
|
566
|
+
types_header: 'ceedling_partial_module_types.h'
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
expect(result).to eq([
|
|
570
|
+
UserInclude.new('header1.h'),
|
|
571
|
+
UserInclude.new('ceedling_partial_module_types.h'),
|
|
572
|
+
UserInclude.new('header2.h')
|
|
573
|
+
])
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
it "appends the shared types header when the module's own header wasn't in the includes list to begin with" do
|
|
577
|
+
includes = [UserInclude.new('header1.h')]
|
|
578
|
+
result = @partializer.remap_implementation_header_includes(
|
|
579
|
+
name: 'module',
|
|
580
|
+
includes: includes,
|
|
581
|
+
partials: {},
|
|
582
|
+
types_header: 'ceedling_partial_module_types.h'
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
expect(result).to eq([
|
|
586
|
+
UserInclude.new('header1.h'),
|
|
587
|
+
UserInclude.new('ceedling_partial_module_types.h')
|
|
588
|
+
])
|
|
589
|
+
end
|
|
550
590
|
end
|
|
551
591
|
|
|
552
592
|
###
|
|
@@ -733,6 +773,28 @@ describe Partializer do
|
|
|
733
773
|
|
|
734
774
|
expect(result).to match_array([UserInclude.new('header1.h')])
|
|
735
775
|
end
|
|
776
|
+
|
|
777
|
+
# See the matching example in #remap_implementation_header_includes for the full
|
|
778
|
+
# rationale; the mockable interface header needs the identical ordering guarantee.
|
|
779
|
+
it "splices the shared types header in at the module's own original list position, not at the end" do
|
|
780
|
+
includes = [
|
|
781
|
+
UserInclude.new('header1.h'),
|
|
782
|
+
UserInclude.new('module.h'),
|
|
783
|
+
UserInclude.new('header2.h')
|
|
784
|
+
]
|
|
785
|
+
result = @partializer.remap_interface_header_includes(
|
|
786
|
+
name: 'module',
|
|
787
|
+
includes: includes,
|
|
788
|
+
partials: {},
|
|
789
|
+
types_header: 'ceedling_partial_module_types.h'
|
|
790
|
+
)
|
|
791
|
+
|
|
792
|
+
expect(result).to eq([
|
|
793
|
+
UserInclude.new('header1.h'),
|
|
794
|
+
UserInclude.new('ceedling_partial_module_types.h'),
|
|
795
|
+
UserInclude.new('header2.h')
|
|
796
|
+
])
|
|
797
|
+
end
|
|
736
798
|
end
|
|
737
799
|
|
|
738
800
|
###
|
|
@@ -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
|
|
@@ -200,8 +200,10 @@ class UnityTestRunnerGenerator
|
|
|
200
200
|
source_lines = source.split("\n")
|
|
201
201
|
source_index = 0
|
|
202
202
|
tests_and_line_numbers.size.times do |i|
|
|
203
|
+
# Compile once per test
|
|
204
|
+
name_regex = /(?:^|\s)#{tests_and_line_numbers[i][:test]}(?:\s|\()/
|
|
203
205
|
source_lines[source_index..].each_with_index do |line, index|
|
|
204
|
-
next unless line =~
|
|
206
|
+
next unless line =~ name_regex
|
|
205
207
|
|
|
206
208
|
source_index += index
|
|
207
209
|
tests_and_line_numbers[i][:line_number] = source_index + 1
|
|
@@ -124,7 +124,7 @@ if $0 == __FILE__
|
|
|
124
124
|
begin
|
|
125
125
|
# look in the specified or current directory for result files
|
|
126
126
|
args[0] ||= './'
|
|
127
|
-
targets = "#{
|
|
127
|
+
targets = "#{args[0].tr('\\', '/')}**/*.test*"
|
|
128
128
|
results = Dir[targets]
|
|
129
129
|
|
|
130
130
|
raise "No *.testpass, *.testfail, or *.testresults files found in '#{targets}'" if results.empty?
|
|
@@ -133,7 +133,7 @@ if $0 == __FILE__
|
|
|
133
133
|
|
|
134
134
|
# set the root path
|
|
135
135
|
args[1] ||= "#{Dir.pwd}/"
|
|
136
|
-
uts.root =
|
|
136
|
+
uts.root = args[1]
|
|
137
137
|
|
|
138
138
|
# run the summarizer
|
|
139
139
|
puts uts.run
|
|
@@ -13,6 +13,13 @@ Prior to 2008, the project was an internal project and not released to the publi
|
|
|
13
13
|
|
|
14
14
|
## Log
|
|
15
15
|
|
|
16
|
+
### Unity 2.7.2
|
|
17
|
+
|
|
18
|
+
Significant Bugfixes:
|
|
19
|
+
|
|
20
|
+
- Default `UNITY_INCLUDE_EXEC_TIME` macros compile as ISO C99, are statement-safe, and accept `-Wsign-conversion` (#838)
|
|
21
|
+
- `unity_test_summary.rb` honors its own default result directory and root path again. @youdie006
|
|
22
|
+
|
|
16
23
|
### Unity 2.7.0 (July 2026)
|
|
17
24
|
|
|
18
25
|
New Features:
|
|
@@ -488,6 +488,10 @@ Define this to measure and report execution time for each test in the suite. Whe
|
|
|
488
488
|
it's best to automatically find a way to determine the time in milliseconds. On most Windows, macos, or
|
|
489
489
|
Linux environments, this is automatic. If not, you can give Unity more information.
|
|
490
490
|
|
|
491
|
+
On Unix and macOS, the default uses POSIX `clock_gettime(CLOCK_MONOTONIC)` when that clock is available.
|
|
492
|
+
If it is not (strict ISO C with no POSIX clocks in `<time.h>`), it falls back to the standard `clock()` function,
|
|
493
|
+
which is also the Windows default.
|
|
494
|
+
|
|
491
495
|
#### `UNITY_CLOCK_MS`
|
|
492
496
|
|
|
493
497
|
If you're working on a system (embedded or otherwise) which has an accessible millisecond timer. You can
|
|
@@ -395,33 +395,34 @@ typedef UNITY_FLOAT_TYPE UNITY_FLOAT;
|
|
|
395
395
|
!defined(UNITY_EXEC_TIME_STOP) && \
|
|
396
396
|
!defined(UNITY_PRINT_EXEC_TIME) && \
|
|
397
397
|
!defined(UNITY_TIME_TYPE)
|
|
398
|
-
/* If none
|
|
398
|
+
/* If none of these macros are defined then try to provide a default implementation */
|
|
399
399
|
|
|
400
400
|
#if defined(UNITY_CLOCK_MS)
|
|
401
401
|
/* This is a simple way to get a default implementation on platforms that support getting a millisecond counter */
|
|
402
402
|
#define UNITY_TIME_TYPE UNITY_UINT
|
|
403
403
|
#define UNITY_EXEC_TIME_START() Unity.CurrentTestStartTime = UNITY_CLOCK_MS()
|
|
404
404
|
#define UNITY_EXEC_TIME_STOP() Unity.CurrentTestStopTime = UNITY_CLOCK_MS()
|
|
405
|
-
#define UNITY_PRINT_EXEC_TIME() { \
|
|
405
|
+
#define UNITY_PRINT_EXEC_TIME() do { \
|
|
406
406
|
UNITY_UINT execTimeMs = (Unity.CurrentTestStopTime - Unity.CurrentTestStartTime); \
|
|
407
407
|
UnityPrint(" ("); \
|
|
408
408
|
UnityPrintNumberUnsigned(execTimeMs); \
|
|
409
409
|
UnityPrint(" ms)"); \
|
|
410
|
-
}
|
|
410
|
+
} while (0)
|
|
411
411
|
#elif defined(_WIN32)
|
|
412
412
|
#include <time.h>
|
|
413
413
|
#define UNITY_TIME_TYPE clock_t
|
|
414
414
|
#define UNITY_GET_TIME(t) t = (clock_t)((clock() * 1000) / CLOCKS_PER_SEC)
|
|
415
415
|
#define UNITY_EXEC_TIME_START() UNITY_GET_TIME(Unity.CurrentTestStartTime)
|
|
416
416
|
#define UNITY_EXEC_TIME_STOP() UNITY_GET_TIME(Unity.CurrentTestStopTime)
|
|
417
|
-
#define UNITY_PRINT_EXEC_TIME() { \
|
|
418
|
-
UNITY_UINT execTimeMs = (Unity.CurrentTestStopTime - Unity.CurrentTestStartTime); \
|
|
417
|
+
#define UNITY_PRINT_EXEC_TIME() do { \
|
|
418
|
+
UNITY_UINT execTimeMs = (UNITY_UINT)(Unity.CurrentTestStopTime - Unity.CurrentTestStartTime); \
|
|
419
419
|
UnityPrint(" ("); \
|
|
420
420
|
UnityPrintNumberUnsigned(execTimeMs); \
|
|
421
421
|
UnityPrint(" ms)"); \
|
|
422
|
-
}
|
|
422
|
+
} while (0)
|
|
423
423
|
#elif defined(__unix__) || defined(__APPLE__)
|
|
424
424
|
#include <time.h>
|
|
425
|
+
#if defined(CLOCK_MONOTONIC)
|
|
425
426
|
#define UNITY_TIME_TYPE struct timespec
|
|
426
427
|
#define UNITY_GET_TIME(t) clock_gettime(CLOCK_MONOTONIC, &t)
|
|
427
428
|
#define UNITY_EXEC_TIME_START() UNITY_GET_TIME(Unity.CurrentTestStartTime)
|
|
@@ -432,7 +433,20 @@ typedef UNITY_FLOAT_TYPE UNITY_FLOAT;
|
|
|
432
433
|
UnityPrint(" ("); \
|
|
433
434
|
UnityPrintNumberUnsigned(execTimeMs); \
|
|
434
435
|
UnityPrint(" ms)"); \
|
|
435
|
-
} while(0)
|
|
436
|
+
} while (0)
|
|
437
|
+
#else
|
|
438
|
+
/* CLOCK_MONOTONIC is POSIX, not ISO C. Fall back so -std=c99 still compiles. */
|
|
439
|
+
#define UNITY_TIME_TYPE clock_t
|
|
440
|
+
#define UNITY_GET_TIME(t) t = (clock_t)((clock() * 1000) / CLOCKS_PER_SEC)
|
|
441
|
+
#define UNITY_EXEC_TIME_START() UNITY_GET_TIME(Unity.CurrentTestStartTime)
|
|
442
|
+
#define UNITY_EXEC_TIME_STOP() UNITY_GET_TIME(Unity.CurrentTestStopTime)
|
|
443
|
+
#define UNITY_PRINT_EXEC_TIME() do { \
|
|
444
|
+
UNITY_UINT execTimeMs = (UNITY_UINT)(Unity.CurrentTestStopTime - Unity.CurrentTestStartTime); \
|
|
445
|
+
UnityPrint(" ("); \
|
|
446
|
+
UnityPrintNumberUnsigned(execTimeMs); \
|
|
447
|
+
UnityPrint(" ms)"); \
|
|
448
|
+
} while (0)
|
|
449
|
+
#endif
|
|
436
450
|
#endif
|
|
437
451
|
#endif
|
|
438
452
|
#endif
|
data/vendor/unity/test/Makefile
CHANGED
|
@@ -43,7 +43,7 @@ TARGET = build/testunity-cov.exe
|
|
|
43
43
|
# To generate coverage, call 'make -s', the default target runs.
|
|
44
44
|
# For verbose output of all the tests, run 'make test'.
|
|
45
45
|
default: test
|
|
46
|
-
.PHONY: default coverage test clean
|
|
46
|
+
.PHONY: default coverage test clean intDetection execTimeDetection
|
|
47
47
|
coverage: $(SRC1) $(SRC2) $(SRC3) $(SRC4) $(SRC5) $(SRC6) $(SRC7) $(SRC8)
|
|
48
48
|
cd $(BUILD_DIR) && \
|
|
49
49
|
$(CC) $(CFLAGS) $(DEFINES) $(foreach i,$(SRC1), ../$i) $(COV_FLAGS) -o ../$(TARGET)
|
|
@@ -122,10 +122,17 @@ test: $(SRC1) $(SRC2) $(SRC3) $(SRC4) $(SRC5) $(SRC6) $(SRC7) $(SRC8)
|
|
|
122
122
|
|
|
123
123
|
# Compile only, for testing that preprocessor detection works
|
|
124
124
|
UNITY_C_ONLY =-c ../src/unity.c -o $(BUILD_DIR)/unity.o
|
|
125
|
-
intDetection:
|
|
125
|
+
intDetection: | $(BUILD_DIR)
|
|
126
126
|
$(CC) $(CFLAGS) $(INC_DIR) $(UNITY_C_ONLY) -D UNITY_EXCLUDE_STDINT_H
|
|
127
127
|
$(CC) $(CFLAGS) $(INC_DIR) $(UNITY_C_ONLY) -D UNITY_EXCLUDE_LIMITS_H
|
|
128
128
|
|
|
129
|
+
# UNITY_INCLUDE_EXEC_TIME is optional and was historically untested, so the
|
|
130
|
+
# default timer macros could rot. Compile them with Unity's own warning flags
|
|
131
|
+
# and with -Wsign-conversion (the failure reported in #838).
|
|
132
|
+
execTimeDetection: | $(BUILD_DIR)
|
|
133
|
+
$(CC) $(CFLAGS) $(INC_DIR) $(UNITY_C_ONLY) -D UNITY_INCLUDE_EXEC_TIME
|
|
134
|
+
$(CC) $(CFLAGS) -Wsign-conversion $(INC_DIR) $(UNITY_C_ONLY) -D UNITY_INCLUDE_EXEC_TIME
|
|
135
|
+
|
|
129
136
|
$(BUILD_DIR)/test_unity_arraysRunner.c: tests/test_unity_arrays.c | $(BUILD_DIR)
|
|
130
137
|
awk $(AWK_SCRIPT) tests/test_unity_arrays.c > $@
|
|
131
138
|
|
|
@@ -407,6 +407,7 @@ module RakefileHelpers
|
|
|
407
407
|
combined_output = ''
|
|
408
408
|
[ "make -s", # test with all defaults
|
|
409
409
|
"make -s coverage", # test with coverage
|
|
410
|
+
"make -s execTimeDetection", # compile default UNITY_INCLUDE_EXEC_TIME macros
|
|
410
411
|
"cd #{File.join("..","extras","fixture",'test')} && make -s default noStdlibMalloc",
|
|
411
412
|
"cd #{File.join("..","extras","fixture",'test')} && make -s C89",
|
|
412
413
|
"cd #{File.join("..","extras","memory",'test')} && make -s default noStdlibMalloc",
|
|
@@ -1334,6 +1334,55 @@ should 'GenerateSuiteTeardownWhenBeginAndEndAreOmitted' do
|
|
|
1334
1334
|
$generate_test_runner_tests += 1
|
|
1335
1335
|
end
|
|
1336
1336
|
|
|
1337
|
+
should 'FindTestsLineNumbersWhenNameStartsTheLine' do
|
|
1338
|
+
# The return type may sit on its own line, leaving the test name at column 0.
|
|
1339
|
+
# The line search must still find the definition (and must not scan to the end
|
|
1340
|
+
# of the file for every test, which is what made large files slow).
|
|
1341
|
+
source = "#include \"unity.h\"\n" \
|
|
1342
|
+
"\n" \
|
|
1343
|
+
"void\n" \
|
|
1344
|
+
"test_FirstAtColumnZero(void)\n" \
|
|
1345
|
+
"{\n" \
|
|
1346
|
+
"}\n" \
|
|
1347
|
+
"\n" \
|
|
1348
|
+
"void\n" \
|
|
1349
|
+
"test_SecondAtColumnZero(void)\n" \
|
|
1350
|
+
"{\n" \
|
|
1351
|
+
"}\n"
|
|
1352
|
+
found = UnityTestRunnerGenerator.new({}).find_tests(source).map { |t| [t[:test], t[:line_number]] }
|
|
1353
|
+
expected = [['test_FirstAtColumnZero', 4], ['test_SecondAtColumnZero', 9]]
|
|
1354
|
+
if found == expected
|
|
1355
|
+
report 'Runner_FindTestsLineNumbersWhenNameStartsTheLine:PASS'
|
|
1356
|
+
else
|
|
1357
|
+
report " FAIL: expected #{expected.inspect}, got #{found.inspect}"
|
|
1358
|
+
report 'Runner_FindTestsLineNumbersWhenNameStartsTheLine:FAIL'
|
|
1359
|
+
$generate_test_runner_failures += 1
|
|
1360
|
+
end
|
|
1361
|
+
$generate_test_runner_tests += 1
|
|
1362
|
+
end
|
|
1363
|
+
|
|
1364
|
+
should 'FindTestsLineNumbersWhenOneNameIsAPrefixOfAnother' do
|
|
1365
|
+
# Issue #288: test_my_function must not be located inside
|
|
1366
|
+
# test_my_function_invalid_behavior, which is defined first.
|
|
1367
|
+
source = "void test_my_function_invalid_behavior(void)\n" \
|
|
1368
|
+
"{\n" \
|
|
1369
|
+
"}\n" \
|
|
1370
|
+
"\n" \
|
|
1371
|
+
"void test_my_function(void)\n" \
|
|
1372
|
+
"{\n" \
|
|
1373
|
+
"}\n"
|
|
1374
|
+
found = UnityTestRunnerGenerator.new({}).find_tests(source).map { |t| [t[:test], t[:line_number]] }
|
|
1375
|
+
expected = [['test_my_function_invalid_behavior', 1], ['test_my_function', 5]]
|
|
1376
|
+
if found == expected
|
|
1377
|
+
report 'Runner_FindTestsLineNumbersWhenOneNameIsAPrefixOfAnother:PASS'
|
|
1378
|
+
else
|
|
1379
|
+
report " FAIL: expected #{expected.inspect}, got #{found.inspect}"
|
|
1380
|
+
report 'Runner_FindTestsLineNumbersWhenOneNameIsAPrefixOfAnother:FAIL'
|
|
1381
|
+
$generate_test_runner_failures += 1
|
|
1382
|
+
end
|
|
1383
|
+
$generate_test_runner_tests += 1
|
|
1384
|
+
end
|
|
1385
|
+
|
|
1337
1386
|
RUNNER_TESTS.each do |testset|
|
|
1338
1387
|
basename = File.basename(testset[:testfile], C_EXTENSION)
|
|
1339
1388
|
testset_name = "Runner_#{basename}_#{testset[:name]}"
|