Skip to content

Commit 2b51f17

Browse files
authored
Merge pull request #205 from poissoncorp/RDBC-795
RDBC-795 Make test driver use the embedded server
2 parents 8f552ab + 53224ae commit 2b51f17

File tree

4 files changed

+103
-109
lines changed

4 files changed

+103
-109
lines changed

.github/workflows/RavenClient.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ jobs:
7272
- name: Install client deps
7373
run: pip install -e .
7474

75+
- name: Install embedded RavenDB
76+
run: pip install ravendb-embedded
77+
7578
- name: Run certifi script
7679
run: python ./.github/workflows/add_ca.py
7780

ravendb/tests/driver/raven_server_runner.py

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,58 @@
11
from __future__ import annotations
2-
32
import os
43
import pathlib
5-
import subprocess
4+
import glob
5+
from typing import TYPE_CHECKING
6+
7+
from ravendb_embedded.embedded_server import EmbeddedServer
8+
from ravendb_embedded.options import ServerOptions, SecurityOptions
9+
from ravendb_embedded.provide import ExternalServerProvider
10+
611
from ravendb.tests.driver.raven_server_locator import RavenServerLocator
712

13+
if TYPE_CHECKING:
14+
from ravendb.tests.test_base import TestBase
15+
816

917
class RavenServerRunner:
18+
@staticmethod # workaround for places where we cannot import TestBase.TestSecuredServiceLocator
19+
def is_locator_secured(locator: RavenServerLocator) -> bool:
20+
return hasattr(locator, "client_certificate_path")
21+
1022
@staticmethod
11-
def run(locator: RavenServerLocator) -> subprocess.Popen:
23+
def get_server_options_for_embedded_server(
24+
locator: RavenServerLocator, server_dll_parent_dir: str
25+
) -> ServerOptions:
1226
process_start_info = RavenServerRunner.get_process_start_info(locator)
27+
server_options = ServerOptions()
28+
server_options.server_url = "http://127.0.0.1:0"
29+
server_options.command_line_args = ["--Features.Availability=Experimental"]
30+
server_options.command_line_args.extend(process_start_info.arguments)
31+
server_options.provider = ExternalServerProvider(server_dll_parent_dir)
32+
server_options.target_server_location = server_dll_parent_dir
33+
34+
if RavenServerRunner.is_locator_secured(locator):
35+
locator: "TestBase.TestSecuredServiceLocator"
36+
server_options.security = SecurityOptions()
37+
server_options.security.server_pfx_certificate_path = locator.server_certificate_path
38+
server_options.security.client_pem_certificate_path = locator.client_certificate_path
39+
server_options.security.ca_certificate_path = locator.server_ca_path
40+
server_options.server_url = locator.https_server_url
41+
42+
return server_options
43+
44+
@staticmethod
45+
def get_embedded_server(locator: RavenServerLocator) -> EmbeddedServer:
46+
raven_server_dll_pardir = pathlib.Path(locator.get_server_path()).parent.__str__()
47+
server_options = RavenServerRunner.get_server_options_for_embedded_server(locator, raven_server_dll_pardir)
1348

14-
arguments = [process_start_info.command]
15-
arguments.extend(process_start_info.arguments)
49+
embedded = EmbeddedServer()
50+
embedded.start_server(server_options)
1651

17-
return subprocess.Popen(arguments, stdout=subprocess.PIPE, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
52+
return embedded
1853

1954
@staticmethod
20-
def get_process_start_info(locator: RavenServerLocator) -> __ProcessStartInfo:
55+
def get_process_start_info(locator: RavenServerLocator) -> _ProcessStartInfo:
2156
server_path = pathlib.Path(locator.get_server_path())
2257
if not server_path.exists():
2358
raise FileNotFoundError(f"Server file was not found: {locator.get_server_path()}")
@@ -32,7 +67,7 @@ def get_process_start_info(locator: RavenServerLocator) -> __ProcessStartInfo:
3267

3368
command_arguments.extend(locator.command_arguments)
3469

35-
process_start_info = RavenServerRunner.__ProcessStartInfo(locator.command, *command_arguments)
70+
process_start_info = RavenServerRunner._ProcessStartInfo(locator.command, *command_arguments)
3671

3772
return process_start_info
3873

@@ -43,7 +78,7 @@ def get_process_id(fallback: str) -> str:
4378
except Exception:
4479
return fallback
4580

46-
class __ProcessStartInfo:
81+
class _ProcessStartInfo:
4782
def __init__(self, command: str, *arguments: str):
4883
self.command = command
4984
self.arguments = arguments

ravendb/tests/driver/raven_test_driver.py

Lines changed: 10 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import os
21
import subprocess
3-
import time
4-
from typing import Callable
5-
2+
from typing import TYPE_CHECKING, Tuple
3+
from ravendb_embedded.embedded_server import EmbeddedServer
64
from ravendb.documents.store.definition import DocumentStore
75
from ravendb.infrastructure.graph import Genre, Movie, User
8-
from ravendb.tests.driver.raven_server_locator import RavenServerLocator
96
from ravendb.tests.driver.raven_server_runner import RavenServerRunner
107

8+
if TYPE_CHECKING:
9+
from ravendb.tests.driver.raven_server_locator import RavenServerLocator
10+
1111

1212
class RavenTestDriver:
1313
debug = False
@@ -19,46 +19,13 @@ def __init__(self):
1919
def disposed(self) -> bool:
2020
return self._disposed
2121

22-
def _run_server_internal(
23-
self, locator: RavenServerLocator, configure_store: Callable[[DocumentStore], None]
24-
) -> (DocumentStore, subprocess.Popen):
25-
process = RavenServerRunner.run(locator)
26-
self._report_info("Starting global server")
27-
url = None
28-
stdout = process.stdout
29-
startup_duration = time.perf_counter()
30-
read_lines = []
31-
while True:
32-
line = stdout.readline().decode("utf-8")
33-
read_lines.append(line)
34-
35-
if line is None:
36-
raise RuntimeError(str.join(os.linesep, read_lines) + process.stdin.read().decode("utf-8"))
37-
38-
if time.perf_counter() - startup_duration > 60:
39-
break
40-
41-
prefix = "Server available on: "
42-
if line.startswith(prefix):
43-
url = line[len(prefix) :].rstrip()
44-
break
45-
46-
if url is None:
47-
self._report_info("Url is None")
48-
try:
49-
process.kill()
50-
except Exception as e:
51-
self._report_error(e)
52-
53-
raise RuntimeError("Unable to start server")
54-
print(url)
55-
store = DocumentStore([url], "test.manager")
22+
@staticmethod
23+
def _run_embedded_server_internal(locator: "RavenServerLocator") -> Tuple[DocumentStore, EmbeddedServer]:
24+
embedded_server = RavenServerRunner.get_embedded_server(locator)
25+
store = embedded_server.get_document_store("test.manager")
5626
store.conventions.disable_topology_updates = True
5727

58-
if configure_store is not None:
59-
configure_store(store)
60-
61-
return store.initialize(), process
28+
return store, embedded_server
6229

6330
@staticmethod
6431
def _kill_process(p: subprocess.Popen) -> None:

ravendb/tests/test_base.py

Lines changed: 46 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
import sys
77
import os
88
from enum import Enum
9-
from subprocess import Popen
10-
from typing import Iterable, List, Union, Optional, Set
9+
from typing import Iterable, List, Optional, Set
1110
from datetime import timedelta
11+
from ravendb_embedded.embedded_server import EmbeddedServer
1212

1313
from ravendb.documents.operations.revisions import (
1414
ConfigureRevisionsOperationResult,
@@ -147,17 +147,20 @@ def __init__(self, patched):
147147

148148

149149
class TestBase(unittest.TestCase, RavenTestDriver):
150-
__global_server: Union[None, DocumentStore] = None
151-
__global_server_process: Union[None, Popen] = None
150+
_global_embedded_server: Optional[EmbeddedServer] = None
151+
_global_secured_embedded_server: Optional[EmbeddedServer] = None
152152

153-
__global_secured_server: Union[None, DocumentStore] = None
154-
__global_secured_server_process: Union[None, Popen] = None
153+
_global_server_store: Optional[DocumentStore] = None
154+
_global_secured_server_store: Optional[DocumentStore] = None
155155

156156
__run_server_lock = threading.Lock()
157157

158158
index = 0
159159

160-
class __TestServiceLocator(RavenServerLocator):
160+
class TestServiceLocator(RavenServerLocator):
161+
def get_server_path(self) -> str:
162+
return super().get_server_path()
163+
161164
@property
162165
def server_path(self) -> str:
163166
return super().get_server_path()
@@ -170,7 +173,7 @@ def command_arguments(self) -> List[str]:
170173
"--Features.Availability=Experimental",
171174
]
172175

173-
class __TestSecuredServiceLocator(RavenServerLocator):
176+
class TestSecuredServiceLocator(RavenServerLocator):
174177
ENV_CLIENT_CERTIFICATE_PATH = "RAVENDB_PYTHON_TEST_CLIENT_CERTIFICATE_PATH"
175178
ENV_SERVER_CERTIFICATE_PATH = "RAVENDB_PYTHON_TEST_SERVER_CERTIFICATE_PATH"
176179
ENV_TEST_CA_PATH = "RAVENDB_PYTHON_TEST_CA_PATH"
@@ -181,7 +184,7 @@ def get_server_path(self) -> str:
181184

182185
@property
183186
def command_arguments(self) -> List[str]:
184-
https_server_url = self.__https_server_url
187+
https_server_url = self.https_server_url
185188
tcp_server_url = https_server_url.replace("https", "tcp", 1).rsplit(":", 1)[0] + ":38882"
186189
return [
187190
f"--Security.Certificate.Path={self.server_certificate_path}",
@@ -190,7 +193,7 @@ def command_arguments(self) -> List[str]:
190193
]
191194

192195
@property
193-
def __https_server_url(self) -> str:
196+
def https_server_url(self) -> str:
194197
https_server_url = os.environ[self.ENV_HTTPS_SERVER_URL]
195198
if https_server_url.isspace():
196199
raise ValueError(
@@ -226,27 +229,23 @@ def server_certificate_path(self) -> str:
226229
def server_ca_path(self) -> str:
227230
return os.getenv(self.ENV_TEST_CA_PATH)
228231

229-
def __get_locator(self, secured: bool):
230-
return self.__secured_locator if secured else self.__locator
232+
def _get_locator(self, secured: bool):
233+
return self._secured_locator if secured else self._locator
231234

232-
def __get_global_server(self, secured: bool):
233-
return self.__global_secured_server if secured else self.__global_server
235+
def _get_global_server_store(self, secured: bool):
236+
return self._global_secured_server_store if secured else self._global_server_store
234237

235-
def __run_server(self, secured: bool):
236-
def __configure_store(s: DocumentStore) -> None:
237-
if secured:
238-
s.certificate_pem_path = self.test_client_certificate_url
239-
s.trust_store_path = self.test_ca_certificate_url
240-
241-
store, process = self._run_server_internal(self.__get_locator(secured), __configure_store)
242-
self.__set_global_server_process(secured, process)
238+
def _run_embedded_server(self, secured: bool):
239+
store, embedded_server = self._run_embedded_server_internal(self._get_locator(secured))
243240

244241
if secured:
245-
TestBase.__global_secured_server = store
242+
TestBase._global_secured_server_store = store
243+
TestBase._global_secured_embedded_server = embedded_server
246244
else:
247-
TestBase.__global_server = store
245+
TestBase._global_server_store = store
246+
TestBase._global_embedded_server = embedded_server
248247

249-
atexit.register(threading.Thread(target=self.__kill_global_server_process, args=[secured]).run)
248+
atexit.register(threading.Thread(target=self._discard_embedded_server, args=[secured]).run)
250249
return store
251250

252251
def _customize_db_record(self, db_record: DatabaseRecord) -> None:
@@ -264,11 +263,11 @@ def secured_document_store(self) -> DocumentStore:
264263

265264
@property
266265
def test_client_certificate_url(self) -> str:
267-
return self.__secured_locator.client_certificate_path
266+
return self._secured_locator.client_certificate_path
268267

269268
@property
270269
def test_ca_certificate_url(self) -> str:
271-
return self.__secured_locator.server_ca_path
270+
return self._secured_locator.server_ca_path
272271

273272
def get_document_store(
274273
self,
@@ -280,12 +279,12 @@ def get_document_store(
280279
name = f"{database}_{TestBase.index}"
281280
TestBase._report_info(f"get_document_store for db {database}.")
282281

283-
if self.__get_global_server(secured) is None:
282+
if self._get_global_server_store(secured) is None:
284283
with self.__run_server_lock:
285-
if self.__get_global_server(secured) is None:
286-
self.__run_server(secured)
284+
if self._get_global_server_store(secured) is None:
285+
self._run_embedded_server(secured)
287286

288-
document_store = self.__get_global_server(secured)
287+
document_store = self._get_global_server_store(secured)
289288
database_record = DatabaseRecord(name)
290289

291290
self._customize_db_record(database_record)
@@ -303,7 +302,7 @@ def get_document_store(
303302
store.initialize()
304303

305304
def __after_close():
306-
if store not in self.__document_stores:
305+
if store not in self._document_stores:
307306
return
308307

309308
try:
@@ -317,30 +316,21 @@ def __after_close():
317316
if wait_for_indexing_timeout is not None:
318317
self.wait_for_indexing(store, name, wait_for_indexing_timeout)
319318

320-
self.__document_stores.add(store)
319+
self._document_stores.add(store)
321320
return store
322321

323-
@staticmethod
324-
def __kill_global_server_process(secured: bool) -> None:
325-
if secured:
326-
p = TestBase.__global_secured_server_process
327-
TestBase.__global_secured_server_process = None
328-
TestBase.__global_secured_server.close()
329-
TestBase.__global_secured_server = None
330-
else:
331-
p = TestBase.__global_server_process
332-
TestBase.__global_server_process = None
333-
TestBase.__global_server.close()
334-
TestBase.__global_server = None
335-
336-
RavenTestDriver._kill_process(p)
337-
338-
@staticmethod
339-
def __set_global_server_process(secured: bool, process: Popen) -> None:
322+
@classmethod
323+
def _discard_embedded_server(cls, secured: bool) -> None:
340324
if secured:
341-
TestBase.__global_secured_server_process = process
325+
cls._global_secured_server_store.close()
326+
cls._global_secured_server_store = None
327+
cls._global_secured_embedded_server.close()
328+
cls._global_secured_embedded_server = None
342329
else:
343-
TestBase.__global_server_process = process
330+
cls._global_server_store.close()
331+
cls._global_server_store = None
332+
cls._global_embedded_server.close()
333+
cls._global_embedded_server = None
344334

345335
@staticmethod
346336
def delete_all_topology_files():
@@ -412,11 +402,10 @@ def setConvention(self, conventions):
412402
self.conventions = conventions
413403

414404
def setUp(self):
415-
# todo: investigate if line below is replaceable by more sophisticated code, we don't want to call TestCase init
416405
RavenTestDriver.__init__(self)
417-
self.__locator = TestBase.__TestServiceLocator()
418-
self.__secured_locator = TestBase.__TestSecuredServiceLocator()
419-
self.__document_stores: Set[DocumentStore] = set()
406+
self._locator = TestBase.TestServiceLocator()
407+
self._secured_locator = TestBase.TestSecuredServiceLocator()
408+
self._document_stores: Set[DocumentStore] = set()
420409
conventions = getattr(self, "conventions", None)
421410
self.default_urls = ["http://127.0.0.1:8080"]
422411
self.default_database = "NorthWindTest"
@@ -435,7 +424,7 @@ def tearDown(self):
435424

436425
exceptions = []
437426

438-
for document_store in self.__document_stores:
427+
for document_store in self._document_stores:
439428
try:
440429
document_store.close()
441430
except Exception as e:

0 commit comments

Comments
 (0)