Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,22 @@ jobs:
env:
RAVENDB_TEST_SERVER_URL: http://localhost:8080
run: python labs/01_attach_to_server.py

self-contained:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- name: Set up Python 3.13
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with:
python-version: "3.13"

- name: Install package
run: |
python -m pip install --upgrade pip
pip install -e .

- name: Run Lab 04 (self-contained, no system .NET)
run: python labs/04_embedded_no_dotnet.py
50 changes: 42 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ Python 3.10+ is required.

## Providing a server: pick one

The driver needs a RavenDB server to talk to. There are two ways to give it one; choose based on
whether you want .NET on the test machine.
The driver needs a RavenDB server to talk to. Choose how it should get one based on your
environment.

### 1. Embedded server (default, needs .NET)

Expand All @@ -32,7 +32,29 @@ installed:
Check with `dotnet --list-runtimes`. The requirement tracks the embedded server and can change on
a minor upgrade, so re-check it when you bump versions.

### 2. Attach to a server you run yourself (no .NET)
### 2. Self-contained embedded server (no system .NET)

Let the driver download and cache a self-contained RavenDB build. It manages the server for you
without calling `dotnet`:

```python
from ravendb_embedded import ServerOptions
from ravendb_test_driver import RavenTestDriver

options = ServerOptions()
options.with_auto_downloaded_server()
RavenTestDriver.configure_server(options)
```

The embedded package detects the host operating system and architecture, so the same test
configuration is portable across supported Windows, Linux, and macOS machines.

Call `configure_server()` before the first `get_document_store()`. The first run downloads
100 MB+; later runs reuse the cache. Self-contained mode needs no system .NET, but normal RavenDB
OS dependencies still apply; minimal Linux images may need their distribution's ICU package. See
[`labs/04-embedded-no-dotnet.md`](labs/04-embedded-no-dotnet.md).

### 3. Attach to a server you run yourself (no .NET)

If you would rather not put .NET on the test machine (containerized CI, locked-down hosts), run
RavenDB yourself (Docker, testcontainers, a shared CI service) and point the driver at its URL.
Expand All @@ -42,13 +64,23 @@ The driver skips the embedded boot entirely and still creates an isolated databa
from ravendb_test_driver import RavenTestDriver

RavenTestDriver.configure_external_server("http://localhost:8080")
# or set RAVENDB_TEST_SERVER_URL in the environment (handy for CI)
# or set RAVENDB_TEST_SERVER_URL in the environment
```

Call it once, before the first `get_document_store()`. A runnable Docker / testcontainers guide
is in [`labs/01-attach-to-server.md`](labs/01-attach-to-server.md). For the embedded and
self-contained server options, see the
[`ravendb-python-embedded`](https://github.com/ravendb/ravendb-python-embedded) repository.
For HTTPS with client-certificate authentication:

```python
RavenTestDriver.configure_external_server(
"https://my-ravendb",
certificate_pem_path="client.pem",
trust_store_path="ca.crt", # needed when the server CA is not already trusted
)
```

The equivalent environment variables are `RAVENDB_TEST_SERVER_URL`,
`RAVENDB_TEST_SERVER_CERT`, and `RAVENDB_TEST_SERVER_CA`. Call the configuration method once,
before the first `get_document_store()`. A runnable Docker / testcontainers guide is in
[`labs/01-attach-to-server.md`](labs/01-attach-to-server.md).

## Usage

Expand Down Expand Up @@ -87,6 +119,8 @@ Runnable example: [`labs/03-seeding-indexes.md`](labs/03-seeding-indexes.md).

## Links

The runnable lab scripts live in this repository and are not installed into `site-packages`.

- PyPI: https://pypi.org/project/ravendb-test-driver/
- GitHub: https://github.com/ravendb/ravendb-python-testdriver
- Server and self-contained options: https://github.com/ravendb/ravendb-python-embedded
17 changes: 7 additions & 10 deletions labs/01-attach-to-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ RavenTestDriver.configure_external_server("http://localhost:8080")
```

For a secured (https) server, pass the client certificate: `configure_external_server(url,
certificate_pem_path=...)` (or set `RAVENDB_TEST_SERVER_CERT`); attaching to https without one
fails fast with a clear message.
certificate_pem_path=..., trust_store_path=...)`. The trust store is needed when the server CA is
not already trusted. The environment equivalents are `RAVENDB_TEST_SERVER_CERT` and
`RAVENDB_TEST_SERVER_CA`; attaching to HTTPS without a client certificate fails fast.

Then use the driver exactly as with the embedded server:

Expand All @@ -36,12 +37,13 @@ The complete runnable example is [`01_attach_to_server.py`](01_attach_to_server.
## Run a server with Docker

```bash
docker run -d -p 8080:8080 \
docker run --rm -d --name ravendb-test-driver-lab -p 8080:8080 \
-e RAVEN_Setup_Mode=None -e RAVEN_License_Eula_Accepted=true \
-e RAVEN_Security_UnsecuredAccessAllowed=PublicNetwork -e RAVEN_ServerUrl=http://0.0.0.0:8080 \
ravendb/ravendb:7.2-ubuntu-latest

RAVENDB_TEST_SERVER_URL=http://localhost:8080 python labs/01_attach_to_server.py
docker stop ravendb-test-driver-lab
```

## Run a server with testcontainers-python
Expand All @@ -68,11 +70,7 @@ from ravendb_test_driver import RavenTestDriver
RavenTestDriver.configure_external_server(url)
```

## In CI

This repo's own CI uses a GitHub Actions service container (see `.github/workflows/tests.yml`,
the `attach` job): it runs `ravendb/ravendb:7.2-ubuntu-latest`, waits for it, then runs the
attach test with `RAVENDB_TEST_SERVER_URL` set and no .NET installed.
Stop the container in your fixture or test cleanup.

## Cleanup on a shared server

Expand All @@ -84,5 +82,4 @@ and prune leftover `test_*` databases between runs.
## Takeaway

No embedded server and no .NET, at the cost of running RavenDB yourself. If you would rather
have the driver run the server for you (with or without .NET), see the embedded options in the
`ravendb-python-embedded` repository.
have the driver download and manage a self-contained server, use Lab 04.
3 changes: 2 additions & 1 deletion labs/02-embedded-per-test.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,5 @@ to the other. That isolation is what keeps tests independent.
## Takeaway

No server to manage in your tests: the driver runs one and gives each test its own database. To
run without .NET, attach to a server you start yourself (Lab 01).
run without .NET, attach to a server you start yourself (Lab 01), or let the driver automatically
download, cache, and manage a self-contained embedded server (Lab 04).
44 changes: 44 additions & 0 deletions labs/04-embedded-no-dotnet.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Lab 04: Self-contained embedded server without system .NET

**For:** test suites that want the driver to manage RavenDB without installing .NET and without
running a separate server. `ravendb-embedded` downloads and caches a self-contained RavenDB build,
then the test driver uses it while keeping each test database isolated.

The embedded package detects the host operating system and architecture at runtime. This keeps the
same test suite portable across supported Windows, Linux, and macOS machines without per-platform
server paths or a matching system .NET installation.

## Run it

Run from a checkout of this repository:

```bash
pip install ravendb-test-driver
python labs/04_embedded_no_dotnet.py
```

The complete example is [`04_embedded_no_dotnet.py`](04_embedded_no_dotnet.py). The core is:

```python
from ravendb_embedded import ServerOptions
from ravendb_test_driver import RavenTestDriver

options = ServerOptions()
options.with_auto_downloaded_server()
RavenTestDriver.configure_server(options)

with RavenTestDriver() as driver:
with driver.get_document_store() as store:
... # isolated database on a self-contained server
```

The first run downloads a self-contained build (100 MB+) to
`~/.cache/ravendb-embedded`; later runs reuse that cache. Pass `cache_root` to
`with_auto_downloaded_server()` when your build system restores a different cache directory.
Normal RavenDB operating-system dependencies still apply; minimal Linux images may need their
distribution's ICU package.

## Takeaway

This mode keeps the zero-server-management experience of embedded tests while making the same test
configuration portable across supported platforms without requiring a system .NET runtime.
41 changes: 41 additions & 0 deletions labs/04_embedded_no_dotnet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""Lab 04: Self-contained embedded server without system .NET.

For: test suites that want the driver to manage RavenDB without installing .NET or running a
separate server. The first run downloads and caches a self-contained RavenDB build.

Run: python labs/04_embedded_no_dotnet.py
"""

import atexit
import shutil
import tempfile
from pathlib import Path

from ravendb_embedded import ServerOptions
from ravendb_test_driver import RavenTestDriver


def main() -> None:
work = tempfile.mkdtemp()
atexit.register(lambda: shutil.rmtree(work, ignore_errors=True))

options = ServerOptions()
options.dot_net_path = "__no_dotnet__"
options.with_auto_downloaded_server()
options.data_directory = str(Path(work, "data"))
options.logs_path = str(Path(work, "logs"))
RavenTestDriver.configure_server(options)

with RavenTestDriver() as driver:
with driver.get_document_store() as store:
with store.open_session() as session:
session.store({"name": "no-dotnet"}, "people/1")
session.save_changes()
with store.open_session() as session:
assert session.load("people/1", dict)["name"] == "no-dotnet"

print("Lab 04 OK: self-contained embedded server ran without system .NET.")


if __name__ == "__main__":
main()
9 changes: 6 additions & 3 deletions labs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
Runnable, self-checking guides for using the test driver. Each lab ships a script next to it, so
you can run the exact code the guide shows.

The scripts are part of this repository and are not installed into `site-packages`. Clone or
download the repository first, then run them from its root.

| Lab | Covers | Needs system .NET? |
|-----|--------|--------------------|
| [01](01-attach-to-server.md) | Attach to a server you run yourself (Docker, testcontainers, shared CI) | No |
| [02](02-embedded-per-test.md) | Embedded server, one isolated database per test (the default) | Yes |
| [03](03-seeding-indexes.md) | Seed data and query an index (`setup_database`, `wait_for_indexing`) | Yes |
| [04](04-embedded-no-dotnet.md) | Self-contained embedded server, downloaded and cached automatically | No |

Looking for how to run RavenDB itself (embedded, or self-contained without .NET)? That belongs to
the [`ravendb-python-embedded`](https://github.com/ravendb/ravendb-python-embedded) package and
has its own labs there.
For lower-level server configuration, see the
[`ravendb-python-embedded`](https://github.com/ravendb/ravendb-python-embedded) labs.
20 changes: 17 additions & 3 deletions ravendb_test_driver/raven_test_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class RavenTestDriver:
_EMPTY_SETTINGS_FILE_NAME: Optional[str] = None
_EXTERNAL_SERVER_URL: Optional[str] = None
_EXTERNAL_SERVER_CERT: Optional[str] = None
_EXTERNAL_SERVER_TRUST_STORE: Optional[str] = None

def __init__(self) -> None:
self.disposed = False
Expand Down Expand Up @@ -68,11 +69,16 @@ def configure_server(options: ServerOptions) -> None:
RavenTestDriver._GLOBAL_SERVER_OPTIONS = options

@staticmethod
def configure_external_server(url: str, certificate_pem_path: str = None) -> None:
def configure_external_server(
url: str,
certificate_pem_path: str = None,
trust_store_path: str = None,
) -> None:
"""Attach to a server you run yourself (no embedded boot, no .NET); still one database per test.

For a secured (https) server, pass the client certificate `.pem`. Equivalent to setting
RAVENDB_TEST_SERVER_URL (and RAVENDB_TEST_SERVER_CERT). Call before the first get_document_store.
For HTTPS, pass the client certificate and, when needed, the CA trust store. The equivalent
environment variables are RAVENDB_TEST_SERVER_URL, RAVENDB_TEST_SERVER_CERT, and
RAVENDB_TEST_SERVER_CA. Call before the first get_document_store.
"""
if RavenTestDriver._TEST_SERVER_STORE.is_value_created:
raise RuntimeError(
Expand All @@ -81,6 +87,7 @@ def configure_external_server(url: str, certificate_pem_path: str = None) -> Non
)
RavenTestDriver._EXTERNAL_SERVER_URL = url
RavenTestDriver._EXTERNAL_SERVER_CERT = certificate_pem_path
RavenTestDriver._EXTERNAL_SERVER_TRUST_STORE = trust_store_path

def get_document_store(
self,
Expand All @@ -97,6 +104,10 @@ def get_document_store(
document_store.maintenance.server.send(create_database_operation)

store = DocumentStore(document_store.urls, name)
if document_store.certificate_pem_path:
store.certificate_pem_path = document_store.certificate_pem_path
if document_store.trust_store_path:
store.trust_store_path = document_store.trust_store_path

self.pre_initialize(store)
store.initialize()
Expand Down Expand Up @@ -252,6 +263,7 @@ def run_server(cls) -> DocumentStore:
if external_url:
# Attach to an existing server; do not boot the embedded one (no .NET needed).
certificate = cls._EXTERNAL_SERVER_CERT or os.environ.get("RAVENDB_TEST_SERVER_CERT")
trust_store = cls._EXTERNAL_SERVER_TRUST_STORE or os.environ.get("RAVENDB_TEST_SERVER_CA")
if external_url.lower().startswith("https") and not certificate:
raise RavenException(
f"Attaching to a secured server ({external_url}) needs a client certificate; pass "
Expand All @@ -260,6 +272,8 @@ def run_server(cls) -> DocumentStore:
store = DocumentStore(external_url, None)
if certificate:
store.certificate_pem_path = certificate
if trust_store:
store.trust_store_path = trust_store
store.initialize()
return store

Expand Down
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
setup(
name="ravendb-test-driver",
packages=find_packages(exclude=["*.tests.*", "tests", "*.tests", "tests.*"]),
version="7.2.5",
version="7.2.5.post1",
description="RavenDB package for writing integration tests against RavenDB server",
long_description_content_type="text/markdown",
long_description=open("README.md").read(),
Expand All @@ -13,6 +13,6 @@
license="MIT",
keywords=["ravendb", "nosql", "database", "test", "driver"],
python_requires=">=3.10",
license_files="LICENSE",
install_requires=["ravendb-embedded==7.2.5", "ravendb==7.2.3.post1"],
license_files=["LICENSE"],
install_requires=["ravendb-embedded==7.2.5.post1", "ravendb==7.2.3.post1"],
)
Loading
Loading