Skip to content

Commit

Permalink
Added Tests and improved indexing
Browse files Browse the repository at this point in the history
  • Loading branch information
Abhishek's Macbook Pro authored and Abhishek's Macbook Pro committed Sep 30, 2023
1 parent 4269aa2 commit 67294d8
Show file tree
Hide file tree
Showing 16 changed files with 319 additions and 34 deletions.
28 changes: 28 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: test

on: [push, pull_request]

jobs:
run-tests:
strategy:
fail-fast: false
matrix:
st-version: [3, 4]
os: ["ubuntu-latest", "macOS-latest", "windows-latest"]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: SublimeText/UnitTesting/actions/setup@v1
with:
sublime-text-version: ${{ matrix.st-version }}
- uses: SublimeText/UnitTesting/actions/run-tests@v1
with:
coverage: true
codecov-upload: true

run-syntax-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: SublimeText/UnitTesting/actions/setup@v1
- uses: SublimeText/UnitTesting/actions/run-syntax-tests@v1
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,6 @@ cython_debug/
~/
exp.py
exp.sh
exp2.py
exp2.py
pyrock.sublime-project
pyrock.sublime-workspace
16 changes: 11 additions & 5 deletions py_rock.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
import importlib
import sublime
import sublime_plugin
from sublime import Edit
from typing import Optional

# Reloads the submodules
from .src import reloader
importlib.reload(reloader)
reloader.reload()

from .src.commands.base_indexer import BaseIndexer
from .src.commands.import_symbol import ImportSymbolCommand
from .src.commands.re_index_imports import ReIndexImportsCommand
from .src.commands.admin import AdminManager
from .src.logger import Logger
from .src.constants import PyRockConstants


logger = Logger(__name__)
admin = AdminManager(window=sublime.active_window())

Expand All @@ -25,7 +30,7 @@ def plugin_unloaded():


class PyRockCommand(sublime_plugin.TextCommand):
def run(self, edit: Edit, action: str):
def run(self, edit: Edit, action: str, test: bool = False):
# Run admin checks
admin.run()

Expand All @@ -35,14 +40,15 @@ def run(self, edit: Edit, action: str):
cmd = ImportSymbolCommand(
window=sublime.active_window(),
edit=edit,
view=self.view
view=self.view,
test=test,
)
cmd.run()
if action == "re_index_imports":
cmd = ReIndexImportsCommand()
cmd = ReIndexImportsCommand(test=test)
cmd.run(sublime.active_window())

def is_enabled(self):
def is_enabled(self, action: str, test: bool = False):
"""
Disable command if view is not python file or syntax is not python
"""
Expand Down
33 changes: 19 additions & 14 deletions src/commands/base_indexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,21 +96,8 @@ def _run_import_indexer(self, window: Window, indexer_command: str) -> Tuple[boo
logger.error(message)

return script_success, message

def _run_indexer(self, window: Window, force=False):
if self._is_indexing_needed() and not force:
logger.debug("Indexing not needed")
window.status_message("Indexing not needed")
return

self._command_error_evidence: List[str] = []

self._generate_serialized_settings()

window.set_status_bar_visible(True)

window.status_message("Indexing imports...")

def _get_import_command(self) -> str:
unix_env_bash = """
set -e
source "{venv_path}"
Expand Down Expand Up @@ -155,6 +142,24 @@ def _run_indexer(self, window: Window, force=False):
import_command = windows_without_env_bash.format(
indexer_script_path=self._get_indexer_script_path()
)

return import_command

def _run_indexer(self, window: Window, force=False):
if self._is_indexing_needed() and not force:
logger.debug("Indexing not needed")
window.status_message("Indexing not needed")
return

self._command_error_evidence: List[str] = []

self._generate_serialized_settings()

window.set_status_bar_visible(True)

window.status_message("Indexing imports...")

import_command: str = self._get_import_command()

logger.debug(f"Import shell command using: {import_command}")
success, message = self._run_import_indexer(window, import_command)
Expand Down
36 changes: 23 additions & 13 deletions src/commands/import_symbol.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@


class ImportSymbolCommand:
def __init__(self, window, edit, view):
def __init__(self, window, edit, view, test: bool = False):
self.window = window
self.sublime_edit = edit
self.view = view
self.test = test

def _update_existing_import_statement_region(
self,
Expand Down Expand Up @@ -123,25 +124,31 @@ def _add_import_to_view(self, index: int):


def load_user_python_imports(self) -> Optional[Dict[str, Dict]]:
file_path = os.path.join(PyRockConstants.INDEX_CACHE_DIRECTORY, PyRockConstants.IMPORT_INDEX_FILE_NAME)
import time
file_path = os.path.join(
PyRockConstants.INDEX_CACHE_DIRECTORY,
PyRockConstants.IMPORT_INDEX_FILE_NAME
)

import_map: Dict[str, Dict] = {}

start_time = time.perf_counter()
if os.path.exists(file_path):
with open(file_path, 'r') as f:
import_map = json.load(f)
else:
logger.debug("No user python import index found")
return None

print(f"***10 Time taken {time.perf_counter() - start_time}")

return import_map

def generate_imports_from_sublime_result(
self,
selected_text: str,
symbol_locations: List[SymbolLocation]
) -> Dict[str, str]:
import_statements: Dict[str, str] = {}
) -> Dict[str, Dict]:
import_statements: Dict[str, Dict] = {}

for symbol_location in symbol_locations:
if symbol_location.display_name.endswith(".py"):
Expand All @@ -157,8 +164,8 @@ def generate_imports_from_user_python_imports(
self,
selected_text: str,
user_python_import_map: Dict[str, Dict]
) -> Dict[str, str]:
import_statements: Dict[str, str] = {}
) -> Dict[str, Dict]:
import_statements: Dict[str, Dict] = {}

if user_python_import_map.get(selected_text[0], {}).get(selected_text[-1]):
import_paths = user_python_import_map[selected_text[0]][selected_text[-1]]
Expand Down Expand Up @@ -202,7 +209,7 @@ def run(self):
)
logger.debug(f"Sublime importer result {symbol_locations}")

self.import_statements: Dict[str, str] = {}
self.import_statements: Dict[str, Dict] = {}

if selected_text:
self.import_statements = self.generate_imports_from_sublime_result(
Expand All @@ -229,8 +236,11 @@ def run(self):
)

if len(self.import_statements) > 0:
self.view.show_popup_menu(
items=list(self.import_statements.keys()),
on_done=self._add_import_to_view,
flags=0
)
if self.test:
self._add_import_to_view(index=0)
else:
self.view.show_popup_menu(
items=list(self.import_statements.keys()),
on_done=self._add_import_to_view,
flags=0
)
5 changes: 4 additions & 1 deletion src/commands/re_index_imports.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import sublime
from sublime import Window
from ..commands.base_indexer import BaseIndexer
from .base_indexer import BaseIndexer
from ..logger import Logger
from pathlib import Path

Expand All @@ -10,6 +10,9 @@


class ReIndexImportsCommand(BaseIndexer):
def __init__(self, test: bool = False):
self.test = test

def run(self, window: Window):
result: bool = sublime.ok_cancel_dialog(
msg="Are you sure to re-index imports?",
Expand Down
39 changes: 39 additions & 0 deletions src/reloader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import importlib
import os
from types import ModuleType
from typing import Optional

def import_sub_module(module_path) -> Optional[ModuleType]:
"""
:module_path -> In form of "Plugin.foo.bar"
"""
try:
print(f"Reloading: {module_path}")
return importlib.import_module(module_path)
except ModuleNotFoundError:
print(f"Unable to import {module_path}")
return None

def walk_sub_modules(plugin_name, base_path):
for dir, _, files in os.walk(base_path):
parent_module_imported = False
# If its a root directory
if plugin_name == os.path.basename(dir):
parent_module_path = plugin_name
else:
# This gives result as Plugin.foo.bar
parent_module_path = f"{plugin_name}{'.'.join(str(dir).split(plugin_name)[-1].split(os.path.sep))}"
for file in files:
file_name, extension = os.path.splitext(file)
if extension == '.py':
# Importing once only
if not parent_module_imported:
# Only trying to import parent once we know it has .py file
import_sub_module(parent_module_path)
parent_module_imported = True
# Importing .py file
import_sub_module(f"{parent_module_path}.{file_name}")

def reload():
plugin = importlib.import_module("PyRock")
walk_sub_modules("PyRock", plugin.__path__[0])
26 changes: 26 additions & 0 deletions src/scripts/indexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from collections import defaultdict
import importlib
import traceback
import typing
from typing import List, Dict, Tuple, Set
from types import FunctionType, ModuleType
import logging
Expand Down Expand Up @@ -73,6 +74,18 @@ def get_module_members(self, module) -> List[Tuple[str, ModuleType]]:
except Exception:
return []

def get_built_in_function_members(self, module):
try:
return inspect.getmembers(module, inspect.isbuiltin)
except Exception:
return []

def get_all_members(self, module):
try:
return inspect.getmembers(module)
except Exception:
return []

def _index_module_class_and_function_members(
self,
parent_module_path: str,
Expand All @@ -88,6 +101,19 @@ def _index_module_class_and_function_members(
function_path = f"{parent_module_path}.{function_name}"
self._store_in_map(function_path)

module_built_in_functions: List[Tuple[str, FunctionType]] = self.get_built_in_function_members(module)
for built_in_name, function_obj in module_built_in_functions:
built_in_path = f"{parent_module_path}.{built_in_name}"
self._store_in_map(built_in_path)

# Special case to handle typing module classes
if "typing" in parent_module_path:
all_members = self.get_all_members(module)
for member_name, member_obj in all_members:
if isinstance(member_obj, typing._GenericAlias):
alias_path = f"{parent_module_path}.{member_name}"
self._store_in_map(alias_path)

def _index_sub_module_members(
self,
parent_module_name: str,
Expand Down
Empty file added tests/__init__.py
Empty file.
19 changes: 19 additions & 0 deletions tests/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import sublime
from unittesting import DeferrableTestCase


class PyRockTestBase(DeferrableTestCase):
def setUp(self):
self.view = sublime.active_window().new_file(
syntax='Packages/Python/Python.sublime-syntax'
)
self.window = self.view.window()
self.sublime_settings = sublime.load_settings("Preferences.sublime-settings")
self.sublime_settings.set("close_windows_when_empty", False)

def tearDown(self):
if self.view:
self.view.set_scratch(True)
self.view.window().focus_view(self.view)
self.view.window().run_command("close_file")

Empty file added tests/commands/__init__.py
Empty file.
23 changes: 23 additions & 0 deletions tests/commands/test_base_indexer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import sublime
from unittest import mock
from tests.base import PyRockTestBase
from PyRock.src.commands.base_indexer import BaseIndexer


class TestIndexer(PyRockTestBase):
def setUp(self):
super().setUp()

@mock.patch("PyRock.src.commands.base_indexer.BaseIndexer._run_import_indexer")
def test_run_indexer(
self,
mocked_run_import_indexer,
):
mocked_run_import_indexer.return_value = (True, "")

base_indexer = BaseIndexer()
base_indexer._run_indexer(self.window, True)

import_command = base_indexer._get_import_command()

mocked_run_import_indexer.assert_called_once_with(self.window, import_command)
Loading

0 comments on commit 67294d8

Please sign in to comment.