crosshair-tool 0.0.99__cp312-cp312-macosx_10_13_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- _crosshair_tracers.cpython-312-darwin.so +0 -0
- crosshair/__init__.py +42 -0
- crosshair/__main__.py +8 -0
- crosshair/_mark_stacks.h +790 -0
- crosshair/_preliminaries_test.py +18 -0
- crosshair/_tracers.h +94 -0
- crosshair/_tracers_pycompat.h +522 -0
- crosshair/_tracers_test.py +138 -0
- crosshair/abcstring.py +245 -0
- crosshair/auditwall.py +190 -0
- crosshair/auditwall_test.py +77 -0
- crosshair/codeconfig.py +113 -0
- crosshair/codeconfig_test.py +117 -0
- crosshair/condition_parser.py +1237 -0
- crosshair/condition_parser_test.py +497 -0
- crosshair/conftest.py +30 -0
- crosshair/copyext.py +155 -0
- crosshair/copyext_test.py +84 -0
- crosshair/core.py +1763 -0
- crosshair/core_and_libs.py +149 -0
- crosshair/core_regestered_types_test.py +82 -0
- crosshair/core_test.py +1316 -0
- crosshair/diff_behavior.py +314 -0
- crosshair/diff_behavior_test.py +261 -0
- crosshair/dynamic_typing.py +346 -0
- crosshair/dynamic_typing_test.py +210 -0
- crosshair/enforce.py +282 -0
- crosshair/enforce_test.py +182 -0
- crosshair/examples/PEP316/__init__.py +1 -0
- crosshair/examples/PEP316/bugs_detected/__init__.py +0 -0
- crosshair/examples/PEP316/bugs_detected/getattr_magic.py +16 -0
- crosshair/examples/PEP316/bugs_detected/hash_consistent_with_equals.py +31 -0
- crosshair/examples/PEP316/bugs_detected/shopping_cart.py +24 -0
- crosshair/examples/PEP316/bugs_detected/showcase.py +39 -0
- crosshair/examples/PEP316/correct_code/__init__.py +0 -0
- crosshair/examples/PEP316/correct_code/arith.py +60 -0
- crosshair/examples/PEP316/correct_code/chess.py +77 -0
- crosshair/examples/PEP316/correct_code/nesting_inference.py +17 -0
- crosshair/examples/PEP316/correct_code/numpy_examples.py +132 -0
- crosshair/examples/PEP316/correct_code/rolling_average.py +35 -0
- crosshair/examples/PEP316/correct_code/showcase.py +104 -0
- crosshair/examples/__init__.py +0 -0
- crosshair/examples/check_examples_test.py +146 -0
- crosshair/examples/deal/__init__.py +1 -0
- crosshair/examples/icontract/__init__.py +1 -0
- crosshair/examples/icontract/bugs_detected/__init__.py +0 -0
- crosshair/examples/icontract/bugs_detected/showcase.py +41 -0
- crosshair/examples/icontract/bugs_detected/wrong_sign.py +8 -0
- crosshair/examples/icontract/correct_code/__init__.py +0 -0
- crosshair/examples/icontract/correct_code/arith.py +51 -0
- crosshair/examples/icontract/correct_code/showcase.py +94 -0
- crosshair/fnutil.py +391 -0
- crosshair/fnutil_test.py +75 -0
- crosshair/fuzz_core_test.py +516 -0
- crosshair/libimpl/__init__.py +0 -0
- crosshair/libimpl/arraylib.py +161 -0
- crosshair/libimpl/binascii_ch_test.py +30 -0
- crosshair/libimpl/binascii_test.py +67 -0
- crosshair/libimpl/binasciilib.py +150 -0
- crosshair/libimpl/bisectlib_test.py +23 -0
- crosshair/libimpl/builtinslib.py +5228 -0
- crosshair/libimpl/builtinslib_ch_test.py +1191 -0
- crosshair/libimpl/builtinslib_test.py +3735 -0
- crosshair/libimpl/codecslib.py +86 -0
- crosshair/libimpl/codecslib_test.py +86 -0
- crosshair/libimpl/collectionslib.py +264 -0
- crosshair/libimpl/collectionslib_ch_test.py +252 -0
- crosshair/libimpl/collectionslib_test.py +332 -0
- crosshair/libimpl/copylib.py +23 -0
- crosshair/libimpl/copylib_test.py +18 -0
- crosshair/libimpl/datetimelib.py +2559 -0
- crosshair/libimpl/datetimelib_ch_test.py +354 -0
- crosshair/libimpl/datetimelib_test.py +112 -0
- crosshair/libimpl/decimallib.py +5257 -0
- crosshair/libimpl/decimallib_ch_test.py +78 -0
- crosshair/libimpl/decimallib_test.py +76 -0
- crosshair/libimpl/encodings/__init__.py +23 -0
- crosshair/libimpl/encodings/_encutil.py +187 -0
- crosshair/libimpl/encodings/ascii.py +44 -0
- crosshair/libimpl/encodings/latin_1.py +40 -0
- crosshair/libimpl/encodings/utf_8.py +93 -0
- crosshair/libimpl/encodings_ch_test.py +83 -0
- crosshair/libimpl/fractionlib.py +16 -0
- crosshair/libimpl/fractionlib_test.py +80 -0
- crosshair/libimpl/functoolslib.py +34 -0
- crosshair/libimpl/functoolslib_test.py +56 -0
- crosshair/libimpl/hashliblib.py +30 -0
- crosshair/libimpl/hashliblib_test.py +18 -0
- crosshair/libimpl/heapqlib.py +47 -0
- crosshair/libimpl/heapqlib_test.py +21 -0
- crosshair/libimpl/importliblib.py +18 -0
- crosshair/libimpl/importliblib_test.py +38 -0
- crosshair/libimpl/iolib.py +216 -0
- crosshair/libimpl/iolib_ch_test.py +128 -0
- crosshair/libimpl/iolib_test.py +19 -0
- crosshair/libimpl/ipaddresslib.py +8 -0
- crosshair/libimpl/itertoolslib.py +44 -0
- crosshair/libimpl/itertoolslib_test.py +44 -0
- crosshair/libimpl/jsonlib.py +984 -0
- crosshair/libimpl/jsonlib_ch_test.py +42 -0
- crosshair/libimpl/jsonlib_test.py +51 -0
- crosshair/libimpl/mathlib.py +179 -0
- crosshair/libimpl/mathlib_ch_test.py +44 -0
- crosshair/libimpl/mathlib_test.py +67 -0
- crosshair/libimpl/oslib.py +7 -0
- crosshair/libimpl/pathliblib_test.py +10 -0
- crosshair/libimpl/randomlib.py +178 -0
- crosshair/libimpl/randomlib_test.py +120 -0
- crosshair/libimpl/relib.py +846 -0
- crosshair/libimpl/relib_ch_test.py +169 -0
- crosshair/libimpl/relib_test.py +493 -0
- crosshair/libimpl/timelib.py +72 -0
- crosshair/libimpl/timelib_test.py +82 -0
- crosshair/libimpl/typeslib.py +15 -0
- crosshair/libimpl/typeslib_test.py +36 -0
- crosshair/libimpl/unicodedatalib.py +75 -0
- crosshair/libimpl/unicodedatalib_test.py +42 -0
- crosshair/libimpl/urlliblib.py +23 -0
- crosshair/libimpl/urlliblib_test.py +19 -0
- crosshair/libimpl/weakreflib.py +13 -0
- crosshair/libimpl/weakreflib_test.py +69 -0
- crosshair/libimpl/zliblib.py +15 -0
- crosshair/libimpl/zliblib_test.py +13 -0
- crosshair/lsp_server.py +261 -0
- crosshair/lsp_server_test.py +30 -0
- crosshair/main.py +973 -0
- crosshair/main_test.py +543 -0
- crosshair/objectproxy.py +376 -0
- crosshair/objectproxy_test.py +41 -0
- crosshair/opcode_intercept.py +601 -0
- crosshair/opcode_intercept_test.py +304 -0
- crosshair/options.py +218 -0
- crosshair/options_test.py +10 -0
- crosshair/patch_equivalence_test.py +75 -0
- crosshair/path_cover.py +209 -0
- crosshair/path_cover_test.py +138 -0
- crosshair/path_search.py +161 -0
- crosshair/path_search_test.py +52 -0
- crosshair/pathing_oracle.py +271 -0
- crosshair/pathing_oracle_test.py +21 -0
- crosshair/pure_importer.py +27 -0
- crosshair/pure_importer_test.py +16 -0
- crosshair/py.typed +0 -0
- crosshair/register_contract.py +273 -0
- crosshair/register_contract_test.py +190 -0
- crosshair/simplestructs.py +1165 -0
- crosshair/simplestructs_test.py +283 -0
- crosshair/smtlib.py +24 -0
- crosshair/smtlib_test.py +14 -0
- crosshair/statespace.py +1199 -0
- crosshair/statespace_test.py +108 -0
- crosshair/stubs_parser.py +352 -0
- crosshair/stubs_parser_test.py +43 -0
- crosshair/test_util.py +329 -0
- crosshair/test_util_test.py +26 -0
- crosshair/tools/__init__.py +0 -0
- crosshair/tools/check_help_in_doc.py +264 -0
- crosshair/tools/check_init_and_setup_coincide.py +119 -0
- crosshair/tools/generate_demo_table.py +127 -0
- crosshair/tracers.py +544 -0
- crosshair/tracers_test.py +154 -0
- crosshair/type_repo.py +151 -0
- crosshair/unicode_categories.py +589 -0
- crosshair/unicode_categories_test.py +27 -0
- crosshair/util.py +741 -0
- crosshair/util_test.py +173 -0
- crosshair/watcher.py +307 -0
- crosshair/watcher_test.py +107 -0
- crosshair/z3util.py +76 -0
- crosshair/z3util_test.py +11 -0
- crosshair_tool-0.0.99.dist-info/METADATA +144 -0
- crosshair_tool-0.0.99.dist-info/RECORD +176 -0
- crosshair_tool-0.0.99.dist-info/WHEEL +6 -0
- crosshair_tool-0.0.99.dist-info/entry_points.txt +3 -0
- crosshair_tool-0.0.99.dist-info/licenses/LICENSE +93 -0
- crosshair_tool-0.0.99.dist-info/top_level.txt +2 -0
crosshair/lsp_server.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
import pathlib
|
|
4
|
+
import re
|
|
5
|
+
import sys
|
|
6
|
+
import time
|
|
7
|
+
from collections import defaultdict
|
|
8
|
+
from dataclasses import dataclass, field
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from threading import Thread
|
|
11
|
+
from typing import Any, Counter, Dict, List, Optional
|
|
12
|
+
from urllib.parse import unquote, urlparse
|
|
13
|
+
|
|
14
|
+
from lsprotocol.types import (
|
|
15
|
+
SHUTDOWN,
|
|
16
|
+
TEXT_DOCUMENT_DID_CHANGE,
|
|
17
|
+
TEXT_DOCUMENT_DID_CLOSE,
|
|
18
|
+
TEXT_DOCUMENT_DID_OPEN,
|
|
19
|
+
TEXT_DOCUMENT_DID_SAVE,
|
|
20
|
+
Diagnostic,
|
|
21
|
+
DiagnosticSeverity,
|
|
22
|
+
DidChangeTextDocumentParams,
|
|
23
|
+
DidCloseTextDocumentParams,
|
|
24
|
+
DidOpenTextDocumentParams,
|
|
25
|
+
Position,
|
|
26
|
+
Range,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
from pygls.lsp.server import LanguageServer # (v2.x)
|
|
31
|
+
except ImportError:
|
|
32
|
+
from pygls.server import LanguageServer # (v1.x)
|
|
33
|
+
|
|
34
|
+
from crosshair import __version__, env_info
|
|
35
|
+
from crosshair.options import DEFAULT_OPTIONS, AnalysisOptionSet
|
|
36
|
+
from crosshair.statespace import AnalysisMessage, MessageType
|
|
37
|
+
from crosshair.watcher import Watcher
|
|
38
|
+
|
|
39
|
+
# logging.basicConfig(
|
|
40
|
+
# filename="/Users/pschanely/pygls.log", filemode="w", level=logging.DEBUG
|
|
41
|
+
# )
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class CrossHairLanguageServer(LanguageServer):
|
|
45
|
+
def __init__(self, options: AnalysisOptionSet):
|
|
46
|
+
self.options = options
|
|
47
|
+
super().__init__("CrossHairServer", __version__)
|
|
48
|
+
|
|
49
|
+
def window_log_message(self, message: str):
|
|
50
|
+
# Just for version 1 & 2 compatibility
|
|
51
|
+
if hasattr(self, "show_message_log"):
|
|
52
|
+
self.show_message_log(message)
|
|
53
|
+
else:
|
|
54
|
+
super().window_log_message(message)
|
|
55
|
+
|
|
56
|
+
CMD_REGISTER_COMPLETIONS = "registerCompletions"
|
|
57
|
+
CMD_UNREGISTER_COMPLETIONS = "unregisterCompletions"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_diagnostic(message: AnalysisMessage, doclines: List[str]) -> Diagnostic:
|
|
61
|
+
line = message.line - 1
|
|
62
|
+
col = endcol = message.column
|
|
63
|
+
if col == 0:
|
|
64
|
+
if line < len(doclines):
|
|
65
|
+
line_text = doclines[line]
|
|
66
|
+
match = re.compile(r"^\s*(.*?)\s*$").fullmatch(line_text)
|
|
67
|
+
if match:
|
|
68
|
+
col, endcol = match.start(1), match.end(1)
|
|
69
|
+
if message.state < MessageType.PRE_UNSAT:
|
|
70
|
+
severity = DiagnosticSeverity.Information
|
|
71
|
+
elif message.state == MessageType.PRE_UNSAT:
|
|
72
|
+
severity = DiagnosticSeverity.Warning
|
|
73
|
+
else:
|
|
74
|
+
severity = DiagnosticSeverity.Error
|
|
75
|
+
return Diagnostic(
|
|
76
|
+
range=Range(
|
|
77
|
+
start=Position(line=line, character=col),
|
|
78
|
+
end=Position(line=line, character=endcol),
|
|
79
|
+
),
|
|
80
|
+
severity=severity,
|
|
81
|
+
message=message.message,
|
|
82
|
+
source="CrossHair",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def publish_messages(
|
|
87
|
+
messages: Dict[str, Optional[Dict[int, AnalysisMessage]]],
|
|
88
|
+
server: CrossHairLanguageServer,
|
|
89
|
+
):
|
|
90
|
+
for uri, file_messages in list(messages.items()):
|
|
91
|
+
doc = server.workspace.documents.get(uri, None)
|
|
92
|
+
if file_messages is None:
|
|
93
|
+
# closed/editing, and client already notified as empty
|
|
94
|
+
continue
|
|
95
|
+
diagnostics = []
|
|
96
|
+
for message in file_messages.values():
|
|
97
|
+
if message.state < MessageType.PRE_UNSAT:
|
|
98
|
+
continue
|
|
99
|
+
# TODO: consider server.window_log_message()ing the long description
|
|
100
|
+
diagnostics.append(get_diagnostic(message, doc.lines if doc else []))
|
|
101
|
+
server.publish_diagnostics(uri, diagnostics)
|
|
102
|
+
if not diagnostics:
|
|
103
|
+
# After we publish an empty set, it's safe to forget about the file:
|
|
104
|
+
del messages[uri]
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@dataclass
|
|
108
|
+
class LocalState:
|
|
109
|
+
watcher: Watcher
|
|
110
|
+
server: CrossHairLanguageServer
|
|
111
|
+
active_messages: Dict[str, Optional[Dict[int, AnalysisMessage]]] = field(
|
|
112
|
+
default_factory=lambda: defaultdict(dict)
|
|
113
|
+
)
|
|
114
|
+
should_shutdown: bool = False
|
|
115
|
+
|
|
116
|
+
def start_loop_thread(self):
|
|
117
|
+
self.loop = Thread(target=self.run_watch_loop)
|
|
118
|
+
self.loop.start()
|
|
119
|
+
|
|
120
|
+
def shutdown(self):
|
|
121
|
+
self.should_shutdown = True
|
|
122
|
+
self.watcher.shutdown()
|
|
123
|
+
self.loop.join(3.0) # run_iteration should check the flag 1x / second
|
|
124
|
+
|
|
125
|
+
def run_watch_loop(
|
|
126
|
+
self,
|
|
127
|
+
max_watch_iterations: int = sys.maxsize,
|
|
128
|
+
) -> None:
|
|
129
|
+
def log(*a):
|
|
130
|
+
pass # self.server.window_log_message(*a)
|
|
131
|
+
|
|
132
|
+
log("loop thread started")
|
|
133
|
+
watcher = self.watcher
|
|
134
|
+
server = self.server
|
|
135
|
+
active_messages = self.active_messages
|
|
136
|
+
restart = True
|
|
137
|
+
stats: Counter[str] = Counter()
|
|
138
|
+
for _i in range(max_watch_iterations):
|
|
139
|
+
if self.should_shutdown:
|
|
140
|
+
return
|
|
141
|
+
if restart:
|
|
142
|
+
numfiles = len(watcher._modtimes)
|
|
143
|
+
server.window_log_message(
|
|
144
|
+
f"Scanning {numfiles} file(s) for properties to check."
|
|
145
|
+
)
|
|
146
|
+
max_uninteresting_iterations = (
|
|
147
|
+
DEFAULT_OPTIONS.get_max_uninteresting_iterations()
|
|
148
|
+
)
|
|
149
|
+
restart = False
|
|
150
|
+
stats = Counter()
|
|
151
|
+
for k, v in list(active_messages.items()):
|
|
152
|
+
active_messages[k] = None if v is None else {}
|
|
153
|
+
else:
|
|
154
|
+
time.sleep(0.25)
|
|
155
|
+
max_uninteresting_iterations = min(
|
|
156
|
+
sys.maxsize, 2 * max_uninteresting_iterations
|
|
157
|
+
)
|
|
158
|
+
log(f"iteration starting" + str(_i))
|
|
159
|
+
for curstats, messages in watcher.run_iteration(
|
|
160
|
+
max_uninteresting_iterations
|
|
161
|
+
):
|
|
162
|
+
log(f"iteration yielded {curstats, messages}")
|
|
163
|
+
stats.update(curstats)
|
|
164
|
+
for message in messages:
|
|
165
|
+
filename, line = (message.filename, message.line)
|
|
166
|
+
file_messages = active_messages[Path(filename).as_uri()]
|
|
167
|
+
if file_messages is not None and (
|
|
168
|
+
line not in file_messages
|
|
169
|
+
or file_messages[line].state < message.state
|
|
170
|
+
):
|
|
171
|
+
file_messages[line] = message
|
|
172
|
+
numpaths = stats["num_paths"]
|
|
173
|
+
if self.should_shutdown:
|
|
174
|
+
return
|
|
175
|
+
if numpaths > 0:
|
|
176
|
+
status = f"Analyzed {numpaths} paths in {len(watcher._modtimes)} files."
|
|
177
|
+
server.window_log_message(status)
|
|
178
|
+
publish_messages(active_messages, server)
|
|
179
|
+
if watcher._change_flag:
|
|
180
|
+
watcher._change_flag = False
|
|
181
|
+
restart = True
|
|
182
|
+
log(f"done run loop")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
_LS: Optional[LocalState] = None
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def getlocalstate(server: CrossHairLanguageServer) -> LocalState:
|
|
189
|
+
global _LS
|
|
190
|
+
if _LS is None:
|
|
191
|
+
server.window_log_message(env_info())
|
|
192
|
+
watcher = Watcher([], server.options)
|
|
193
|
+
watcher.startpool()
|
|
194
|
+
_LS = LocalState(watcher, server)
|
|
195
|
+
_LS.start_loop_thread()
|
|
196
|
+
else:
|
|
197
|
+
_LS.server = server
|
|
198
|
+
return _LS
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def update_paths(server: CrossHairLanguageServer):
|
|
202
|
+
paths = []
|
|
203
|
+
for uri, doc in server.workspace.documents.items():
|
|
204
|
+
if not doc.filename.endswith(".py"):
|
|
205
|
+
continue
|
|
206
|
+
parsed = urlparse(uri)
|
|
207
|
+
if parsed.netloc != "":
|
|
208
|
+
continue
|
|
209
|
+
path = unquote(parsed.path)
|
|
210
|
+
if os.name == "nt":
|
|
211
|
+
while path.startswith("/"):
|
|
212
|
+
path = path[1:] # Remove leading slashes on windows
|
|
213
|
+
path = path.replace("/", "\\")
|
|
214
|
+
paths.append(pathlib.Path(path))
|
|
215
|
+
watcher = getlocalstate(server).watcher
|
|
216
|
+
server.window_log_message("New path set: " + repr(paths))
|
|
217
|
+
watcher.update_paths(paths)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def create_lsp_server(options: AnalysisOptionSet) -> CrossHairLanguageServer:
|
|
221
|
+
|
|
222
|
+
crosshair_lsp_server = CrossHairLanguageServer(options)
|
|
223
|
+
|
|
224
|
+
@crosshair_lsp_server.feature(TEXT_DOCUMENT_DID_CHANGE)
|
|
225
|
+
def did_change(
|
|
226
|
+
server: CrossHairLanguageServer, params: DidChangeTextDocumentParams
|
|
227
|
+
):
|
|
228
|
+
# server.window_log_message("did_change")
|
|
229
|
+
uri = params.text_document.uri
|
|
230
|
+
getlocalstate(server).active_messages[uri] = None
|
|
231
|
+
server.publish_diagnostics(uri, [])
|
|
232
|
+
|
|
233
|
+
@crosshair_lsp_server.feature(TEXT_DOCUMENT_DID_CLOSE)
|
|
234
|
+
def did_close(server: CrossHairLanguageServer, params: DidCloseTextDocumentParams):
|
|
235
|
+
# server.window_log_message("did_close")
|
|
236
|
+
uri = params.text_document.uri
|
|
237
|
+
getlocalstate(server).active_messages[uri] = None
|
|
238
|
+
server.publish_diagnostics(uri, [])
|
|
239
|
+
update_paths(server)
|
|
240
|
+
|
|
241
|
+
@crosshair_lsp_server.feature(TEXT_DOCUMENT_DID_OPEN)
|
|
242
|
+
def did_open(server: CrossHairLanguageServer, params: DidOpenTextDocumentParams):
|
|
243
|
+
uri = params.text_document.uri
|
|
244
|
+
getlocalstate(server).active_messages[uri] = {}
|
|
245
|
+
update_paths(server)
|
|
246
|
+
|
|
247
|
+
@crosshair_lsp_server.feature(TEXT_DOCUMENT_DID_SAVE)
|
|
248
|
+
def did_save(server: CrossHairLanguageServer, params: DidOpenTextDocumentParams):
|
|
249
|
+
uri = params.text_document.uri
|
|
250
|
+
update_paths(server)
|
|
251
|
+
getlocalstate(server).active_messages[uri] = {}
|
|
252
|
+
server.publish_diagnostics(uri, [])
|
|
253
|
+
|
|
254
|
+
@crosshair_lsp_server.feature(SHUTDOWN)
|
|
255
|
+
def did_shutdown(
|
|
256
|
+
server: CrossHairLanguageServer,
|
|
257
|
+
params: Any,
|
|
258
|
+
):
|
|
259
|
+
getlocalstate(server).shutdown()
|
|
260
|
+
|
|
261
|
+
return crosshair_lsp_server
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from lsprotocol.types import Diagnostic, DiagnosticSeverity, Position, Range
|
|
3
|
+
|
|
4
|
+
from crosshair.lsp_server import LocalState, create_lsp_server, get_diagnostic
|
|
5
|
+
from crosshair.options import AnalysisOptionSet
|
|
6
|
+
from crosshair.statespace import AnalysisMessage, MessageType
|
|
7
|
+
from crosshair.watcher import Watcher
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture
|
|
11
|
+
def state():
|
|
12
|
+
server = create_lsp_server(AnalysisOptionSet())
|
|
13
|
+
return LocalState(Watcher([]), server)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_get_diagnostic():
|
|
17
|
+
msg = AnalysisMessage(MessageType.POST_ERR, "exception raised", __file__, 2, 0, "")
|
|
18
|
+
assert get_diagnostic(msg, ["one", " two ", "three"]) == Diagnostic(
|
|
19
|
+
range=Range(
|
|
20
|
+
start=Position(line=1, character=2),
|
|
21
|
+
end=Position(line=1, character=5),
|
|
22
|
+
),
|
|
23
|
+
severity=DiagnosticSeverity.Error,
|
|
24
|
+
message="exception raised",
|
|
25
|
+
source="CrossHair",
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_watch_loop(state: LocalState):
|
|
30
|
+
state.run_watch_loop(max_watch_iterations=1)
|