Skip to content

Commit 3c40738

Browse files
fix: install integration deps per-integration in test runner (#29)
1 parent 0aa012e commit 3c40738

1 file changed

Lines changed: 72 additions & 32 deletions

File tree

scripts/run_tests.py

Lines changed: 72 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
Integration Test Runner
44
55
Runs pytest unit tests for integration directories that have test_*_unit.py files.
6+
Each integration is tested separately with its own dependencies installed from its
7+
requirements.txt, so SDK version pins are respected per integration.
68
79
Usage:
810
python scripts/run_tests.py [dir ...]
@@ -68,12 +70,51 @@ def get_integration_dirs(args: list[str]) -> list[Path]:
6870

6971
# Auto-detect: all subdirectories with a config.json
7072
return sorted(
71-
p
72-
for p in Path(".").iterdir()
73-
if p.is_dir() and p.name not in SKIP_FOLDERS and (p / "config.json").exists()
73+
p for p in Path(".").iterdir() if p.is_dir() and p.name not in SKIP_FOLDERS and (p / "config.json").exists()
7474
)
7575

7676

77+
def install_integration_deps(integration_dir: Path) -> bool:
78+
"""Install an integration's requirements.txt. Returns True on success."""
79+
req_file = integration_dir / "requirements.txt"
80+
if not req_file.is_file():
81+
return True
82+
83+
result = subprocess.run(
84+
[sys.executable, "-m", "pip", "install", "-r", str(req_file), "-q"],
85+
capture_output=True,
86+
text=True,
87+
)
88+
if result.returncode != 0:
89+
print(f" ❌ Failed to install dependencies for {integration_dir.name}")
90+
if result.stderr.strip():
91+
for line in result.stderr.strip().splitlines():
92+
print(f" {line}")
93+
return False
94+
return True
95+
96+
97+
def run_integration_tests(integration_dir: Path, test_files: list[Path]) -> int:
98+
"""Run pytest for a single integration. Returns the pytest exit code."""
99+
cmd = [
100+
sys.executable,
101+
"-m",
102+
"pytest",
103+
"--import-mode=importlib",
104+
"-m",
105+
"unit",
106+
"-v",
107+
"--tb=short",
108+
"--no-header",
109+
"--cov",
110+
str(integration_dir),
111+
"--cov-report=term-missing:skip-covered",
112+
*[str(f) for f in test_files],
113+
]
114+
115+
return subprocess.run(cmd).returncode
116+
117+
77118
def main() -> int:
78119
args = sys.argv[1:]
79120
dirs = get_integration_dirs(args)
@@ -99,38 +140,37 @@ def main() -> int:
99140
print("⚠️ No unit tests to run")
100141
return 0
101142

102-
# Build pytest command
103-
test_file_args = []
104-
cov_args = []
105-
for d, files in testable:
106-
cov_args.extend(["--cov", str(d)])
107-
for f in files:
108-
test_file_args.append(str(f))
109-
110-
cmd = [
111-
sys.executable, "-m", "pytest",
112-
"--import-mode=importlib",
113-
"-m", "unit",
114-
"-v",
115-
"--tb=short",
116-
"--no-header",
117-
*cov_args,
118-
"--cov-report=term-missing:skip-covered",
119-
*test_file_args,
120-
]
121-
122143
print(f"🧪 Running unit tests for: {', '.join(d.name for d, _ in testable)}")
123144
print()
124145

125-
result = subprocess.run(cmd)
126-
127-
print()
128-
if result.returncode == 0:
129-
print(f"✅ Tests passed for {len(testable)} integration(s)")
130-
else:
131-
print(f"❌ Tests failed (exit code {result.returncode})")
132-
133-
return result.returncode
146+
# Run each integration separately with its own dependencies
147+
failed = []
148+
passed = []
149+
for d, test_files in testable:
150+
print(f"{'=' * 60}")
151+
print(f" {d.name}")
152+
print(f"{'=' * 60}")
153+
154+
if not install_integration_deps(d):
155+
failed.append(d.name)
156+
continue
157+
158+
exit_code = run_integration_tests(d, test_files)
159+
if exit_code == 0:
160+
passed.append(d.name)
161+
else:
162+
failed.append(d.name)
163+
print()
164+
165+
# Summary
166+
print("=" * 60)
167+
if passed:
168+
print(f"✅ Tests passed: {', '.join(passed)}")
169+
if failed:
170+
print(f"❌ Tests failed: {', '.join(failed)}")
171+
print("=" * 60)
172+
173+
return 1 if failed else 0
134174

135175

136176
if __name__ == "__main__":

0 commit comments

Comments
 (0)