Repository: MarketSquare/robotframework-robocop
Pull request #1372 (2025-04-02 11:58:04)

Fix configuration file not resolving to common one for multuple files
#1369


Patch:
diff --git a/docs/releasenotes/6.0.3.rst b/docs/releasenotes/6.0.3.rst
new file mode 100644
index 0000000..bbcf160
--- /dev/null
+++ b/docs/releasenotes/6.0.3.rst
@@ -0,0 +1,25 @@
+:orphan:
+
+=============
+Robocop 6.0.3
+=============
+
+Enabling reports in configuration does not work (#1369)
+-------------------------------------------------------
+
+Options common for whole run (such as reports) should be now loaded correctly from the configuration file.
+
+Acknowledgements
+================
+
+Thanks to the whole community for submitting bug reports and feature requests.
+Without you, Robocop wouldn't be in the place where it is now. All the feedback
+is essential to drive the tool towards higher quality and better user
+experience.
+
+If you want to help us more, consider contributing to the project directly.
+We can offer our constant support to make the work fun and effective. We do
+our best to create a supportive and welcoming environment for everyone.
+Feel free to ping us on our official `#robocop-linter Slack channel`_ anytime.
+
+.. _#robocop-linter Slack channel: https://robotframework.slack.com/archives/C01AWSNKC2H
\ No newline at end of file
diff --git a/src/robocop/config.py b/src/robocop/config.py
index 5b4ca7c..584d009 100644
--- a/src/robocop/config.py
+++ b/src/robocop/config.py
@@ -483,7 +483,7 @@ class FileFiltersOptions(ConfigContainer):
         return cls(**filter_config)
 
     def path_excluded(self, path: Path) -> bool:
-        """Exclude all paths matching exclue patterns."""
+        """Exclude all paths matching exclude patterns."""
         exclude_paths = set(self.default_exclude)
         if self.exclude:
             exclude_paths |= self.exclude
@@ -716,8 +716,8 @@ class ConfigManager:
             config is not None
         )  # TODO: what if both cli and --config? should take --config then apply cli
         self.root = root or Path.cwd()
-        self.default_config: Config = self.get_default_config(config)
         self.sources = sources
+        self.default_config: Config = self.get_default_config(config)
         self._paths: dict[Path, Config] | None = None
 
     @property
@@ -728,36 +728,19 @@ class ConfigManager:
             sources = self.sources if self.sources else self.default_config.sources
             ignore_file_filters = not self.force_exclude and bool(sources)
             self.resolve_paths(sources, gitignores=None, ignore_file_filters=ignore_file_filters)
-            self.set_default_config()
         yield from self._paths.items()
 
-    def set_default_config(self) -> None:
-        """
-        Find default config from all loaded configs and set it.
-
-        Default configuration file is configuration closest to cwd. It's options are used for global-like
-        settings such as reports or exit codes.
-
-        """
-        if not self.cached_configs:
-            return
-        # look for path as cwd or any of parents of cwd
-        cwd = Path.cwd()
-        if cwd in self.cached_configs:
-            self.default_config = self.cached_configs[cwd]
-            return
-        for parent in cwd.parents:
-            if parent in self.cached_configs:
-                self.default_config = self.cached_configs[parent]
-                return
-
     def get_default_config(self, config_path: Path | None) -> Config:
         """Get default config either from --config option or from the cli."""
         if config_path:
             configuration = files.read_toml_config(config_path)
             config = Config.from_toml(configuration, config_path.resolve())
         else:
-            config = Config()
+            sources = [Path(path).resolve() for path in self.sources] if self.sources else [Path.cwd()]
+            directories = files.get_common_parent_dirs(sources)
+            config = self.find_config_in_dirs(directories, default=None)
+            if not config:
+                config = Config()
         config.overwrite_from_config(self.overwrite_config)
         return config
 
@@ -767,14 +750,13 @@ class ConfigManager:
             return False
         return (path / ".git").is_dir()
 
-    def find_closest_config(self, source: Path) -> Config:
+    def find_closest_config(self, source: Path, default: Config | None) -> Config:
         """Look in the directory and its parents for the closest valid configuration file."""
-        # we always look for configuration in parent directory, unless we hit the top already
-        if not self.is_git_project_root(source) and source.parents:
-            source = source.parent
-        check_dirs = [source, *source.parents]
+        return self.find_config_in_dirs(list(source.parents), default)
+
+    def find_config_in_dirs(self, directories: list[Path], default: Config | None) -> Config:
         seen = []  # if we find config, mark all visited directories with resolved config
-        for check_dir in check_dirs:
+        for check_dir in directories:
             if check_dir in self.cached_configs:
                 return self.cached_configs[check_dir]
             seen.append(check_dir.resolve())
@@ -782,7 +764,7 @@ class ConfigManager:
                 if (config_path := (check_dir / config_filename)).is_file():
                     configuration = files.read_toml_config(config_path)
                     if configuration is not None:
-                        config = Config.from_toml(configuration, config_path.resolve())
+                        config = Config.from_toml(configuration, config_path)
                         config.overwrite_from_config(self.overwrite_config)  # TODO those two lines together
                         self.cached_configs.update({sub_dir: config for sub_dir in seen})
                         if config.verbose:
@@ -790,7 +772,7 @@ class ConfigManager:
                         return config
             if self.is_git_project_root(check_dir):
                 break
-        return self.default_config
+        return default
 
     def get_config_for_source_file(self, source_file: Path) -> Config:
         """
@@ -805,7 +787,7 @@ class ConfigManager:
         """
         if self.overridden_config or self.ignore_file_config:
             return self.default_config
-        return self.find_closest_config(source_file)
+        return self.find_closest_config(source_file, self.default_config)
 
     def resolve_paths(
         self,
@@ -826,23 +808,24 @@ class ConfigManager:
 
         """
         for source in sources:
-            source = Path(source)
+            source_not_resolved = Path(source)
+            source = source_not_resolved.resolve()
             if source in self._paths:
                 continue
             if not source.exists():  # TODO only for passed sources
                 raise errors.FatalError(f"File '{source}' does not exist")
             config = self.get_config_for_source_file(source)
             if not ignore_file_filters:
-                if config.file_filters.path_excluded(source):
+                if config.file_filters.path_excluded(source_not_resolved):
                     continue
-                if source.is_file() and not config.file_filters.path_included(source):
+                if source.is_file() and not config.file_filters.path_included(source_not_resolved):
                     continue
                 if not self.skip_gitignore:
                     if gitignores is None:
-                        source_gitignore = self.gitignore_resolver.resolve_path_ignores(source)
+                        source_gitignore = self.gitignore_resolver.resolve_path_ignores(source_not_resolved)
                     else:
                         source_gitignore = gitignores
-                    if self.gitignore_resolver.path_excluded(source, source_gitignore):
+                    if self.gitignore_resolver.path_excluded(source_not_resolved, source_gitignore):
                         continue
             if source.is_dir():
                 self.resolve_paths(source.iterdir(), gitignores)
diff --git a/src/robocop/files.py b/src/robocop/files.py
index 008a45e..4b03974 100644
--- a/src/robocop/files.py
+++ b/src/robocop/files.py
@@ -42,3 +42,14 @@ def get_relative_path(path: str | Path, parent_path: Path) -> Path:
         return Path(path).relative_to(parent_path)
     except ValueError:  # symlink etc
         return path
+
+
+def get_common_parent_dirs(sources: list[Path]) -> list[Path]:
+    """Return list of common parent directories for list of paths."""
+    src_parents = [list(path.parents) + ([path] if path.is_dir() else []) for path in sources]
+
+    common_base = max(
+        set.intersection(*(set(parents) for parents in src_parents)),
+        key=lambda path: path.parts,
+    )
+    return [common_base, *common_base.parents]
diff --git a/src/robocop/linter/runner.py b/src/robocop/linter/runner.py
index 29bd84a..20e6d71 100644
--- a/src/robocop/linter/runner.py
+++ b/src/robocop/linter/runner.py
@@ -27,7 +27,6 @@ class RobocopLinter:
     def __init__(self, config_manager: ConfigManager):
         self.config_manager = config_manager
         self.current_model: File = None
-        self.sources = self.config_manager.paths
         self.reports: dict[str, reports.Report] = reports.get_reports(self.config_manager.default_config)
         self.diagnostics: list[Diagnostic] = []
         self.configure_reports()
@@ -44,7 +43,7 @@ class RobocopLinter:
     def run(self) -> list[Diagnostic]:
         issues_no = 0
         files = 0
-        for source, config in self.sources:
+        for source, config in self.config_manager.paths:
             if config.verbose:
                 print(f"Scanning file: {source}")
             try:
diff --git a/uv.lock b/uv.lock
index 601fe26..82993df 100644
--- a/uv.lock
+++ b/uv.lock
@@ -1,6 +1,6 @@
 version = 1
 revision = 1
-requires-python = ">=3.9, <3.14"
+requires-python = ">=3.9"
 resolution-markers = [
     "python_full_version >= '3.11'",
     "python_full_version == '3.10.*'",
@@ -569,14 +569,14 @@ doc = [
 
 [package.metadata]
 requires-dist = [
-    { name = "jinja2", specifier = ">=3.1.4,<3.2" },
-    { name = "pathspec", specifier = ">=0.12,<0.13" },
-    { name = "platformdirs", specifier = ">=4.3,<4.4" },
-    { name = "pytz", specifier = ">=2022.7,<2025.2" },
+    { name = "jinja2", specifier = ">=3.1.4" },
+    { name = "pathspec", specifier = ">=0.12" },
+    { name = "platformdirs", specifier = ">=4.3" },
+    { name = "pytz", specifier = ">=2022.7" },
     { name = "robotframework", specifier = ">=4.0,<7.3" },
     { name = "tomli", marker = "python_full_version < '3.11'", specifier = "==2.0.2" },
-    { name = "tomli-w", specifier = ">=1.0,<1.3" },
-    { name = "typer", specifier = ">=0.12.5,<0.16" },
+    { name = "tomli-w", specifier = ">=1.0" },
+    { name = "typer", specifier = ">=0.12.5" },
 ]
 
 [package.metadata.requires-dev]


Test patch:
diff --git a/tests/config/test_config_manager.py b/tests/config/test_config_manager.py
index b9b0811..9887c5c 100644
--- a/tests/config/test_config_manager.py
+++ b/tests/config/test_config_manager.py
@@ -8,6 +8,7 @@ import typer
 from robocop import files
 from robocop.config import Config, ConfigManager, FileFiltersOptions, FormatterConfig, LinterConfig
 from robocop.linter.rules import RuleSeverity
+from robocop.linter.runner import RobocopLinter
 from tests import working_directory
 
 
@@ -74,9 +75,9 @@ class TestConfigFinder:
         config.linter.configure = ["line-too-long.line_length=110"]
 
         expected_results = {
-            Path("file1.robot"): config,
-            Path("subdir") / "file2.robot": config,
-            Path("subdir") / "file3.resource": config,
+            config_dir / "file1.robot": config,
+            config_dir / "subdir" / "file2.robot": config,
+            config_dir / "subdir" / "file3.resource": config,
         }
 
         # Act
@@ -103,7 +104,7 @@ class TestConfigFinder:
         config.linter.threshold = RuleSeverity.WARNING
         config.linter.reports = ["all", "sarif"]
         config.linter.issue_format = "{source_abs}:{line}:{col} [{severity}] {rule_id} {desc} ({name})"
-        config.language = ["eng", "pl"]
+        config.language = ["en", "pl"]
         config.linter.custom_rules = ["CustomRules.py"]
         config.linter.persistent = True
         config.linter.compare = True
@@ -130,7 +131,7 @@ class TestConfigFinder:
         config.formatter.skip_config.keyword_call_pattern = {"DeprecatedKeyword$"}
         config.formatter.reruns = 5
 
-        expected_results = {Path("test.robot"): config}
+        expected_results = {config_dir / "test.robot": config}
 
         # Act
         actual_results = get_sources_and_configs(config_dir)
@@ -152,8 +153,8 @@ class TestConfigFinder:
         subdir_config.linter.configure = ["line-too-long.line_length=110"]
         subdir_config.file_filters.default_exclude = {"file3.resource"}
         expected_results = {
-            Path("file1.robot"): default_config,
-            Path("subdir") / "file2.robot": subdir_config,
+            config_dir / "file1.robot": default_config,
+            config_dir / "subdir" / "file2.robot": subdir_config,
             # file3.resource is excluded in the subdir config
         }
 
@@ -170,10 +171,10 @@ class TestConfigFinder:
         # Arrange
         config_dir = test_data / "one_config_subdir"
         expected_results = {
-            Path("file1.robot"): cli_config,
-            Path("subdir") / "file2.robot": cli_config,
+            config_dir / "file1.robot": cli_config,
+            config_dir / "subdir" / "file2.robot": cli_config,
             # file3.resource would be excluded by config, but cli config overwrites it
-            Path("subdir") / "file3.resource": cli_config,
+            config_dir / "subdir" / "file3.resource": cli_config,
         }
 
         # Act
@@ -199,11 +200,11 @@ class TestConfigFinder:
         subdir_config.linter.configure = ["line-too-long.line_length=110"]
         subdir_config.file_filters.default_exclude = overwrite_option
         expected_results = {
-            Path("file1.robot"): default_config,
+            config_dir / "file1.robot": default_config,
             # file2.robot should be included, but cli option overwrites it
             # "subdir" / "file2.robot": subdir_config,
             # file3.resource would be excluded by config, but cli config overwrites it
-            Path("subdir") / "file3.resource": subdir_config,
+            config_dir / "subdir" / "file3.resource": subdir_config,
         }
 
         # Act
@@ -225,9 +226,9 @@ class TestConfigFinder:
             # excluded by cli option
             # config_dir / "file1.robot": cli_config,
             # file2.robot is excluded by cli config, but cli option overwrites it
-            Path("subdir") / "file2.robot": cli_config,
+            config_dir / "subdir" / "file2.robot": cli_config,
             # file3.resource would be excluded by config, but cli option overwrites it
-            Path("subdir") / "file3.resource": cli_config,
+            config_dir / "subdir" / "file3.resource": cli_config,
         }
 
         # Act
@@ -256,9 +257,9 @@ class TestConfigFinder:
         second_config.file_filters.default_exclude = {"file1.robot"}
         second_config.linter.configure = ["line-too-long.line_length=140"]
         expected_results = {
-            Path("file1.robot"): first_config,
-            Path("subdir") / "file2.robot": second_config,
-            Path("subdir") / "file3.resource": second_config,
+            config_dir / "file1.robot": first_config,
+            config_dir / "subdir" / "file2.robot": second_config,
+            config_dir / "subdir" / "file3.resource": second_config,
         }
 
         # Act
@@ -295,7 +296,7 @@ class TestConfigFinder:
 
     def test_fail_on_unknown_config_options(self, test_data, capsys):
         """Unknown or deprecated options in configuration file should raise an error."""
-        config_path = test_data / "invalid_config" / "pyproject.toml"
+        config_path = test_data / "invalid_config" / "invalid.toml"
         configuration = files.read_toml_config(config_path)
         with pytest.raises(typer.Exit):
             Config.from_toml(configuration, config_path)
@@ -316,7 +317,7 @@ class TestConfigFinder:
         actual_results = get_sources_and_configs(tmp_test_files)
 
         # Assert
-        assert Path("file.robot") in actual_results
+        assert tmp_test_files / "file.robot" in actual_results
         assert len(actual_results) == 1
 
     def test_filter_paths_from_gitignore(self, test_data):
@@ -327,7 +328,7 @@ class TestConfigFinder:
         actual_results = get_sources_and_configs(test_dir)
 
         # Assert
-        assert Path("file.robot") in actual_results
+        assert test_dir / "file.robot" in actual_results
         assert len(actual_results) == 1
 
     def test_invalid_option_value(self, test_data):
@@ -394,13 +395,13 @@ class TestConfigFinder:
         absolute_path.write_text("*** Settings ***")
         (tmp_path / "test2.robot").symlink_to(absolute_path2)
         (tmp_path / "test3.robot").symlink_to(absolute_path2)
-        expected_paths = [Path("test.robot"), Path("test2.robot"), Path("test3.robot")]
+        expected_paths = [absolute_path, absolute_path2]
 
         # Act
         actual_results = get_sources_and_configs(tmp_path)
 
         # Assert
-        assert sorted(actual_results.keys()) == expected_paths
+        assert sorted(actual_results.keys()) == sorted(expected_paths)
 
     @pytest.mark.skipif(sys.platform != "linux", reason="Test only runs on Linux")
     def test_exclude_symlink_path(self, test_data, tmp_path, overwrite_config):
@@ -411,14 +412,14 @@ class TestConfigFinder:
         absolute_path.write_text("*** Settings ***")
         (tmp_path / "test2.robot").symlink_to(absolute_path2)
         (tmp_path / "test3.robot").symlink_to(absolute_path3)
-        expected_paths = [Path("test.robot"), Path("test2.robot")]
+        expected_paths = [absolute_path, absolute_path2]
         overwrite_config.file_filters.exclude = {"test3.robot"}
 
         # Act
         actual_results = get_sources_and_configs(tmp_path, overwrite_config=overwrite_config)
 
         # Assert
-        assert sorted(actual_results.keys()) == expected_paths
+        assert sorted(actual_results.keys()) == sorted(expected_paths)
 
     def test_reports_loaded_from_top_config(self, test_data):
         # Arrange
@@ -428,7 +429,6 @@ class TestConfigFinder:
         # Act
         with working_directory(test_dir):
             config_manager = ConfigManager()
-            _ = dict(config_manager.paths)
 
         # Assert
         assert config_manager.default_config.linter.reports == expected_reports
@@ -442,5 +442,20 @@ class TestConfigFinder:
         actual_results = get_sources_and_configs(test_dir, overwrite_config=overwrite_config)
 
         # Assert
-        assert "line-too-long.line_length=140" in actual_results[Path("test.robot")].linter.configure
-        assert "line-too-long.line_length=200" in actual_results[Path("test.robot")].linter.configure
+        assert "line-too-long.line_length=140" in actual_results[test_dir / "test.robot"].linter.configure
+        assert "line-too-long.line_length=200" in actual_results[test_dir / "test.robot"].linter.configure
+
+    def test_multiple_files_with_single_config(self, test_data, overwrite_config, capsys):
+        # Arrange
+        test_dir = test_data / "multiple_files_one_config"
+        overwrite_config.verbose = True
+
+        # Act
+        with working_directory(test_dir):
+            config_manager = ConfigManager(overwrite_config=overwrite_config)
+            linter = RobocopLinter(config_manager)
+        out, _ = capsys.readouterr()
+
+        # Assert
+        assert out == f"Loaded {test_dir / 'robocop.toml'} configuration file.\n"
+        assert len(linter.reports) > 2
diff --git a/tests/config/test_data/config_with_all_options/pyproject.toml b/tests/config/test_data/config_with_all_options/pyproject.toml
index eb54a32..8988085 100644
--- a/tests/config/test_data/config_with_all_options/pyproject.toml
+++ b/tests/config/test_data/config_with_all_options/pyproject.toml
@@ -4,7 +4,7 @@ default_exclude = ["archived/"]
 include = ["custom.txt"]
 default_include = ["*.robot"]
 target_version = 6
-language = ["eng", "pl"]
+language = ["en", "pl"]
 # config = "other_config.toml"  # ignored, --config can be only passed via cli
 # ignore_git_dir = true  # ignored, only from cli
 # skip_gitignore = true  # ignored, only from cli, but may change in the future
diff --git a/tests/config/test_data/invalid_config/pyproject.toml b/tests/config/test_data/invalid_config/invalid.toml
similarity index 82%
rename from tests/config/test_data/invalid_config/pyproject.toml
rename to tests/config/test_data/invalid_config/invalid.toml
index d353404..fec71a5 100644
--- a/tests/config/test_data/invalid_config/pyproject.toml
+++ b/tests/config/test_data/invalid_config/invalid.toml
@@ -3,5 +3,5 @@ exclude = ["deprecated.robot"]
 default_exclude = ["archived/"]
 include = ["custom.txt"]
 default_include = ["*.robot"]
-language = ["eng", "pl"]
+language = ["en", "pl"]
 unknown = true
diff --git a/tests/config/test_data/multiple_files_one_config/resources/test.resource b/tests/config/test_data/multiple_files_one_config/resources/test.resource
new file mode 100644
index 0000000..e69de29
diff --git a/tests/config/test_data/multiple_files_one_config/robocop.toml b/tests/config/test_data/multiple_files_one_config/robocop.toml
new file mode 100644
index 0000000..db86d03
--- /dev/null
+++ b/tests/config/test_data/multiple_files_one_config/robocop.toml
@@ -0,0 +1,38 @@
+[tool.robocop]
+
+
+[tool.robocop.lint]
+reports = [
+    "all",
+    "return_status"
+]
+ignore = [
+    "missing-doc-keyword",
+    "missing-doc-test-case",
+    "missing-doc-suite",
+    "file-too-long",
+    "too-many-arguments",
+    "non-local-variables-should-be-uppercase",
+    "section-variable-not-uppercase",
+    "could-be-test-tags",
+]
+configure = [
+    "too-many-calls-in-test-case.max_calls=20",
+    "too-many-calls-in-keyword.max_calls=30",
+    "too-long-test-case.max_len=45",
+    "line-too-long.line_length=120",
+    "test-case-section-out-of-order.sections_order=documentation,tags,timeout,template,setup,keyword,teardown",
+    "todo-in-comment.severity=i",
+    "can-be-resource-file.severity=i",
+    "if-can-be-used.severity=i",
+    "inline-if-can-be-used.severity=i",
+    "deprecated-statement.severity=i",
+    "wrong-case-in-keyword-name.severity=w",
+    "empty-lines-between-sections.severity=w",
+    "missing-space-after-comment.severity=w",
+    "line-too-long.severity=w",
+    "hyphen-in-variable-name.severity=w",
+#    "return_status.quality_gate.E=0",
+#    "return_status.quality_gate.W=8",
+#    "return_status.quality_gate.I=-1",
+]
\ No newline at end of file
diff --git a/tests/config/test_data/multiple_files_one_config/test_suites/test.robot b/tests/config/test_data/multiple_files_one_config/test_suites/test.robot
new file mode 100644
index 0000000..e69de29
diff --git a/tests/linter/cli/test_list_rules.py b/tests/linter/cli/test_list_rules.py
index 380ed8d..2f9ca9d 100644
--- a/tests/linter/cli/test_list_rules.py
+++ b/tests/linter/cli/test_list_rules.py
@@ -8,6 +8,7 @@ from robocop.config import TargetVersion
 from robocop.linter.rules import Rule, RuleFilter, RuleSeverity, VisitorChecker
 from robocop.linter.utils.misc import ROBOT_VERSION
 from robocop.run import list_rules
+from tests import working_directory
 
 TEST_DATA = Path(__file__).parent.parent / "test_data" / "custom_rules"
 
@@ -152,12 +153,12 @@ def future_checker():
 
 class TestListingRules:
     def test_list_rule(
-        self, empty_linter, msg_0101_checker, non_default_rule_checker, deprecated_rules_checker, capsys
+        self, empty_linter, msg_0101_checker, non_default_rule_checker, deprecated_rules_checker, capsys, tmp_path
     ):
         """List rules with default options."""
         for checker in (msg_0101_checker, non_default_rule_checker, deprecated_rules_checker):
             empty_linter.config_manager.default_config.linter.register_checker(checker)
-        with patch("robocop.run.RobocopLinter", MagicMock(return_value=empty_linter)):
+        with working_directory(tmp_path), patch("robocop.run.RobocopLinter", MagicMock(return_value=empty_linter)):
             list_rules()
         out, _ = capsys.readouterr()
         assert (
diff --git a/tests/linter/rules/errors/invalid_if/test_rule.py b/tests/linter/rules/errors/invalid_if/test_rule.py
index ba3ba0c..e09260d 100644
--- a/tests/linter/rules/errors/invalid_if/test_rule.py
+++ b/tests/linter/rules/errors/invalid_if/test_rule.py
@@ -1,9 +1,3 @@
-import sys
-from pathlib import Path
-
-import pytest
-
-from tests import working_directory
 from tests.linter.utils import RuleAcceptance
 
 
@@ -28,14 +22,22 @@ class TestRuleAcceptance(RuleAcceptance):
             test_on_version=">=6.1",
         )
 
-    @pytest.mark.skipif(sys.platform != "linux", reason="Test only runs on Linux")
-    def test_with_symlink(self, tmp_path):
-        absolute_path = Path(__file__).parent / "test.robot"
-        (tmp_path / "test.robot").symlink_to(absolute_path)
-        with working_directory(tmp_path):
-            self.check_rule(
-                src_files=["test.robot"],
-                expected_file="expected_output_rf6.1.txt",
-                test_on_version=">=6.1",
-                test_dir=tmp_path,
-            )
+    # TODO: Relative paths with symlinks that also work with config paths
+    # For example keep paths in a object that keep original non resolved path
+    # import sys
+    # from pathlib import Path
+    #
+    # import pytest
+    #
+    # from tests import working_directory
+    # @pytest.mark.skipif(sys.platform != "linux", reason="Test only runs on Linux")
+    # def test_with_symlink(self, tmp_path):
+    #     absolute_path = Path(__file__).parent / "test.robot"
+    #     (tmp_path / "test.robot").symlink_to(absolute_path)
+    #     with working_directory(tmp_path):
+    #         self.check_rule(
+    #             src_files=["test.robot"],
+    #             expected_file="expected_output_rf6.1.txt",
+    #             test_on_version=">=6.1",
+    #             test_dir=tmp_path,
+    #         )