Repository: OCA/pylint-odoo

Issue:
Pre-commit check with pylint jobs>1 
<!-- Provide a general summary of the issue in the Title above.
Before opening the issue, please check that there isn't another issue addressing the same problem already created -->

## Module
The name of the module that has a bug.
pylint-odoo/checkers/odoo_addons.py

## Describe the bug
A clear and concise description of what the bug is.

When running pylint-odoo with pylint configuration with jobs=3 (anything different from jobs=1) than it ends with error 


Traceback (most recent call last):
  File "/usr/lib/python3.11/concurrent/futures/process.py", line 261, in _process_worker
    r = call_item.fn(*call_item.args, **call_item.kwargs)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/concurrent/futures/process.py", line 210, in _process_chunk
    return [fn(*args) for args in chunk]
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/concurrent/futures/process.py", line 210, in <listcomp>
    return [fn(*args) for args in chunk]
            ^^^^^^^^^
  File "/home/kolmano/.cache/pre-commit/repoh9417f4h/py_env-python3.11/lib/python3.11/site-packages/pylint/lint/parallel.py", line 79, in _worker_check_single_file
    _worker_linter.check_single_file_item(file_item)
  File "/home/kolmano/.cache/pre-commit/repoh9417f4h/py_env-python3.11/lib/python3.11/site-packages/pylint/lint/pylinter.py", line 738, in check_single_file_item
    with self._astroid_module_checker() as check_astroid_module:
  File "/usr/lib/python3.11/contextlib.py", line 137, in __enter__
    return next(self.gen)
           ^^^^^^^^^^^^^^
  File "/home/kolmano/.cache/pre-commit/repoh9417f4h/py_env-python3.11/lib/python3.11/site-packages/pylint/lint/pylinter.py", line 946, in _astroid_module_checker
    checker.open()
  File "/home/kolmano/.cache/pre-commit/repoh9417f4h/py_env-python3.11/lib/python3.11/site-packages/pylint_odoo/checkers/odoo_addons.py", line 650, in open
    self.linter.config.deprecated_field_parameters = self.colon_list_to_dict(
                                                     ^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/kolmano/.cache/pre-commit/repoh9417f4h/py_env-python3.11/lib/python3.11/site-packages/pylint_odoo/checkers/odoo_addons.py", line 682, in colon_list_to_dict
    return dict(item.split(":") for item in colon_list)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: dictionary update sequence element #0 has length 1; 2 is required




## To Reproduce
Reproduced on version 9.1.2 and 8.0.20 

Steps to reproduce the behavior:
1. set jobs=3 in .pylintrc configuration file ([MAIN] section)
2. exec 'pre-commit run --all-files'

**Expected behavior**
Not to raise error

**Additional context**
Tested on ubuntu 22

Hints from issue comments:
@moylop260: Could you share the output of the following commands, please?

`python3 -m pylint --version`
`python3 -c "import pylint_odoo;print(pylint_odoo.__version__)"`
@moylop260: > Could you share the output of the following commands, please?

Forget it, I can reproduced it using the following DockerFile

```Dockerfile
# docker build -t pylint_jobs_error .
FROM ubuntu:22.04
RUN apt update && \
    apt install -y python3 python3-pip
RUN pip install pylint-odoo
```

Also, generating the following unittest it is reproduced in the CI:

```diff
diff --git a/tests/test_main.py b/tests/test_main.py
index e0cb608..c16e28c 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -144,6 +144,13 @@ class MainTest(unittest.TestCase):
         real_errors = pylint_res.linter.stats.by_msg
         self.assertEqual(self.expected_errors, real_errors)

+    def test_22_jobs(self):
+        """Using jobs could raise new errors"""
+        self.default_extra_params += ["--jobs=2"]
+        pylint_res = self.run_pylint(self.paths_modules, verbose=True)
+        real_errors = pylint_res.linter.stats.by_msg
+        self.assertEqual(self.expected_errors, real_errors)
+
```


Solution patch:
diff --git a/src/pylint_odoo/checkers/odoo_addons.py b/src/pylint_odoo/checkers/odoo_addons.py
index e58ddc9..8345358 100644
--- a/src/pylint_odoo/checkers/odoo_addons.py
+++ b/src/pylint_odoo/checkers/odoo_addons.py
@@ -575,6 +575,8 @@ class OdooAddons(OdooBaseChecker, BaseChecker):
         self._module_versions = {}
         self._module_migrations = defaultdict(set)
         self._deprecated_odoo_methods = set()
+        self.deprecated_field_parameters = {}
+        self._odoo_inherit_items = defaultdict(set)
 
     @staticmethod
     def version_greater_equal_than(original, against):
@@ -614,7 +616,9 @@ class OdooAddons(OdooBaseChecker, BaseChecker):
 
         if self.linter.is_message_enabled("manifest-behind-migrations"):
             for module, migrations in self._module_migrations.items():
-                module_version, manifest_node = self._module_versions[module]
+                module_version, manifest_node = self._module_versions.get(module) or (None, None)
+                if (module_version, manifest_node) == (None, None):
+                    continue
                 for migration in migrations:
                     try:
                         if not self.version_greater_equal_than(module_version, migration):
@@ -646,10 +650,7 @@ class OdooAddons(OdooBaseChecker, BaseChecker):
 
     def open(self):
         super().open()
-        self._odoo_inherit_items = defaultdict(set)
-        self.linter.config.deprecated_field_parameters = self.colon_list_to_dict(
-            self.linter.config.deprecated_field_parameters
-        )
+        self.deprecated_field_parameters = self.colon_list_to_dict(self.linter.config.deprecated_field_parameters)
 
         if self.linter.config.deprecated_odoo_model_methods:
             deprecated_model_methods = ast.literal_eval(self.linter.config.deprecated_odoo_model_methods)
@@ -869,7 +870,7 @@ class OdooAddons(OdooBaseChecker, BaseChecker):
                     self.add_message("attribute-string-redundant", node=node)
                 if isinstance(argument, nodes.Keyword):
                     argument_aux = argument.value
-                    deprecated = self.linter.config.deprecated_field_parameters
+                    deprecated = self.deprecated_field_parameters
                     if (
                         argument.arg in ["compute", "search", "inverse"]
                         and isinstance(argument_aux, nodes.Const)
diff --git a/src/pylint_odoo/misc.py b/src/pylint_odoo/misc.py
index 1ca5351..78a803c 100644
--- a/src/pylint_odoo/misc.py
+++ b/src/pylint_odoo/misc.py
@@ -89,15 +89,20 @@ def top_path(path):
         return path.root or path.drive
 
 
+def full_norm_path(path):
+    """Expand paths in all possible ways"""
+    return os.path.normpath(os.path.realpath(os.path.abspath(os.path.expanduser(os.path.expandvars(path.strip())))))
+
+
 @lru_cache(maxsize=256)
 def walk_up(path, filenames, top):
     """Look for "filenames" walking up in parent paths of "path"
     but limited only to "top" path
     """
-    if path == top:
+    if full_norm_path(path) == full_norm_path(top):
         return None
     for filename in filenames:
         path_filename = os.path.join(path, filename)
-        if os.path.isfile(path_filename):
+        if os.path.isfile(full_norm_path(path_filename)):
             return path_filename
     return walk_up(os.path.dirname(path), filenames, top)


Test patch:
diff --git a/tests/test_main.py b/tests/test_main.py
index e0cb608..00ee0b5 100644
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -424,6 +424,14 @@ def fstring_no_sqli(self):
         }
         self.assertDictEqual(real_errors, expected_errors)
 
+    def test_180_jobs(self):
+        """Using jobs could raise new errors"""
+        self.default_extra_params += ["--jobs=2"]
+        pylint_res = self.run_pylint(self.paths_modules, verbose=True)
+        real_errors = pylint_res.linter.stats.by_msg
+        # TODO: Remove .keys() in order to validate the number of occurrences
+        self.assertEqual(self.expected_errors.keys(), real_errors.keys())
+
     @staticmethod
     def re_replace(sub_start, sub_end, substitution, content):
         re_sub = re.compile(rf"^{re.escape(sub_start)}$.*^{re.escape(sub_end)}$", re.M | re.S)