Skip to content
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions build.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,10 @@ def copy(src, dest):

usage = """
Usage: python build.py --[option]
Example: python build.py --test --install

Options:
--dist : creates the distributable.
--test : runs unit tests.
--install : installs python plugin and generates the pip package
"""
Expand All @@ -113,8 +115,9 @@ def run_tests() -> int:
for i, file_name_path in enumerate(all_python_test_files):
command = ["coverage", "run", file_name_path]
exit_code = call(command) if exit_code == 0 else exit_code
# Keep coverage files
os.rename(".coverage", f".coverage.{i}")
if Path(".coverage").is_file():
# Keep coverage files
os.rename(".coverage", f".coverage.{i}")
call(["coverage", "combine"])
return exit_code

Expand All @@ -123,15 +126,15 @@ def main():
if len(sys.argv) < 2:
print(usage)
else:
if sys.argv[1] == '--dist':
if '--dist' in sys.argv:
create_zip()
generate_package()
else:
if '--test' in sys.argv:
exit_code = run_tests()
if exit_code != 0:
sys.exit(exit_code)
elif sys.argv[1] == '--install':
install()
if '--install' in sys.argv:
install()


if __name__ == '__main__':
Expand Down
21 changes: 10 additions & 11 deletions getgauge/impl_loader.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import glob
import importlib
import inspect
import json
Expand Down Expand Up @@ -58,12 +59,8 @@ def copy_skel_files():


def _import_impl(base_dir, step_impl_dir):
for f in os.listdir(step_impl_dir):
file_path = os.path.join(step_impl_dir, f)
if f.endswith('.py'):
_import_file(base_dir, file_path)
elif path.isdir(file_path):
_import_impl(base_dir, file_path)
for python_file in glob.glob(f"{step_impl_dir}/**/*.py", recursive=True):
_import_file(base_dir, python_file)

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

# Inject instance in each class method (hook/step)
def update_step_registry_with_class(instance, file_path):
""" Inject instance in each class method (hook/step) """
# Resolve the absolute path from relative path
# Note: relative path syntax ".." can appear in between the file_path too like "<Project_Root>/../../Other_Project/src/step_impl/file.py"
file_path = os.path.abspath(file_path) if ".." in str(file_path) else file_path
Expand Down
29 changes: 23 additions & 6 deletions getgauge/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
import os
import re
import sys
from pathlib import Path
from subprocess import call
from typing import Union
from uuid import uuid1

from getgauge import logger
Expand Down Expand Up @@ -172,33 +174,33 @@ def get_step_positions(self, file_name):
positions = []
for step, infos in self.__steps_map.items():
positions = positions + [{'stepValue': step, 'span': i.span}
for i in infos if i.file_name == file_name]
for i in infos if paths_equal(i.file_name, file_name)]
return positions

def _get_all_hooks(self, file_name):
all_hooks = []
for hook in self.hooks:
all_hooks = all_hooks + \
[h for h in getattr(self, "__{}".format(hook))
if h.file_name == file_name]
[h for h in getattr(self, "__{}".format(hook))
if paths_equal(h.file_name, file_name)]
return all_hooks

def get_all_methods_in(self, file_name):
methods = []
for _, infos in self.__steps_map.items():
methods = methods + [i for i in infos if i.file_name == file_name]
methods = methods + [i for i in infos if paths_equal(i.file_name, file_name)]
return methods + self._get_all_hooks(file_name)

def is_file_cached(self, file_name):
for _, infos in self.__steps_map.items():
if any(i.file_name == file_name for i in infos):
if any(paths_equal(i.file_name, file_name) for i in infos):
return True
return False

def remove_steps(self, file_name):
new_map = {}
for step, infos in self.__steps_map.items():
filtered_info = [i for i in infos if i.file_name != file_name]
filtered_info = [i for i in infos if not paths_equal(i.file_name, file_name)]
if len(filtered_info) > 0:
new_map[step] = filtered_info
self.__steps_map = new_map
Expand All @@ -209,6 +211,21 @@ def clear(self):
setattr(self, '__{}'.format(hook), [])


def paths_equal(p1: Union[str, Path], p2: Union[str, Path]) -> bool:
"""
Compare two paths in a cross-platform safe way.
On Windows: case-insensitive, slash-insensitive.
On Linux/macOS: case-sensitive.
"""
p1 = Path(p1).resolve()
p2 = Path(p2).resolve()
if sys.platform.startswith("win"):
# As Windows is case-insensitive, we can use 'normcase' to compare paths!
return os.path.normcase(str(p1)) == os.path.normcase(str(p2))
# Mac (and others) allows to use case-sensitive files/folders!
return p1 == p2


def _filter_hooks(tags, hooks):
filtered_hooks = []
for hook in hooks:
Expand Down
8 changes: 4 additions & 4 deletions getgauge/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ def get_project_root():
def get_step_impl_dirs():
step_impl_dir_names = map(str.strip, os.getenv(STEP_IMPL_DIR_ENV).split(',')) if os.getenv(STEP_IMPL_DIR_ENV) else ['step_impl']
full_path_dir_names = []
for name in step_impl_dir_names:
name = name.replace("/", os.path.sep).replace("\\", os.path.sep)
imple_dir = name if os.path.isabs(name) else os.path.join(get_project_root(), name)
full_path_dir_names.append(imple_dir)
for dir_name in step_impl_dir_names:
dir_name = dir_name.replace("/", os.path.sep).replace("\\", os.path.sep)
impl_dir = dir_name if os.path.isabs(dir_name) else os.path.join(get_project_root(), dir_name)
full_path_dir_names.append(impl_dir)
return full_path_dir_names


Expand Down
2 changes: 1 addition & 1 deletion python.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"id": "python",
"version": "0.4.11",
"version": "0.4.12",
"description": "Python support for gauge",
"run": {
"windows": [
Expand Down
6 changes: 3 additions & 3 deletions tests/test_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ def test_Processor_failing_ending_execution_request(self):
registry.add_after_suite(failing_impl)
request = ExecutionEndingRequest()
response = processor.process_execution_ending_request(request)
print(response)

self.assertIsInstance(response, ExecutionStatusResponse)
self.assertTrue(response.executionResult.failed)
self.assertEqual(ProtoExecutionResult.ASSERTION,
Expand Down Expand Up @@ -528,8 +528,8 @@ def foo():

processor.process_cache_file_request(request)

self.assertEqual(registry.is_implemented('foo1'), False)
self.assertEqual(registry.is_implemented('foo {}'), True)
self.assertFalse(registry.is_implemented('foo1'))
self.assertTrue(registry.is_implemented('foo {}'))

def test_Processor_cache_file_with_changed_status(self):
request = CacheFileRequest()
Expand Down
69 changes: 57 additions & 12 deletions tests/test_registry.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import re
import sys
import unittest

from getgauge.registry import Registry
Expand Down Expand Up @@ -160,9 +161,12 @@ def test_Registry_before_spec_with_tags(self):
registry.add_before_spec(info['func'], info['tags'])

self.assertEqual([info1['func']], [i.impl for i in registry.before_spec([])])
self.assertEqual([x['func'] for x in infos], [i.impl for i in registry.before_spec(['A', 'b'])])
self.assertEqual([info1['func'], info3['func']], [i.impl for i in registry.before_spec(['A', 'b', 'c'])])
self.assertEqual([info1['func'], info3['func']], [i.impl for i in registry.before_spec(['A'])])
self.assertEqual([x['func'] for x in infos], [
i.impl for i in registry.before_spec(['A', 'b'])])
self.assertEqual([info1['func'], info3['func']], [
i.impl for i in registry.before_spec(['A', 'b', 'c'])])
self.assertEqual([info1['func'], info3['func']], [
i.impl for i in registry.before_spec(['A'])])
self.assertEqual([info1['func']], [i.impl for i in registry.before_spec(['A', 'c'])])

def test_Registry_after_spec_with_tags(self):
Expand All @@ -176,9 +180,12 @@ def test_Registry_after_spec_with_tags(self):
registry.add_after_spec(info['func'], info['tags'])

self.assertEqual([info1['func']], [i.impl for i in registry.after_spec([])])
self.assertEqual([x['func'] for x in infos], [i.impl for i in registry.after_spec(['A', 'b'])])
self.assertEqual([info1['func'], info3['func']], [i.impl for i in registry.after_spec(['A', 'b', 'c'])])
self.assertEqual([info1['func'], info3['func']], [i.impl for i in registry.after_spec(['A'])])
self.assertEqual([x['func'] for x in infos], [
i.impl for i in registry.after_spec(['A', 'b'])])
self.assertEqual([info1['func'], info3['func']], [
i.impl for i in registry.after_spec(['A', 'b', 'c'])])
self.assertEqual([info1['func'], info3['func']], [
i.impl for i in registry.after_spec(['A'])])
self.assertEqual([info1['func']], [i.impl for i in registry.after_spec(['A', 'c'])])

def test_Registry_before_scenario(self):
Expand Down Expand Up @@ -269,9 +276,12 @@ def test_Registry_before_step_with_tags(self):
registry.add_before_step(info['func'], info['tags'])

self.assertEqual([info1['func']], [i.impl for i in registry.before_step([])])
self.assertEqual([x['func'] for x in infos], [i.impl for i in registry.before_step(['A', 'b'])])
self.assertEqual([info1['func'], info3['func']], [i.impl for i in registry.before_step(['A', 'b', 'c'])])
self.assertEqual([info1['func'], info3['func']], [i.impl for i in registry.before_step(['A'])])
self.assertEqual([x['func'] for x in infos], [
i.impl for i in registry.before_step(['A', 'b'])])
self.assertEqual([info1['func'], info3['func']], [
i.impl for i in registry.before_step(['A', 'b', 'c'])])
self.assertEqual([info1['func'], info3['func']], [
i.impl for i in registry.before_step(['A'])])
self.assertEqual([info1['func']], [i.impl for i in registry.before_step(['A', 'c'])])

def test_Registry_after_step_with_tags(self):
Expand All @@ -286,9 +296,12 @@ def test_Registry_after_step_with_tags(self):
registry.add_after_step(info['func'], info['tags'])

self.assertEqual([info1['func']], [i.impl for i in registry.after_step([])])
self.assertEqual([x['func'] for x in infos], [i.impl for i in registry.after_step(['A', 'b'])])
self.assertEqual([info1['func'], info3['func']], [i.impl for i in registry.after_step(['A', 'b', 'c'])])
self.assertEqual([info1['func'], info3['func']], [i.impl for i in registry.after_step(['A'])])
self.assertEqual([x['func'] for x in infos], [
i.impl for i in registry.after_step(['A', 'b'])])
self.assertEqual([info1['func'], info3['func']], [
i.impl for i in registry.after_step(['A', 'b', 'c'])])
self.assertEqual([info1['func'], info3['func']], [
i.impl for i in registry.after_step(['A'])])
self.assertEqual([info1['func']], [i.impl for i in registry.after_step(['A', 'c'])])

def test_Registry__step_positions_of_a_given_file(self):
Expand Down Expand Up @@ -361,6 +374,38 @@ def test_Registry_get_all_methods_in_should_give_all_the_methods_define_in_that_
self.assertEqual(3, len(registry.get_all_methods_in("foo.py")))
self.assertEqual(2, len(registry.get_all_methods_in("bar.py")))

@unittest.skipIf(not sys.platform.startswith("win"), "Test is designed to cover Windows like paths")
def test_Registry_get_all_methods_in_should_handle_paths_case_sensitive(self):
lower_c_drive = 'c:/random/path/foo.py'
upper_c_drive = 'C:/random/path/foo.py'

step_infos = [
{'text': 'Foo', 'func': 'func1', 'file_name': lower_c_drive},
{'text': 'Foo <>', 'func': 'func2', 'file_name': upper_c_drive}
]
for info in step_infos:
registry.add_step(info['text'], info['func'], info['file_name'])

""" Note: we should find both steps regardless the different spelling as the path is in fact equal! """
self.assertEqual(2, len(registry.get_all_methods_in(lower_c_drive)))
self.assertEqual(2, len(registry.get_all_methods_in(upper_c_drive)))

@unittest.skipIf(sys.platform.startswith("win"), "Fails on Windows due to case sensitivity")
def test_Registry_get_all_methods_in_should_handle_paths_case_sensitive_on_mac(self):
path1 = '/random/path/foo.py'
path2 = '/random/PATH/foo.py'

step_infos = [
{'text': 'Foo', 'func': 'func1', 'file_name': path1},
{'text': 'Foo <>', 'func': 'func2', 'file_name': path2}
]
for info in step_infos:
registry.add_step(info['text'], info['func'], info['file_name'])

""" Note: since the paths are in fact different, they should be treated as different paths! """
self.assertEqual(1, len(registry.get_all_methods_in(path1)))
self.assertEqual(1, len(registry.get_all_methods_in(path2)))

def tearDown(self):
global registry
registry = Registry()
Expand Down
Loading