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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/GIT_COMMIT_SHA +1 -1
  3. data/README.md +4 -3
  4. data/docs/Changelog.md +28 -0
  5. data/docs/KnownIssues.md +12 -2
  6. data/docs/SECURITY.md +40 -2
  7. data/docs/mkdocs/development/index.md +11 -0
  8. data/docs/mkdocs/plugins/fff.md +1 -1
  9. data/docs/mkdocs/plugins/gcov/gcovr.md +117 -3
  10. data/docs/mkdocs/plugins/gcov/reportgenerator.md +110 -4
  11. data/lib/ceedling/c_extractor/c_extractor_preprocessing.rb +29 -1
  12. data/lib/ceedling/file_path_utils.rb +4 -0
  13. data/lib/ceedling/generators/generator.rb +11 -6
  14. data/lib/ceedling/generators/generator_partials.rb +54 -18
  15. data/lib/ceedling/includes/includes.rb +8 -1
  16. data/lib/ceedling/objects.yml +2 -0
  17. data/lib/ceedling/partials/partializer.rb +204 -96
  18. data/lib/ceedling/test_invoker/test_build_executor.rb +21 -7
  19. data/lib/version.rb +1 -1
  20. data/site-local/development/index.html +7 -0
  21. data/site-local/plugins/fff.html +1 -1
  22. data/site-local/plugins/gcov/gcovr.html +156 -3
  23. data/site-local/plugins/gcov/reportgenerator.html +201 -14
  24. data/site-local/sitemap.xml.gz +0 -0
  25. data/spec/system/partials_types_header_spec.rb +166 -0
  26. data/spec/units/c_extractor/c_extractor_integration_spec.rb +18 -0
  27. data/spec/units/c_extractor/c_extractor_preprocessing_spec.rb +77 -0
  28. data/spec/units/generators/generator_partials_spec.rb +130 -17
  29. data/spec/units/includes/includes_spec.rb +21 -3
  30. data/spec/units/partials/partializer_spec.rb +225 -0
  31. data/vendor/unity/auto/generate_test_runner.rb +3 -1
  32. data/vendor/unity/auto/unity_test_summary.rb +2 -2
  33. data/vendor/unity/docs/UnityChangeLog.md +7 -0
  34. data/vendor/unity/docs/UnityConfigurationGuide.md +4 -0
  35. data/vendor/unity/src/unity_internals.h +21 -7
  36. data/vendor/unity/test/Makefile +9 -2
  37. data/vendor/unity/test/rakefile_helper.rb +1 -0
  38. data/vendor/unity/test/tests/test_generate_test_runner.rb +49 -0
  39. data/vendor/unity/test/tests/test_unity_test_summary.rb +58 -0
  40. 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'