|
1 | | -import json |
2 | | -import types |
3 | | -from pathlib import Path |
4 | | -from typing import Dict |
5 | | - |
| 1 | +# conftest.py |
| 2 | +# |
| 3 | +# Purpose |
| 4 | +# ------- |
| 5 | +# Treat every Python sample under doc-samples/agents/python/** |
| 6 | +# as a Pytest test item. The script “passes” if it runs without |
| 7 | +# raising an exception (exit code 0). |
| 8 | +# |
| 9 | +# How it works |
| 10 | +# ------------ |
| 11 | +# • pytest_collect_file() is a collection hook called for every file |
| 12 | +# Pytest sees. We accept the file if: |
| 13 | +# – it ends with .py |
| 14 | +# – its path is under SAMPLE_ROOT (any depth) |
| 15 | +# • Accepted files become SampleItem objects, which simply execute |
| 16 | +# the script with runpy.run_path(). |
| 17 | +# |
| 18 | +# Edit SAMPLE_ROOT if you move the samples elsewhere. |
| 19 | + |
| 20 | +import pathlib |
| 21 | +import runpy |
6 | 22 | import pytest |
7 | 23 |
|
8 | | -REPO_ROOT = Path(__file__).parent.resolve() |
9 | | - |
10 | | - |
11 | | -@pytest.fixture |
12 | | -def notebook_path( |
13 | | - # Create and activate a new venv for each test that requests `notebook_path` |
14 | | - venv: types.SimpleNamespace, # noqa: ARG001 |
15 | | - notebook_path: Path, |
16 | | -) -> Path: |
17 | | - """Activates a virtual environment for tests that request notebook_path (Jupyter Notebook tests).""" |
18 | | - return notebook_path |
19 | | - |
| 24 | +# Root directory that contains all Python samples |
| 25 | +SAMPLE_ROOT = ( |
| 26 | + pathlib.Path(__file__).parent |
| 27 | + / "doc-samples" |
| 28 | + / "agents" |
| 29 | + / "python" |
| 30 | +).resolve() |
20 | 31 |
|
21 | | -@pytest.fixture |
22 | | -def deployment_outputs() -> Dict[str, str]: |
23 | | - """The outputs of the deployment used to setup resources for testing samples. |
24 | 32 |
|
25 | | - Depends on the existence of a `deployment.json` file in the root of the repository, |
26 | | - which is the output of running `az deployment sub create -o json` |
| 33 | +def _is_under_sample_root(path_obj: pathlib.Path) -> bool: |
27 | 34 | """ |
28 | | - deployment_file_path = REPO_ROOT / "deployment.json" |
| 35 | + Return True if `path_obj` is inside SAMPLE_ROOT (recursive). |
29 | 36 |
|
| 37 | + Using Path.relative_to() is the safest way and works on all OSes. |
| 38 | + """ |
30 | 39 | try: |
31 | | - with deployment_file_path.open() as f: |
32 | | - deployment = json.load(f) |
33 | | - except (FileExistsError, json.JSONDecodeError) as e: |
34 | | - raise AssertionError("Please use azure-cli to perform a deployment and same result to deployment.json") from e |
35 | | - |
36 | | - properties = deployment.get("properties") |
37 | | - |
38 | | - if properties is None or "outputs" not in properties: |
39 | | - raise AssertionError("Key 'properties.outputs' not present in deployment json") |
40 | | - |
41 | | - outputs = properties.get("outputs") |
| 40 | + path_obj.resolve().relative_to(SAMPLE_ROOT) |
| 41 | + return True |
| 42 | + except ValueError: |
| 43 | + return False |
42 | 44 |
|
43 | | - return {output_name: output["value"] for output_name, output in outputs.items()} |
44 | | - |
45 | | - |
46 | | -@pytest.fixture |
47 | | -def azure_ai_project(deployment_outputs: Dict[str, str]) -> Dict[str, str]: |
48 | | - """Azure ai project dictionary.""" |
49 | | - return { |
50 | | - "subscription_id": deployment_outputs["subscription_id"], |
51 | | - "resource_group_name": deployment_outputs["resource_group_name"], |
52 | | - "project_name": deployment_outputs["project_name"], |
53 | | - } |
54 | | - |
55 | | - |
56 | | -@pytest.fixture |
57 | | -def azure_ai_project_connection_string(deployment_outputs: Dict[str, str]) -> str: |
58 | | - """The connection string for the azure ai project""" |
59 | | - return ";".join( |
60 | | - [ |
61 | | - f"{deployment_outputs['project_location']}.api.azureml.ms", |
62 | | - deployment_outputs["subscription_id"], |
63 | | - deployment_outputs["resource_group_name"], |
64 | | - deployment_outputs["project_name"], |
65 | | - ] |
66 | | - ) |
67 | 45 |
|
| 46 | +def pytest_collect_file(parent, path): |
| 47 | + """ |
| 48 | + PyTest collection hook: decide whether *path* should become a test item. |
| 49 | + `path` is a py.path.local object; convert to Path for easier checks. |
| 50 | + """ |
| 51 | + if path.ext == ".py" and _is_under_sample_root(pathlib.Path(path)): |
| 52 | + return SampleItem.from_parent(parent, fspath=path) |
68 | 53 |
|
69 | | -@pytest.fixture |
70 | | -def azure_openai_endpoint(deployment_outputs: Dict[str, str]) -> str: |
71 | | - """The azure openai endpoint for the azure ai project.""" |
72 | | - return deployment_outputs["azure_openai_endpoint"] |
73 | 54 |
|
| 55 | +class SampleItem(pytest.Item): |
| 56 | + """ |
| 57 | + Wrapper around an arbitrary Python script. |
| 58 | + The test *passes* if the script finishes without an exception. |
| 59 | + """ |
74 | 60 |
|
75 | | -@pytest.fixture |
76 | | -def azure_openai_gpt4_deployment(deployment_outputs: Dict[str, str]) -> str: |
77 | | - """The deployment name of the gpt-4 deployment.""" |
78 | | - return deployment_outputs["azure_openai_gpt4_deployment_name"] |
| 61 | + def runtest(self): |
| 62 | + # Execute the script in its own namespace. |
| 63 | + runpy.run_path(str(self.fspath)) |
79 | 64 |
|
| 65 | + def repr_failure(self, excinfo): |
| 66 | + # Nicely format any exception raised during runtest(). |
| 67 | + return f"Sample {self.fspath} failed:\n{excinfo.value}" |
80 | 68 |
|
81 | | -@pytest.fixture |
82 | | -def azure_openai_gpt4_api_version(deployment_outputs: Dict[str, str]) -> str: |
83 | | - """The api version of the gpt-4 deployment.""" |
84 | | - return deployment_outputs["azure_openai_gpt4_api_version"] |
| 69 | + def reportinfo(self): |
| 70 | + return self.fspath, 0, "sample script" |
0 commit comments