Source code for operators.dbt.dbt_test
# dbt_test.py
from airflow.utils.decorators import apply_defaults
from airflow.utils.context import Context
from operators.dbt.dbt_base import BaseDBTOperator
[docs]
class DBTTestOperator(BaseDBTOperator):
"""
Execute `dbt test` on the remote server.
:param models: list of model names (without .sql) to test; defaults to all
:param vars: dict of vars to pass into dbt
"""
ui_color = "#DB0058"
ui_fgcolor = "#FFFFFF"
custom_operator_name = "DBTTestOperator"
@apply_defaults
def __init__(
self,
**kwargs,
):
super().__init__(**kwargs)
[docs]
def execute(self, context: Context):
import time
super().execute(context)
# build and run
cmd = " ".join(self.build_command("dbt test"))
self.log.info(f"Running: {cmd}")
clock_start = time.time()
try:
# When we create a new table, DBT doesn't get notified automatically.
# So we try to run the command, and if compilation fails, we refresh the source tables and try again.
# We don't refresh sources every time because it adds ~45 seconds to the run time.
try:
self.run_ssh_command(cmd)
except Exception as e:
print(e)
# If less than a minute has passed since we started, try refreshing sources and re-running
# If its been more than a minute, then it isn't a compilation issue, so just fail out
if time.time() - clock_start < 60:
self.log.info(
"dbt test failed, attempting to refresh sources and re-run..."
)
super().run_dbt_refresh_sources()
self.run_ssh_command(cmd)
else:
raise
except Exception:
self.log.error("dbt test failed!")
raise
finally:
import json, base64
test_results = {"passed": [], "failed": [], "warned": [], "errored": []}
try:
run_results_output = self.run_ssh_command(
f"cat {self.target_path}/run_results.json | base64 -w 0"
)
decoded = base64.b64decode(run_results_output).decode("utf-8")
run_results = json.loads(decoded)
for result in run_results.get("results", []):
test_name = result.get("unique_id", "unknown")
status = result.get("status", "unknown")
entry = {
"test_name": test_name,
"status": status,
"failures": result.get("failures", 0),
"message": result.get("message", ""),
}
if status == "pass":
test_results["passed"].append(entry)
elif status == "fail":
test_results["failed"].append(entry)
elif status == "warn":
test_results["warned"].append(entry)
else:
test_results["errored"].append(entry)
self.log.info(
f"Test results: {len(test_results['passed'])} passed, "
f"{len(test_results['failed'])} failed, "
f"{len(test_results['warned'])} warned, "
f"{len(test_results['errored'])} errored"
)
except Exception as e:
self.log.warning(f"Could not parse test results: {e}")
result = {
"job_id": self.job_id,
"target_path": self.target_path,
"test_results": test_results,
}
context["ti"].xcom_push(key="return_value", value=result)
self.log.info("dbt test completed successfully.")
return result