Files
wehub-resource-sync c6af9e284a
Tests / catch-all (windows-latest) (push) Has been cancelled
Tests / jvm (macos-latest) (push) Has been cancelled
Tests / jvm (ubuntu-latest) (push) Has been cancelled
Tests / jvm (windows-latest) (push) Has been cancelled
Tests / native (macos-latest) (push) Has been cancelled
Tests / native (ubuntu-latest) (push) Has been cancelled
Tests / native (windows-latest) (push) Has been cancelled
Tests / niche (ubuntu-latest) (push) Has been cancelled
Tests / other-langs (macos-latest) (push) Has been cancelled
Tests / other-langs (ubuntu-latest) (push) Has been cancelled
Tests / other-langs (windows-latest) (push) Has been cancelled
Tests / catch-all (macos-latest) (push) Has been cancelled
Tests / catch-all (ubuntu-latest) (push) Has been cancelled
Docs Build / build (push) Has been cancelled
Docs Build / deploy (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Codespell / Check for spelling errors (push) Has been cancelled
Build and Push Docker Images / build-and-push (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:39:38 +08:00

273 lines
15 KiB
Python

import os
from pathlib import Path
from unittest import mock
import pytest
from solidlsp import SolidLanguageServer
from solidlsp.ls_config import Language
from solidlsp.ls_utils import SymbolUtils
from solidlsp.settings import SolidLSPSettings
from test.conftest import language_tests_enabled
from test.solidlsp.conftest import format_symbol_for_assert, has_malformed_name, request_all_symbols
pytestmark = pytest.mark.skipif(not language_tests_enabled(Language.BSL), reason="BSL tests are disabled (niche, slow and flaky)")
@pytest.mark.bsl
class TestBSLLanguageServer:
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.BSL], indirect=True)
def test_ls_is_running(self, language_server: SolidLanguageServer, repo_path: Path) -> None:
"""Language server starts and attaches to the test repository."""
assert language_server.is_running()
assert Path(language_server.language_server.repository_root_path).resolve() == repo_path.resolve()
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
symbols = language_server.request_full_symbol_tree()
assert SymbolUtils.symbol_tree_contains_name(symbols, "ВывестиСообщение"), "ВывестиСообщение not found in symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, "ПолучитьПриветствие"), "ПолучитьПриветствие not found in symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, "Инициализировать"), "Инициализировать not found in symbol tree"
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
def test_document_symbols(self, language_server: SolidLanguageServer) -> None:
doc_symbols = language_server.request_document_symbols("CommonModule.bsl")
all_symbols, _ = doc_symbols.get_all_symbols_and_roots()
names = [s.get("name") for s in all_symbols if s.get("name")]
assert "ВывестиСообщение" in names, f"ВывестиСообщение not found in CommonModule.bsl symbols. Found: {names}"
assert "ПолучитьПриветствие" in names, f"ПолучитьПриветствие not found in CommonModule.bsl symbols. Found: {names}"
assert "ВызватьПриветствие" in names, f"ВызватьПриветствие not found in CommonModule.bsl symbols. Found: {names}"
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
def test_full_symbol_tree_within_file(self, language_server: SolidLanguageServer) -> None:
"""Scoping the full-tree request to a single file returns that file's symbols."""
symbols = language_server.request_full_symbol_tree(within_relative_path="ObjectModule.bsl")
assert SymbolUtils.symbol_tree_contains_name(symbols, "Инициализировать"), (
"Инициализировать not found in ObjectModule.bsl symbol tree"
)
assert SymbolUtils.symbol_tree_contains_name(symbols, "ПолучитьСостояние"), (
"ПолучитьСостояние not found in ObjectModule.bsl symbol tree"
)
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
def test_find_references_within_file(self, language_server: SolidLanguageServer) -> None:
# CommonModule.bsl (0-indexed):
# line 2: Процедура ВывестиСообщение(Текст) Экспорт <- declaration (name starts at col 10)
# line 13: ВывестиСообщение(Сообщение); <- internal call
# This test asserts within-file resolution on a bare ``.bsl`` file (no surrounding
# 1C project metadata). Cross-module resolution is exercised separately via the
# dedicated ``src/`` fixture in ``test_find_references_across_files`` below.
refs = language_server.request_references("CommonModule.bsl", line=2, column=10)
assert refs, "Expected at least one reference to ВывестиСообщение"
file_names = [ref.get("relativePath", "") for ref in refs]
assert any("CommonModule.bsl" in f for f in file_names), f"Expected self-reference in CommonModule.bsl, got: {file_names}"
# the internal call is on line 13
matching_lines = [ref["range"]["start"]["line"] for ref in refs if "CommonModule.bsl" in ref.get("relativePath", "")]
assert 12 in matching_lines, f"Expected a reference on line 12 (0-indexed), got lines: {matching_lines}"
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
def test_find_references_to_function_within_file(self, language_server: SolidLanguageServer) -> None:
# CommonModule.bsl (0-indexed):
# line 6: Функция ПолучитьПриветствие(Имя) Экспорт <- declaration (name starts at col 8)
# line 12: Сообщение = ПолучитьПриветствие("Мир"); <- internal call at col 16
refs = language_server.request_references("CommonModule.bsl", line=6, column=8)
assert refs, "Expected at least one reference to ПолучитьПриветствие"
matching_lines = [ref["range"]["start"]["line"] for ref in refs if "CommonModule.bsl" in ref.get("relativePath", "")]
assert 11 in matching_lines, f"Expected a reference on line 11 (0-indexed), got lines: {matching_lines}"
# --- Cross-file reference resolution ---
#
# bsl-language-server links common modules across files only when the fixture ships
# proper 1C Configuration metadata (``Configuration.xml`` + per-module ``.mdo`` /
# ``Ext/Module.bsl`` layout). The ``src/`` subtree under the BSL test repo provides
# a minimal-but-complete Designer-format dump for exactly this purpose. The two
# tests below request references and the go-to-definition from the cross-module
# call site ``ОбщийМодуль1.ВывестиСообщение(...)`` in ``ОбщийМодуль2``.
_CROSS_REF_MODULE1 = os.path.join("src", "CommonModules", "ОбщийМодуль1", "Ext", "Module.bsl")
_CROSS_REF_MODULE2 = os.path.join("src", "CommonModules", "ОбщийМодуль2", "Ext", "Module.bsl")
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
def test_find_references_across_files(self, language_server: SolidLanguageServer) -> None:
"""``request_references`` from the declaration must include the cross-module call-site."""
# ОбщийМодуль1 / Ext / Module.bsl (0-indexed):
# line 2: Процедура ВывестиСообщение(Текст) Экспорт <- declaration (name at col 10)
# ОбщийМодуль2 / Ext / Module.bsl (0-indexed):
# line 3: ОбщийМодуль1.ВывестиСообщение("Hello from CommonModule2"); <- cross-module call (col 17)
refs = language_server.request_references(self._CROSS_REF_MODULE1, line=2, column=10)
assert refs, "Expected at least one reference to ВывестиСообщение across modules"
call_site_paths = [ref.get("relativePath", "") for ref in refs if "ОбщийМодуль2" in ref.get("relativePath", "")]
assert call_site_paths, (
f"Expected a cross-module reference from ОбщийМодуль2, got relativePaths: {[ref.get('relativePath', '') for ref in refs]}"
)
call_site_lines = [ref["range"]["start"]["line"] for ref in refs if "ОбщийМодуль2" in ref.get("relativePath", "")]
assert 3 in call_site_lines, f"Expected a reference at ОбщийМодуль2/Module.bsl line 3, got lines: {call_site_lines}"
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
def test_find_definition_across_files(self, language_server: SolidLanguageServer) -> None:
"""``request_definition`` from a cross-module call-site must resolve to the other module."""
# cursor on "ВывестиСообщение" inside
# ОбщийМодуль1.ВывестиСообщение("Hello from CommonModule2"); (line 3, col 17 — 0-indexed)
definitions = language_server.request_definition(self._CROSS_REF_MODULE2, line=3, column=17)
assert definitions, "Expected a cross-module definition for ВывестиСообщение"
target_paths = [d.get("relativePath", "") for d in definitions]
assert any("ОбщийМодуль1" in p for p in target_paths), f"Expected definition to resolve to ОбщийМодуль1, got: {target_paths}"
target_lines = [d["range"]["start"]["line"] for d in definitions if "ОбщийМодуль1" in d.get("relativePath", "")]
assert 2 in target_lines, f"Expected the definition to point at ОбщийМодуль1/Module.bsl line 2, got: {target_lines}"
@pytest.mark.parametrize("language_server", [Language.BSL], indirect=True)
def test_bare_symbol_names(self, language_server: SolidLanguageServer) -> None:
all_symbols = request_all_symbols(language_server)
malformed = [s for s in all_symbols if has_malformed_name(s)]
if malformed:
pytest.fail(
f"Found malformed symbols: {[format_symbol_for_assert(s) for s in malformed]}",
pytrace=False,
)
# ---------------------------------------------------------------------------
# Unit tests — no language server needed, always run regardless of Java
# ---------------------------------------------------------------------------
def test_bsl_filename_matcher() -> None:
matcher = Language.BSL.get_source_fn_matcher()
assert matcher.is_relevant_filename("module.bsl")
assert matcher.is_relevant_filename("script.os")
assert not matcher.is_relevant_filename("module.py")
def test_bsl_enum_registration() -> None:
assert Language.BSL.value == "bsl"
assert Language.BSL.get_ls_class().__name__ == "BSLLanguageServer"
def test_bsl_dependency_provider_default_version() -> None:
"""DependencyProvider resolves the default version to the expected versioned JAR path."""
from solidlsp.language_servers.bsl_language_server import (
DEFAULT_BSL_LS_VERSION,
BSLLanguageServer,
)
settings = SolidLSPSettings()
provider = BSLLanguageServer.DependencyProvider(
settings.get_ls_specific_settings(Language.BSL),
"/tmp/ls_resources",
)
expected_version = DEFAULT_BSL_LS_VERSION
expected_jar_dir = os.path.join("/tmp/ls_resources", f"bsl-ls-{expected_version}")
expected_jar_path = os.path.join(expected_jar_dir, f"bsl-language-server-{expected_version}-exec.jar")
# pretend JAR is already on disk so no download is attempted
with mock.patch("os.path.exists", return_value=True):
jar_path = provider._get_or_install_core_dependency()
assert jar_path == expected_jar_path
def test_bsl_dependency_provider_custom_version_no_sha() -> None:
"""User-overridden versions must install without SHA256 verification."""
from solidlsp.language_servers.bsl_language_server import BSLLanguageServer
from solidlsp.language_servers.common import RuntimeDependencyCollection
settings = SolidLSPSettings()
settings.ls_specific_settings[Language.BSL] = {"bsl_ls_version": "0.28.0"}
provider = BSLLanguageServer.DependencyProvider(
settings.get_ls_specific_settings(Language.BSL),
"/tmp/ls_resources",
)
custom_version = "0.28.0"
expected_jar_dir = os.path.join("/tmp/ls_resources", f"bsl-ls-{custom_version}")
expected_jar_path = os.path.join(expected_jar_dir, f"bsl-language-server-{custom_version}-exec.jar")
installed_deps = []
def fake_install(self_inner, install_dir):
installed_deps.extend(self_inner.get_dependencies_for_current_platform())
os.makedirs(install_dir, exist_ok=True)
open(expected_jar_path, "w").close()
with mock.patch.object(RuntimeDependencyCollection, "install", fake_install):
jar_path = provider._get_or_install_core_dependency()
assert jar_path == expected_jar_path
assert len(installed_deps) == 1
assert installed_deps[0].sha256 is None, "SHA256 must be None for user-overridden version"
if os.path.exists(expected_jar_path):
os.remove(expected_jar_path)
if os.path.exists(expected_jar_dir):
os.rmdir(expected_jar_dir)
def test_bsl_launch_command_uses_ls_path_without_download() -> None:
"""
When ``ls_path`` is set, the public launch-command flow must return the user's JAR
unchanged and must NOT invoke the download / install path. This covers the real
code path used at runtime (``create_launch_command``), not just private helpers.
"""
from solidlsp.language_servers import bsl_language_server
from solidlsp.language_servers.bsl_language_server import BSLLanguageServer
settings = SolidLSPSettings()
settings.ls_specific_settings[Language.BSL] = {"ls_path": "/custom/path/bsl-language-server.jar"}
provider = BSLLanguageServer.DependencyProvider(
settings.get_ls_specific_settings(Language.BSL),
"/tmp/ls_resources",
)
with (
mock.patch.object(bsl_language_server.shutil, "which", return_value="/usr/bin/java"),
mock.patch.object(bsl_language_server, "_get_java_major_version", return_value=21),
mock.patch.object(
BSLLanguageServer.DependencyProvider,
"_get_or_install_core_dependency",
side_effect=AssertionError("must not be called when ls_path is provided"),
) as install_mock,
):
cmd = provider.create_launch_command()
assert cmd == ["java", "-jar", "/custom/path/bsl-language-server.jar"]
install_mock.assert_not_called()
def test_bsl_launch_command_requires_java() -> None:
"""Launch command construction must fail fast when Java is missing."""
from solidlsp.language_servers import bsl_language_server
from solidlsp.language_servers.bsl_language_server import BSLLanguageServer
settings = SolidLSPSettings()
settings.ls_specific_settings[Language.BSL] = {"ls_path": "/custom/path/bsl-language-server.jar"}
provider = BSLLanguageServer.DependencyProvider(
settings.get_ls_specific_settings(Language.BSL),
"/tmp/ls_resources",
)
with mock.patch.object(bsl_language_server.shutil, "which", return_value=None):
with pytest.raises(RuntimeError, match="not found on PATH"):
provider.create_launch_command()
def test_bsl_launch_command_rejects_old_java() -> None:
"""Java older than the minimum supported major version must be rejected up front."""
from solidlsp.language_servers import bsl_language_server
from solidlsp.language_servers.bsl_language_server import BSL_LS_MIN_JAVA_VERSION, BSLLanguageServer
settings = SolidLSPSettings()
settings.ls_specific_settings[Language.BSL] = {"ls_path": "/custom/path/bsl-language-server.jar"}
provider = BSLLanguageServer.DependencyProvider(
settings.get_ls_specific_settings(Language.BSL),
"/tmp/ls_resources",
)
with (
mock.patch.object(bsl_language_server.shutil, "which", return_value="/usr/bin/java"),
mock.patch.object(bsl_language_server, "_get_java_major_version", return_value=BSL_LS_MIN_JAVA_VERSION - 1),
):
with pytest.raises(RuntimeError, match=f"Java {BSL_LS_MIN_JAVA_VERSION}\\+"):
provider.create_launch_command()