ceedling 1.2.1 → 1.2.2
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 +11 -9
- data/Rakefile +2 -1
- data/assets/features/ceedling +1 -1
- data/assets/fixtures/test_source_file_duplicate_basenames/test_calc_typo.c +14 -0
- data/assets/fixtures/tests_with_duplicate_test_names/test_foo.c +21 -0
- data/bin/actionator.rb +6 -12
- data/bin/app_cfg.rb +14 -1
- data/bin/ceedling +13 -3
- data/bin/cli.rb +25 -6
- data/bin/cli_handler.rb +113 -73
- data/bin/cli_helper.rb +51 -78
- data/bin/installation_selector.rb +81 -0
- data/bin/launchinator.rb +69 -0
- data/bin/mixin_resolvinator.rb +26 -36
- data/bin/objects.yml +13 -0
- data/bin/path_validator.rb +25 -29
- data/bin/versionator.rb +47 -48
- data/ceedling.gemspec +19 -15
- data/docs/BreakingChanges.md +70 -1
- data/docs/Changelog.md +74 -3
- data/docs/KnownIssues.md +90 -4
- data/docs/ReleaseNotes.md +13 -0
- data/lib/ceedling/build_log_writer.rb +33 -0
- data/lib/ceedling/config/build_root_policy.rb +70 -0
- data/lib/ceedling/config/configurator.rb +14 -16
- data/lib/ceedling/config/configurator_builder.rb +58 -28
- data/lib/ceedling/config/configurator_paths_validator.rb +179 -0
- data/lib/ceedling/config/configurator_plugins.rb +1 -1
- data/lib/ceedling/config/configurator_setup.rb +15 -28
- data/lib/ceedling/config/configurator_validator.rb +1 -110
- data/lib/ceedling/defaults.rb +4 -4
- data/lib/ceedling/dependencies/README.md +18 -16
- data/lib/ceedling/dependencies/dependency_cache_store.rb +20 -3
- data/lib/ceedling/dependencies/dependency_hasher.rb +35 -1
- data/lib/ceedling/filename_extension.rb +7 -1
- data/lib/ceedling/filesystem/FILE_TREE.md +253 -0
- data/lib/ceedling/filesystem/PATH_HANDLING.md +304 -0
- data/lib/ceedling/filesystem/README.md +41 -0
- data/lib/ceedling/filesystem/build_subpath.rb +102 -0
- data/lib/ceedling/filesystem/destination_collision.rb +25 -0
- data/lib/ceedling/{file_finder.rb → filesystem/file_finder.rb} +159 -104
- data/lib/ceedling/{file_finder_helper.rb → filesystem/file_finder_helper.rb} +36 -31
- data/lib/ceedling/filesystem/file_path_collection_utils.rb +183 -0
- data/lib/ceedling/{file_path_utils.rb → filesystem/file_path_utils.rb} +108 -97
- data/lib/ceedling/{file_system_wrapper.rb → filesystem/file_system_wrapper.rb} +6 -5
- data/lib/ceedling/filesystem/file_tree.rb +241 -0
- data/lib/ceedling/{file_wrapper.rb → filesystem/file_wrapper.rb} +45 -38
- data/lib/ceedling/filesystem/path_exclusions.rb +30 -0
- data/lib/ceedling/{path_matcher.rb → filesystem/path_matcher.rb} +27 -23
- data/lib/ceedling/{path_mirror.rb → filesystem/path_mirror.rb} +19 -18
- data/lib/ceedling/filesystem/path_spellings.rb +55 -0
- data/lib/ceedling/filesystem/project_paths.rb +184 -0
- data/lib/ceedling/filesystem/working_dir.rb +40 -0
- data/lib/ceedling/generators/generator.rb +5 -6
- data/lib/ceedling/generators/generator_partials.rb +1 -1
- data/lib/ceedling/generators/generator_test_results_backtrace.rb +7 -7
- data/lib/ceedling/handoff.rb +121 -0
- data/lib/ceedling/includes/include_factory.rb +3 -3
- data/lib/ceedling/includes/include_pathinator.rb +67 -10
- data/lib/ceedling/includes/includes.rb +1 -1
- data/lib/ceedling/installation.rb +92 -0
- data/lib/ceedling/library_resolver.rb +99 -0
- data/lib/ceedling/objects.yml +23 -2
- data/lib/ceedling/partials/partializer.rb +4 -4
- data/lib/ceedling/partials/partializer_config.rb +3 -1
- data/lib/ceedling/plugins/report_log_writer_plugin.rb +3 -5
- data/lib/ceedling/preprocess/preprocessinator_file_assembler.rb +1 -1
- data/lib/ceedling/preprocess/preprocessinator_includes_handler.rb +1 -1
- data/lib/ceedling/preprocess/preprocessinator_line_marker_includes_extractor.rb +1 -1
- data/lib/ceedling/rake_app/rakefile_component_resolver.rb +10 -17
- data/lib/ceedling/rakefile.rb +15 -1
- data/lib/ceedling/rakefiles/base/tasks_base.rake +1 -1
- data/lib/ceedling/rakefiles/release/rules_release.rake +4 -7
- data/lib/ceedling/release_invoker/release_build_executor.rb +33 -44
- data/lib/ceedling/release_invoker/release_build_planner.rb +14 -6
- data/lib/ceedling/reportinator.rb +3 -4
- data/lib/ceedling/setupinator.rb +94 -90
- data/lib/ceedling/test_context_extractor.rb +5 -1
- data/lib/ceedling/test_invoker/PIPELINE.md +9 -7
- data/lib/ceedling/test_invoker/mock_locator.rb +60 -2
- data/lib/ceedling/test_invoker/partials_manager.rb +12 -0
- data/lib/ceedling/test_invoker/standin_headerinator.rb +22 -13
- data/lib/ceedling/test_invoker/test_build_executor.rb +24 -40
- data/lib/ceedling/test_invoker/test_build_planner.rb +37 -18
- data/lib/ceedling/test_invoker/test_build_setup.rb +17 -12
- data/lib/ceedling/test_invoker/test_build_validations.rb +16 -31
- data/lib/ceedling/test_invoker/test_file_preprocessinator.rb +1 -1
- data/lib/ceedling/test_invoker/test_invoker.rb +6 -1
- data/lib/ceedling/test_invoker/test_pipeline_helpers.rb +1 -1
- data/lib/ceedling/test_invoker/test_source_file_directive_resolver.rb +40 -20
- data/lib/ceedling/tool_executor.rb +9 -3
- data/lib/ceedling/yaml_wrapper.rb +3 -2
- data/lib/version.rb +1 -1
- data/plugins/bullseye/config/defaults_bullseye.rb +1 -1
- data/plugins/bullseye/lib/bullseye.rb +9 -7
- data/plugins/cppcheck/config/defaults_cppcheck.rb +1 -1
- data/plugins/dependencies/config/defaults.yml +6 -6
- data/plugins/dependencies/dependencies.rake +4 -1
- data/plugins/dependencies/lib/dependencies.rb +18 -8
- data/plugins/dependencies/lib/dependency_spec.rb +17 -5
- data/plugins/fff/lib/fff.rb +45 -39
- data/plugins/fff/spec/fff_cmock_wrapper_spec.rb +128 -0
- data/plugins/gcov/config/defaults_gcov.rb +1 -1
- data/plugins/gcov/lib/console_reportinator.rb +4 -18
- data/plugins/gcov/lib/gcov.rb +9 -2
- data/plugins/gcov/lib/gcovr_reportinator.rb +8 -40
- data/plugins/gcov/lib/gcovr_thresholds.rb +74 -0
- data/plugins/gcov/lib/reportgenerator_reportinator.rb +1 -1
- data/plugins/module_generator/lib/module_generator.rb +3 -3
- data/plugins/report_tests_teamcity_stdout/lib/report_tests_teamcity_stdout.rb +53 -80
- data/plugins/sanitize/lib/sanitize_records.rb +3 -6
- data/plugins/valgrind/lib/valgrind.rb +14 -7
- data/site-local/configuration/reference/cmock.html +2 -1
- data/site-local/configuration/reference/files.html +9 -3
- data/site-local/configuration/reference/libraries.html +21 -3
- data/site-local/configuration/reference/paths.html +29 -6
- data/site-local/configuration/reference/project.html +17 -7
- data/site-local/configuration/reference/release-build.html +2 -0
- data/site-local/configuration/reference/tools.html +9 -0
- data/site-local/configuration/which-ceedling.html +377 -17
- data/site-local/development/releases.html +5 -1
- data/site-local/development/workflow.html +48 -43
- data/site-local/getting-started/builds.html +2 -0
- data/site-local/getting-started/command-line.html +56 -4
- data/site-local/plugins/dependencies.html +6 -3
- data/site-local/plugins/fff.html +42 -11
- data/site-local/plugins/gcov/gcovr.html +15 -12
- data/site-local/plugins/gcov/setup.html +2 -1
- data/site-local/plugins/report-tests-raw-output-log.html +3 -1
- data/site-local/plugins/report-tests-teamcity-stdout.html +6 -3
- data/site-local/plugins/sanitize.html +7 -6
- data/site-local/plugins/valgrind.html +8 -7
- data/site-local/project/upgrade.html +3 -0
- data/site-local/reference/command-line.html +9 -2
- data/site-local/sitemap.xml.gz +0 -0
- data/site-local/snapshot/plugins/bullseye/config/defaults_bullseye.rb +1 -1
- data/site-local/snapshot/plugins/gcov/config/defaults_gcov.rb +1 -1
- data/site-local/testing-guide/conventions.html +53 -0
- data/spec/integration/actionator_file_operations_spec.rb +91 -1
- data/spec/integration/compile_commands_json_db_spec.rb +1 -1
- data/spec/integration/configurator_validation_spec.rb +1 -1
- data/spec/integration/dependencies/dependency_tracker_cache_hardening_spec.rb +26 -0
- data/spec/integration/dependencies/dependency_tracker_core_spec.rb +22 -0
- data/spec/integration/launcher_handoff_spec.rb +297 -0
- data/spec/integration/mixin_loading_spec.rb +1 -1
- data/spec/integration/module_generator_spec.rb +3 -3
- data/spec/integration/nested_test_artifacts_spec.rb +99 -0
- data/spec/integration/partials_module_resolution_spec.rb +7 -4
- data/spec/integration/path_intake_scenarios_spec.rb +101 -0
- data/spec/integration/path_scenarios_spec.rb +322 -0
- data/spec/integration/quote_include_isolator_spec.rb +1 -1
- data/spec/integration/report_build_warnings_log_spec.rb +1 -1
- data/spec/integration/report_tests_log_factory_spec.rb +1 -1
- data/spec/integration/test_executable_manifest_spec.rb +1 -1
- data/spec/integration/test_includes_extractinator_spec.rb +2 -1
- data/spec/integration/test_naming_spec.rb +198 -0
- data/spec/integration/test_source_file_checks_spec.rb +166 -0
- data/spec/integration/tool_validator_spec.rb +1 -1
- data/spec/integration/upgrade_docs_spec.rb +84 -0
- data/spec/integration/vendor_copy_transient_io_retry_spec.rb +74 -5
- data/spec/support/file_tree_helper.rb +88 -0
- data/spec/support/integration/configurator_integration_helper.rb +6 -6
- data/spec/support/integration/dependency_tracker_integration_helper.rb +1 -1
- data/spec/support/integration/partials_generation_integration_helper.rb +2 -2
- data/spec/support/integration/path_scenario_harness.rb +173 -0
- data/spec/support/integration/spec_integration_helper.rb +1 -1
- data/spec/support/integration/test_includes_harness.rb +13 -2
- data/spec/support/spec_helper.rb +2 -0
- data/spec/support/system/spec_system_helper.rb +7 -6
- data/spec/support/system/system_context.rb +21 -3
- data/spec/support/working_dir_tracking.rb +49 -0
- data/spec/system/build_output_mirroring_spec.rb +2 -2
- data/spec/system/conditional_mock_includes_spec.rb +1 -1
- data/spec/system/delta_builds_spec.rb +137 -11
- data/spec/system/gcov_deployment_spec.rb +1 -0
- data/spec/system/header_include_path_validation_spec.rb +4 -4
- data/spec/system/launcher_handoff_spec.rb +82 -0
- data/spec/system/mock_include_folders_spec.rb +91 -0
- data/spec/system/parent_directory_reference_spec.rb +5 -3
- data/spec/system/path_handling_probes_spec.rb +192 -0
- data/spec/system/preprocessing_parameterized_tests_spec.rb +1 -1
- data/spec/system/rake_release_task_path_disambiguation_spec.rb +6 -6
- data/spec/system/rake_test_task_path_disambiguation_spec.rb +85 -0
- data/spec/system/special_path_characters_spec.rb +86 -0
- data/spec/system/support/common_test_cases.rb +23 -21
- data/spec/system/support/gcov_common_test_cases.rb +37 -9
- data/spec/system/support/sanitize_common_test_cases.rb +2 -2
- data/spec/system/support/valgrind_common_test_cases.rb +12 -12
- data/spec/system/test_pipeline_stop_points_spec.rb +4 -4
- data/spec/system/test_runner_system_include_spec.rb +1 -1
- data/spec/system/test_source_file_directive_spec.rb +34 -5
- data/spec/units/bin/actionator_spec.rb +14 -17
- data/spec/units/bin/app_cfg_spec.rb +37 -0
- data/spec/units/bin/cli_handler_spec.rb +282 -6
- data/spec/units/bin/cli_helper_spec.rb +116 -45
- data/spec/units/bin/installation_selector_spec.rb +114 -0
- data/spec/units/bin/launchinator_spec.rb +171 -0
- data/spec/units/bin/path_validator_spec.rb +10 -0
- data/spec/units/bin/versionator_spec.rb +38 -6
- data/spec/units/build_log_writer_spec.rb +57 -0
- data/spec/units/config/build_root_policy_spec.rb +61 -0
- data/spec/units/config/configurator_builder_spec.rb +37 -6
- data/spec/units/config/configurator_paths_validator_spec.rb +292 -0
- data/spec/units/config/configurator_setup_spec.rb +9 -65
- data/spec/units/config/configurator_spec.rb +27 -5
- data/spec/units/config/configurator_validator_spec.rb +1 -172
- data/spec/units/default_tools_quoting_spec.rb +83 -0
- data/spec/units/dependencies/dependency_cache_store_spec.rb +33 -2
- data/spec/units/dependencies/dependency_debug_tree_spec.rb +1 -1
- data/spec/units/dependencies/dependency_hasher_spec.rb +42 -1
- data/spec/units/dependencies/dependency_path_normalizer_spec.rb +1 -1
- data/spec/units/dependencies/dependency_tracker_spec.rb +3 -1
- data/spec/units/filename_extension_spec.rb +11 -0
- data/spec/units/filesystem/build_subpath_spec.rb +122 -0
- data/spec/units/filesystem/destination_collision_spec.rb +30 -0
- data/spec/units/{file_finder_helper_spec.rb → filesystem/file_finder_helper_spec.rb} +46 -1
- data/spec/units/{file_finder_spec.rb → filesystem/file_finder_spec.rb} +188 -22
- data/spec/units/{file_path_collection_utils_spec.rb → filesystem/file_path_collection_utils_spec.rb} +181 -1
- data/spec/units/{file_path_utils_spec.rb → filesystem/file_path_utils_spec.rb} +204 -87
- data/spec/units/filesystem/file_system_wrapper_spec.rb +33 -0
- data/spec/units/filesystem/file_tree_spec.rb +511 -0
- data/spec/units/filesystem/file_tree_windows_spec.rb +78 -0
- data/spec/units/{file_wrapper_spec.rb → filesystem/file_wrapper_spec.rb} +95 -12
- data/spec/units/{path_matcher_spec.rb → filesystem/path_matcher_spec.rb} +12 -1
- data/spec/units/{path_mirror_spec.rb → filesystem/path_mirror_spec.rb} +32 -4
- data/spec/units/filesystem/project_paths_spec.rb +147 -0
- data/spec/units/filesystem/working_dir_spec.rb +71 -0
- data/spec/units/generators/generator_spec.rb +12 -6
- data/spec/units/generators/generator_test_results_backtrace_spec.rb +50 -46
- data/spec/units/generators/generator_test_results_spec.rb +1 -1
- data/spec/units/handoff_spec.rb +205 -0
- data/spec/units/includes/include_factory_spec.rb +3 -3
- data/spec/units/includes/include_pathinator_spec.rb +46 -0
- data/spec/units/installation_spec.rb +228 -0
- data/spec/units/library_resolver_spec.rb +157 -0
- data/spec/units/mock_locator_spec.rb +76 -1
- data/spec/units/partials/partializer_config_spec.rb +7 -0
- data/spec/units/partials/partializer_spec.rb +1 -0
- data/spec/units/partials_manager_spec.rb +17 -0
- data/spec/units/plugins/bullseye_spec.rb +15 -2
- data/spec/units/plugins/console_reportinator_spec.rb +18 -20
- data/spec/units/plugins/dependencies_spec.rb +33 -7
- data/spec/units/plugins/dependency_spec_spec.rb +17 -0
- data/spec/units/plugins/gcov_spec.rb +28 -3
- data/spec/units/plugins/gcovr_reportinator_spec.rb +19 -47
- data/spec/units/plugins/gcovr_thresholds_spec.rb +95 -0
- data/spec/units/plugins/module_generator_spec.rb +5 -5
- data/spec/units/plugins/report_build_warnings_log_spec.rb +3 -3
- data/spec/units/plugins/report_log_writer_plugin_spec.rb +10 -1
- data/spec/units/plugins/report_tests_raw_output_log_spec.rb +13 -2
- data/spec/units/plugins/report_tests_teamcity_stdout_spec.rb +77 -3
- data/spec/units/plugins/reportgenerator_reportinator_spec.rb +1 -1
- data/spec/units/plugins/sanitize_records_spec.rb +3 -1
- data/spec/units/plugins/sanitize_spec.rb +1 -1
- data/spec/units/plugins/valgrind_spec.rb +23 -8
- data/spec/units/preprocess/preprocessinator_comment_stripper_spec.rb +1 -1
- data/spec/units/rake_app/rakefile_component_resolver_spec.rb +21 -0
- data/spec/units/release_build_executor_spec.rb +53 -14
- data/spec/units/release_build_planner_spec.rb +15 -3
- data/spec/units/reportinator_spec.rb +5 -5
- data/spec/units/standin_headerinator_spec.rb +31 -1
- data/spec/units/test_build_executor_spec.rb +65 -3
- data/spec/units/test_build_planner_spec.rb +104 -2
- data/spec/units/test_build_setup_spec.rb +96 -12
- data/spec/units/test_context_extractor_spec.rb +25 -0
- data/spec/units/test_file_preprocessinator_spec.rb +17 -1
- data/spec/units/test_includes_extractinator_spec.rb +5 -1
- data/spec/units/test_invoker/objects_wiring_spec.rb +6 -3
- data/spec/units/test_invoker_spec.rb +14 -1
- data/spec/units/test_source_file_directive_resolver_spec.rb +38 -7
- data/spec/units/tool_executor_spec.rb +26 -0
- data/spec/units/yaml_wrapper_spec.rb +11 -0
- metadata +109 -25
- data/lib/ceedling/file_path_collection_utils.rb +0 -164
|
@@ -0,0 +1,184 @@
|
|
|
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 'pathname'
|
|
9
|
+
require 'rbconfig'
|
|
10
|
+
require 'ceedling/filesystem/working_dir'
|
|
11
|
+
|
|
12
|
+
# One spelling for every path a project names.
|
|
13
|
+
#
|
|
14
|
+
# A place can be written many ways: relative or absolute, with `./`, `..`, `~`, backslashes,
|
|
15
|
+
# or through a symlink. Intake spells each path once, relative to the working directory
|
|
16
|
+
# (`canonical`). Comparisons then use whole directories, never text prefixes (`contains?`).
|
|
17
|
+
# BuildSubpath places build files from these spellings.
|
|
18
|
+
#
|
|
19
|
+
# Pure functions over strings, except where a function says it reads the disk, and no
|
|
20
|
+
# dependency on DIY, so the launcher in bin/ shares it.
|
|
21
|
+
module ProjectPaths
|
|
22
|
+
|
|
23
|
+
DECORATOR = /\A[+-]:/
|
|
24
|
+
WINDOWS = (RbConfig::CONFIG['host_os'] =~ /mswin|mingw|msys|ucrt/i) ? true : false
|
|
25
|
+
|
|
26
|
+
# What `absolute` makes of a path, which `clean_absolute?` finds already so: rooted, `C:/`
|
|
27
|
+
# on Windows, and free of `.`/`..` segments, doubled `/`, and backslashes. Windows'
|
|
28
|
+
# File.expand_path also lengthens a short `~` name and drops a trailing dot or space.
|
|
29
|
+
ROOTED = WINDOWS ? %r{\A[A-Z]:/} : %r{\A/}
|
|
30
|
+
ROOT_LENGTH = WINDOWS ? 3 : 1
|
|
31
|
+
UNCLEAN = WINDOWS ? %r{//|/\.\.?(?:/|\z)|\\|~|[. ](?:/|\z)} : %r{//|/\.\.?(?:/|\z)|\\}
|
|
32
|
+
|
|
33
|
+
@lock = Mutex.new
|
|
34
|
+
@absolutes = {}
|
|
35
|
+
|
|
36
|
+
# The working directory with every symlink resolved, read once (see WorkingDir)
|
|
37
|
+
def self.working_dir
|
|
38
|
+
return WorkingDir.path
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# A path written as a project file writes it, in Ceedling's form: trimmed, `/`-separated,
|
|
42
|
+
# with no trailing separator, and `~` read as the home directory. A `+:`/`-:` decorator is
|
|
43
|
+
# kept. `~user` is left as written.
|
|
44
|
+
def self.standardize(path)
|
|
45
|
+
path = path.to_s.strip.tr( '\\', '/' )
|
|
46
|
+
decorator = path[DECORATOR] || ''
|
|
47
|
+
path = expand_home( path.delete_prefix( decorator ).strip )
|
|
48
|
+
path = path.sub( %r{/+\z}, '' ) unless path.match?( %r{\A(?:[A-Za-z]:)?/+\z} )
|
|
49
|
+
return decorator + path
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# `path` as an absolute path, its `..` and `.` resolved against the working directory
|
|
53
|
+
# without reading the disk. A drive letter is upper case. Remembered for each working
|
|
54
|
+
# directory and home directory, since a build compares the same few roots many times.
|
|
55
|
+
def self.absolute(path)
|
|
56
|
+
return path if clean_absolute?( path )
|
|
57
|
+
|
|
58
|
+
key = [working_dir, Dir.home, path.to_s]
|
|
59
|
+
cached = @lock.synchronize { @absolutes[key] }
|
|
60
|
+
return cached unless cached.nil?
|
|
61
|
+
|
|
62
|
+
absolute = absolute_of( path ).freeze
|
|
63
|
+
return @lock.synchronize { @absolutes[key] = absolute }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The one spelling intake records for `path`: relative to the working directory, with the
|
|
67
|
+
# symlinks along its existing part resolved. A path with no relative form (another drive,
|
|
68
|
+
# a UNC share) stays absolute. Reads the disk.
|
|
69
|
+
def self.canonical(path)
|
|
70
|
+
return relative( real_absolute( path ) )
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# `absolute` with the symlinks along its existing part resolved. Reads the disk.
|
|
74
|
+
def self.real_absolute(path)
|
|
75
|
+
return real( absolute( path ) )
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# `absolute` relative to the working directory, or absolute when it has no relative form.
|
|
79
|
+
# The working directory itself is `.`.
|
|
80
|
+
def self.relative(absolute)
|
|
81
|
+
return absolute if unc?( absolute )
|
|
82
|
+
|
|
83
|
+
# Most paths lie below the working directory, which needs no Pathname walk
|
|
84
|
+
return '.' if same?( absolute, working_dir )
|
|
85
|
+
return absolute[(working_dir.length + 1)..] if clean_absolute?( absolute ) and starts_with?( absolute, "#{working_dir}/" )
|
|
86
|
+
|
|
87
|
+
return Pathname.new( absolute ).relative_path_from( Pathname.new( working_dir ) ).to_s
|
|
88
|
+
rescue ::ArgumentError
|
|
89
|
+
return absolute
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Whether `path` is `root` or lies below it, compared as whole directories. Windows folds case.
|
|
93
|
+
def self.contains?(root, path)
|
|
94
|
+
return !subdir_below( root, path ).nil?
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# The directories from `root` down to `path`: '' for `root` itself, or nil when `path`
|
|
98
|
+
# doesn't lie below `root`. Never holds `..`, and is never absolute.
|
|
99
|
+
def self.subdir_below(root, path)
|
|
100
|
+
base = absolute( root )
|
|
101
|
+
full = absolute( path )
|
|
102
|
+
return '' if same?( base, full )
|
|
103
|
+
|
|
104
|
+
prefix = base.end_with?( '/' ) ? base : "#{base}/"
|
|
105
|
+
return nil unless starts_with?( full, prefix )
|
|
106
|
+
|
|
107
|
+
return full[prefix.length..]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# A path's segments, `.` and empty segments dropped
|
|
111
|
+
def self.segments(path)
|
|
112
|
+
return path.to_s.split( %r{[\\/]} ).reject { |segment| segment.empty? || segment == '.' }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def self.absolute?(path)
|
|
116
|
+
return path.start_with?( '/' ) || path.match?( %r{\A[A-Za-z]:/} )
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# A Windows share. Elsewhere a doubled leading separator is the filesystem root.
|
|
120
|
+
def self.unc?(path)
|
|
121
|
+
return WINDOWS && path.start_with?( '//' )
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Whether two spellings are the same text, folding case on Windows
|
|
125
|
+
def self.same?(a, b)
|
|
126
|
+
return WINDOWS ? a.casecmp?( b ) : a == b
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def self.starts_with?(path, prefix)
|
|
130
|
+
return WINDOWS ? path.downcase.start_with?( prefix.downcase ) : path.start_with?( prefix )
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Already absolute as `absolute` would spell it: rooted (with an upper case drive on
|
|
134
|
+
# Windows), with no `.` or `..` segment, doubled or trailing `/`, backslash, or surrounding
|
|
135
|
+
# space. On Windows, File.expand_path also lengthens a short `~` name and drops a trailing
|
|
136
|
+
# dot or space, so a segment with any of those is never clean.
|
|
137
|
+
def self.clean_absolute?(path)
|
|
138
|
+
return false unless path.is_a?( String ) and path.match?( ROOTED )
|
|
139
|
+
return false if path.match?( UNCLEAN ) or path != path.strip
|
|
140
|
+
|
|
141
|
+
return path.length == ROOT_LENGTH || !path.end_with?( '/' )
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# A `~user` path names a directory of that name, which File.expand_path would read as the
|
|
145
|
+
# user's home (or raise for no such user). A share's `..` never leaves the share.
|
|
146
|
+
def self.absolute_of(path)
|
|
147
|
+
path = standardize( path )
|
|
148
|
+
return unc_absolute( path ) if unc?( path )
|
|
149
|
+
|
|
150
|
+
path = "./#{path}" if path.start_with?( '~' )
|
|
151
|
+
return File.expand_path( path, working_dir ).squeeze( '/' ).sub( /\A[a-z](?=:)/ ) { |drive| drive.upcase }
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def self.unc_absolute(path)
|
|
155
|
+
server, share, *rest = path.split( '/' ).reject { |segment| segment.empty? || segment == '.' }
|
|
156
|
+
kept = rest.each_with_object( [] ) { |segment, resolved| segment == '..' ? resolved.pop : resolved.push( segment ) }
|
|
157
|
+
return "//#{[server, share, *kept].compact.join( '/' )}"
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def self.expand_home(path)
|
|
161
|
+
return path unless path == '~' || path.start_with?( '~/' )
|
|
162
|
+
|
|
163
|
+
return Dir.home + path.delete_prefix( '~' )
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# The real path of the longest existing part of `absolute`, with the rest appended
|
|
167
|
+
def self.real(absolute)
|
|
168
|
+
return File.realpath( absolute ) if File.exist?( absolute )
|
|
169
|
+
|
|
170
|
+
existing = absolute
|
|
171
|
+
missing = []
|
|
172
|
+
until File.exist?( existing ) || existing == File.dirname( existing )
|
|
173
|
+
missing.unshift( File.basename( existing ) )
|
|
174
|
+
existing = File.dirname( existing )
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
return absolute unless File.exist?( existing )
|
|
178
|
+
|
|
179
|
+
return File.join( File.realpath( existing ), *missing )
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
private_class_method :clean_absolute?, :absolute_of, :unc_absolute, :expand_home, :real
|
|
183
|
+
|
|
184
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
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
|
+
# The directory Ceedling runs in, read once.
|
|
9
|
+
#
|
|
10
|
+
# A build resolves paths against the working directory tens of thousands of times, and
|
|
11
|
+
# asking the operating system for it costs tens of microseconds on macOS. Ceedling changes
|
|
12
|
+
# directory only through `chdir`, which forgets the remembered directory going in and
|
|
13
|
+
# coming out.
|
|
14
|
+
#
|
|
15
|
+
# No dependency on DIY, so the launcher in bin/ shares it.
|
|
16
|
+
module WorkingDir
|
|
17
|
+
|
|
18
|
+
@lock = Mutex.new
|
|
19
|
+
@path = nil
|
|
20
|
+
|
|
21
|
+
# The working directory with every symlink resolved
|
|
22
|
+
def self.path
|
|
23
|
+
return @path || @lock.synchronize { @path ||= File.realpath( Dir.pwd ).freeze }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Changes directory for the block, as Dir.chdir does, so paths resolved inside it resolve
|
|
27
|
+
# against the new directory and paths resolved after it against the old one
|
|
28
|
+
def self.chdir(dir, &block)
|
|
29
|
+
forget
|
|
30
|
+
return Dir.chdir( dir, &block )
|
|
31
|
+
ensure
|
|
32
|
+
forget
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# The next `path` reads the working directory again
|
|
36
|
+
def self.forget
|
|
37
|
+
@lock.synchronize { @path = nil }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
end
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
require 'ceedling/constants'
|
|
9
9
|
require 'ceedling/exceptions'
|
|
10
10
|
require 'ceedling/includes/includes'
|
|
11
|
-
require 'ceedling/file_path_utils'
|
|
11
|
+
require 'ceedling/filesystem/file_path_utils'
|
|
12
12
|
require 'rake'
|
|
13
13
|
|
|
14
14
|
class Generator
|
|
@@ -120,7 +120,7 @@ class Generator
|
|
|
120
120
|
return @generator_partials.generate_implementation( **arg_hash )
|
|
121
121
|
end
|
|
122
122
|
|
|
123
|
-
def generate_mock(context:, mock:, test:, input_filepath:, output_path:, folder: nil, overrides: {})
|
|
123
|
+
def generate_mock(context:, mock:, test:, input_filepath:, output_path:, mock_root:, folder: nil, overrides: {})
|
|
124
124
|
arg_hash = {
|
|
125
125
|
:header_file => input_filepath,
|
|
126
126
|
:test => test,
|
|
@@ -139,8 +139,7 @@ class Generator
|
|
|
139
139
|
# - Make CMock thread-safe
|
|
140
140
|
|
|
141
141
|
# Get default config created by Ceedling and customize it
|
|
142
|
-
# CMock writes
|
|
143
|
-
mock_root = folder ? output_path.delete_suffix( File.join( '', folder ) ) : output_path
|
|
142
|
+
# CMock writes below `mock_root` at `folder`, which leads to `output_path`
|
|
144
143
|
config = @generator_mocks.build_configuration( mock_root, overrides: overrides )
|
|
145
144
|
|
|
146
145
|
# Generate mock
|
|
@@ -514,8 +513,8 @@ class Generator
|
|
|
514
513
|
explain = ->(result) { crash_message( crash_explanation( arg_hash, result ) ) }
|
|
515
514
|
|
|
516
515
|
case backtrace
|
|
517
|
-
when :gdb then @backtrace.do_gdb( filename, executable, shell_result, test_cases, context: context )
|
|
518
|
-
when :simple then @backtrace.do_simple( filename, executable, shell_result, test_cases,
|
|
516
|
+
when :gdb then @backtrace.do_gdb( filename, executable, shell_result, test_cases, context: context, test_name: arg_hash[:test_name] )
|
|
517
|
+
when :simple then @backtrace.do_simple( filename, executable, shell_result, test_cases, tool: arg_hash[:tool], explain: explain )
|
|
519
518
|
# A crash is otherwise reported as a single failure
|
|
520
519
|
else @generator_test_results.create_crash_failure( filename, shell_result, test_cases, message: crash_message( explanation ) || 'Test executable crashed' )
|
|
521
520
|
end
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# SPDX-License-Identifier: MIT
|
|
6
6
|
# =========================================================================
|
|
7
7
|
|
|
8
|
-
require 'ceedling/file_wrapper'
|
|
8
|
+
require 'ceedling/filesystem/file_wrapper'
|
|
9
9
|
require 'ceedling/partials/partials'
|
|
10
10
|
require 'ceedling/c_extractor/c_extractor_types'
|
|
11
11
|
require 'ceedling/c_extractor/c_extractor_constants'
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
class GeneratorTestResultsBacktrace
|
|
9
9
|
|
|
10
10
|
constructor :configurator, :tool_executor, :generator_test_results, :generator_helper,
|
|
11
|
-
:file_path_utils, :
|
|
11
|
+
:file_path_utils, :build_log_writer, :loginator
|
|
12
12
|
|
|
13
13
|
def setup()
|
|
14
14
|
@RESULTS_COLLECTOR = Struct.new( :passed, :failed, :ignored, :output, keyword_init:true )
|
|
@@ -31,7 +31,10 @@ class GeneratorTestResultsBacktrace
|
|
|
31
31
|
# Writes the full gdb transcript to a per-test-case log file and assembles a
|
|
32
32
|
# terse crash label (signal + description, optional source line in backticks)
|
|
33
33
|
# for each failing test case. Returns a modified shell_result with regenerated output.
|
|
34
|
-
|
|
34
|
+
#
|
|
35
|
+
# `test_name` places the logs, so same-named tests keep separate ones. Each log holds only
|
|
36
|
+
# this build's crash.
|
|
37
|
+
def do_gdb(filename, executable, shell_result, test_cases, context:, test_name:)
|
|
35
38
|
gdb_script_filepath = File.join( @configurator.project_build_tests_root, BACKTRACE_GDB_SCRIPT_FILE )
|
|
36
39
|
|
|
37
40
|
# Clean stats tracker
|
|
@@ -40,8 +43,6 @@ class GeneratorTestResultsBacktrace
|
|
|
40
43
|
# Reset time
|
|
41
44
|
shell_result[:time] = 0
|
|
42
45
|
|
|
43
|
-
test_name = File.basename( filename, '.*' )
|
|
44
|
-
|
|
45
46
|
# True once some retry group has actually shown crash evidence of its own -- an
|
|
46
47
|
# unresolved member, or (below) a group whose real status contradicts a fully clean
|
|
47
48
|
# set of matches. If this stays false across every group, the whole diagnostic never
|
|
@@ -120,8 +121,7 @@ class GeneratorTestResultsBacktrace
|
|
|
120
121
|
|
|
121
122
|
# Per-test-case log file: <log_path>/<context>/<test_name>/<test_case>.gdb.log
|
|
122
123
|
log_path = @file_path_utils.form_test_gdb_log( test_name, context: context, name: crashed_case[:test] )
|
|
123
|
-
@
|
|
124
|
-
@file_wrapper.write( log_path, "=== #{crashed_case[:test]} ===\n#{crash_result[:output]}\n", 'a' )
|
|
124
|
+
@build_log_writer.write( log_path, "=== #{crashed_case[:test]} ===\n#{crash_result[:output]}\n" )
|
|
125
125
|
|
|
126
126
|
unresolved.each do |test_case|
|
|
127
127
|
group_results[:failed] += 1
|
|
@@ -296,7 +296,7 @@ class GeneratorTestResultsBacktrace
|
|
|
296
296
|
# such as Valgrind's, keeps the main run's report.
|
|
297
297
|
#
|
|
298
298
|
# `explain` names the cause of a rerun's crash, if a plugin recognizes it.
|
|
299
|
-
def do_simple(filename, executable, shell_result, test_cases,
|
|
299
|
+
def do_simple(filename, executable, shell_result, test_cases, tool: nil, explain: nil)
|
|
300
300
|
results = @RESULTS_COLLECTOR.new( passed:0, failed:0, ignored:0, output:[] )
|
|
301
301
|
shell_result[:time] = 0 # Summed over the per-group sub-process runs
|
|
302
302
|
any_crashed = false
|
|
@@ -0,0 +1,121 @@
|
|
|
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 'rbconfig'
|
|
9
|
+
require_relative 'installation'
|
|
10
|
+
|
|
11
|
+
# The protocol by which one Ceedling installation hands a command off to another: the
|
|
12
|
+
# selected installation's own launcher replaces this process, given the original command
|
|
13
|
+
# line. Environment markers tell that process who launched it and stop it handing off again.
|
|
14
|
+
#
|
|
15
|
+
# Like Installation, this file depends on nothing else in lib/, so an application loaded
|
|
16
|
+
# by an older launcher can load it with require_relative.
|
|
17
|
+
module Handoff
|
|
18
|
+
|
|
19
|
+
LAUNCHER_VAR = 'CEEDLING_HANDOFF_LAUNCHER'
|
|
20
|
+
TARGET_VAR = 'CEEDLING_HANDOFF_TARGET'
|
|
21
|
+
|
|
22
|
+
# Removes the markers from env, so no subprocess of this build inherits them, and
|
|
23
|
+
# returns them, or nil when no launcher handed off to this process
|
|
24
|
+
def self.take_markers(env)
|
|
25
|
+
launcher = env.delete( LAUNCHER_VAR )
|
|
26
|
+
target = env.delete( TARGET_VAR )
|
|
27
|
+
return nil if target.nil?
|
|
28
|
+
|
|
29
|
+
return {launcher: launcher, target: target}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Whether the markers name own as the installation a launcher handed off to
|
|
33
|
+
def self.received?(markers, own:)
|
|
34
|
+
return false if markers.nil? or markers[:target].nil?
|
|
35
|
+
|
|
36
|
+
return Installation.new( markers[:target] ) == own
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.handing_off_message(launcher:, target:)
|
|
40
|
+
return "#{launcher.describe( 'Ceedling launcher' )} is handing off to #{target.describe()}, which runs in its own process."
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.received_message(markers, own:)
|
|
44
|
+
return "#{own.describe()} is now running, launched from #{Installation.new( markers[:launcher] ).describe()}."
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Replaces this process with target's own launcher. Nothing after this call runs, not
|
|
48
|
+
# even END blocks, so logging drains and the standard streams flush first.
|
|
49
|
+
def self.exec(target:, launcher:, argv:, loginator:)
|
|
50
|
+
loginator.wrapup()
|
|
51
|
+
$stdout.flush()
|
|
52
|
+
$stderr.flush()
|
|
53
|
+
|
|
54
|
+
markers = {LAUNCHER_VAR => launcher.root, TARGET_VAR => target.root}
|
|
55
|
+
replace_process( markers, RbConfig.ruby, target.bin_path, *argv )
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Finishes a hand-off that a launcher before 1.2.2 cannot make: it loads this application
|
|
59
|
+
# into its own process. Every such launcher kept the original command line in the
|
|
60
|
+
# SystemWrapper it hands over, so the application restarts as its own launcher. Without
|
|
61
|
+
# that command line, or inside a hand-off already, it warns and continues in-process,
|
|
62
|
+
# so a restart never cycles.
|
|
63
|
+
def self.complete_in_process(app_root:, app_cfg:, handoff_objects:, env:)
|
|
64
|
+
launcher = Installation.new( launcher_root_of( app_cfg ) )
|
|
65
|
+
app = Installation.new( app_root )
|
|
66
|
+
return if launcher == app
|
|
67
|
+
|
|
68
|
+
loginator = handoff_objects[:loginator]
|
|
69
|
+
argv = command_line( handoff_objects[:system_wrapper] )
|
|
70
|
+
restart = !(argv.nil? or in_handoff?( app_cfg, env ))
|
|
71
|
+
|
|
72
|
+
loginator.log( loaded_message( launcher, app, restart ), Verbosity::COMPLAIN )
|
|
73
|
+
exec( target:app, launcher:launcher, argv:argv, loginator:loginator ) if restart
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# The one seam to the operating system, so specs can stand in for it
|
|
77
|
+
def self.replace_process(env, *command)
|
|
78
|
+
Kernel.exec( env, *command )
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# The launcher's CeedlingAppConfig is defined in its own bin/
|
|
82
|
+
def self.launcher_root_of(app_cfg)
|
|
83
|
+
filepath, _ = app_cfg.class.instance_method( :initialize ).source_location
|
|
84
|
+
return File.expand_path( '..', File.dirname( filepath ) )
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def self.command_line(system_wrapper)
|
|
88
|
+
return nil if !system_wrapper.respond_to?( :get_cmdline )
|
|
89
|
+
|
|
90
|
+
return system_wrapper.get_cmdline()
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def self.in_handoff?(app_cfg, env)
|
|
94
|
+
return true if !app_cfg[:handoff].nil?
|
|
95
|
+
|
|
96
|
+
return !(env[TARGET_VAR].nil? and env[LAUNCHER_VAR].nil?)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def self.loaded_message(launcher, app, restart)
|
|
100
|
+
loaded = "#{launcher.describe( 'Ceedling launcher' )} loaded #{app.describe()} into its own process."
|
|
101
|
+
return "#{loaded} Restarting in #{app.name()}'s own process. Upgrade Ceedling and review your installations to avoid this." if restart
|
|
102
|
+
|
|
103
|
+
return "#{loaded} #{continuing_advice( app )}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def self.continuing_advice(app)
|
|
107
|
+
return "Continuing in the launcher's process, which can fail or misbehave. Upgrade Ceedling, or run this " +
|
|
108
|
+
"installation's own launcher directly with `ruby #{display_path( app.bin_path )}`. A project created " +
|
|
109
|
+
"with `--local` has a launch script at its root that does this: run `./ceedling` from the project root " +
|
|
110
|
+
"(`.\\ceedling.cmd` on Windows)."
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Relative to the working directory when beneath it, as a user would type it
|
|
114
|
+
def self.display_path(filepath)
|
|
115
|
+
cwd = File.join( Dir.pwd, '' )
|
|
116
|
+
return filepath.start_with?( cwd ) ? filepath.delete_prefix( cwd ) : filepath
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private_class_method :launcher_root_of, :command_line, :in_handoff?, :loaded_message, :continuing_advice, :display_path
|
|
120
|
+
|
|
121
|
+
end
|
|
@@ -40,9 +40,9 @@ class IncludeFactory
|
|
|
40
40
|
|
|
41
41
|
# A resolved mock include's filepath may carry a leading build directory path that
|
|
42
42
|
# snuck in from discovering an empty mock stand-in or an already-generated mock file.
|
|
43
|
-
# That leading path is the current test's own mock subdirectory --
|
|
44
|
-
#
|
|
45
|
-
#
|
|
43
|
+
# That leading path is the current test's own mock subdirectory -- named by the test's
|
|
44
|
+
# path from the project root, so it can be more than one segment deep (e.g.
|
|
45
|
+
# `test/adc/TestFoo`) -- followed by however many further segments mirror
|
|
46
46
|
# the mocked header's own relative directory (e.g. `calculators/`). Knowing the current
|
|
47
47
|
# test's identity lets exactly the first part be removed, leaving any header-mirroring
|
|
48
48
|
# segments after it untouched. Without that identity, fall back to stripping everything
|
|
@@ -7,10 +7,18 @@
|
|
|
7
7
|
|
|
8
8
|
require 'pathname'
|
|
9
9
|
require 'ceedling/exceptions'
|
|
10
|
-
require 'ceedling/file_path_utils'
|
|
10
|
+
require 'ceedling/filesystem/file_path_utils'
|
|
11
|
+
require 'ceedling/filesystem/project_paths'
|
|
11
12
|
|
|
12
13
|
class IncludePathinator
|
|
13
14
|
|
|
15
|
+
NO_HEADERS_MESSAGE = "No header files found in project.\n" +
|
|
16
|
+
"Add search paths to :paths ↳ :include in your project file and/or use TEST_INCLUDE_PATH() in your test files.\n" +
|
|
17
|
+
"Verify header files with `ceedling paths:include` and\\or `ceedling files:include`."
|
|
18
|
+
|
|
19
|
+
# What `header_listing` remembers for a directory under the build root
|
|
20
|
+
LISTED_FRESH = :listed_fresh
|
|
21
|
+
|
|
14
22
|
constructor :configurator, :test_context_extractor, :loginator, :file_wrapper
|
|
15
23
|
|
|
16
24
|
def setup
|
|
@@ -20,6 +28,10 @@ class IncludePathinator
|
|
|
20
28
|
|
|
21
29
|
# Alias for brevity
|
|
22
30
|
@extractor = @test_context_extractor
|
|
31
|
+
|
|
32
|
+
# Each search path's header listing for one test build (see `header_listing`)
|
|
33
|
+
@listings = {}
|
|
34
|
+
@listings_lock = Mutex.new
|
|
23
35
|
end
|
|
24
36
|
|
|
25
37
|
def validate_test_build_directive_paths
|
|
@@ -52,12 +64,7 @@ class IncludePathinator
|
|
|
52
64
|
|
|
53
65
|
headers.uniq!
|
|
54
66
|
|
|
55
|
-
if headers.
|
|
56
|
-
error = "No header files found in project.\n" +
|
|
57
|
-
"Add search paths to :paths ↳ :include in your project file and/or use TEST_INCLUDE_PATH() in your test files.\n" +
|
|
58
|
-
"Verify header files with `ceedling paths:include` and\\or `ceedling files:include`."
|
|
59
|
-
@loginator.log( error, Verbosity::COMPLAIN )
|
|
60
|
-
end
|
|
67
|
+
@loginator.log( NO_HEADERS_MESSAGE, Verbosity::COMPLAIN ) if headers.empty?
|
|
61
68
|
|
|
62
69
|
return headers
|
|
63
70
|
end
|
|
@@ -70,11 +77,16 @@ class IncludePathinator
|
|
|
70
77
|
def ordered_header_files(search_paths)
|
|
71
78
|
headers = []
|
|
72
79
|
search_paths.each do |path|
|
|
73
|
-
headers
|
|
80
|
+
headers.concat( header_listing( path ) )
|
|
74
81
|
end
|
|
75
82
|
return headers.uniq
|
|
76
83
|
end
|
|
77
84
|
|
|
85
|
+
# Starts a test build with every search path listed afresh
|
|
86
|
+
def forget_header_listings
|
|
87
|
+
@listings_lock.synchronize { @listings.clear }
|
|
88
|
+
end
|
|
89
|
+
|
|
78
90
|
# The project's whole header collection, reordered so headers beneath this test's
|
|
79
91
|
# own earlier search paths come first.
|
|
80
92
|
#
|
|
@@ -88,10 +100,10 @@ class IncludePathinator
|
|
|
88
100
|
# reference that distinguishes nothing resolving exactly as it did before.
|
|
89
101
|
def prioritized_header_files(search_paths)
|
|
90
102
|
roots = search_paths.map { |path| FilePathUtils.no_decorators( path ) }.reject(&:empty?)
|
|
103
|
+
ranks = root_ranks( roots )
|
|
91
104
|
|
|
92
105
|
@configurator.collection_all_headers.to_a.each_with_index.sort_by do |header, index|
|
|
93
|
-
|
|
94
|
-
[rank, index]
|
|
106
|
+
[header_rank( header, ranks, roots.length ), index]
|
|
95
107
|
end.map(&:first)
|
|
96
108
|
end
|
|
97
109
|
|
|
@@ -116,4 +128,49 @@ class IncludePathinator
|
|
|
116
128
|
return paths
|
|
117
129
|
end
|
|
118
130
|
|
|
131
|
+
private
|
|
132
|
+
|
|
133
|
+
# One search path's headers. A directory outside the build root keeps its listing for the
|
|
134
|
+
# test build, since a build never writes there, and a test's search paths are listed
|
|
135
|
+
# several times a test. One under the build root is listed every time, since the build
|
|
136
|
+
# fills it as it runs.
|
|
137
|
+
def header_listing(path)
|
|
138
|
+
key = [ProjectPaths.working_dir, path]
|
|
139
|
+
remembered = @listings_lock.synchronize { @listings[key] }
|
|
140
|
+
return remembered if remembered.is_a?( Array )
|
|
141
|
+
|
|
142
|
+
listing = @file_wrapper.directory_listing( @configurator.extension_header.glob_patterns( path ) ).freeze
|
|
143
|
+
return listing if remembered == LISTED_FRESH
|
|
144
|
+
|
|
145
|
+
kept = ProjectPaths.contains?( @configurator.project_build_root, path ) ? LISTED_FRESH : listing
|
|
146
|
+
@listings_lock.synchronize { @listings[key] ||= kept }
|
|
147
|
+
return listing
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Each root's place in the search order by its absolute path, the first one kept, so a
|
|
151
|
+
# header's rank comes from walking up its own directories rather than trying every root
|
|
152
|
+
def root_ranks(roots)
|
|
153
|
+
return roots.each_with_index.with_object( {} ) { |(root, index), ranks| ranks[rank_key( ProjectPaths.absolute( root ) )] ||= index }
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# The earliest-searched root a header lies below, or `none` for no root
|
|
157
|
+
def header_rank(header, ranks, none)
|
|
158
|
+
rank = none
|
|
159
|
+
path = ProjectPaths.absolute( header )
|
|
160
|
+
loop do
|
|
161
|
+
parent = File.dirname( path )
|
|
162
|
+
break if parent == path
|
|
163
|
+
|
|
164
|
+
path = parent
|
|
165
|
+
rank = [rank, ranks.fetch( rank_key( path ), none )].min
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
return rank
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Windows compares paths without regard to case
|
|
172
|
+
def rank_key(path)
|
|
173
|
+
return ProjectPaths::WINDOWS ? path.downcase : path
|
|
174
|
+
end
|
|
175
|
+
|
|
119
176
|
end
|