ceedling 1.1.8 → 1.1.10
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 +4 -3
- data/docs/Changelog.md +28 -0
- data/docs/KnownIssues.md +12 -2
- data/docs/SECURITY.md +40 -2
- data/docs/mkdocs/development/index.md +11 -0
- data/docs/mkdocs/plugins/fff.md +1 -1
- data/docs/mkdocs/plugins/gcov/gcovr.md +117 -3
- data/docs/mkdocs/plugins/gcov/reportgenerator.md +110 -4
- data/lib/ceedling/c_extractor/c_extractor_preprocessing.rb +29 -1
- data/lib/ceedling/file_path_utils.rb +4 -0
- data/lib/ceedling/generators/generator.rb +11 -6
- data/lib/ceedling/generators/generator_partials.rb +54 -18
- data/lib/ceedling/includes/includes.rb +8 -1
- data/lib/ceedling/objects.yml +2 -0
- data/lib/ceedling/partials/partializer.rb +204 -96
- data/lib/ceedling/test_invoker/test_build_executor.rb +21 -7
- data/lib/version.rb +1 -1
- data/site-local/development/index.html +7 -0
- data/site-local/plugins/fff.html +1 -1
- data/site-local/plugins/gcov/gcovr.html +156 -3
- data/site-local/plugins/gcov/reportgenerator.html +201 -14
- data/site-local/sitemap.xml.gz +0 -0
- data/spec/system/partials_types_header_spec.rb +166 -0
- data/spec/units/c_extractor/c_extractor_integration_spec.rb +18 -0
- data/spec/units/c_extractor/c_extractor_preprocessing_spec.rb +77 -0
- data/spec/units/generators/generator_partials_spec.rb +130 -17
- data/spec/units/includes/includes_spec.rb +21 -3
- data/spec/units/partials/partializer_spec.rb +225 -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
|
@@ -1007,6 +1007,24 @@ describe CExtractor do
|
|
|
1007
1007
|
expect(plain.decorators).to eq([])
|
|
1008
1008
|
end
|
|
1009
1009
|
|
|
1010
|
+
it "normalizes a preprocessor-inserted space before ## in a backslash-continued macro definition" do
|
|
1011
|
+
file_contents = <<~CONTENTS
|
|
1012
|
+
#define MODULE_DEV_TYPE_CB(dev_type) \\
|
|
1013
|
+
void MODULE_TrigCbDev ##dev_type(void) \\
|
|
1014
|
+
{ \\
|
|
1015
|
+
MODULE_CommonTrigCb(); \\
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
static AlertEntry_t s_alert_table[ALERT_MANAGER_MAX_ALERTS];
|
|
1019
|
+
CONTENTS
|
|
1020
|
+
|
|
1021
|
+
contents = extract_from.call(file_contents)
|
|
1022
|
+
|
|
1023
|
+
expect(contents.macro_definitions.length).to eq 1
|
|
1024
|
+
expect(contents.macro_definitions[0].text).to include('MODULE_TrigCbDev##dev_type')
|
|
1025
|
+
expect(contents.macro_definitions[0].text).not_to include('MODULE_TrigCbDev ##dev_type')
|
|
1026
|
+
end
|
|
1027
|
+
|
|
1010
1028
|
end
|
|
1011
1029
|
|
|
1012
1030
|
end
|
|
@@ -388,6 +388,83 @@ describe CExtractorPreprocessing do
|
|
|
388
388
|
expect(scanner.pos).to eq 0
|
|
389
389
|
end
|
|
390
390
|
|
|
391
|
+
context "whitespace around # and ## operators" do
|
|
392
|
+
it "trims a space before ##" do
|
|
393
|
+
result, _pos = try_directive("#define M(x) void f ##x(void) { g(); }\n")
|
|
394
|
+
expect(result).to eq [true, "#define M(x) void f##x(void) { g(); }"]
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
it "trims a space after ##" do
|
|
398
|
+
result, _pos = try_directive("#define M(x) void f## x(void) { g(); }\n")
|
|
399
|
+
expect(result).to eq [true, "#define M(x) void f##x(void) { g(); }"]
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
it "trims space on both sides of ##" do
|
|
403
|
+
result, _pos = try_directive("#define M(x) void f ## x(void) { g(); }\n")
|
|
404
|
+
expect(result).to eq [true, "#define M(x) void f##x(void) { g(); }"]
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
it "trims a space after the stringize operator #" do
|
|
408
|
+
result, _pos = try_directive("#define S(x) # x\n")
|
|
409
|
+
expect(result).to eq [true, "#define S(x) #x"]
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
it "trims a space before a stringize operator directly gluing onto a preceding identifier" do
|
|
413
|
+
result, _pos = try_directive("#define M(a, b) a #b\n")
|
|
414
|
+
expect(result).to eq [true, "#define M(a, b) a#b"]
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
it "does not touch whitespace before # or ## when preceded by punctuation, not an identifier being pasted" do
|
|
418
|
+
# The space here separates the macro's own parameter list from a
|
|
419
|
+
# replacement list that happens to start with the operator -- that's
|
|
420
|
+
# ordinary formatting, not preprocessor-reconstruction spacing around an
|
|
421
|
+
# operand being pasted, so it's out of this fix's scope.
|
|
422
|
+
result, _pos = try_directive("#define S(x) #x\n")
|
|
423
|
+
expect(result).to eq [true, "#define S(x) #x"]
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
it "normalizes the GNU comma-deletion ,##__VA_ARGS__ form" do
|
|
427
|
+
result, _pos = try_directive("#define LOG(fmt, ...) printf(fmt, ## __VA_ARGS__)\n")
|
|
428
|
+
expect(result).to eq [true, "#define LOG(fmt, ...) printf(fmt, ##__VA_ARGS__)"]
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
it "is idempotent for already-canonical ## spacing" do
|
|
432
|
+
input = "#define M(x) void f##x(void) { g(); }\n"
|
|
433
|
+
result, _pos = try_directive(input)
|
|
434
|
+
expect(result).to eq [true, input.rstrip]
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
it "is idempotent for already-canonical # spacing" do
|
|
438
|
+
input = "#define S(x) #x\n"
|
|
439
|
+
result, _pos = try_directive(input)
|
|
440
|
+
expect(result).to eq [true, input.rstrip]
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
it "does not touch ## inside a string literal" do
|
|
444
|
+
input = %q{#define M(x) "a ## b"} + "\n"
|
|
445
|
+
result, _pos = try_directive(input)
|
|
446
|
+
expect(result).to eq [true, input.rstrip]
|
|
447
|
+
end
|
|
448
|
+
|
|
449
|
+
it "does not touch ## inside a // comment" do
|
|
450
|
+
input = "#define M(x) x // a ## b\n"
|
|
451
|
+
result, _pos = try_directive(input)
|
|
452
|
+
expect(result).to eq [true, input.rstrip]
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
it "does not touch ## inside a /* */ comment" do
|
|
456
|
+
input = "#define M(x) x /* a ## b */\n"
|
|
457
|
+
result, _pos = try_directive(input)
|
|
458
|
+
expect(result).to eq [true, input.rstrip]
|
|
459
|
+
end
|
|
460
|
+
|
|
461
|
+
it "leaves ordinary whitespace unrelated to # or ## untouched" do
|
|
462
|
+
input = "#define M(x) void f ( ) ;\n"
|
|
463
|
+
result, _pos = try_directive(input)
|
|
464
|
+
expect(result).to eq [true, input.rstrip]
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
|
|
391
468
|
end
|
|
392
469
|
|
|
393
470
|
context "#filter_directive" do
|
|
@@ -61,6 +61,21 @@ describe GeneratorPartials do
|
|
|
61
61
|
CExtractorTypes::CModule.new()
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
+
# A StringIO standing in for the generated types header, with the filename lookup stubbed.
|
|
65
|
+
# Several examples differ only in what they hand generate_types, not in this plumbing.
|
|
66
|
+
def types_buffer(name, header_filename)
|
|
67
|
+
allow(@file_path_utils).to receive(:form_partial_types_header_filename)
|
|
68
|
+
.with(name)
|
|
69
|
+
.and_return(header_filename)
|
|
70
|
+
|
|
71
|
+
buf = StringIO.new()
|
|
72
|
+
allow(@file_wrapper).to receive(:open)
|
|
73
|
+
.with(File.join('/path/to/output', header_filename), 'wb')
|
|
74
|
+
.and_yield(buf)
|
|
75
|
+
|
|
76
|
+
buf
|
|
77
|
+
end
|
|
78
|
+
|
|
64
79
|
context "#generate_implementation" do
|
|
65
80
|
it "should call generate_header() and generate_source() with correct parameters" do
|
|
66
81
|
# Setup
|
|
@@ -131,7 +146,7 @@ describe GeneratorPartials do
|
|
|
131
146
|
header_includes,
|
|
132
147
|
defns,
|
|
133
148
|
c_module,
|
|
134
|
-
true
|
|
149
|
+
include_variables: true, include_guard: nil
|
|
135
150
|
)
|
|
136
151
|
|
|
137
152
|
# Verify generate_source was called with correct parameters
|
|
@@ -208,7 +223,7 @@ describe GeneratorPartials do
|
|
|
208
223
|
includes,
|
|
209
224
|
decls,
|
|
210
225
|
c_module,
|
|
211
|
-
false
|
|
226
|
+
include_variables: false, include_guard: nil
|
|
212
227
|
)
|
|
213
228
|
|
|
214
229
|
# Verify file path utilities were called
|
|
@@ -447,6 +462,104 @@ describe GeneratorPartials do
|
|
|
447
462
|
|
|
448
463
|
expect( buf.string ).to include( "struct Point { int x; int y; };\nstruct Color { int r; int g; int b; };\n" )
|
|
449
464
|
end
|
|
465
|
+
# The spoof and the carried includes are what make this header compile wherever it lands.
|
|
466
|
+
# Order between them is load-bearing: a carried header can transitively reach the real
|
|
467
|
+
# module header, so the guard has to be satisfied before any include is processed.
|
|
468
|
+
it "states the module's own guard before the includes it carries, then the types" do
|
|
469
|
+
file_contents = <<~CONTENTS
|
|
470
|
+
#ifndef __CEEDLING_GENERATED_MY_MODULE_TYPES_H__
|
|
471
|
+
#define __CEEDLING_GENERATED_MY_MODULE_TYPES_H__
|
|
472
|
+
|
|
473
|
+
#define MY_MODULE_H
|
|
474
|
+
|
|
475
|
+
#include "foundation.h"
|
|
476
|
+
#include <stdint.h>
|
|
477
|
+
|
|
478
|
+
typedef uint8_t Byte;
|
|
479
|
+
|
|
480
|
+
#endif // __CEEDLING_GENERATED_MY_MODULE_TYPES_H__
|
|
481
|
+
|
|
482
|
+
CONTENTS
|
|
483
|
+
|
|
484
|
+
buf = types_buffer( 'my_module', 'my_module_types.h' )
|
|
485
|
+
typedef_stmt = CExtractorTypes::CStatement.new(text: "typedef uint8_t Byte;", line_num: 1)
|
|
486
|
+
|
|
487
|
+
@generator.generate_types(
|
|
488
|
+
name: 'my_module',
|
|
489
|
+
c_module: CExtractorTypes::CModule.new(
|
|
490
|
+
type_definitions: [typedef_stmt],
|
|
491
|
+
element_sequence: [typedef_stmt]
|
|
492
|
+
),
|
|
493
|
+
output_path: '/path/to/output',
|
|
494
|
+
includes: [UserInclude.new('foundation.h'), SystemInclude.new('stdint.h')],
|
|
495
|
+
include_guard: 'MY_MODULE_H'
|
|
496
|
+
)
|
|
497
|
+
|
|
498
|
+
expect( buf.string.strip() ).to eq file_contents.strip()
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
it "states no guard when the module's header has none to spoof" do
|
|
502
|
+
buf = types_buffer( 'my_module', 'my_module_types.h' )
|
|
503
|
+
typedef_stmt = CExtractorTypes::CStatement.new(text: "typedef uint8_t Byte;", line_num: 1)
|
|
504
|
+
|
|
505
|
+
@generator.generate_types(
|
|
506
|
+
name: 'my_module',
|
|
507
|
+
c_module: CExtractorTypes::CModule.new(
|
|
508
|
+
type_definitions: [typedef_stmt],
|
|
509
|
+
element_sequence: [typedef_stmt]
|
|
510
|
+
),
|
|
511
|
+
output_path: '/path/to/output',
|
|
512
|
+
includes: [UserInclude.new('foundation.h')],
|
|
513
|
+
include_guard: nil
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
expect( buf.string ).to include('#include "foundation.h"')
|
|
517
|
+
expect( buf.string.scan(/^#define /).length ).to eq(1) # the generated guard alone
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
# The real header's guard also arrives among the extracted macros, since it survives
|
|
521
|
+
# preprocessing. Stating it deliberately and carrying it too would duplicate it.
|
|
522
|
+
it "drops the carried copy of the guard it states itself" do
|
|
523
|
+
buf = types_buffer( 'my_module', 'my_module_types.h' )
|
|
524
|
+
guard_macro = CExtractorTypes::CStatement.new(text: "#define MY_MODULE_H", line_num: 1)
|
|
525
|
+
typedef_stmt = CExtractorTypes::CStatement.new(text: "typedef uint8_t Byte;", line_num: 2)
|
|
526
|
+
|
|
527
|
+
@generator.generate_types(
|
|
528
|
+
name: 'my_module',
|
|
529
|
+
c_module: CExtractorTypes::CModule.new(
|
|
530
|
+
type_definitions: [typedef_stmt],
|
|
531
|
+
macro_definitions: [guard_macro],
|
|
532
|
+
element_sequence: [guard_macro, typedef_stmt]
|
|
533
|
+
),
|
|
534
|
+
output_path: '/path/to/output',
|
|
535
|
+
include_guard: 'MY_MODULE_H'
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
expect( buf.string.scan(/^#define MY_MODULE_H$/).length ).to eq(1)
|
|
539
|
+
end
|
|
540
|
+
|
|
541
|
+
# A macro whose name merely begins with the guard's, and a guard-named macro that actually
|
|
542
|
+
# carries a value. Neither is the guard statement.
|
|
543
|
+
it "keeps a macro whose name only resembles the guard" do
|
|
544
|
+
buf = types_buffer( 'my_module', 'my_module_types.h' )
|
|
545
|
+
near_macro = CExtractorTypes::CStatement.new(text: "#define MY_MODULE_H_LIMIT 8", line_num: 1)
|
|
546
|
+
valued_macro = CExtractorTypes::CStatement.new(text: "#define MY_MODULE_H 2", line_num: 2)
|
|
547
|
+
typedef_stmt = CExtractorTypes::CStatement.new(text: "typedef uint8_t Byte;", line_num: 3)
|
|
548
|
+
|
|
549
|
+
@generator.generate_types(
|
|
550
|
+
name: 'my_module',
|
|
551
|
+
c_module: CExtractorTypes::CModule.new(
|
|
552
|
+
type_definitions: [typedef_stmt],
|
|
553
|
+
macro_definitions: [near_macro, valued_macro],
|
|
554
|
+
element_sequence: [near_macro, valued_macro, typedef_stmt]
|
|
555
|
+
),
|
|
556
|
+
output_path: '/path/to/output',
|
|
557
|
+
include_guard: 'MY_MODULE_H'
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
expect( buf.string ).to include('#define MY_MODULE_H_LIMIT 8')
|
|
561
|
+
expect( buf.string ).to include('#define MY_MODULE_H 2')
|
|
562
|
+
end
|
|
450
563
|
end
|
|
451
564
|
|
|
452
565
|
context "#generate_header (private method)" do
|
|
@@ -462,7 +575,7 @@ describe GeneratorPartials do
|
|
|
462
575
|
|
|
463
576
|
CONTENTS
|
|
464
577
|
|
|
465
|
-
@generator.send(:generate_header, buf, 'foo_bar', [], [], empty_module, false)
|
|
578
|
+
@generator.send(:generate_header, buf, 'foo_bar', [], [], empty_module, include_variables: false)
|
|
466
579
|
expect( buf.string.strip() ).to eq file_contents.strip()
|
|
467
580
|
end
|
|
468
581
|
|
|
@@ -483,7 +596,7 @@ describe GeneratorPartials do
|
|
|
483
596
|
buf,
|
|
484
597
|
'Apples-and-Bananas',
|
|
485
598
|
[UserInclude.new('foo.h'), UserInclude.new('bar.h')],
|
|
486
|
-
[], empty_module, false
|
|
599
|
+
[], empty_module, include_variables: false
|
|
487
600
|
)
|
|
488
601
|
|
|
489
602
|
expect( buf.string.strip() ).to eq file_contents.strip()
|
|
@@ -505,7 +618,7 @@ describe GeneratorPartials do
|
|
|
505
618
|
make_var(name: 'slices_of_bread', type: 'unsigned int', text: 'unsigned int slices_of_bread = 10;'),
|
|
506
619
|
make_var(name: 'crumbs', type: 'char', text: 'char crumbs[10];')
|
|
507
620
|
)
|
|
508
|
-
@generator.send(:generate_header, buf, 'pb-and-j', [], [], c_module, true)
|
|
621
|
+
@generator.send(:generate_header, buf, 'pb-and-j', [], [], c_module, include_variables: true)
|
|
509
622
|
expect( buf.string.strip() ).to eq file_contents.strip()
|
|
510
623
|
end
|
|
511
624
|
|
|
@@ -513,7 +626,7 @@ describe GeneratorPartials do
|
|
|
513
626
|
c_module = make_module(
|
|
514
627
|
make_var(name: 'cmd', type: 'cmd_t', text: 'cmd_t cmd = { 0 };', decorators: ['static', 'volatile'])
|
|
515
628
|
)
|
|
516
|
-
@generator.send(:generate_header, buf, 'mymod', [], [], c_module, true)
|
|
629
|
+
@generator.send(:generate_header, buf, 'mymod', [], [], c_module, include_variables: true)
|
|
517
630
|
|
|
518
631
|
expect(buf.string).to include('extern volatile cmd_t cmd;')
|
|
519
632
|
end
|
|
@@ -522,7 +635,7 @@ describe GeneratorPartials do
|
|
|
522
635
|
c_module = make_module(
|
|
523
636
|
make_var(name: 'lookup_table', type: 'uint8_t* const', text: 'uint8_t* const lookup_table;', decorators: ['static', 'const'])
|
|
524
637
|
)
|
|
525
|
-
@generator.send(:generate_header, buf, 'mymod', [], [], c_module, true)
|
|
638
|
+
@generator.send(:generate_header, buf, 'mymod', [], [], c_module, include_variables: true)
|
|
526
639
|
|
|
527
640
|
expect(buf.string).to include('extern const uint8_t* const lookup_table;')
|
|
528
641
|
end
|
|
@@ -576,7 +689,7 @@ describe GeneratorPartials do
|
|
|
576
689
|
[UserInclude.new('Eeny.h'), UserInclude.new('Meeny.h')],
|
|
577
690
|
decls,
|
|
578
691
|
c_module,
|
|
579
|
-
true
|
|
692
|
+
include_variables: true
|
|
580
693
|
)
|
|
581
694
|
|
|
582
695
|
expect( buf.string.strip() ).to eq file_contents.strip()
|
|
@@ -601,7 +714,7 @@ describe GeneratorPartials do
|
|
|
601
714
|
make_stmt(text: "struct Point { int x; int y; };")
|
|
602
715
|
)
|
|
603
716
|
|
|
604
|
-
@generator.send(:generate_header, buf, 'defs', [], [], c_module, false)
|
|
717
|
+
@generator.send(:generate_header, buf, 'defs', [], [], c_module, include_variables: false)
|
|
605
718
|
expect( buf.string.strip() ).to eq file_contents.strip()
|
|
606
719
|
end
|
|
607
720
|
|
|
@@ -629,7 +742,7 @@ describe GeneratorPartials do
|
|
|
629
742
|
make_stmt(text: "#define IDX_REF (ADS124S08_REG_ADDR_REF - START_ADDRESS)", line_num: 3)
|
|
630
743
|
)
|
|
631
744
|
|
|
632
|
-
@generator.send(:generate_header, buf, 'regs', [], [], c_module, false)
|
|
745
|
+
@generator.send(:generate_header, buf, 'regs', [], [], c_module, include_variables: false)
|
|
633
746
|
expect( buf.string.strip() ).to eq file_contents.strip()
|
|
634
747
|
end
|
|
635
748
|
|
|
@@ -644,7 +757,7 @@ describe GeneratorPartials do
|
|
|
644
757
|
make_stmt(text: "#define IDX_REF (ADS124S08_REG_ADDR_REF - START_ADDRESS)", line_num: 3)
|
|
645
758
|
)
|
|
646
759
|
|
|
647
|
-
@generator.send(:generate_header, buf, 'regs', [], [], c_module, false)
|
|
760
|
+
@generator.send(:generate_header, buf, 'regs', [], [], c_module, include_variables: false)
|
|
648
761
|
|
|
649
762
|
expect( buf.string ).to include(
|
|
650
763
|
"#define START_ADDRESS 0x00 // don't change this\n" +
|
|
@@ -694,7 +807,7 @@ describe GeneratorPartials do
|
|
|
694
807
|
element_sequence: [macro_stmt, typedef_stmt, var_decl, aggregate_stmt]
|
|
695
808
|
)
|
|
696
809
|
|
|
697
|
-
@generator.send(:generate_header, buf, 'all_statements', [], [], c_module, true)
|
|
810
|
+
@generator.send(:generate_header, buf, 'all_statements', [], [], c_module, include_variables: true)
|
|
698
811
|
expect( buf.string.strip() ).to eq file_contents.strip()
|
|
699
812
|
end
|
|
700
813
|
|
|
@@ -718,7 +831,7 @@ describe GeneratorPartials do
|
|
|
718
831
|
make_stmt(text: "typedef uint8_t Byte;", line_num: 3)
|
|
719
832
|
)
|
|
720
833
|
|
|
721
|
-
@generator.send(:generate_header, buf, 'mixed', [], [], c_module, true)
|
|
834
|
+
@generator.send(:generate_header, buf, 'mixed', [], [], c_module, include_variables: true)
|
|
722
835
|
expect( buf.string.strip() ).to eq file_contents.strip()
|
|
723
836
|
end
|
|
724
837
|
|
|
@@ -738,7 +851,7 @@ describe GeneratorPartials do
|
|
|
738
851
|
make_var( name: 'counter', type: 'int', text: 'int counter;', line_num: 2)
|
|
739
852
|
)
|
|
740
853
|
|
|
741
|
-
@generator.send(:generate_header, buf, 'interface', [], [], c_module, false)
|
|
854
|
+
@generator.send(:generate_header, buf, 'interface', [], [], c_module, include_variables: false)
|
|
742
855
|
expect( buf.string.strip() ).to eq file_contents.strip()
|
|
743
856
|
end
|
|
744
857
|
|
|
@@ -780,7 +893,7 @@ describe GeneratorPartials do
|
|
|
780
893
|
element_sequence: [typedef_stmt, foo_raw, var_decl, bar_raw]
|
|
781
894
|
)
|
|
782
895
|
|
|
783
|
-
@generator.send(:generate_header, buf, 'interleaved', [], [foo_decl, bar_decl], c_module, true)
|
|
896
|
+
@generator.send(:generate_header, buf, 'interleaved', [], [foo_decl, bar_decl], c_module, include_variables: true)
|
|
784
897
|
expect( buf.string.strip() ).to eq file_contents.strip()
|
|
785
898
|
end
|
|
786
899
|
|
|
@@ -792,7 +905,7 @@ describe GeneratorPartials do
|
|
|
792
905
|
raw = CExtractorTypes::CFunctionDeclaration.new(name: 'exported_func')
|
|
793
906
|
c_module = make_module(raw)
|
|
794
907
|
|
|
795
|
-
@generator.send(:generate_header, buf, 'mymod', [], [decl], c_module, false)
|
|
908
|
+
@generator.send(:generate_header, buf, 'mymod', [], [decl], c_module, include_variables: false)
|
|
796
909
|
|
|
797
910
|
expect(buf.string).to include('__declspec(dllexport) void exported_func(void);')
|
|
798
911
|
end
|
|
@@ -802,7 +915,7 @@ describe GeneratorPartials do
|
|
|
802
915
|
make_var(name: 'counter', type: 'int', text: 'int counter __attribute__((aligned(16)));')
|
|
803
916
|
)
|
|
804
917
|
|
|
805
|
-
@generator.send(:generate_header, buf, 'mymod', [], [], c_module, true)
|
|
918
|
+
@generator.send(:generate_header, buf, 'mymod', [], [], c_module, include_variables: true)
|
|
806
919
|
|
|
807
920
|
expect(buf.string).to include('extern int counter;')
|
|
808
921
|
expect(buf.string).not_to include('__attribute__')
|
|
@@ -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([])
|
|
@@ -23,12 +23,18 @@ describe Partializer do
|
|
|
23
23
|
@reportinator = Reportinator.new
|
|
24
24
|
@loginator = double("Loginator").as_null_object
|
|
25
25
|
|
|
26
|
+
# Guard extraction reads the module's own header text through these two.
|
|
27
|
+
@preprocessinator_reconstructor = double("PreprocessinatorReconstructor")
|
|
28
|
+
@file_wrapper = double("FileWrapper")
|
|
29
|
+
|
|
26
30
|
@partializer = described_class.new(
|
|
27
31
|
{
|
|
28
32
|
:partializer_helper => @partializer_helper,
|
|
29
33
|
:file_finder => @file_finder,
|
|
30
34
|
:c_extractor => @c_extractor,
|
|
31
35
|
:file_path_utils => @file_path_utils,
|
|
36
|
+
:preprocessinator_reconstructor => @preprocessinator_reconstructor,
|
|
37
|
+
:file_wrapper => @file_wrapper,
|
|
32
38
|
:reportinator => @reportinator,
|
|
33
39
|
:loginator => @loginator
|
|
34
40
|
}
|
|
@@ -547,6 +553,46 @@ describe Partializer do
|
|
|
547
553
|
|
|
548
554
|
expect(result).to match_array([UserInclude.new('header1.h')])
|
|
549
555
|
end
|
|
556
|
+
|
|
557
|
+
# The shared types header's own top-of-file guard-spoofing macro must
|
|
558
|
+
# be defined before anything later in the list gets a chance to transitively
|
|
559
|
+
# re-include this module's real header a second, unguarded way -- splicing the
|
|
560
|
+
# types header in at the module's own original position (rather than appending it
|
|
561
|
+
# after everything else) is what guarantees that ordering.
|
|
562
|
+
it "splices the shared types header in at the module's own original list position, not at the end" do
|
|
563
|
+
includes = [
|
|
564
|
+
UserInclude.new('header1.h'),
|
|
565
|
+
UserInclude.new('module.h'),
|
|
566
|
+
UserInclude.new('header2.h')
|
|
567
|
+
]
|
|
568
|
+
result = @partializer.remap_implementation_header_includes(
|
|
569
|
+
name: 'module',
|
|
570
|
+
includes: includes,
|
|
571
|
+
partials: {},
|
|
572
|
+
types_header: 'ceedling_partial_module_types.h'
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
expect(result).to eq([
|
|
576
|
+
UserInclude.new('header1.h'),
|
|
577
|
+
UserInclude.new('ceedling_partial_module_types.h'),
|
|
578
|
+
UserInclude.new('header2.h')
|
|
579
|
+
])
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
it "appends the shared types header when the module's own header wasn't in the includes list to begin with" do
|
|
583
|
+
includes = [UserInclude.new('header1.h')]
|
|
584
|
+
result = @partializer.remap_implementation_header_includes(
|
|
585
|
+
name: 'module',
|
|
586
|
+
includes: includes,
|
|
587
|
+
partials: {},
|
|
588
|
+
types_header: 'ceedling_partial_module_types.h'
|
|
589
|
+
)
|
|
590
|
+
|
|
591
|
+
expect(result).to eq([
|
|
592
|
+
UserInclude.new('header1.h'),
|
|
593
|
+
UserInclude.new('ceedling_partial_module_types.h')
|
|
594
|
+
])
|
|
595
|
+
end
|
|
550
596
|
end
|
|
551
597
|
|
|
552
598
|
###
|
|
@@ -733,6 +779,28 @@ describe Partializer do
|
|
|
733
779
|
|
|
734
780
|
expect(result).to match_array([UserInclude.new('header1.h')])
|
|
735
781
|
end
|
|
782
|
+
|
|
783
|
+
# See the matching example in #remap_implementation_header_includes for the full
|
|
784
|
+
# rationale; the mockable interface header needs the identical ordering guarantee.
|
|
785
|
+
it "splices the shared types header in at the module's own original list position, not at the end" do
|
|
786
|
+
includes = [
|
|
787
|
+
UserInclude.new('header1.h'),
|
|
788
|
+
UserInclude.new('module.h'),
|
|
789
|
+
UserInclude.new('header2.h')
|
|
790
|
+
]
|
|
791
|
+
result = @partializer.remap_interface_header_includes(
|
|
792
|
+
name: 'module',
|
|
793
|
+
includes: includes,
|
|
794
|
+
partials: {},
|
|
795
|
+
types_header: 'ceedling_partial_module_types.h'
|
|
796
|
+
)
|
|
797
|
+
|
|
798
|
+
expect(result).to eq([
|
|
799
|
+
UserInclude.new('header1.h'),
|
|
800
|
+
UserInclude.new('ceedling_partial_module_types.h'),
|
|
801
|
+
UserInclude.new('header2.h')
|
|
802
|
+
])
|
|
803
|
+
end
|
|
736
804
|
end
|
|
737
805
|
|
|
738
806
|
###
|
|
@@ -747,6 +815,40 @@ describe Partializer do
|
|
|
747
815
|
end
|
|
748
816
|
|
|
749
817
|
context "#remap_implementation_source_includes" do
|
|
818
|
+
# A module tested and mocked in one test file splits its own content across two generated
|
|
819
|
+
# headers: the implementation carries the public bodies and the interface declares the
|
|
820
|
+
# private functions those bodies call. The generated source needs both, so this module's own
|
|
821
|
+
# interface header belongs in its own include list.
|
|
822
|
+
it "carries this module's own interface header when it is also mocked" do
|
|
823
|
+
allow(@file_path_utils).to receive(:form_partial_implementation_header_filename)
|
|
824
|
+
.with('module').and_return('ceedling_partial_module_impl.h')
|
|
825
|
+
allow(@file_path_utils).to receive(:form_partial_interface_header_filename)
|
|
826
|
+
.with('module').and_return('ceedling_partial_module_interface.h')
|
|
827
|
+
|
|
828
|
+
result = @partializer.remap_implementation_source_includes(
|
|
829
|
+
name: 'module',
|
|
830
|
+
includes: [UserInclude.new('module.h')],
|
|
831
|
+
partials: { 'module' => make_partial_config(tests_type: :public, mocks_type: :private) }
|
|
832
|
+
)
|
|
833
|
+
|
|
834
|
+
expect( result.map( &:filename ) ).to eq(
|
|
835
|
+
['ceedling_partial_module_impl.h', 'ceedling_partial_module_interface.h']
|
|
836
|
+
)
|
|
837
|
+
end
|
|
838
|
+
|
|
839
|
+
it "carries no interface header for a module that is tested but not mocked" do
|
|
840
|
+
allow(@file_path_utils).to receive(:form_partial_implementation_header_filename)
|
|
841
|
+
.with('module').and_return('ceedling_partial_module_impl.h')
|
|
842
|
+
|
|
843
|
+
result = @partializer.remap_implementation_source_includes(
|
|
844
|
+
name: 'module',
|
|
845
|
+
includes: [UserInclude.new('module.h')],
|
|
846
|
+
partials: { 'module' => make_partial_config(tests_type: :public) }
|
|
847
|
+
)
|
|
848
|
+
|
|
849
|
+
expect( result.map( &:filename ) ).to eq(['ceedling_partial_module_impl.h'])
|
|
850
|
+
end
|
|
851
|
+
|
|
750
852
|
it "returns implementation header when input is empty and no partials" do
|
|
751
853
|
includes = []
|
|
752
854
|
partials = {}
|
|
@@ -1152,6 +1254,129 @@ describe Partializer do
|
|
|
1152
1254
|
### extract_module_contents()
|
|
1153
1255
|
###
|
|
1154
1256
|
|
|
1257
|
+
context "#extract_module_include_guard" do
|
|
1258
|
+
# Read from the original header, not the reconstituted copy: that copy carries a synthetic
|
|
1259
|
+
# guard which #sanitize then strips, so it is not a dependable source for the name to spoof.
|
|
1260
|
+
it "reads a bounded prefix of the module's own header and returns the guard found" do
|
|
1261
|
+
expect(@file_wrapper).to receive(:read)
|
|
1262
|
+
.with('src/Foo.h', Partializer::GUARD_SCAN_BYTES)
|
|
1263
|
+
.and_return("#ifndef FOO_H\n#define FOO_H\n")
|
|
1264
|
+
allow(@preprocessinator_reconstructor).to receive(:extract_include_guard)
|
|
1265
|
+
.with("#ifndef FOO_H\n#define FOO_H\n")
|
|
1266
|
+
.and_return('FOO_H')
|
|
1267
|
+
|
|
1268
|
+
expect( @partializer.extract_module_include_guard('src/Foo.h') ).to eq('FOO_H')
|
|
1269
|
+
end
|
|
1270
|
+
|
|
1271
|
+
# A `#pragma once` header offers no macro any generated file could define, which is what a
|
|
1272
|
+
# nil return means to every caller.
|
|
1273
|
+
it "returns nil when the header carries no guard macro" do
|
|
1274
|
+
allow(@file_wrapper).to receive(:read).and_return("#pragma once\n")
|
|
1275
|
+
allow(@preprocessinator_reconstructor).to receive(:extract_include_guard).and_return(nil)
|
|
1276
|
+
|
|
1277
|
+
expect( @partializer.extract_module_include_guard('src/Foo.h') ).to be_nil
|
|
1278
|
+
end
|
|
1279
|
+
|
|
1280
|
+
it "returns nil for an absent filepath without reading anything" do
|
|
1281
|
+
expect(@file_wrapper).not_to receive(:read)
|
|
1282
|
+
|
|
1283
|
+
expect( @partializer.extract_module_include_guard(nil) ).to be_nil
|
|
1284
|
+
end
|
|
1285
|
+
end
|
|
1286
|
+
|
|
1287
|
+
context "#remap_types_header_includes" do
|
|
1288
|
+
# Nothing replaces this module's own header here. The types header cannot include itself, so
|
|
1289
|
+
# its own entry is simply dropped.
|
|
1290
|
+
it "drops the module's own header without substituting anything" do
|
|
1291
|
+
result = @partializer.remap_types_header_includes(
|
|
1292
|
+
name: 'module',
|
|
1293
|
+
includes: [UserInclude.new('module.h'), UserInclude.new('foundation.h')],
|
|
1294
|
+
partials: {}
|
|
1295
|
+
)
|
|
1296
|
+
|
|
1297
|
+
expect( result ).to eq([UserInclude.new('foundation.h')])
|
|
1298
|
+
end
|
|
1299
|
+
|
|
1300
|
+
it "keeps every other include in its original relative order" do
|
|
1301
|
+
result = @partializer.remap_types_header_includes(
|
|
1302
|
+
name: 'module',
|
|
1303
|
+
includes: [UserInclude.new('first.h'), UserInclude.new('module.h'), UserInclude.new('last.h')],
|
|
1304
|
+
partials: {}
|
|
1305
|
+
)
|
|
1306
|
+
|
|
1307
|
+
expect( result.map( &:filename ) ).to eq(['first.h', 'last.h'])
|
|
1308
|
+
end
|
|
1309
|
+
|
|
1310
|
+
# Another partialized module's real header would reintroduce exactly the content that
|
|
1311
|
+
# module's own Partial replaced, so a mocked one is redirected to its generated interface
|
|
1312
|
+
# header and an unmocked one is dropped.
|
|
1313
|
+
it "redirects a mocked partialized dependency to its interface header" do
|
|
1314
|
+
allow(@file_path_utils).to receive(:form_partial_interface_header_filename)
|
|
1315
|
+
.with('other')
|
|
1316
|
+
.and_return('ceedling_partial_other_interface.h')
|
|
1317
|
+
|
|
1318
|
+
result = @partializer.remap_types_header_includes(
|
|
1319
|
+
name: 'module',
|
|
1320
|
+
includes: [UserInclude.new('module.h'), UserInclude.new('other.h')],
|
|
1321
|
+
partials: { 'other' => make_partial_config(mocks_type: :public) }
|
|
1322
|
+
)
|
|
1323
|
+
|
|
1324
|
+
expect( result.map( &:filename ) ).to eq(['ceedling_partial_other_interface.h'])
|
|
1325
|
+
end
|
|
1326
|
+
|
|
1327
|
+
it "drops a partialized dependency that is not mocked" do
|
|
1328
|
+
result = @partializer.remap_types_header_includes(
|
|
1329
|
+
name: 'module',
|
|
1330
|
+
includes: [UserInclude.new('module.h'), UserInclude.new('other.h')],
|
|
1331
|
+
partials: { 'other' => make_partial_config(tests_type: :public) }
|
|
1332
|
+
)
|
|
1333
|
+
|
|
1334
|
+
expect( result ).to be_empty
|
|
1335
|
+
end
|
|
1336
|
+
|
|
1337
|
+
# Comparing a module name to an include means comparing names. A header whose basename
|
|
1338
|
+
# merely extends the module's is a different file.
|
|
1339
|
+
it "leaves a header whose name only extends a partialized module's name" do
|
|
1340
|
+
result = @partializer.remap_types_header_includes(
|
|
1341
|
+
name: 'module',
|
|
1342
|
+
includes: [UserInclude.new('module.h'), UserInclude.new('other_extra.h'), UserInclude.new('other.h')],
|
|
1343
|
+
partials: { 'other' => make_partial_config(tests_type: :public) }
|
|
1344
|
+
)
|
|
1345
|
+
|
|
1346
|
+
expect( result.map( &:filename ) ).to eq(['other_extra.h'])
|
|
1347
|
+
end
|
|
1348
|
+
|
|
1349
|
+
# The interface header includes the types header, so carrying it back into the types header
|
|
1350
|
+
# would fold that file's function declarations into this one.
|
|
1351
|
+
it "never carries this module's own interface header, even when it is also mocked" do
|
|
1352
|
+
allow(@file_path_utils).to receive(:form_partial_interface_header_filename)
|
|
1353
|
+
.and_return('ceedling_partial_module_interface.h')
|
|
1354
|
+
|
|
1355
|
+
result = @partializer.remap_types_header_includes(
|
|
1356
|
+
name: 'module',
|
|
1357
|
+
includes: [UserInclude.new('module.h'), UserInclude.new('foundation.h')],
|
|
1358
|
+
partials: { 'module' => make_partial_config(tests_type: :public, mocks_type: :private) }
|
|
1359
|
+
)
|
|
1360
|
+
|
|
1361
|
+
expect( result.map( &:filename ) ).to eq(['foundation.h'])
|
|
1362
|
+
end
|
|
1363
|
+
|
|
1364
|
+
it "logs the carried list under its own noun when a test is named" do
|
|
1365
|
+
expect(@loginator).to receive(:log_list).with(
|
|
1366
|
+
anything,
|
|
1367
|
+
'Dependency includes to carry into the shared types header for Partial a_test::module:',
|
|
1368
|
+
Verbosity::OBNOXIOUS
|
|
1369
|
+
)
|
|
1370
|
+
|
|
1371
|
+
@partializer.remap_types_header_includes(
|
|
1372
|
+
name: 'module',
|
|
1373
|
+
includes: [UserInclude.new('foundation.h')],
|
|
1374
|
+
partials: {},
|
|
1375
|
+
test: 'a_test'
|
|
1376
|
+
)
|
|
1377
|
+
end
|
|
1378
|
+
end
|
|
1379
|
+
|
|
1155
1380
|
context "#extract_module_contents" do
|
|
1156
1381
|
before(:each) do
|
|
1157
1382
|
@name = 'TestModule'
|