Skip to content

Commit

Permalink
better test coverage for new features (#64)
Browse files Browse the repository at this point in the history
fixed bug with "lint" command.
  • Loading branch information
mgor authored Aug 9, 2024
1 parent 704210e commit b583958
Show file tree
Hide file tree
Showing 14 changed files with 201 additions and 93 deletions.
4 changes: 1 addition & 3 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,6 @@
"python.testing.pytestArgs": [
"-c",
"./pyproject.toml",
"-o",
"testpaths=tests",
"--cov-reset",
"--cov=./src/grizzly_ls",
"--cov-report=xml:./.coverage.xml",
Expand All @@ -61,7 +59,7 @@
],
"coverage-gutters.ignoredPathGlobs": "**/{node_modules,venv,.venv,vendor,tests}/**",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
"source.fixAll.eslint": "always"
},
"eslint.validate": [
"typescript"
Expand Down
2 changes: 1 addition & 1 deletion client/vscode/src/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export class GherkinPreview {
const [success, content]: [boolean, string | undefined] = await vscode.commands.executeCommand(
'grizzly-ls/render-gherkin', {
content: textDocument.getText(),
uri: textDocument.uri.path,
path: textDocument.uri.path,
on_the_fly,
}
);
Expand Down
2 changes: 1 addition & 1 deletion client/vscode/tests/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export function run(): Promise<void> {
ui: 'tdd',
color: true,
});
mocha.timeout(100000);
mocha.timeout(300000);

const testsRoot = __dirname;

Expand Down
47 changes: 31 additions & 16 deletions grizzly-ls/src/grizzly_ls/cli.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from __future__ import annotations

from typing import TYPE_CHECKING, List, Optional
from typing import TYPE_CHECKING, List, Optional, Dict
from argparse import Namespace as Arguments
from pathlib import Path

Expand Down Expand Up @@ -30,18 +30,21 @@ def _get_severity_color(severity: Optional[DiagnosticSeverity]) -> str:
return Fore.RESET


def diagnostic_to_text(filename: str, diagnostic: Diagnostic) -> str:
def diagnostic_to_text(filename: str, diagnostic: Diagnostic, max_length: int) -> str:
color = _get_severity_color(diagnostic.severity)
severity = diagnostic.severity.name if diagnostic.severity is not None else 'UNKNOWN'
severity = diagnostic.severity.name if diagnostic.severity is not None else 'unknown'
message = ': '.join(diagnostic.message.split('\n'))

return '\t'.join(
[
f'{filename}:{diagnostic.range.start.line + 1}:{diagnostic.range.start.character + 1}',
f'{color}{severity.lower()}{Fore.RESET}',
message,
]
)
message_file = f'{filename}:{diagnostic.range.start.line + 1}:{diagnostic.range.start.character + 1}'
message_severity = f'{color}{severity.lower()}{Fore.RESET}'

# take line number into consideration, max 9999:9999
max_length += 9

# color and reset codes makes the string 10 bytes longer than the actual text length -+
# |
# v----------------------------+
return f'{message_file:<{max_length}} {message_severity:<17} {message}'


def cli(ls: GrizzlyLanguageServer, args: Arguments) -> int:
Expand Down Expand Up @@ -71,19 +74,31 @@ def cli(ls: GrizzlyLanguageServer, args: Arguments) -> int:
files.append(file)

rc: int = 0
grouped_diagnostics: Dict[str, List[Diagnostic]] = {}
max_length = 0

for file in files:
text_document = TextDocument(file.resolve().as_uri())
ls.language = find_language(text_document.source)
diagnostics = validate_gherkin(ls, text_document)

if len(diagnostics) < 1:
continue
for uri, _diagnostics in diagnostics.items():
if len(_diagnostics) < 1:
continue

rc = 1
file = Path(uri.replace('file://', ''))
filename = file.as_posix().replace(Path.cwd().as_posix(), '').lstrip('/\\')
max_length = max(max_length, len(filename))

filename = file.as_posix().replace(Path.cwd().as_posix(), '').lstrip('/\\')
if filename not in grouped_diagnostics:
grouped_diagnostics.update({filename: []})

grouped_diagnostics[filename].extend(_diagnostics)

if len(grouped_diagnostics) > 0:
rc = 1

for diagnostic in diagnostics[text_document.uri]:
print(diagnostic_to_text(filename, diagnostic))
for filename, _diagnostics in grouped_diagnostics.items():
print('\n'.join(diagnostic_to_text(filename, diagnostic, max_length) for diagnostic in _diagnostics))

return rc
36 changes: 6 additions & 30 deletions grizzly-ls/src/grizzly_ls/server/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,15 @@
from time import sleep
from collections import deque
from logging import ERROR
from contextlib import suppress

from pygls.server import LanguageServer
from pygls.workspace import TextDocument
from pygls.capabilities import get_capability
from lsprotocol import types as lsp
from jinja2 import Environment

from grizzly_ls import __version__
from grizzly_ls.text import Normalizer, get_step_parts
from grizzly_ls.utils import run_command, OnlyScenarioTag, LogOutputChannelLogger
from grizzly_ls.utils import run_command, LogOutputChannelLogger
from grizzly_ls.model import Step
from grizzly_ls.constants import FEATURE_INSTALL, COMMAND_REBUILD_INVENTORY, COMMAND_RUN_DIAGNOSTICS, COMMAND_RENDER_GHERKIN, LANGUAGE_ID
from grizzly_ls.text import (
Expand All @@ -62,6 +60,7 @@
from .features.diagnostics import validate_gherkin
from .features.code_actions import generate_quick_fixes
from .inventory import compile_inventory, compile_keyword_inventory
from .commands import render_gherkin


class GrizzlyLanguageServer(LanguageServer):
Expand Down Expand Up @@ -803,41 +802,18 @@ def command_render_gherkin(ls: GrizzlyLanguageServer, *args: Any) -> Tuple[bool,
options = cast(Dict[str, str], args[0][0])

content = options.get('content', None)
uri = options.get('uri', None)
path = options.get('path', None)
on_the_fly = options.get('on_the_fly', False)

if content is None or uri is None:
if content is None or path is None:
content = 'no content to preview'
ls.logger.error(content, notify=True)
return False, content

OnlyScenarioTag.logger = ls.logger

feature_file = Path(uri)

environment = Environment(autoescape=False, extensions=[OnlyScenarioTag])
environment.extend(feature_file=feature_file)

try:
template = environment.from_string(content)
content = template.render()
buffer: List[str] = []
# <!-- sanatize content
for line in content.splitlines():
# make any html tag characters in comments are replaced with respective html entity code
with suppress(Exception):
if line.lstrip()[0] == '#':
line = line.replace('<', '&lt;')
line = line.replace('>', '&gt;')

buffer.append(line)
# // -->

content = '\n'.join(buffer)

return True, content
return True, render_gherkin(path, content, ls.logger.logger)
except Exception:
if not on_the_fly:
ls.logger.exception(f'failed to render {uri}', notify=True)
ls.logger.exception(f'failed to render {path}', notify=True)
return False, f'Failed to render\n{ls.logger.get_current_exception()}'
return False, None
34 changes: 34 additions & 0 deletions grizzly-ls/src/grizzly_ls/server/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from __future__ import annotations

from typing import List, TYPE_CHECKING
from pathlib import Path
from contextlib import suppress

from jinja2 import Environment

from grizzly_ls.utils import OnlyScenarioTag

if TYPE_CHECKING:
from logging import Logger


def render_gherkin(path: str, content: str, logger: Logger) -> str:
feature_file = Path(path)
OnlyScenarioTag.logger = logger
environment = Environment(autoescape=False, extensions=[OnlyScenarioTag])
environment.extend(feature_file=feature_file)
template = environment.from_string(content)
content = template.render()
buffer: List[str] = []
# <!-- sanatize content
for line in content.splitlines():
# make any html tag characters in comments are replaced with respective html entity code
with suppress(Exception):
if line.lstrip()[0] == '#':
line = line.replace('<', '&lt;')
line = line.replace('>', '&gt;')

buffer.append(line)
# // -->

return '\n'.join(buffer)
3 changes: 2 additions & 1 deletion grizzly-ls/src/grizzly_ls/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from typing import Dict, List, Optional, Tuple, Union, Iterable
from pathlib import Path
from logging import Logger

from jinja2.lexer import Token, TokenStream
from jinja2_simple_tags import StandaloneTag
Expand Down Expand Up @@ -130,7 +131,7 @@ def exception(self, message: str, *, notify: bool = False) -> None:

class OnlyScenarioTag(StandaloneTag):
tags = {'scenario'}
logger: LogOutputChannelLogger
logger: Logger

def preprocess(self, source: str, name: Optional[str], filename: Optional[str] = None) -> str:
self._source = source
Expand Down
17 changes: 17 additions & 0 deletions grizzly-ls/tests/e2e/test_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from pathlib import Path

from grizzly_ls.utils import run_command


def test_cli_lint() -> None:
cwd = Path(__file__).parent.parent.parent.parent / 'tests' / 'project'
rc, output = run_command(
['grizzly-ls', 'lint', '.'],
cwd=cwd.as_posix(),
)

try:
assert rc == 0
except AssertionError:
print('\n'.join(output))
raise
78 changes: 38 additions & 40 deletions grizzly-ls/tests/unit/server/feature/test_diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ def test_validate_gherkin_scenario_tag(lsp_fixture: LspFixture) -> None:
ls = lsp_fixture.server

feature_file = lsp_fixture.datadir / 'features' / 'test_validate_gherkin_scenario_tag.feature'
included_feature_file = lsp_fixture.datadir / 'features' / 'test_validate_gherkin_scenario_tag_include.feature'

try:
# <!-- jinja2 expression, not scenario tag -- ignored
Expand Down Expand Up @@ -416,7 +417,6 @@ def test_validate_gherkin_scenario_tag(lsp_fixture: LspFixture) -> None:
)
text_document = TextDocument(feature_file.as_posix())

included_feature_file = lsp_fixture.datadir / 'features' / 'test_validate_gherkin_scenario_tag_include.feature'
included_feature_file.unlink(missing_ok=True)

diagnostics = validate_gherkin(ls, text_document)
Expand All @@ -430,50 +430,47 @@ def test_validate_gherkin_scenario_tag(lsp_fixture: LspFixture) -> None:

assert diagnostic.message == f'Included feature file "{feature_file_name}" does not exist'

try:
included_feature_file.touch()
included_feature_file.touch()

diagnostics = validate_gherkin(ls, text_document)
diagnostic = next(iter(diagnostics[text_document.uri]))
diagnostics = validate_gherkin(ls, text_document)
diagnostic = next(iter(diagnostics[text_document.uri]))

assert diagnostic.message == f'Included feature file "{feature_file_name}" does not have any scenarios'

assert diagnostic.message == f'Included feature file "{feature_file_name}" does not have any scenarios'
included_feature_file.write_text('''Egenskap: test''', encoding='utf-8')

included_feature_file.write_text('''Egenskap: test''', encoding='utf-8')
diagnostics = validate_gherkin(ls, text_document)
diagnostic = next(iter(diagnostics[text_document.uri]))

diagnostics = validate_gherkin(ls, text_document)
diagnostic = next(iter(diagnostics[text_document.uri]))
assert diagnostic.message == 'Parser failure in state init\nNo feature found.'

assert diagnostic.message == 'Parser failure in state init\nNo feature found.'
included_feature_file.write_text('''Feature: test''', encoding='utf-8')

included_feature_file.write_text('''Feature: test''', encoding='utf-8')
diagnostics = validate_gherkin(ls, text_document)
diagnostic = next(iter(diagnostics[text_document.uri]))

diagnostics = validate_gherkin(ls, text_document)
diagnostic = next(iter(diagnostics[text_document.uri]))
assert diagnostic.message == f'Scenario "foo" does not exist in included feature "{feature_file_name}"'

assert diagnostic.message == f'Scenario "foo" does not exist in included feature "{feature_file_name}"'
included_feature_file.write_text(
'''Feature: test
Scenario: foo''',
encoding='utf-8',
)

included_feature_file.write_text(
'''Feature: test
Scenario: foo''',
encoding='utf-8',
)
diagnostics = validate_gherkin(ls, text_document)
diagnostic = next(iter(diagnostics[text_document.uri]))

diagnostics = validate_gherkin(ls, text_document)
diagnostic = next(iter(diagnostics[text_document.uri]))
assert diagnostic.message == f'Scenario "foo" in "{feature_file_name}" does not have any steps'

assert diagnostic.message == f'Scenario "foo" in "{feature_file_name}" does not have any steps'
included_feature_file.write_text(
'''Feature: test
Scenario: foo
Given a step expression''',
encoding='utf-8',
)

included_feature_file.write_text(
'''Feature: test
Scenario: foo
Given a step expression''',
encoding='utf-8',
)

diagnostics = validate_gherkin(ls, text_document)
assert diagnostics == {text_document.uri: []}
finally:
included_feature_file.unlink()
diagnostics = validate_gherkin(ls, text_document)
assert diagnostics == {text_document.uri: []}
# // -->

# <!-- scenario tag values argument
Expand All @@ -485,8 +482,8 @@ def test_validate_gherkin_scenario_tag(lsp_fixture: LspFixture) -> None:
encoding='utf-8',
)

included_feature_file_1 = lsp_fixture.datadir / 'features' / 'test_validate_gherkin_scenario_tag_include.feature'
included_feature_file_1.write_text(
included_feature_file = lsp_fixture.datadir / 'features' / 'test_validate_gherkin_scenario_tag_include.feature'
included_feature_file.write_text(
"""Feature:
Scenario: include
Given a step expression
Expand All @@ -510,7 +507,7 @@ def test_validate_gherkin_scenario_tag(lsp_fixture: LspFixture) -> None:
assert str(diagnostic.range) == '2:106-2:115'
assert diagnostic.severity == lsp.DiagnosticSeverity.Error

included_feature_file_1.write_text(
included_feature_file.write_text(
"""Feature:
Scenario: include
Given a step expression named "{$ foo $}"
Expand All @@ -536,14 +533,14 @@ def test_validate_gherkin_scenario_tag(lsp_fixture: LspFixture) -> None:
assert str(diagnostic.range) == '2:8-2:118'
assert diagnostic.severity == lsp.DiagnosticSeverity.Warning

assert len(diagnostics[included_feature_file_1.as_uri()]) == 1
diagnostic = diagnostics[included_feature_file_1.as_uri()][0]
assert len(diagnostics[included_feature_file.as_uri()]) == 1
diagnostic = diagnostics[included_feature_file.as_uri()][0]

assert diagnostic.message == 'Variable "baz" has not been declared in scenario tag'
assert str(diagnostic.range) == '3:37-3:46'
assert diagnostic.severity == lsp.DiagnosticSeverity.Error

included_feature_file_1.write_text(
included_feature_file.write_text(
"""Feature:
Scenario: include
Given a step expression named "{$ foo $}"
Expand All @@ -558,4 +555,5 @@ def test_validate_gherkin_scenario_tag(lsp_fixture: LspFixture) -> None:
assert diagnostics == {text_document.uri: []}
# // -->
finally:
feature_file.unlink()
included_feature_file.unlink(missing_ok=True)
feature_file.unlink(missing_ok=True)
Loading

0 comments on commit b583958

Please sign in to comment.