RavenDB-27141 Sync the Python test driver with the C# test driver - #20
Merged
poissoncorp merged 32 commits intoAug 27, 2026
Merged
Conversation
…bug/Done contract
… make open_browser an instance method
…t an environment one
…ming database names
Making it the default gave no easy way out: opting out means subclassing RavenTestDriver or mutating a class attribute globally, and neither is something a suite should have to do to keep the names it already has.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Syncs the Python test driver with
src/Raven.TestDriver, from a line-by-line audit of both drivers. Version goes to7.2.5.post3.67 tests, nothing skipped. CI is green on all four jobs: embedded on ubuntu, embedded on windows, attach against Docker with no .NET installed, and the self-contained download.
New features
Embedded test servers run in memory
Every
get_document_store()call creates a database and deletes it when the store closes. That cycle now happens in memory, because the driver appends--RunInMemory=trueto the server it boots, which is what the .NET driver has always done. Only the server log is written, into a scratch directory removed when the test process exits.The second half matters as much: a data directory left at the
ravendb-embeddeddefault used to point inside the installed package, so a test run wrote intosite-packages/ravendb_embedded/RavenDB. The driver now redirects that to a temporary directory, and logs follow it.The tradeoff is real, so there are two ways out. Either say so on the options object:
or put the argument on the command line yourself, which the driver never overrides:
Secured embedded servers actually work
Point the driver at a server certificate and the client certificate your tests authenticate with. The driver takes the client material the embedded server was started with and puts it on every store it hands out, so sessions just work.
Before this, the driver built its stores without any certificate, so the first request against a secured embedded server failed. A secured server configured with no client certificate is now rejected before the server starts, instead of failing later on the first request.
Runnable end to end in the new
labs/05_secured_embedded.py, which generates its own certificates.pre_configure_database: change a database before it is createdsetup_databasehas always been the place to seed documents and create indexes, but it runs on a database that already exists.pre_configure_databaseruns one step earlier, on theDatabaseRecorditself, beforeCreateDatabaseOperationgoes out. That is where database settings, revisions, expiration, encryption and topology live. It is a straight port ofPreConfigureDatabasefrom the .NET driver, which the Python and JVM drivers never had.The three override points now run in this order, and each gets a different thing:
TestServerOptions: one place for test-server defaultsconfigure_serverused to take a rawravendb_embedded.ServerOptions, which is the same type you would use to run a production embedded server.TestServerOptionsis a subclass of it that names the intent and carries the knobs that only make sense for a test server, starting withrun_in_memory.configure_serverstill accepts a plainServerOptions, and the driver applies the same test defaults either way, so no existing call site has to change:Database names that say which test they came from
The .NET driver names each database after the calling test method. Python can now do the same. Off by default, because it changes every generated name.
Names are sanitized to
[A-Za-z0-9_.-], and a caller with no usable name (a lambda, module level) falls back totest.Per-process unique database names
The counter alone restarts in every process, so two runners against one shared server hand out the same names and delete each other's databases. Setting this adds the process id.
# test_stores_a_person_31241_7 instead of test_stores_a_person_7Off by default, because it changes every generated name.
stop_test_server()The test server is shared by the whole process and nothing used to close it, so its shutdown cost landed at interpreter exit, after your runner had already printed its summary. Now you can put it where it belongs. It is idempotent, and the server starts again on the next
get_document_store().Breaking changes
Explicit configuration now beats
RAVENDB_TEST_SERVER_URLThe driver picks its server from three places:
configure_external_server(...),configure_server(...), and theRAVENDB_TEST_SERVER_URLenvironment variable. The environment used to win over everything, silently. That is dangerous, because the driver creates and hard-deletes databases on whichever server it ends up using, so a suite pinned to a local embedded server could be redirected onto a shared one and delete databases there.Now the code wins and the driver says what it ignored.
If you relied on the environment overriding your code, drop the
configure_server()call and let the environment pick the server:Test data no longer lands on disk
This is the other side of the in-memory feature above. A suite that seeded a large fixture and let it spill to disk now holds it in RAM, the configured data directory stays empty, and there are no files to inspect after a failing run.
Fixes
Closing a driver that still held an open store crashed
close()iterated_document_storeswhile each store's after-close callback popped itself out of that same dict. Python raises on that, and because the exception came from theforstatement rather than from inside thetry, it escaped uncaught: the driver was left half-closed, remaining stores were never closed andon_driver_closednever fired.The loop now iterates a snapshot, which is what the .NET driver does with
_documentStores.Keys.ToList(). This is the contract the README always advertised: whatever you forget to close, the driver closes and its database is deleted.wait_for_indexingreturned early during a side-by-side index swapWhen you change the definition of an existing index, RavenDB does not replace it in place. It builds a second index next to it, named
ReplacementOf/YourIndex, and swaps it in once that one has caught up. Python excluded those replacement indexes from the staleness check, which is the exact inverse of the .NET driver: the wait returned while the swap was still pending, so the next query read the pre-swap index and the test passed on stale results.A suite that never redeploys an index definition sees no difference at all, because no replacement index ever exists. One that does gets correct results instead of stale ones. The one new failure mode: if a swap never completes, because the replacement errored out or one was left behind, this now raises
TimeoutExceptionwhere it used to return. Give it more room if your swap is genuinely slow:Two live drivers asked for the same database name
self._INDEX += 1(now_DATABASE_COUNTER) read a class attribute and writes an instance one, so every driver instance restarted numbering at 1 and two live drivers both asked fortest_1. The second create was rejected, and closing the first store deleted the database the other one was still using.Generated names therefore shift: a suite that used to see
test_1three times now seestest_1,test_2,test_3. They were never a documented contract, so readstore.databaseif you pinned one.RavenTestDriver._DATABASE_COUNTER = 0still works if you reset it between suites.Teardown failures were flattened into a string
Closing a driver used to flatten every teardown failure into one string, losing the types and the tracebacks. It now raises a typed error that keeps the originals.
DriverCloseErrorsubclassesRuntimeError, so this keeps working unchanged:If you matched on the message text, that text changed: it is now counted and type-prefixed.
Debug/Donewas loaded instead of checked, and never deletedwait_for_user_to_continue_the_testloaded the whole marker document into the session and left it in the database, so a second wait on the same store returned immediately.wait_for_user_to_continue_the_testcould hang a CI job foreverIt was an unconditional
while Truewith no timeout, so a call left in committed code blocked until the CI job's own wall-clock limit. It is now bounded, and there is a kill switch.RAVENDB_TEST_WAIT_FOR_USER=0 python -m unittest discover -s tests # skip it entirelyA wait is also unbounded automatically while a debugger is attached, which is how the .NET driver behaves.
An explicit zero timeout was ignored
timeout or timedelta(seconds=60)andif options.wait_for_indexing_timeout:both treattimedelta(0)as "not set", so asking for no wait at all gave you either a full minute or no check.Temporary files were left behind
The generated empty settings file was never removed, and the cleanup loop read
shutil.rmtree'sNonereturn as a failure flag, so a successful delete still cost a retry pass.Proven by a test that runs a full driver session in a child interpreter and asserts no temp directory or settings file survives its exit.
A no-leader failure during teardown failed the test
Deleting a test database goes through the cluster, which can be between leaders. The .NET driver swallows
NoLeaderExceptionthere; Python did not, so a leadership hiccup failed an otherwise green test.Error messages, cause chains and a silent browser failure
The configuration error named the Java driver's methods, wrapped exceptions dropped
__cause__, andopen_browserraised an emptyRuntimeErrorwhile ignoringwebbrowser.openreturningFalseon a headless machine.Option preparation mutated the caller's list
run_serverinserted arguments straight intooptions.command_line_args, the list you passed in. Harmless while there was one writer, not harmless once in-memory injection added a second. The driver now builds its own copy.Deprecations
run_server,default_server_optionsandcleanup_temp_dirsare now privateAll three are absent from the .NET driver and private in the JVM one. They were public here by accident.
Nothing breaks in this release: the old names still resolve and only warn. They go away in a later one.
Not in this PR
Dump-based seeding (
DatabaseDumpFilePath/DatabaseDumpFileStream) is the one .NET feature still missing. It is blocked in the client, not here:ravendb/documents/store/definition.pystill has a bare# todo: database smuggler, so there is no import to call. Tracked separately, to be done in the client first.Verification
tests/test_end_to_end.pyboots a real server; the rest are hermetic.Logs/server.logand zero.voronfiles.configure_server, then writes and reads through the driver's store.GetDatabaseNamesOperation, both when a store closes and when the driver closes stores left open.RAVENDB_TEST_SERVER_URLit boots a second embedded server and attaches to that; CI still runs it against Docker with no .NET installed.black --checkclean, labs 01 to 05 pass.