Pipeline E2E and Smoke Testing Across Environments Guide¶
End-to-end (E2E) and smoke tests execute pipelines across multiple execution modes and storage backends.
- Specification-driven smoke suite:
tests/cdm_data_loaders/pipelines/test_pipeline_smoke_spec.py - Unified test runner:
tests/cdm_data_loaders/pipelines/framework.py(PipelineTestRunner) - Environment resolver:
tests/pipeline_env.py(SmokeEnvConfig,resolve_smoke_env) - dlt config isolation:
tests/dlt_config_isolation.py(write_dlt_config_dir)
1. Execution Modes and Storage Backends¶
PipelineTestRunner executes a pipeline scenario in one of three modes:
| Execution Mode | Description | Entry Point |
|---|---|---|
core |
In-process execution calling pipeline_module.run(settings) |
Direct Python call |
cli |
Subprocess execution via pydantic-settings' CliRunner (CliRunner) or subprocess.run |
Registered CLI entry point (e.g., load-pubmed, load-ncbi-datasets) |
docker |
Container execution mounting local paths or passing remote URLs | docker run --rm ... <image> <cli_command> ... |
Built-in storage environments in tests/cdm_data_loaders/pipelines/test_pipeline_smoke_spec.py:
| Environment ID | Input Storage | Output and Destination Storage | Markers / Requirements |
|---|---|---|---|
local_fs |
Local temporary directory | Local filesystem destination (tmp_path / "local_fs_bucket") |
Always enabled |
s3_moto |
In-memory Moto S3 bucket (s3://input-bucket/<run_slug>/...) |
In-memory Moto S3 bucket (s3://cdm-lake/<run_slug> and s3://output-bucket/<run_slug>) |
Always enabled |
s3_ceph |
Local Ceph Nano S3 bucket | Local Ceph Nano S3 bucket | Requires -m requires_ceph and running Ceph container |
custom |
Configured via --dlt-config-dir and --settings-file |
Configured via --dlt-config-dir and --settings-file |
Enabled when --dlt-config-dir or --settings-file is passed |
2. Running Built-In Environments¶
Run All Smoke Scenarios (local_fs and s3_moto)¶
Filter by Pipeline (--smoke-pipeline)¶
Pass --smoke-pipeline once or multiple times to restrict the suite to specific pipelines:
uv run pytest tests/cdm_data_loaders/pipelines/test_pipeline_smoke_spec.py \
--smoke-pipeline pubmed \
-m "not requires_ceph"
Filter by Environment or Execution Mode (-k)¶
Run only local_fs in core mode:
Run only s3_moto in cli mode:
Run Against Local Ceph (s3_ceph)¶
When a local Ceph Nano container is running, select the requires_ceph marker:
3. Running Against a Custom Environment¶
When --dlt-config-dir or --settings-file is provided on the pytest command line, the test parametrization switches from the built-in local_fs / s3_moto / s3_ceph fixtures to a single custom environment.
Step 1: Create a dlt Configuration Directory (--dlt-config-dir)¶
Create a directory (for example, /tmp/my-env/dlt_conf) containing config.toml and secrets.toml:
# /tmp/my-env/dlt_conf/config.toml
[destination.filesystem]
bucket_url = "s3://my-staging-lake/smoke-tests"
# /tmp/my-env/dlt_conf/secrets.toml
[destination.filesystem.credentials]
endpoint_url = "https://s3.staging.example.org"
aws_access_key_id = "YOUR_ACCESS_KEY"
aws_secret_access_key = "YOUR_SECRET_KEY"
Any destination supported by dlt (for example, filesystem, duckdb, snowflake, postgres) or Iceberg catalog configuration can be specified inside config.toml and secrets.toml.
Step 2: Create a Settings File (--settings-file)¶
Create a TOML, YAML, or JSON settings file (for example, /tmp/my-env/settings.toml) with top-level shared settings or per-pipeline sections under [pipelines.<pipeline_name>]:
# /tmp/my-env/settings.toml
use_destination = "filesystem"
output_dir = "/tmp/my-env/output"
loader_file_format = "parquet"
[pipelines.pubmed]
buffer_size = 500
[pipelines.ncbi_datasets]
buffer_size = 100
Settings Precedence (lowest to highest):
1. Scenario settings defined in specs/pipelines/<pipeline>/<case>/settings.toml and case.yaml.
2. Top-level keys in --settings-file (filtered to valid fields of the target pipeline's Settings model).
3. Pipeline-specific keys in [pipelines.<pipeline_name>] inside --settings-file.
4. Per-run isolated input_dir and output_dir generated by the test runner.
Step 3: Execute the Smoke Test Suite¶
uv run pytest tests/cdm_data_loaders/pipelines/test_pipeline_smoke_spec.py \
--dlt-config-dir /tmp/my-env/dlt_conf \
--settings-file /tmp/my-env/settings.toml \
--smoke-pipeline ncbi_datasets
4. Running with Apache Iceberg (loader_file_format = "iceberg" and .pyiceberg.yaml)¶
When testing Iceberg output against a catalog (such as a Polaris REST catalog or SQL catalog) configured via .pyiceberg.yaml:
- Place
.pyiceberg.yamlin$HOME(~/.pyiceberg.yaml): - Standard PyIceberg configuration lives at
$HOME/.pyiceberg.yaml. load_pipeline_env_from_dir()andPipelineTestRunnerignore any.pyiceberg.yamlplaced inside--dlt-config-dir.- In Docker mode (
--run-docker),PipelineTestRunnerstages$HOME/.pyiceberg.yamlfrom the host into/input_dir/dlt_conf/.pyiceberg.yaml, andscripts/entrypoint.shcopies it to$HOME/.pyiceberg.yamlinside the container. - Manage Table Lifecycle Across Runs in a Scratch Catalog:
- Because preset pipelines such as
ncbi_datasetsandpubmeduse a fixeddataset_name(ncbi_datasetsorpubmed), consecutive test cases writing to a persistent catalog (such as Polaris) will append to existing catalog tables unless tables are dropped before or after each run. - Use either
pytestCLI flags orsettings.tomlkeys to control catalog table cleanup:
| CLI Flag | settings.toml Key |
Behavior |
|---|---|---|
--drop-existing-tables |
drop_existing_tables = true |
Drops any existing tables for the target pipeline's dataset from the Iceberg catalog before each test run starts. |
--drop-tables-after |
drop_tables_after = true |
Drops any tables added to the Iceberg catalog during the test run when the test finishes (finally: block). |
Both drop_existing_tables and drop_tables_after can be set at the top level of settings.toml or under [pipelines.<pipeline_name>].
Example settings.toml for running ncbi_datasets with Iceberg and a Polaris REST catalog configured in ~/.pyiceberg.yaml:
# /tmp/my-env/settings.toml
use_destination = "s3"
loader_file_format = "iceberg"
drop_existing_tables = true
drop_tables_after = true
Example pytest invocation:
uv run pytest tests/cdm_data_loaders/pipelines/test_pipeline_smoke_spec.py \
--dlt-config-dir /tmp/my-env/dlt_conf \
--settings-file /tmp/my-env/settings.toml \
--smoke-pipeline ncbi_datasets \
--drop-existing-tables \
--drop-tables-after
5. Running in Docker Mode (--run-docker)¶
Enable the docker execution mode by passing --run-docker and --docker-image (defaults to cdm-data-loaders:latest):
# Build the image first
docker build -t cdm-data-loaders:latest .
# Run smoke tests in Docker against a custom S3 environment
uv run pytest tests/cdm_data_loaders/pipelines/test_pipeline_smoke_spec.py \
--dlt-config-dir /tmp/my-env/dlt_conf \
--settings-file /tmp/my-env/settings.toml \
--run-docker \
--docker-image cdm-data-loaders:latest \
-k "docker"
How PipelineTestRunner handles Docker mounts and environment variables:
- Local paths: When input_dir, output_dir, or dlt bucket_url are local paths, PipelineTestRunner bind-mounts them into /mnt/input, /mnt/output, and /mnt/dlt_dest inside the container.
- Remote URLs (s3://...): When input_dir or output_dir are s3:// URLs, PipelineTestRunner passes the remote URLs to the CLI command inside the container without bind-mounting local directories.
- dlt configuration: PipelineTestRunner mounts the isolated dlt_conf directory at /mnt/dlt_conf and sets DLT_PROJECT_DIR=/mnt/dlt_conf and DLT_DATA_DIR=/mnt/dlt_data inside the container.
- S3 credentials: AWS/S3 environment variables (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_S3_ENDPOINT, FSSPEC_S3_ENDPOINT_URL, FSSPEC_S3_KEY, FSSPEC_S3_SECRET) are forwarded into the container when set.
6. Per-Run Isolation on Shared Buckets¶
When running against a shared S3 bucket or shared local directory, test_pipeline_smoke_spec.py isolates each test invocation using a deterministic run_slug:
- Input files: Staged under
<input_base>/<run_slug>/(both local paths ands3://URLs viafsspec). - Output reports and logs: Written under
<output_base>/<run_slug>/. - dlt destination: Written under
<bucket_url>/<run_slug>withpreserve_dlt_destination=True.
This prevents table collisions, schema pollution, or report overwrites when multiple scenarios or pipelines target the same bucket.
7. What Every Run Verifies¶
Regardless of execution mode (core, cli, docker) or storage backend (local_fs, s3_moto, s3_ceph, custom), assert_smoke_run_output() in tests/cdm_data_loaders/pipelines/helpers.py verifies:
- Exit Code Contract: Even when a run records rejected files or failed records (
status: "completed with errors"), the CLI and Docker process must exit with code0so the KBase Container Task Service (CTS) preserves output artifacts and logs. - Summary Report (
report.txt): Parsesstatus,Metrics(files_processed,records_extracted,records_failed,files_rejected),Output Tables, andErrorsfrom<output_dir>/<run_id>/report.txtand matches them againstexpected_reportinrules.yaml. - Audit Table (
_load_info): Reads the_load_infotable from the dlt destination and asserts that the audit row exists forrun_id. - Destination Tables (
expected/*.json): Reads every output table from the dlt dataset viaread_destination_tables(), normalizes volatile dlt metadata (_dlt_load_id,_dlt_id), strips null columns that dlt omits from materialization, and asserts row-level equality againstspecs/pipelines/<pipeline>/expected/<scenario>_<table>.json.