Skip to content

Commit 6445f88

Browse files
committed
Handle case-senstive dir/file paths by using pathlib.Path
1 parent 00c1e98 commit 6445f88

File tree

5 files changed

+37
-27
lines changed

5 files changed

+37
-27
lines changed

build.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,9 @@ def run_tests() -> int:
115115
for i, file_name_path in enumerate(all_python_test_files):
116116
command = ["coverage", "run", file_name_path]
117117
exit_code = call(command) if exit_code == 0 else exit_code
118-
# Keep coverage files
119-
os.rename(".coverage", f".coverage.{i}")
118+
if Path(".coverage").is_file():
119+
# Keep coverage files
120+
os.rename(".coverage", f".coverage.{i}")
120121
call(["coverage", "combine"])
121122
return exit_code
122123

getgauge/impl_loader.py

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import glob
12
import importlib
23
import inspect
34
import json
@@ -58,12 +59,8 @@ def copy_skel_files():
5859

5960

6061
def _import_impl(base_dir, step_impl_dir):
61-
for f in os.listdir(step_impl_dir):
62-
file_path = os.path.join(step_impl_dir, f)
63-
if f.endswith('.py'):
64-
_import_file(base_dir, file_path)
65-
elif path.isdir(file_path):
66-
_import_impl(base_dir, file_path)
62+
for python_file in glob.glob(f"{step_impl_dir}/**/*.py", recursive=True):
63+
_import_file(base_dir, python_file)
6764

6865
@contextmanager
6966
def use_temporary_sys_path():
@@ -88,15 +85,17 @@ def _import_file(base_dir, file_path):
8885
classes = inspect.getmembers(m, lambda member: inspect.isclass(member) and member.__module__ == module_name)
8986
if len(classes) > 0:
9087
for c in classes:
91-
file = inspect.getfile(c[1])
92-
# Create instance of step implementation class.
93-
if _has_methods_with_gauge_decoratores(c[1]):
94-
update_step_registry_with_class(c[1](), file_path) # c[1]() will create a new instance of the class
88+
class_obj = c[1]
89+
if _has_methods_with_gauge_decoratores(class_obj):
90+
update_step_registry_with_class(
91+
instance=class_obj(), # class_obj() will create a new instance of the class
92+
file_path=file_path
93+
)
9594
except:
9695
logger.fatal('Exception occurred while loading step implementations from file: {}.\n{}'.format(rel_path, traceback.format_exc()))
9796

98-
# Inject instance in each class method (hook/step)
9997
def update_step_registry_with_class(instance, file_path):
98+
""" Inject instance in each class method (hook/step) """
10099
# Resolve the absolute path from relative path
101100
# Note: relative path syntax ".." can appear in between the file_path too like "<Project_Root>/../../Other_Project/src/step_impl/file.py"
102101
file_path = os.path.abspath(file_path) if ".." in str(file_path) else file_path

getgauge/registry.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import os
33
import re
44
import sys
5+
from pathlib import Path
56
from subprocess import call
67
from uuid import uuid1
78

@@ -172,35 +173,33 @@ def get_step_positions(self, file_name):
172173
positions = []
173174
for step, infos in self.__steps_map.items():
174175
positions = positions + [{'stepValue': step, 'span': i.span}
175-
for i in infos if str(i.file_name).lower() == str(file_name).lower()]
176+
for i in infos if paths_equal(i.file_name, file_name)]
176177
return positions
177178

178179
def _get_all_hooks(self, file_name):
179180
all_hooks = []
180181
for hook in self.hooks:
181182
all_hooks = all_hooks + \
182-
[h for h in getattr(self, "__{}".format(hook))
183-
if str(h.file_name).lower() == str(file_name).lower()]
183+
[h for h in getattr(self, "__{}".format(hook))
184+
if paths_equal(h.file_name, file_name)]
184185
return all_hooks
185186

186187
def get_all_methods_in(self, file_name):
187188
methods = []
188189
for _, infos in self.__steps_map.items():
189-
# Using relative paths may lead to different spelling of the C drive (lower or capital C)
190-
methods = methods + [i for i in infos if str(i.file_name).lower() == str(file_name).lower()]
190+
methods = methods + [i for i in infos if paths_equal(i.file_name, file_name)]
191191
return methods + self._get_all_hooks(file_name)
192192

193193
def is_file_cached(self, file_name):
194194
for _, infos in self.__steps_map.items():
195-
# Using relative paths may lead to different spelling of the C drive (lower or capital C)
196-
if any(str(i.file_name).lower() == str(file_name).lower() for i in infos):
195+
if any(Path(i.file_name).resolve() == Path(file_name).resolve() for i in infos):
197196
return True
198197
return False
199198

200199
def remove_steps(self, file_name):
201200
new_map = {}
202201
for step, infos in self.__steps_map.items():
203-
filtered_info = [i for i in infos if i.file_name != file_name]
202+
filtered_info = [i for i in infos if not paths_equal(i.file_name, file_name)]
204203
if len(filtered_info) > 0:
205204
new_map[step] = filtered_info
206205
self.__steps_map = new_map
@@ -211,6 +210,17 @@ def clear(self):
211210
setattr(self, '__{}'.format(hook), [])
212211

213212

213+
def paths_equal(p1: str | Path, p2: str | Path) -> bool:
214+
"""
215+
Compare two paths in a cross-platform safe way.
216+
On Windows: case-insensitive, slash-insensitive.
217+
On Linux/macOS: case-sensitive.
218+
"""
219+
p1 = Path(p1).resolve()
220+
p2 = Path(p2).resolve()
221+
return os.path.normcase(str(p1)) == os.path.normcase(str(p2))
222+
223+
214224
def _filter_hooks(tags, hooks):
215225
filtered_hooks = []
216226
for hook in hooks:

getgauge/util.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ def get_project_root():
1414
def get_step_impl_dirs():
1515
step_impl_dir_names = map(str.strip, os.getenv(STEP_IMPL_DIR_ENV).split(',')) if os.getenv(STEP_IMPL_DIR_ENV) else ['step_impl']
1616
full_path_dir_names = []
17-
for name in step_impl_dir_names:
18-
name = name.replace("/", os.path.sep).replace("\\", os.path.sep)
19-
impl_dir = name if os.path.isabs(name) else os.path.join(get_project_root(), name)
17+
for dir_name in step_impl_dir_names:
18+
dir_name = dir_name.replace("/", os.path.sep).replace("\\", os.path.sep)
19+
impl_dir = dir_name if os.path.isabs(dir_name) else os.path.join(get_project_root(), dir_name)
2020
full_path_dir_names.append(impl_dir)
2121
return full_path_dir_names
2222

tests/test_processor.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ def test_Processor_failing_ending_execution_request(self):
354354
registry.add_after_suite(failing_impl)
355355
request = ExecutionEndingRequest()
356356
response = processor.process_execution_ending_request(request)
357-
print(response)
357+
358358
self.assertIsInstance(response, ExecutionStatusResponse)
359359
self.assertTrue(response.executionResult.failed)
360360
self.assertEqual(ProtoExecutionResult.ASSERTION,
@@ -528,8 +528,8 @@ def foo():
528528

529529
processor.process_cache_file_request(request)
530530

531-
self.assertEqual(registry.is_implemented('foo1'), False)
532-
self.assertEqual(registry.is_implemented('foo {}'), True)
531+
self.assertFalse(registry.is_implemented('foo1'))
532+
self.assertTrue(registry.is_implemented('foo {}'))
533533

534534
def test_Processor_cache_file_with_changed_status(self):
535535
request = CacheFileRequest()

0 commit comments

Comments
 (0)