Skip to content

Commit f6e47a7

Browse files
committed
👷 ci: gate the schema on SchemaStore's own checks
The schema reached SchemaStore only on a release tag, and nothing on the way there compiled it the way SchemaStore does: the freshness test compares generator output against the committed file, and the tombi tests validate TOML documents rather than the schema itself. A schema that ajv strict mode rejects therefore surfaced as a failed sync pull request after the release had shipped. The sync workflow now clones SchemaStore, stages tox's schema and runs their checker for it, on every pull request touching the schema and again before the release sync opens a pull request.
1 parent ac771a6 commit f6e47a7

3 files changed

Lines changed: 48 additions & 11 deletions

File tree

.github/workflows/update-schemastore.yaml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,42 @@ name: Update SchemaStore
22
on:
33
push:
44
tags: ["*"]
5+
pull_request:
6+
paths:
7+
- "src/tox/tox.schema.json"
8+
- ".github/workflows/update-schemastore.yaml"
59
permissions:
610
contents: read
711
jobs:
12+
validate:
13+
name: schema passes SchemaStore's checks
14+
runs-on: ubuntu-24.04
15+
steps:
16+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
17+
with:
18+
persist-credentials: false
19+
- name: Clone SchemaStore
20+
run: git clone --depth 1 https://github.com/SchemaStore/schemastore /tmp/schemastore
21+
- name: Install SchemaStore's dependencies
22+
run: npm ci
23+
working-directory: /tmp/schemastore
24+
- name: Stage tox's schema
25+
run: |
26+
python3 -c "
27+
import json
28+
with open('${{ github.workspace }}/src/tox/tox.schema.json') as f:
29+
schema = json.load(f)
30+
schema['\$id'] = 'https://json.schemastore.org/tox.json'
31+
with open('/tmp/schemastore/src/schemas/json/tox.json', 'w') as f:
32+
json.dump(schema, f, indent=2)
33+
f.write('\n')
34+
"
35+
- name: Run SchemaStore's checks against it
36+
run: node ./cli.js check --schema-name=tox.json
37+
working-directory: /tmp/schemastore
838
update-schemastore:
39+
needs: validate
40+
if: github.event_name == 'push'
941
runs-on: ubuntu-24.04
1042
environment: schemastore
1143
env:

docs/changelog/4051.contrib.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Check tox's JSON Schema against SchemaStore's own validator on every pull request that touches it, and before the
2+
release sync opens a pull request there - by :user:`gaborbernat`.

tests/session/cmd/test_schema.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import re
55
import shutil
66
import subprocess
7+
from collections.abc import Mapping, Sequence
78
from pathlib import Path
89
from textwrap import dedent
910
from typing import TYPE_CHECKING, Any, Protocol
@@ -14,6 +15,7 @@
1415
from collections.abc import Iterator
1516

1617
from tox.pytest import MonkeyPatch, ToxProjectCreator
18+
from tox.util.json_types import JsonValue
1719

1820

1921
class LintWorkspace(Protocol):
@@ -145,26 +147,27 @@ def test_schema_freshness(
145147
)
146148

147149

148-
def _subschemas(node: Any, pointer: str = "#") -> Iterator[tuple[str, dict[str, Any]]]:
149-
if isinstance(node, dict):
150+
def _subschemas(node: JsonValue, pointer: str = "#") -> Iterator[tuple[str, Mapping[str, JsonValue]]]:
151+
if isinstance(node, Mapping):
150152
yield pointer, node
151153
for key, value in node.items():
152154
yield from _subschemas(value, f"{pointer}/{key}")
153-
elif isinstance(node, list):
155+
elif isinstance(node, Sequence) and not isinstance(node, str):
154156
for index, value in enumerate(node):
155157
yield from _subschemas(value, f"{pointer}/{index}")
156158

157159

158-
def test_schema_required_properties_are_declared(committed_schema: dict[str, Any]) -> None:
160+
def test_schema_required_properties_are_declared(committed_schema: Mapping[str, JsonValue]) -> None:
159161
# SchemaStore compiles the published schema with ajv strict mode, which rejects a required property that the
160162
# schema never declares, so catch it here rather than in a release-time sync PR (see tox-dev/tox#4051)
161-
undeclared = [
162-
f"{pointer} requires {name!r}"
163-
for pointer, schema in _subschemas(committed_schema)
164-
if isinstance(schema.get("required"), list)
165-
for name in schema["required"]
166-
if name not in schema.get("properties", {})
167-
]
163+
undeclared: list[str] = []
164+
for pointer, schema in _subschemas(committed_schema):
165+
required = schema.get("required")
166+
if not isinstance(required, Sequence) or isinstance(required, str):
167+
continue
168+
properties = schema.get("properties")
169+
declared = set(properties) if isinstance(properties, Mapping) else set()
170+
undeclared += [f"{pointer} requires {name!r}" for name in required if name not in declared]
168171
assert undeclared == []
169172

170173

0 commit comments

Comments
 (0)