Class: TestBuildExecutor

Inherits:
Object show all
Includes:
TestInvokerTypes
Defined in:
lib/ceedling/test_invoker/test_build_executor.rb

Instance Method Summary collapse

Instance Method Details

#convert_libraries_to_argumentsObject



530
531
532
533
534
535
536
# File 'lib/ceedling/test_invoker/test_build_executor.rb', line 530

def convert_libraries_to_arguments()
  args = ((@configurator.project_config_hash[:libraries_test] || []) + ((defined? LIBRARIES_SYSTEM) ? LIBRARIES_SYSTEM : [])).flatten
  if (defined? LIBRARIES_FLAG)
    args.map! { |v| LIBRARIES_FLAG.gsub( /\$\{1\}/, v ) }
  end
  return args
end

#generate_executable_now(context:, build_path:, executable:, objects:, flags:, lib_args:, lib_paths:, options:) ⇒ Object


Helper methods



473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
# File 'lib/ceedling/test_invoker/test_build_executor.rb', line 473

def generate_executable_now(context:, build_path:, executable:, objects:, flags:, lib_args:, lib_paths:, options:)
  begin
    @generator.generate_executable_file(
      options[:test_linker],
      context,
      objects.map { |v| "\"#{v}\"" },
      flags,
      executable,
      @file_path_utils.form_test_build_map_filepath( build_path, executable ),
      lib_args,
      lib_paths
    )
  rescue ShellException => ex
    if ex.shell_result[:output] =~ /symbol/i
      notice =    "If the linker reports missing symbols, the following may be to blame:\n" +
                  "  1. This test lacks #include statements corresponding to needed source files (see note below).\n" +
                  "  2. Project file paths omit source files corresponding to #include statements in this test.\n" +
                  "  3. Complex macros, #ifdefs, etc. have obscured correct #include statements in this test.\n" +
                  "  4. Your project is attempting to mix C++ and C file extensions (not supported).\n"
      if @configurator.project_use_mocks
        notice += "  5. This test does not #include needed mocks (that triggers their generation).\n"
      end

      notice +=   "\n"
      notice +=   "NOTE: A test file directs the build of a test executable with #include statemetns:\n" +
                  "  * By convention, Ceedling assumes header filenames correspond to source filenames.\n" +
                  "  * Which code files to compile and link are determined by #include statements.\n"
      if @configurator.project_use_mocks
        notice += "  * An #include statement convention directs the generation of mocks from header files.\n"
      end

      notice +=   "\n"
      notice +=   "OPTIONS:\n" +
                  "  1. Doublecheck this test's #include statements.\n" +
                  "  2. Simplify complex macros or fully specify symbols for this test in :project ↳ :defines.\n" +
                  "  3. If no header file corresponds to the needed source file, use the TEST_SOURCE_FILE()\n" +
                  "     build diective macro in this test to inject a source file into the build.\n\n" +
                  "See the docs on conventions, paths, preprocessing, compilation symbols, and build directive macros.\n\n"

      @loginator.log( notice, Verbosity::COMPLAIN, LogLabels::NOTICE )
    end

    raise ex
  end
end

#get_library_paths_to_argumentsObject



538
539
540
541
542
543
544
# File 'lib/ceedling/test_invoker/test_build_executor.rb', line 538

def get_library_paths_to_arguments()
  paths = (defined? PATHS_LIBRARIES) ? (PATHS_LIBRARIES || []).clone : []
  if (defined? LIBRARIES_PATH_FLAG)
    paths.map! { |v| LIBRARIES_PATH_FLAG.gsub( /\$\{1\}/, v ) }
  end
  return paths
end

#run_fixture_now(context:, test_name:, test_filepath:, executable:, result:, options:) ⇒ Object



519
520
521
522
523
524
525
526
527
528
# File 'lib/ceedling/test_invoker/test_build_executor.rb', line 519

def run_fixture_now(context:, test_name:, test_filepath:, executable:, result:, options:)
  @generator.generate_test_results(
    tool:          options[:test_fixture],
    context:       context,
    test_name:     test_name,
    test_filepath: test_filepath,
    executable:    executable,
    result:        result
  )
end

#setupObject



31
32
33
# File 'lib/ceedling/test_invoker/test_build_executor.rb', line 31

def setup()
  @context_extractor = @test_context_extractor
end

#stage_build_executables(state) ⇒ Object

Stage 16: Link test executables.



426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
# File 'lib/ceedling/test_invoker/test_build_executor.rb', line 426

def stage_build_executables(state)
  lib_args  = convert_libraries_to_arguments()
  lib_paths = get_library_paths_to_arguments()

  @batchinator.exec(workload: :compile, things: state.testables) do |_, testable|
    remove_partials_source_objects( testable.objects, testable.partials.configs )

    arg_hash = {
      context:    state.context,
      build_path: testable.paths[:build],
      executable: testable.executable,
      objects:    testable.objects,
      flags:      testable.link_flags,
      lib_args:   lib_args,
      lib_paths:  lib_paths,
      options:    state.options
    }

    generate_executable_now( **arg_hash )
  end
end

#stage_build_objects(state) ⇒ Object

Stage 15: Compile all test build objects in parallel.



411
412
413
414
415
416
417
418
419
420
421
422
423
# File 'lib/ceedling/test_invoker/test_build_executor.rb', line 411

def stage_build_objects(state)
  @batchinator.exec(workload: :compile, things: state.objects_list) do |obj|
    src = @file_finder.find_build_input_file( filepath: obj[:obj], context: state.context )
    compile_test_component(
      tool:    obj[:tool],
      context: state.context,
      test:    obj[:test],
      source:  src,
      object:  obj[:obj],
      state:   state
    )
  end
end

#stage_collect_runner_details(state) ⇒ Object

Stage 12: Collect test runner details (test case names) from preprocessed test files.



381
382
383
384
385
386
387
388
389
390
391
392
# File 'lib/ceedling/test_invoker/test_build_executor.rb', line 381

def stage_collect_runner_details(state)
  @batchinator.exec(workload: :compile, things: state.testables) do |_, testable|
    msg = @reportinator.generate_module_progress(
      operation:   'Parsing test case names',
      module_name: testable.name,
      filename:    File.basename( testable.filepath )
    )
    @loginator.log( msg )

    @context_extractor.collect_test_runner_details( testable.filepath, testable.runner[:input_filepath] )
  end
end

#stage_execute(state) ⇒ Object

Stage 17: Execute test fixtures and collect results.



449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
# File 'lib/ceedling/test_invoker/test_build_executor.rb', line 449

def stage_execute(state)
  @batchinator.exec(workload: :test, things: state.testables) do |_, testable|
    begin
      arg_hash = {
        context:       state.context,
        test_name:     testable.name,
        test_filepath: testable.filepath,
        executable:    testable.executable,
        result:        testable.results_pass,
        options:       state.options
      }

      run_fixture_now( **arg_hash )

    ensure
      @plugin_manager.post_test( testable.filepath )
    end
  end
end

#stage_generate_mocks(state) ⇒ Object

Stage 10: Generate mocks for all tests.



311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
# File 'lib/ceedling/test_invoker/test_build_executor.rb', line 311

def stage_generate_mocks(state)
  @batchinator.exec(workload: :compile, things: state.mocks_list) do |mock|
    details  = mock[:details]
    testable = mock[:testable]

    output_path = File.join( testable.paths[:mocks], details[:path] )
    @file_wrapper.mkdir( output_path )

    arg_hash = {
      context:        state.context,
      mock:           mock[:name],
      test:           testable.name,
      input_filepath: details[:input],
      output_path:    output_path
    }

    @generator.generate_mock( **arg_hash )
  end
end

#stage_generate_partials(state) ⇒ Object

Stage 8: Extract and generate partial implementation and interface files.



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/ceedling/test_invoker/test_build_executor.rb', line 160

def stage_generate_partials(state)
  directives_only = @configurator.test_build_preprocess_directives_only_available

  partials = []
  state.testables.each do |_, testable|
    next if testable.partials.configs.empty?
    testable.partials.configs.each do |_, config|
      partials << { config: config, testable: testable }
    end
  end

  @batchinator.exec(workload: :compile, things: partials) do |partial|
    config   = partial[:config]
    testable = partial[:testable]
    name     = testable.name

    module_contents = @partializer.extract_module_contents(
      name,
      config,
      !directives_only
    )

    @partializer.validate_config( c_module: module_contents, config: config, name: name )

    @partializer.sanitize( module_contents )

    implementation = @partializer.extract_implementation_functions(
      test:        name,
      partial:     config.module,
      definitions: module_contents.function_definitions,
      config:      config
    )

    interface = @partializer.extract_interface_functions(
      test:         name,
      partial:      config.module,
      definitions:  module_contents.function_definitions,
      declarations: module_contents.function_declarations,
      config:       config
    )

    @partializer.validate_extracted_functions(
      name:      name,
      partial:   config.module,
      impl:      implementation,
      interface: interface
    )

    arg_hash = {
      test:                 name,
      partial:              config.module,
      function_definitions: implementation,
      c_module:             module_contents,
      header_includes:      @partializer.remap_implementation_header_includes(
                              name:     config.module,
                              includes: (config.source.includes + config.header.includes),
                              partials: testable.partials.configs,
                              test:     name
                            ),
      source_includes:      @partializer.remap_implementation_source_includes(
                              name:     config.module,
                              includes: (config.source.includes + config.header.includes),
                              partials: testable.partials.configs,
                              test:     name
                            ),
      input_filepath:       config.source.filepath,
      output_path:          testable.paths[:partials]
    }

    unless implementation.nil?
      @generator.generate_partial_implementation( **arg_hash )
      state.lock.synchronize { testable.partials.tests << config.module }
    end

    arg_hash = {
      test:                  name,
      partial:               config.module,
      function_declarations: interface,
      includes:              @partializer.remap_interface_header_includes(
                               name:     config.module,
                               includes: (config.source.includes + config.header.includes),
                               partials: testable.partials.configs,
                               test:     name
                             ),
      c_module:              module_contents,
      input_filepath:        config.header.filepath,
      output_path:           testable.paths[:partials]
    }

    unless interface.nil?
      @generator.generate_partial_interface( **arg_hash )
      state.lock.synchronize { testable.partials.mocks << config.module }
    end
  end
end

#stage_generate_runners(state) ⇒ Object

Stage 13: Generate test runner files.



395
396
397
398
399
400
401
402
403
404
405
406
407
408
# File 'lib/ceedling/test_invoker/test_build_executor.rb', line 395

def stage_generate_runners(state)
  @batchinator.exec(workload: :compile, things: state.testables) do |_, testable|
    arg_hash = {
      context:         state.context,
      mocks:           @context_extractor.lookup_mock_header_includes_list( testable.filepath ),
      includes:        @context_extractor.lookup_nonmock_header_includes_list( testable.filepath ),
      test_filepath:   testable.filepath,
      input_filepath:  testable.runner[:input_filepath],
      runner_filepath: testable.runner[:output_filepath]
    }

    @generator.generate_test_runner( **arg_hash )
  end
end

#stage_preprocess_mocks(state) ⇒ Object

Stage 9: Preprocess header files to be mocked.



257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# File 'lib/ceedling/test_invoker/test_build_executor.rb', line 257

def stage_preprocess_mocks(state)
  directives_only = @configurator.test_build_preprocess_directives_only_available

  # Generate directive-only preprocessor output if available
  @batchinator.exec(workload: :compile, things: state.mocks_list) do |mock|
    details  = mock[:details]
    testable = mock[:testable]
    name     = testable.name
    filepath = details[:source]

    arg_hash = {
      filepath:      filepath,
      test:          name,
      flags:         testable.preprocess_flags,
      include_paths: testable.search_paths,
      vendor_paths:  [@configurator.project_build_vendor_ceedling_path],
      defines:       testable.preprocess_defines
    }

    _filepath = @preprocessinator.generate_directives_only_output( **arg_hash )

    if _filepath.nil?
      msg = "Failed to generate directive-only preprocessor output (fallback methods will be used) for #{filepath}"
      @loginator.log( msg, Verbosity::COMPLAIN )
    end

    mock[:directives_only_filepath] = _filepath
  end if directives_only

  # Preprocess and assemble header files to be mocked
  @batchinator.exec(workload: :compile, things: state.mocks_list) do |mock|
    details                  = mock[:details]
    testable                 = mock[:testable]
    directives_only_filepath = mock[:directives_only_filepath]

    extras = (@configurator.cmock_treat_inlines == :include)

    arg_hash = {
      test:                     testable.name,
      filepath:                 details[:source],
      directives_only_filepath: directives_only_filepath,
      fallback:                 (!directives_only or directives_only_filepath.nil?),
      flags:                    testable.preprocess_flags,
      include_paths:            testable.search_paths,
      vendor_paths:             [@configurator.project_build_vendor_ceedling_path],
      defines:                  testable.preprocess_defines,
      extras:                   extras
    }

    @preprocessinator.preprocess_mockable_header_file( **arg_hash )
  end
end

#stage_preprocess_partial_headers(state) ⇒ Object

Stage 6: Preprocess partial header files for extract-and-generate pass.



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/ceedling/test_invoker/test_build_executor.rb', line 36

def stage_preprocess_partial_headers(state)
  directives_only = @configurator.test_build_preprocess_directives_only_available

  # Generate directive-only preprocessor output if available
  @batchinator.exec(workload: :compile, things: state.partials_headers) do |details|
    config   = details[:config]
    testable = details[:testable]
    name     = testable.name

    arg_hash = {
      filepath:      config.filepath,
      test:          name,
      flags:         testable.preprocess_flags,
      include_paths: testable.search_paths,
      vendor_paths:  [@configurator.project_build_vendor_ceedling_path],
      defines:       testable.preprocess_defines
    }

    details[:directives_only_filepath] = @preprocessinator.generate_directives_only_output( **arg_hash )
  end if directives_only

  # Preprocess and assemble header files
  @batchinator.exec(workload: :compile, things: state.partials_headers) do |details|
    config                   = details[:config]
    testable                 = details[:testable]
    name                     = testable.name
    directives_only_filepath = details[:directives_only_filepath]

    arg_hash = {
      test:                     name,
      filepath:                 config.filepath,
      directives_only_filepath: directives_only_filepath,
      fallback:                 (!directives_only or directives_only_filepath.nil?),
      flags:                    testable.preprocess_flags,
      include_paths:            testable.search_paths,
      vendor_paths:             [@configurator.project_build_vendor_ceedling_path],
      defines:                  testable.preprocess_defines
    }

    config.directives_only_filepath, config.includes = @preprocessinator.preprocess_partial_header_file_preserve_macros( **arg_hash )
  end

  # Full-preprocess partial header files for expanded signature extraction.
  @batchinator.exec(workload: :compile, things: state.partials_headers) do |details|
    config   = details[:config]
    testable = details[:testable]
    name     = testable.name

    arg_hash = {
      filepath:      config.filepath,
      test:          name,
      flags:         testable.preprocess_flags,
      include_paths: testable.search_paths,
      vendor_paths:  [@configurator.project_build_vendor_ceedling_path],
      defines:       testable.preprocess_defines
    }

    config.full_expansion_filepath = @preprocessinator.preprocess_partial_header_expand_macros( **arg_hash )
  end
end

#stage_preprocess_partial_sources(state) ⇒ Object

Stage 7: Preprocess partial source files for extract-and-generate pass.



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/ceedling/test_invoker/test_build_executor.rb', line 98

def stage_preprocess_partial_sources(state)
  directives_only = @configurator.test_build_preprocess_directives_only_available

  # Generate directive-only preprocessor output if available
  @batchinator.exec(workload: :compile, things: state.partials_sources) do |details|
    config   = details[:config]
    testable = details[:testable]
    name     = testable.name

    arg_hash = {
      filepath:      config.filepath,
      test:          name,
      flags:         testable.preprocess_flags,
      include_paths: testable.search_paths,
      vendor_paths:  [@configurator.project_build_vendor_ceedling_path],
      defines:       testable.preprocess_defines
    }

    details[:directives_only_filepath] = @preprocessinator.generate_directives_only_output( **arg_hash )
  end if directives_only

  # Preprocess and assemble source files
  @batchinator.exec(workload: :compile, things: state.partials_sources) do |details|
    config                   = details[:config]
    testable                 = details[:testable]
    name                     = testable.name
    directives_only_filepath = details[:directives_only_filepath]

    arg_hash = {
      test:                     name,
      filepath:                 config.filepath,
      directives_only_filepath: directives_only_filepath,
      fallback:                 (!directives_only or directives_only_filepath.nil?),
      flags:                    testable.preprocess_flags,
      include_paths:            testable.search_paths,
      vendor_paths:             [@configurator.project_build_vendor_ceedling_path],
      defines:                  testable.preprocess_defines
    }

    config.directives_only_filepath, config.includes = @preprocessinator.preprocess_partial_source_file_preserve_macros( **arg_hash )
  end

  # Full-preprocess partial source files for expanded signature extraction.
  @batchinator.exec(workload: :compile, things: state.partials_sources) do |details|
    config   = details[:config]
    testable = details[:testable]
    name     = testable.name

    arg_hash = {
      filepath:      config.filepath,
      test:          name,
      flags:         testable.preprocess_flags,
      include_paths: testable.search_paths,
      vendor_paths:  [@configurator.project_build_vendor_ceedling_path],
      defines:       testable.preprocess_defines
    }

    config.full_expansion_filepath = @preprocessinator.preprocess_partial_source_expand_macros( **arg_hash )
  end
end

#stage_preprocess_test_files(state) ⇒ Object

Stage 11: Preprocess test files and extract source build directives.



332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
# File 'lib/ceedling/test_invoker/test_build_executor.rb', line 332

def stage_preprocess_test_files(state)
  directives_only = @configurator.test_build_preprocess_directives_only_available

  @batchinator.exec(workload: :compile, things: state.testables) do |_, testable|
    filepath                 = testable.filepath
    filename                 = File.basename( filepath )
    name                     = testable.name
    directives_only_filepath = testable.preprocess[:directives_only][:filepath]

    fallback = (!directives_only or directives_only_filepath.nil?)

    arg_hash = {
      test:                     name,
      filepath:                 filepath,
      directives_only_filepath: directives_only_filepath,
      fallback:                 fallback,
      includes:                 @context_extractor.lookup_all_header_includes_list( testable.filepath ),
      flags:                    testable.preprocess_flags,
      include_paths:            testable.search_paths,
      vendor_paths:             [@configurator.project_build_vendor_ceedling_path],
      defines:                  testable.preprocess_defines
    }

    _filepath = @preprocessinator.preprocess_test_file( **arg_hash )

    state.lock.synchronize { testable.runner[:input_filepath] = _filepath }

    msg = @reportinator.generate_progress( "Parsing #{filename} for test source directive macros" )
    @loginator.log( msg )

    if fallback
      _filepath = filepath
    else
      _filepath = @file_path_utils.form_preprocessed_file_compacted_directives_only_filepath( filepath, name )
    end

    @context_extractor.collect_simple_context_from_file(
      _filepath,
      filepath,
      TestContextExtractor::Context::BUILD_DIRECTIVE_SOURCE_FILES
    )

    state.testables.each do |_, t|
      validate_build_directive_source_files( test: name, filepath: t.filepath )
    end
  end
end