fable-engine 1.3.1__py3-none-any.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.
- fable_compressor.py +356 -0
- fable_engine/__init__.py +1 -0
- fable_engine/actions/__init__.py +291 -0
- fable_engine/actions/cas.py +182 -0
- fable_engine/actions/deliberation.py +523 -0
- fable_engine/actions/fleet.py +807 -0
- fable_engine/actions/lifecycle.py +298 -0
- fable_engine/actions/scrapers.py +116 -0
- fable_engine/actions/system3.py +815 -0
- fable_engine/browser.py +824 -0
- fable_engine/cas.py +974 -0
- fable_engine/fable_session.json +510 -0
- fable_engine/guards.py +283 -0
- fable_engine/schema.py +714 -0
- fable_engine/scrapers/__init__.py +32 -0
- fable_engine/scrapers/arxiv.py +115 -0
- fable_engine/scrapers/base.py +386 -0
- fable_engine/scrapers/github.py +129 -0
- fable_engine/scrapers/reddit.py +154 -0
- fable_engine/scrapers/web.py +120 -0
- fable_engine/scrapers/x.py +125 -0
- fable_engine/scrapers/youtube.py +132 -0
- fable_engine/server.py +414 -0
- fable_engine/session.py +1819 -0
- fable_engine/test_server.py +1362 -0
- fable_engine/updater.py +541 -0
- fable_engine-1.3.1.dist-info/LICENSE +22 -0
- fable_engine-1.3.1.dist-info/METADATA +173 -0
- fable_engine-1.3.1.dist-info/RECORD +104 -0
- fable_engine-1.3.1.dist-info/WHEEL +5 -0
- fable_engine-1.3.1.dist-info/entry_points.txt +5 -0
- fable_engine-1.3.1.dist-info/top_level.txt +6 -0
- fable_mode/__init__.py +3 -0
- fable_mode/__main__.py +4 -0
- fable_mode/adapters.py +1014 -0
- fable_mode/installer.py +553 -0
- fable_mode/launcher.py +437 -0
- fable_mode/manifest.py +142 -0
- fable_mode/resources.json +114 -0
- fable_mode/safety.py +103 -0
- fable_mode_entry.py +10 -0
- fable_v2/__init__.py +146 -0
- fable_v2/adapters.py +151 -0
- fable_v2/coder_fleet/__init__.py +100 -0
- fable_v2/coder_fleet/ast_tools.py +158 -0
- fable_v2/coder_fleet/compute.py +199 -0
- fable_v2/coder_fleet/design_engine.py +1316 -0
- fable_v2/coder_fleet/diagnostics.py +293 -0
- fable_v2/coder_fleet/fleet_dispatcher.py +214 -0
- fable_v2/coder_fleet/mock_auditor.py +306 -0
- fable_v2/coder_fleet/mutation.py +216 -0
- fable_v2/coder_fleet/property_oracle.py +260 -0
- fable_v2/coder_fleet/receipt_attestor.py +122 -0
- fable_v2/coder_fleet/red_team_swarm.py +908 -0
- fable_v2/coder_fleet/test_harness.py +198 -0
- fable_v2/coder_fleet/vector_engine.py +1287 -0
- fable_v2/coder_fleet/visual.py +357 -0
- fable_v2/coder_fleet/workspace.py +153 -0
- fable_v2/cortical/__init__.py +20 -0
- fable_v2/cortical/plasticity_engine.py +992 -0
- fable_v2/execution_broker.py +811 -0
- fable_v2/proof_engine.py +1141 -0
- fable_v2/protocol.py +485 -0
- fable_v2/runtime.py +1010 -0
- fable_v2/system3/__init__.py +204 -0
- fable_v2/system3/causal.py +558 -0
- fable_v2/system3/dialectical.py +577 -0
- fable_v2/system3/evolution.py +503 -0
- fable_v2/system3/executive.py +338 -0
- fable_v2/system3/free_energy.py +479 -0
- fable_v2/system3/hyperbolic.py +555 -0
- fable_v2/system3/induction.py +336 -0
- fable_v2/system3/kripke.py +548 -0
- fable_v2/system3/oracle.py +745 -0
- fable_v2/verifiers.py +72 -0
- tests/__init__.py +1 -0
- tests/test_anti_loop_circuit_breaker.py +64 -0
- tests/test_auto_updater.py +407 -0
- tests/test_coder_fleet.py +535 -0
- tests/test_delegation_compiler.py +54 -0
- tests/test_descriptor_boundaries.py +126 -0
- tests/test_design_engine.py +603 -0
- tests/test_epistemic_evidence_validator.py +66 -0
- tests/test_execution_broker.py +233 -0
- tests/test_fable_v2.py +406 -0
- tests/test_fleet_transitions.py +116 -0
- tests/test_fsm_redteam_evolution.py +406 -0
- tests/test_goal_rubric_and_pipeline.py +367 -0
- tests/test_hebbian_plasticity.py +585 -0
- tests/test_packaging_runtime.py +194 -0
- tests/test_proof_engine.py +259 -0
- tests/test_red_team_swarm.py +645 -0
- tests/test_redteam_remediation.py +169 -0
- tests/test_registration_transaction.py +375 -0
- tests/test_requested_regressions.py +467 -0
- tests/test_scrapers.py +370 -0
- tests/test_server_actions.py +93 -0
- tests/test_server_frontier_actions.py +269 -0
- tests/test_server_protocol.py +88 -0
- tests/test_stealth_browser.py +970 -0
- tests/test_system3.py +381 -0
- tests/test_system3_deep_integration.py +385 -0
- tests/test_system3_frontier.py +436 -0
- tests/test_vector_engine.py +608 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import unittest
|
|
2
|
+
import json
|
|
3
|
+
import time
|
|
4
|
+
import tempfile
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
9
|
+
for p in [str(BASE_DIR), str(BASE_DIR / "fable_engine"), str(Path(__file__).resolve().parent)]:
|
|
10
|
+
if p not in sys.path:
|
|
11
|
+
sys.path.insert(0, p)
|
|
12
|
+
|
|
13
|
+
from server import (
|
|
14
|
+
handle_fable_session,
|
|
15
|
+
ACTIVE_SESSIONS,
|
|
16
|
+
SESSIONS_DIR,
|
|
17
|
+
FableSession,
|
|
18
|
+
MIN_TIME_BUDGET_MINUTES,
|
|
19
|
+
GLOBAL_VELOCITY_PROFILER,
|
|
20
|
+
ModelVelocityProfiler,
|
|
21
|
+
)
|
|
22
|
+
from fable_v2.proof_engine import DeterministicProofValidator, ProofType
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class TestServerFrontierActions(unittest.TestCase):
|
|
26
|
+
def setUp(self):
|
|
27
|
+
self.session_name = f"test_frontier_{int(time.time() * 1000)}"
|
|
28
|
+
|
|
29
|
+
def tearDown(self):
|
|
30
|
+
if self.session_name in ACTIVE_SESSIONS:
|
|
31
|
+
del ACTIVE_SESSIONS[self.session_name]
|
|
32
|
+
session_file = SESSIONS_DIR / f"{self.session_name}.json"
|
|
33
|
+
if session_file.exists():
|
|
34
|
+
try:
|
|
35
|
+
session_file.unlink()
|
|
36
|
+
except Exception:
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
def test_min_time_budget_enforcement(self):
|
|
40
|
+
# 1. Reject time budget < 2.0
|
|
41
|
+
res = handle_fable_session({
|
|
42
|
+
"action": "create_session",
|
|
43
|
+
"session_name": self.session_name,
|
|
44
|
+
"objective": "Test budget rejection",
|
|
45
|
+
"time_budget_minutes": 1.5
|
|
46
|
+
})
|
|
47
|
+
self.assertIn("Error:", res)
|
|
48
|
+
self.assertIn("minimum allowed time budget is 2.0 minutes", res)
|
|
49
|
+
|
|
50
|
+
# 2. Accept valid time budget >= 2.0
|
|
51
|
+
res_ok = handle_fable_session({
|
|
52
|
+
"action": "create_session",
|
|
53
|
+
"session_name": self.session_name,
|
|
54
|
+
"objective": "Test valid budget",
|
|
55
|
+
"time_budget_minutes": 5.0
|
|
56
|
+
})
|
|
57
|
+
self.assertIn("Fable Cognitive Session Initialized", res_ok)
|
|
58
|
+
|
|
59
|
+
# 3. Reject set_timer < 2.0
|
|
60
|
+
res_timer_fail = handle_fable_session({
|
|
61
|
+
"action": "set_timer",
|
|
62
|
+
"session_name": self.session_name,
|
|
63
|
+
"time_budget_minutes": 0.5
|
|
64
|
+
})
|
|
65
|
+
self.assertIn("Error:", res_timer_fail)
|
|
66
|
+
self.assertIn("minimum allowed time budget is 2.0 minutes", res_timer_fail)
|
|
67
|
+
|
|
68
|
+
def test_model_velocity_profiler(self):
|
|
69
|
+
profiler = ModelVelocityProfiler(window_size=10)
|
|
70
|
+
# Record rapid high-throughput requests
|
|
71
|
+
t0 = time.time()
|
|
72
|
+
for i in range(5):
|
|
73
|
+
profiler.record_request("test_action", "x" * 2000, timestamp=t0 + i * 0.1)
|
|
74
|
+
|
|
75
|
+
prof = profiler.get_velocity_profile()
|
|
76
|
+
self.assertEqual(prof["model_tier"], "flash")
|
|
77
|
+
self.assertEqual(prof["tier_multiplier"], 2.5)
|
|
78
|
+
self.assertGreater(prof["tokens_per_sec"], 80.0)
|
|
79
|
+
|
|
80
|
+
def test_track_file_change_action(self):
|
|
81
|
+
handle_fable_session({
|
|
82
|
+
"action": "create_session",
|
|
83
|
+
"session_name": self.session_name,
|
|
84
|
+
"objective": "Test file tracking",
|
|
85
|
+
"time_budget_minutes": 10.0
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
with tempfile.NamedTemporaryFile("w", delete=False, suffix=".py") as tf:
|
|
89
|
+
tf.write("print('hello')\n")
|
|
90
|
+
tf_path = tf.name
|
|
91
|
+
|
|
92
|
+
try:
|
|
93
|
+
res = handle_fable_session({
|
|
94
|
+
"action": "track_file_change",
|
|
95
|
+
"session_name": self.session_name,
|
|
96
|
+
"file_path": tf_path,
|
|
97
|
+
"change_type": "modified",
|
|
98
|
+
"diff_summary": "Added hello print statement",
|
|
99
|
+
"rationale": "Bootstrap entrypoint",
|
|
100
|
+
"affected_invariants": ["INV-01"]
|
|
101
|
+
})
|
|
102
|
+
self.assertIn("File Change Tracked", res)
|
|
103
|
+
self.assertIn("MODIFIED", res)
|
|
104
|
+
self.assertIn("File SHA256", res)
|
|
105
|
+
|
|
106
|
+
session = ACTIVE_SESSIONS[self.session_name]
|
|
107
|
+
self.assertEqual(len(session.file_changes), 1)
|
|
108
|
+
self.assertEqual(session.file_changes[0]["change_type"], "modified")
|
|
109
|
+
self.assertIsNotNone(session.file_changes[0]["sha256"])
|
|
110
|
+
finally:
|
|
111
|
+
Path(tf_path).unlink(missing_ok=True)
|
|
112
|
+
|
|
113
|
+
def test_record_visual_mockups_action(self):
|
|
114
|
+
handle_fable_session({
|
|
115
|
+
"action": "create_session",
|
|
116
|
+
"session_name": self.session_name,
|
|
117
|
+
"objective": "Test visual mockups",
|
|
118
|
+
"time_budget_minutes": 10.0
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
mockups = [
|
|
122
|
+
{
|
|
123
|
+
"concept_name": "Kinetic Glassmorphism",
|
|
124
|
+
"aesthetic_archetype": "glassmorphic_modern",
|
|
125
|
+
"prompt": "Futuristic UI with frosted glass cards",
|
|
126
|
+
"palette": "oklch(0.95 0.02 240) / oklch(0.2 0.05 260)",
|
|
127
|
+
"typography": "Geist, Geist Mono",
|
|
128
|
+
"coordinates_data": {"grid": "12-col", "gap": "16px"}
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
"concept_name": "Monochrome Minimal",
|
|
132
|
+
"aesthetic_archetype": "swiss_high_contrast",
|
|
133
|
+
"prompt": "Clean monochrome Swiss typography",
|
|
134
|
+
"palette": "#000000 / #FFFFFF",
|
|
135
|
+
"typography": "Inter, JetBrains Mono",
|
|
136
|
+
"coordinates_data": {"grid": "bento-3x3"}
|
|
137
|
+
}
|
|
138
|
+
]
|
|
139
|
+
|
|
140
|
+
res = handle_fable_session({
|
|
141
|
+
"action": "record_visual_mockups",
|
|
142
|
+
"session_name": self.session_name,
|
|
143
|
+
"mockups": mockups,
|
|
144
|
+
"selected_concept": "Kinetic Glassmorphism"
|
|
145
|
+
})
|
|
146
|
+
self.assertIn("Visual Architectural Mockups Recorded", res)
|
|
147
|
+
self.assertIn("Kinetic Glassmorphism", res)
|
|
148
|
+
|
|
149
|
+
session = ACTIVE_SESSIONS[self.session_name]
|
|
150
|
+
self.assertEqual(len(session.visual_mockups["mockups"]), 2)
|
|
151
|
+
self.assertEqual(session.visual_mockups["selected_concept"], "Kinetic Glassmorphism")
|
|
152
|
+
|
|
153
|
+
def test_verify_proof_action(self):
|
|
154
|
+
handle_fable_session({
|
|
155
|
+
"action": "create_session",
|
|
156
|
+
"session_name": self.session_name,
|
|
157
|
+
"objective": "Test proof verification",
|
|
158
|
+
"time_budget_minutes": 10.0
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
res = handle_fable_session({
|
|
162
|
+
"action": "verify_proof",
|
|
163
|
+
"session_name": self.session_name,
|
|
164
|
+
"claim": "Cargo test passes cleanly with 0 failures",
|
|
165
|
+
"proof_type": "receipt",
|
|
166
|
+
"evidence": "cargo test --all: exit code 0, 42 passed"
|
|
167
|
+
})
|
|
168
|
+
self.assertIn("Deterministic Proof Verification", res)
|
|
169
|
+
self.assertIn("VERIFIED", res)
|
|
170
|
+
self.assertIn("rcpt_", res)
|
|
171
|
+
|
|
172
|
+
def test_get_session_lineage_and_inspect_plan(self):
|
|
173
|
+
handle_fable_session({
|
|
174
|
+
"action": "create_session",
|
|
175
|
+
"session_name": self.session_name,
|
|
176
|
+
"objective": "Full lineage and blueprint inspection",
|
|
177
|
+
"time_budget_minutes": 10.0
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
handle_fable_session({
|
|
181
|
+
"action": "track_file_change",
|
|
182
|
+
"session_name": self.session_name,
|
|
183
|
+
"file_path": "src/core/router.py",
|
|
184
|
+
"change_type": "slated",
|
|
185
|
+
"diff_summary": "Implement atomic lock-free queue"
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
plan_res = handle_fable_session({
|
|
189
|
+
"action": "inspect_plan",
|
|
190
|
+
"session_name": self.session_name
|
|
191
|
+
})
|
|
192
|
+
self.assertIn("Fable Execution Plan & Cognitive Blueprint", plan_res)
|
|
193
|
+
self.assertIn("Cognitive Gate Status", plan_res)
|
|
194
|
+
self.assertIn("src/core/router.py", plan_res)
|
|
195
|
+
|
|
196
|
+
lineage_res = handle_fable_session({
|
|
197
|
+
"action": "get_session_lineage",
|
|
198
|
+
"session_name": self.session_name
|
|
199
|
+
})
|
|
200
|
+
self.assertIn("Omniscient Session Lineage", lineage_res)
|
|
201
|
+
self.assertIn("Slated File Modifications", lineage_res)
|
|
202
|
+
self.assertIn("Model Velocity & Capability Telemetry", lineage_res)
|
|
203
|
+
|
|
204
|
+
def test_tautology_rejection_in_proven_claim(self):
|
|
205
|
+
handle_fable_session({
|
|
206
|
+
"action": "create_session",
|
|
207
|
+
"session_name": self.session_name,
|
|
208
|
+
"objective": "Test tautology rejection",
|
|
209
|
+
"time_budget_minutes": 10.0
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
# Tautological claim should be rejected
|
|
213
|
+
res = handle_fable_session({
|
|
214
|
+
"action": "log_epistemic_item",
|
|
215
|
+
"session_name": self.session_name,
|
|
216
|
+
"tag": "PROVEN",
|
|
217
|
+
"claim": "it works tested verified",
|
|
218
|
+
"evidence": "stdout: ok"
|
|
219
|
+
})
|
|
220
|
+
self.assertIn("Error:", res)
|
|
221
|
+
self.assertIn("must not be tautological or generic", res)
|
|
222
|
+
|
|
223
|
+
def test_validate_event_history_action(self):
|
|
224
|
+
handle_fable_session({
|
|
225
|
+
"action": "create_session",
|
|
226
|
+
"session_name": self.session_name,
|
|
227
|
+
"objective": "Test event history audit",
|
|
228
|
+
"time_budget_minutes": 10.0
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
handle_fable_session({
|
|
232
|
+
"action": "track_file_change",
|
|
233
|
+
"session_name": self.session_name,
|
|
234
|
+
"file_path": "src/unified.py",
|
|
235
|
+
"change_type": "modified",
|
|
236
|
+
"diff_summary": "+ unified event tracking",
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
audit_res = handle_fable_session({
|
|
240
|
+
"action": "validate_event_history",
|
|
241
|
+
"session_name": self.session_name,
|
|
242
|
+
})
|
|
243
|
+
self.assertIn("Cryptographic Event Chain Audit", audit_res)
|
|
244
|
+
self.assertIn("VALID & INTACT", audit_res)
|
|
245
|
+
|
|
246
|
+
def test_cross_process_session_mtime_synchronization(self):
|
|
247
|
+
from fable_engine.server import get_or_load_session, SESSIONS_DIR
|
|
248
|
+
handle_fable_session({
|
|
249
|
+
"action": "create_session",
|
|
250
|
+
"session_name": self.session_name,
|
|
251
|
+
"objective": "Concurrency test",
|
|
252
|
+
"time_budget_minutes": 10.0
|
|
253
|
+
})
|
|
254
|
+
session1 = get_or_load_session(self.session_name)
|
|
255
|
+
file_path = SESSIONS_DIR / f"{self.session_name}.json"
|
|
256
|
+
|
|
257
|
+
# Simulate external process update by loading, modifying, and saving to disk
|
|
258
|
+
data = json.loads(file_path.read_text(encoding="utf-8"))
|
|
259
|
+
data["objective"] = "Updated by external worker process"
|
|
260
|
+
time.sleep(0.02)
|
|
261
|
+
file_path.write_text(json.dumps(data), encoding="utf-8")
|
|
262
|
+
|
|
263
|
+
# get_or_load_session should detect disk mtime delta and refresh in-memory session
|
|
264
|
+
session2 = get_or_load_session(self.session_name)
|
|
265
|
+
self.assertEqual(session2.objective, "Updated by external worker process")
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
if __name__ == "__main__":
|
|
269
|
+
unittest.main()
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import io
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
import tempfile
|
|
7
|
+
import threading
|
|
8
|
+
import unittest
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from unittest.mock import patch
|
|
11
|
+
|
|
12
|
+
from fable_engine.server import FableCASStore, IntegrityError, MAX_RPC_LINE_BYTES, main
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ServerProtocolRegressionTests(unittest.TestCase):
|
|
16
|
+
def test_cached_verified_cas_reads_detect_disk_tampering(self):
|
|
17
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
18
|
+
store = FableCASStore(Path(tmp))
|
|
19
|
+
ref = store.put("trusted")
|
|
20
|
+
path = store.get_file_path(ref)
|
|
21
|
+
path.write_text("tampered", encoding="utf-8")
|
|
22
|
+
with self.assertRaises(IntegrityError):
|
|
23
|
+
store.get_bytes(ref, verify=True)
|
|
24
|
+
self.assertFalse(store.verify_integrity(ref))
|
|
25
|
+
|
|
26
|
+
def test_verify_false_is_explicit_low_level_cache_opt_out(self):
|
|
27
|
+
"""Only the opt-out can return cached bytes after disk tampering."""
|
|
28
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
29
|
+
store = FableCASStore(Path(tmp))
|
|
30
|
+
ref = store.put("trusted")
|
|
31
|
+
store.get_file_path(ref).write_text("tampered", encoding="utf-8")
|
|
32
|
+
self.assertEqual(store.get_bytes(ref, verify=False), b"trusted")
|
|
33
|
+
with self.assertRaises(IntegrityError):
|
|
34
|
+
store.get_text(ref, verify=True)
|
|
35
|
+
|
|
36
|
+
def test_malformed_jsonrpc_requests_are_controlled_errors(self):
|
|
37
|
+
requests = "not-json\n[]\n" + json.dumps({"jsonrpc": "2.0", "id": 3, "method": "ping", "params": []}) + "\n"
|
|
38
|
+
output = io.StringIO()
|
|
39
|
+
with patch("sys.stdin", io.StringIO(requests)), patch("sys.stdout", output):
|
|
40
|
+
main()
|
|
41
|
+
responses = [json.loads(line) for line in output.getvalue().splitlines() if line]
|
|
42
|
+
self.assertEqual([item["error"]["code"] for item in responses], [-32700, -32600, -32600])
|
|
43
|
+
|
|
44
|
+
def test_rpc_line_limit_counts_untrimmed_frame(self):
|
|
45
|
+
output = io.StringIO()
|
|
46
|
+
with patch("sys.stdin", io.StringIO(" " * (MAX_RPC_LINE_BYTES + 1) + "\n")), patch("sys.stdout", output):
|
|
47
|
+
main()
|
|
48
|
+
response = json.loads(output.getvalue())
|
|
49
|
+
self.assertEqual(response["error"]["code"], -32600)
|
|
50
|
+
|
|
51
|
+
def test_interactive_initialize_does_not_wait_for_eof(self):
|
|
52
|
+
"""A short frame must be answered while stdin remains open."""
|
|
53
|
+
root = Path(tempfile.mkdtemp(prefix="fable-interactive-"))
|
|
54
|
+
proc = subprocess.Popen(
|
|
55
|
+
[sys.executable, "-u", os.fspath(Path(__file__).resolve().parents[1] / "fable_engine" / "server.py")],
|
|
56
|
+
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
57
|
+
text=True, encoding="utf-8", cwd=os.fspath(root),
|
|
58
|
+
)
|
|
59
|
+
line = []
|
|
60
|
+
reader = threading.Thread(target=lambda: line.append(proc.stdout.readline()), daemon=True)
|
|
61
|
+
try:
|
|
62
|
+
proc.stdin.write(json.dumps({"jsonrpc": "2.0", "id": 1,
|
|
63
|
+
"method": "initialize", "params": {}}) + "\n")
|
|
64
|
+
proc.stdin.flush()
|
|
65
|
+
reader.start()
|
|
66
|
+
reader.join(timeout=10)
|
|
67
|
+
self.assertFalse(reader.is_alive(), "interactive response waited for EOF")
|
|
68
|
+
self.assertTrue(line and line[0])
|
|
69
|
+
self.assertEqual(json.loads(line[0])["id"], 1)
|
|
70
|
+
finally:
|
|
71
|
+
if proc.stdin:
|
|
72
|
+
proc.stdin.close()
|
|
73
|
+
if proc.poll() is None:
|
|
74
|
+
proc.terminate()
|
|
75
|
+
try:
|
|
76
|
+
proc.wait(timeout=2)
|
|
77
|
+
except subprocess.TimeoutExpired:
|
|
78
|
+
proc.kill(); proc.wait(timeout=2)
|
|
79
|
+
if proc.stdout:
|
|
80
|
+
proc.stdout.close()
|
|
81
|
+
if proc.stderr:
|
|
82
|
+
proc.stderr.close()
|
|
83
|
+
import shutil
|
|
84
|
+
shutil.rmtree(root, ignore_errors=True)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
if __name__ == "__main__":
|
|
88
|
+
unittest.main()
|