Newsletter
TechAnV Blog
Get updates on security engineering, Rust, eBPF, and DevSecOps. No spam, unsubscribe anytime.
Check your inbox and click the confirmation link to complete your subscription.
Testing a Click app with streaming input#
For sqlite-utils#364 I needed to write a test for a Click app which dealt with input streamed to standard input. I needed to run some assertions during that process, which ruled out the usual CliRunner.invoke() testing tool since that works by running the command until completion.
I decided to use subprocess to run the application. Here’s the pattern I came up with for the test:
1def test_insert_streaming_batch_size_1(db_path):2 # https://github.com/simonw/sqlite-utils/issues/3643 # Streaming with --batch-size 1 should commit on each record4 # Can't use CliRunner().invoke() here bacuse we need to5 # run assertions in between writing to process stdin6 proc = subprocess.Popen(7 [8 sys.executable,9 "-m",10 "sqlite_utils",11 "insert",12 db_path,13 "rows",14 "-",15 "--nl",16 "--batch-size",17 "1",18 ],19 stdin=subprocess.PIPE20 )21 proc.stdin.write(b'{"name": "Azi"}\n')22 proc.stdin.flush()23 # Without this delay the data wasn't yet visible24 time.sleep(0.2)25 assert list(Database(db_path)["rows"].rows) == [{"name": "Azi"}]26 proc.stdin.write(b'{"name": "Suna"}\n')27 proc.stdin.flush()28 time.sleep(0.2)29 assert list(Database(db_path)["rows"].rows) == [{"name": "Azi"}, {"name": "Suna"}]30 proc.stdin.close()31 proc.wait()32 assert proc.returncode == 0The first trick I’m using here is running sys.executable to start a Python process. This ensures I run the Python that is available in the current virtual environment.
I modified my sqlite-utils command such that it could also be run using python -m sqlite_utils - see sqlite-utils#368 for details.
Setting stdin=subprocess.PIPE allows me to write data to the process’s standard input using proc.stdin.write().
I realized I needed to call proc.stdin.flush() after each write to ensure the write was pushed to the process in a predictable manner.
At the end of the test, running proc.stdin.close() is equivalent to sending an end-of-file, then proc.wait() ensures the process has finished and terminated.