Add AGENTS.md and document functional test conventions

Add an AGENTS.md routing index for AI coding agents, following the
pattern established by Nova [1]. The TC discussion on the AGENTS.md
community goal [2] concluded that projects should adopt AGENTS.md
individually rather than as a mandated goal, so this adds one for
openstacksdk.

Rather than inlining rules in AGENTS.md, add a "Functional Tests"
section to HACKING.rst documenting conventions that apply to both
human and AI contributors: one test method per resource, _delete_foo
cleanup helpers with addCleanup, failing on misconfigured environments
instead of skipping, and testing both admin and non-admin personas.
AGENTS.md then serves as a routing index pointing to HACKING.rst and
other existing docs.

[1] https://review.opendev.org/c/openstack/nova/+/984540
[2] https://review.opendev.org/c/openstack/governance/+/986765

Assisted-by: Claude claude-opus-4-6 claude-code-2.1.232
Change-Id: If01ebab65446ac02e6db698cc4819e7a8c5b40ef
Signed-off-by: melanie witt <melwittt@gmail.com>
This commit is contained in:
melanie witt committed 2026-08-14 13:48:24 -07:00
1 parent abfb363b78
commit 2819d1c912
2 files changed
+99

No files matched your search

+29
View File
@@ -0,0 +1,29 @@
# AGENTS.md — agent routing index
Agents: explore the repo directly; this file is a routing index, not a
contributor guide.
## Workflow
**For non-trivial planning**, inspect deps and tooling:
`tox.ini` · `.pre-commit-config.yaml` ·
`requirements.txt` · `test-requirements.txt`
**Tests**: Use `tox` or `stestr`; never use `pytest`.
**Routing:**
- Style, hacking, checks: [HACKING.rst](HACKING.rst)
- Unit test conventions: [HACKING.rst](HACKING.rst) (Unit Tests section)
- Functional test conventions: [HACKING.rst](HACKING.rst) (Functional Tests section)
- Contributing: [CONTRIBUTING.rst](CONTRIBUTING.rst)
## Guardrails
- **Tools:** Do not install missing tools with a package manager or `pip`.
- **Review**: openstacksdk uses Gerrit, not GitHub PRs. Do not create
pull requests.
- **Commits**: Use the `Assisted-By:` trailer for AI-assisted contributions
per the [OpenInfra AI policy](https://openinfra.org/legal/ai-policy).
- **Git**: Read-only operations (`git log`, `git diff`, `git status`) are
fine. Do not run mutating operations (`add`, `commit`, `reset`, `push`,
etc.) unless explicitly instructed.
+70
View File
@@ -52,6 +52,76 @@ in a way that protects against local environment.
Test cases should use requests-mock to mock out HTTP interactions rather than
using mock to mock out object access.
Functional Tests
-----------------
Functional tests run against a live devstack and are slow. Follow these
conventions to keep them fast and reliable.
**One test method per resource.** Each test class should have a single
``test_<resource>`` method that covers the full scenario for that resource
(create, get, list, delete). Prerequisites go in ``setUp``; cleanup goes in
``addCleanup``. This avoids redundant setUp/tearDown cycles and prevents a
failure in one operation from causing knock-on failures in other test methods.
.. code-block:: python
class TestFlavor(base.BaseFunctionalTest):
def setUp(self):
super().setUp()
# create prerequisites
def test_flavor(self):
# create, get, list, update, delete
**Use _delete_foo helpers with addCleanup.** Move resource deletion into a
``_delete_foo`` helper and register it via ``addCleanup`` immediately after
creation. This ensures cleanup runs even if an assertion fails mid-test.
.. code-block:: python
def _delete_flavor(self, flavor):
ret = self.admin_compute_client.delete_flavor(flavor)
self.assertIsNone(ret)
def test_flavor(self):
flavor = self.admin_compute_client.create_flavor(...)
self.addCleanup(self._delete_flavor, flavor)
# ... remaining assertions
**Fail on misconfigured environments, don't skip.** Tests run against devstack,
not production clouds. If a prerequisite is missing, fail with a descriptive
message rather than silently skipping, so misconfigurations are caught
immediately.
.. code-block:: python
# Wrong
if not deployables:
self.skipTest('No deployables found')
# Right
self.assertGreater(
len(deployables),
0,
'no deployables found: is the fake driver enabled?',
)
**Test both personas where policy differs.** Use ``self.user_cloud`` for
non-admin operations and ``self.operator_cloud`` for admin operations. Where
services enforce different policy rules for admin vs non-admin, test both
personas.
.. code-block:: python
def test_device_profile(self):
# get and list as regular user
...
def test_device_profile_admin(self):
# get, list, and delete as admin
...
Don't Use setUpClass
--------------------