Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2.x branch #34

Draft
wants to merge 6 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
40 changes: 40 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: CI

on:
push:
branches:
- master
pull_request:
merge_group:

jobs:
check-messages:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: kaste/upgrade-messages-test-action@v1

lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: chartboost/ruff-action@v1

mypy:
strategy:
fail-fast: false
matrix:
platform: ['linux', 'darwin', 'win32']
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.8'
- name: Install mypy
run: |
pip install mypy
- name: Check with mypy
run: |
mypy python_test_plier.py --platform=${{ matrix.platform }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ __pycache__/
.coverage
.tox/
.venv

18 changes: 9 additions & 9 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
matrix:
include:
- name: Linux
os: linux
language: python
- name: OSX
os: osx
osx_image: xcode11.2
language: generic # 'python' is an error on travis MacOS
include:
- name: Linux
os: linux
language: python
- name: OSX
os: osx
osx_image: xcode11.2
language: generic # 'python' is an error on travis MacOS

install:
- pip3 install --upgrade pip
- pip3 install -r test-reqs.txt
script:
- pytest -vv --flake8 --cov=./
after_success:
codecov
codecov
10 changes: 9 additions & 1 deletion Default.sublime-commands
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
[
{
"caption": "Run Python Tests",
"caption": "SublimeTestPlier: Run python tests",
"command": "run_python_tests"
},
{
"caption": "SublimeTestPlier: Settings",
"command": "edit_settings",
"args": {
"base_file": "${packages}/SublimeTestPlier/SublimeTestPlier.sublime-settings",
"default": "{\n$0\n}\n"
}
}
]
7 changes: 7 additions & 0 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[mypy]
python_version = 3.8
check_untyped_defs = True
warn_unused_ignores = True
warn_unreachable = True
mypy_path = typings:stubs
exclude = typings
164 changes: 92 additions & 72 deletions python_test_plier.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,34 @@

MYPY = False
if MYPY:
from typing import Optional
from typing import Optional # noqa: F401


class RunPythonTestsCommand(sublime_plugin.WindowCommand):
external_runner = None
_settings = None

@property
def settings(self):
if self._settings is None:
self._settings = sublime.load_settings("SublimeTestPlier.sublime-settings")
return self._settings

def ansi_installed(self):
sublimeansi_installed = (
'sublimeansi' in self.packages or 'ansiescape' in self.packages
"sublimeansi" in self.packages or "ansiescape" in self.packages
)
utils._log('SublimeANSI installed: %s' % sublimeansi_installed)
utils._log("SublimeANSI installed: %s" % sublimeansi_installed)
return sublimeansi_installed

def setup_runner(self):
self.settings = sublime.load_settings("SublimeTestPlier.sublime-settings")
utils._log("Settings: ", vars(self.settings))
self.default_cmd = self.settings.get('default_cmd')
self.default_cmd = self.settings.get("default_cmd")
utils._log("Default CMD: ", self.default_cmd)

installed_packages = [
filename.split('.')[0]
for filename
in os.listdir(sublime.installed_packages_path())
filename.split(".")[0]
for filename in os.listdir(sublime.installed_packages_path())
]
local_packages = os.listdir(sublime.packages_path())
self.packages = set(
Expand All @@ -41,42 +46,46 @@ def setup_runner(self):
utils._log("Packages: ", self.packages)

# get current filename
self.filename = self.window.active_view().file_name()
if self.window:
view = self.window.active_view()
if view:
self.filename = view.file_name()
utils._log("Filename: ", self.filename)

self.module = self._get_module(self.filename, base=None)
utils._log("Module: ", self.module)

def _get_module(self, filename, base):
""" Convert a filename to a "module" relative to the working path """
if not filename or not filename.endswith('.py'):
utils._log('Cannot get module for non python-source file: ', filename)
return '' # only pytnon modules are supported
"""Convert a filename to a "module" relative to the working path"""
if not filename or not filename.endswith(".py"):
utils._log("Cannot get module for non python-source file: ", filename)
return "" # only pytnon modules are supported
base = base or os.path.join(
self.window.extract_variables().get('project_path', ''),
self.window.extract_variables().get('project_base_name', ''))
utils._log('Getting module for file %s relative to base %s' % (filename, base))
self.window.extract_variables().get("project_path", ""),
self.window.extract_variables().get("project_base_name", ""),
)
utils._log("Getting module for file %s relative to base %s" % (filename, base))
if not filename.startswith(base):
utils._log('Cannot determine module path outside of directory')
return ''
return filename.replace(base, '').replace(os.path.sep, '.')[:-3].strip('.')
utils._log("Cannot determine module path outside of directory")
return ""
return filename.replace(base, "").replace(os.path.sep, ".")[:-3].strip(".")

def _get_default_kwargs(self):
kwargs = {
'cmd': self.default_cmd,
"cmd": self.default_cmd,
# trim the following string in-between interpolated parts
'sep_cleanup': '::',
"sep_cleanup": "::",
}
if self.ansi_installed():
kwargs['syntax'] = "Packages/ANSIescape/ANSI.tmLanguage"
kwargs["syntax"] = "Packages/ANSIescape/ANSI.tmLanguage"
return kwargs

def _format_placeholder(self, cmd, sep, **kwargs):
result = []
for part in cmd:
try:
part = part.format(**kwargs).strip(sep).strip('.')
cleaned_part = re.sub('%s+' % sep, sep, part).strip(sep)
part = part.format(**kwargs).strip(sep).strip(".")
cleaned_part = re.sub("%s+" % sep, sep, part).strip(sep)
if cleaned_part:
result.append(cleaned_part)
except KeyError:
Expand All @@ -87,7 +96,7 @@ def _format_placeholder(self, cmd, sep, **kwargs):
def get_pattern(self, view, python_exec):
utils._log("View: ", view)
pattern = view and utils.get_test(view, use_python=python_exec)
utils._log('Test pattern: ', pattern)
utils._log("Test pattern: ", pattern)
if not pattern:
self.class_name = self.func_name = None
return
Expand All @@ -96,13 +105,13 @@ def get_pattern(self, view, python_exec):
def find_venv_root(self, filename):
# type: (str) -> Optional[str]
dirname = os.path.dirname(os.path.abspath(filename))
while dirname != '/':
if '.venv' in os.listdir(dirname):
venv_file = os.path.join(dirname, '.venv')
while dirname != "/":
if ".venv" in os.listdir(dirname):
venv_file = os.path.join(dirname, ".venv")
with open(venv_file) as f:
venv = f.read().strip()
utils._log("Venv found '%s' checking path" % venv)
venv_path = os.path.expanduser('~/.virtualenvs/%s' % venv)
venv_path = os.path.expanduser("~/.virtualenvs/%s" % venv)
if os.path.exists(venv_path):
utils._log("Venv path exists at '%s'" % venv_path)
return venv_path
Expand All @@ -113,61 +122,70 @@ def find_venv_root(self, filename):
def get_command_kwargs(self, **addl_kwargs):
# prepare default command arguments
kwargs = deepcopy(self._get_default_kwargs())
extra_args = kwargs.pop('extra_cmd_args', [])
extra_args = kwargs.pop("extra_cmd_args", [])
kwargs.update(addl_kwargs)
kwargs['cmd'].extend(extra_args)
kwargs["cmd"].extend(extra_args)

# get the command environment
# TODO: infer from settings.python_interpreter and settings.src_root settings
# as used in https://github.com/JulianEberius/SublimePythonIDE
if 'env' not in kwargs:
kwargs['env'] = {}
if 'PATH' not in kwargs['env']:
if "env" not in kwargs:
kwargs["env"] = {}
if "PATH" not in kwargs["env"]:
# add .venv path if it exists
venv_path = self.find_venv_root(self.filename)
if venv_path:
venv_bin_path = '%s/bin' % venv_path
kwargs['env']['PATH'] = venv_bin_path
if self.filename:
venv_path = self.find_venv_root(self.filename)
if venv_path:
venv_bin_path = "%s/bin" % venv_path
kwargs["env"]["PATH"] = venv_bin_path
else:
utils._log(
"get_command_kwargs: self.filename is None, so PATH cannot be set"
)
# merge path with Sublime's env PATH
kwargs['env']['PATH'] = '%s:%s' % (kwargs['env'].get('PATH', ''), os.environ["PATH"])
kwargs["env"]["PATH"] = "%s:%s" % (
kwargs["env"].get("PATH", ""),
os.environ["PATH"],
)
utils._log("Current PATH is %s" % os.getenv("PATH"))

if 'working_dir' in kwargs:
self.module = self._get_module(self.filename, base=kwargs['working_dir'])
if "working_dir" in kwargs:
self.module = self._get_module(self.filename, base=kwargs["working_dir"])
utils._log("Module updated: ", self.module)
else:
kwargs['working_dir'] = ''
kwargs["working_dir"] = ""

# find the test module/file/pattern in the view
view = self.window.active_view()

# use a given python executable to parse the tests (using ast)
default_python = self.settings.get('python_executable', None)
python_executable = kwargs.pop('python_executable', None) or default_python
default_python = self.settings.get("python_executable", None)
python_executable = kwargs.pop("python_executable", None) or default_python
self.get_pattern(view, python_exec=python_executable)

fmt_args = dict(
module=self.module or '',
filename=self.filename or '',
test_class=self.class_name or '',
test_func=self.func_name or '',
module=self.module or "",
filename=self.filename or "",
test_class=self.class_name or "",
test_func=self.func_name or "",
)
selection = utils.get_selection_content(view)
if selection:
fmt_args['selection'] = selection
fmt_args["selection"] = selection

kwargs['cmd'] = self._format_placeholder(
kwargs['cmd'], kwargs.pop('sep_cleanup'), **fmt_args)
kwargs["cmd"] = self._format_placeholder(
kwargs["cmd"], kwargs.pop("sep_cleanup"), **fmt_args
)

# default external command can be used if not given
if kwargs.get('external', self.external_runner):
kwargs['external'] = kwargs.get('external', self.external_runner)
if kwargs.get("external", self.external_runner):
kwargs["external"] = kwargs.get("external", self.external_runner)

utils._log("Built command: ", kwargs)
return kwargs

def get_external_command(self, external, kwargs):
utils._log('Running external command (%s)' % external)
utils._log("Running external command (%s)" % external)

if isinstance(external, bool):
# if "external": true, use our default
Expand All @@ -176,44 +194,46 @@ def get_external_command(self, external, kwargs):
elif isinstance(external, (list, tuple)):
base_command = external
else:
raise Exception("External command must be either true/false"
" or a list of arguments")

_env = ' '.join('%s=%s' % (ename, evalue) for
ename, evalue in kwargs['env'].items())
change_dir_cmd = ''
if kwargs['working_dir']:
change_dir_cmd = 'cd {path} && '.format(path=kwargs['working_dir'])
raise Exception(
"External command must be either true/false" " or a list of arguments"
)

_env = " ".join(
"%s=%s" % (ename, evalue) for ename, evalue in kwargs["env"].items()
)
change_dir_cmd = ""
if kwargs["working_dir"]:
change_dir_cmd = "cd {path} && ".format(path=kwargs["working_dir"])
elif self.filename:
# for running individual arbitrary test modules
filename_dir = os.path.dirname(self.filename)
change_dir_cmd = 'cd {path} && '.format(path=filename_dir)
change_dir_cmd = "cd {path} && ".format(path=filename_dir)

_cmd = '{cwd}{env_setup} {cmd}'.format(
_cmd = "{cwd}{env_setup} {cmd}".format(
cwd=change_dir_cmd,
cmd=' '.join(kwargs['cmd']),
cmd=" ".join(kwargs["cmd"]),
env_setup=_env,
)
return (base_command) + [_cmd]

def run(self, *args, **command_kwargs):
utils._log('SublimeTestPlier running in debug mode')
utils._log("SublimeTestPlier running in debug mode")
utils._log("Args: %s" % list(args))
utils._log("Kwargs: %s" % command_kwargs)

self.setup_runner()

kwargs = self.get_command_kwargs(**command_kwargs)

if 'external' in kwargs:
cmd = self.get_external_command(kwargs['external'], kwargs)
utils._log('Running external runner with cmd: %s' % kwargs)
return self.window.run_command("exec", {'cmd': cmd})
if "external" in kwargs:
cmd = self.get_external_command(kwargs["external"], kwargs)
utils._log("Running external runner with cmd: %s" % kwargs)
return self.window.run_command("exec", {"cmd": cmd})

elif self.ansi_installed():
utils._log('Running internal command (with ANSI colors)')
utils._log("Running internal command (with ANSI colors)")
return self.window.run_command("ansi_color_build", kwargs)

else:
utils._log('Running internal command (without ANSI colors)')
utils._log("Running internal command (without ANSI colors)")
return self.window.run_command("exec", kwargs)
3 changes: 3 additions & 0 deletions requirements-dev.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ruff
mypy

Loading