Skip to content

Commit 6b23e2f

Browse files
authored
Merge branch 'master' into replace-useRouter-PickProjectToContinue-usages
2 parents 1c97c30 + 27e6fb2 commit 6b23e2f

File tree

194 files changed

+5429
-2791
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

194 files changed

+5429
-2791
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -439,6 +439,7 @@ tests/sentry/api/endpoints/test_organization_attribute_mappings.py @get
439439
/static/app/components/loading/ @getsentry/app-frontend
440440
/static/app/components/events/interfaces/ @getsentry/app-frontend
441441
/static/app/components/forms/ @getsentry/app-frontend
442+
/static/app/components/markdownTextArea.tsx @getsentry/app-frontend
442443
/static/app/locale.tsx @getsentry/app-frontend
443444
## End of Frontend
444445

.github/codeowners-coverage-baseline.txt

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -253,11 +253,6 @@ src/sentry/stacktraces/processing.py
253253
src/sentry/status_checks/__init__.py
254254
src/sentry/status_checks/base.py
255255
src/sentry/status_checks/warnings.py
256-
src/sentry/synapse/__init__.py
257-
src/sentry/synapse/endpoints/__init__.py
258-
src/sentry/synapse/endpoints/authentication.py
259-
src/sentry/synapse/endpoints/org_cell_mappings.py
260-
src/sentry/synapse/paginator.py
261256
src/sentry/tagstore/__init__.py
262257
src/sentry/tagstore/base.py
263258
src/sentry/tagstore/exceptions.py
@@ -643,18 +638,6 @@ static/app/components/events/eventTagsAndScreenshot/tags.tsx
643638
static/app/components/events/eventViewHierarchy.spec.tsx
644639
static/app/components/events/eventViewHierarchy.tsx
645640
static/app/components/events/eventXrayDiff.tsx
646-
static/app/components/events/groupingInfo/groupingComponent.tsx
647-
static/app/components/events/groupingInfo/groupingComponentChildren.tsx
648-
static/app/components/events/groupingInfo/groupingComponentFrames.tsx
649-
static/app/components/events/groupingInfo/groupingComponentStacktrace.tsx
650-
static/app/components/events/groupingInfo/groupingInfo.tsx
651-
static/app/components/events/groupingInfo/groupingInfoSection.spec.tsx
652-
static/app/components/events/groupingInfo/groupingInfoSection.tsx
653-
static/app/components/events/groupingInfo/groupingSummary.tsx
654-
static/app/components/events/groupingInfo/groupingVariant.spec.tsx
655-
static/app/components/events/groupingInfo/groupingVariant.tsx
656-
static/app/components/events/groupingInfo/useEventGroupingInfo.tsx
657-
static/app/components/events/groupingInfo/utils.tsx
658641
static/app/components/events/meta/annotatedText/annotatedTextErrors.tsx
659642
static/app/components/events/meta/annotatedText/annotatedTextValue.tsx
660643
static/app/components/events/meta/annotatedText/filteredAnnotatedTextValue.tsx
@@ -789,8 +772,6 @@ static/app/components/list/index.tsx
789772
static/app/components/list/listItem.tsx
790773
static/app/components/list/utils.tsx
791774
static/app/components/listGroup.tsx
792-
static/app/components/loading/loadingContainer.spec.tsx
793-
static/app/components/loading/loadingContainer.tsx
794775
static/app/components/loadingError.stories.tsx
795776
static/app/components/loadingError.tsx
796777
static/app/components/loadingIndicator.stories.tsx
@@ -2258,7 +2239,6 @@ tests/sentry/receivers/outbox/test_control.py
22582239
tests/sentry/receivers/test_analytics.py
22592240
tests/sentry/receivers/test_core.py
22602241
tests/sentry/receivers/test_data_forwarding.py
2261-
tests/sentry/receivers/test_default_detector.py
22622242
tests/sentry/receivers/test_featureadoption.py
22632243
tests/sentry/receivers/test_onboarding.py
22642244
tests/sentry/receivers/test_releases.py
@@ -2357,10 +2337,6 @@ tests/sentry/sudo/test_middleware.py
23572337
tests/sentry/sudo/test_signals.py
23582338
tests/sentry/sudo/test_utils.py
23592339
tests/sentry/sudo/test_views.py
2360-
tests/sentry/synapse/__init__.py
2361-
tests/sentry/synapse/endpoints/__init__.py
2362-
tests/sentry/synapse/endpoints/test_org_cell_mappings.py
2363-
tests/sentry/synapse/test_paginator.py
23642340
tests/sentry/tagstore/__init__.py
23652341
tests/sentry/tagstore/test_types.py
23662342
tests/sentry/tasks/__init__.py
@@ -2535,7 +2511,6 @@ tests/social_auth/test_utils.py
25352511
tests/tools/__init__.py
25362512
tests/tools/test_api_urls_to_typescript.py
25372513
tests/tools/test_bump_action.py
2538-
tests/tools/test_compute_selected_tests.py
25392514
tests/tools/test_flake8_plugin.py
25402515
tests/tools/test_lint_requirements.py
25412516
tests/tools/test_pin_github_action.py

.github/workflows/backend.yml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -184,14 +184,6 @@ jobs:
184184
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
185185
if: needs.select-tests.outputs.has-selected-tests == 'true'
186186

187-
- name: Setup sentry env
188-
if: needs.select-tests.outputs.has-selected-tests == 'true'
189-
uses: ./.github/actions/setup-sentry
190-
id: setup
191-
with:
192-
mode: backend-ci
193-
skip-devservices: true
194-
195187
- name: Download selected tests artifact
196188
if: needs.select-tests.outputs.has-selected-tests == 'true'
197189
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0

.github/workflows/frontend-snapshots.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: '[NOT REQUIRED] frontend-snapshots'
1+
name: 'frontend-snapshots'
22

33
on:
44
push:

.github/workflows/scripts/calculate-backend-test-shards.py

Lines changed: 114 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
#!/usr/bin/env python3
2+
"""Calculate the number of backend test shards needed for CI.
3+
4+
Uses AST-based static analysis to count tests instead of running
5+
pytest --collect-only, which requires importing every module and
6+
bootstrapping Django (~100s). AST parsing takes a few seconds.
7+
"""
8+
9+
from __future__ import annotations
10+
11+
import ast
212
import json
313
import math
414
import os
515
import re
6-
import subprocess
716
import sys
817
from pathlib import Path
918

@@ -12,85 +21,126 @@
1221
MAX_SHARDS = 22
1322
DEFAULT_SHARDS = MAX_SHARDS
1423

24+
IGNORED_DIRS = frozenset(("tests/acceptance/", "tests/apidocs/", "tests/js/", "tests/tools/"))
25+
26+
27+
def _resolve(node: ast.expr, scope: dict[str, ast.expr]) -> ast.expr:
28+
"""Chase Name and Subscript references back to a concrete AST node."""
29+
if isinstance(node, ast.Name) and node.id in scope:
30+
return _resolve(scope[node.id], scope)
31+
if (
32+
isinstance(node, ast.Subscript)
33+
and isinstance(node.value, ast.Name)
34+
and isinstance(node.slice, ast.Constant)
35+
and isinstance(node.slice.value, int)
36+
and node.value.id in scope
37+
):
38+
target = _resolve(scope[node.value.id], scope)
39+
i = node.slice.value
40+
if isinstance(target, (ast.List, ast.Tuple)) and 0 <= i < len(target.elts):
41+
return _resolve(target.elts[i], scope)
42+
return node
43+
44+
45+
def _parametrize_count(dec: ast.expr, scope: dict[str, ast.expr]) -> int | None:
46+
"""If *dec* is a ``@pytest.mark.parametrize``, return the case count."""
47+
dec = _resolve(dec, scope)
48+
if not isinstance(dec, ast.Call) or len(dec.args) < 2:
49+
return None
50+
f = dec.func
51+
if not (
52+
isinstance(f, ast.Attribute)
53+
and f.attr == "parametrize"
54+
and isinstance(f.value, ast.Attribute)
55+
and f.value.attr == "mark"
56+
and isinstance(f.value.value, ast.Name)
57+
and f.value.value.id == "pytest"
58+
):
59+
return None
60+
argvals = _resolve(dec.args[1], scope)
61+
return len(argvals.elts) if isinstance(argvals, (ast.List, ast.Tuple)) else None
62+
63+
64+
_TEST_FUNC_RE = re.compile(r"^\s*(?:async\s+)?def\s+test_", re.MULTILINE)
65+
66+
67+
def count_tests_in_file(filepath: Path) -> int:
68+
"""Count the test items *filepath* would produce.
69+
70+
Accounts for ``@pytest.mark.parametrize`` multipliers including
71+
stacked decorators.
72+
"""
73+
try:
74+
source = filepath.read_text(encoding="utf-8")
75+
except (UnicodeDecodeError, OSError):
76+
return 0
77+
78+
# Fast path: no parametrize means each def test_ is exactly one test.
79+
if "parametrize" not in source:
80+
return len(_TEST_FUNC_RE.findall(source))
81+
82+
try:
83+
tree = ast.parse(source, filename=str(filepath))
84+
except SyntaxError:
85+
return len(_TEST_FUNC_RE.findall(source))
86+
87+
scope: dict[str, ast.expr] = {}
88+
for node in ast.iter_child_nodes(tree):
89+
if isinstance(node, ast.Assign):
90+
for target in node.targets:
91+
if isinstance(target, ast.Name):
92+
scope[target.id] = node.value
93+
elif isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name) and node.value:
94+
scope[node.target.id] = node.value
95+
96+
total = 0
97+
for node in ast.walk(tree):
98+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name.startswith(
99+
"test_"
100+
):
101+
counts = (
102+
c for d in node.decorator_list if (c := _parametrize_count(d, scope)) is not None
103+
)
104+
total += math.prod(counts, start=1)
105+
return total
106+
15107

16108
def collect_test_count() -> int | None:
17-
"""Collect the number of tests to run, either from selected files or full suite."""
109+
"""Count tests via AST analysis of test files."""
18110
selected_tests_file = os.environ.get("SELECTED_TESTS_FILE")
19111

20112
if selected_tests_file:
21113
path = Path(selected_tests_file)
22114
if not path.exists():
23-
print(f"Selected tests file not found: {selected_tests_file}", file=sys.stderr)
115+
print(
116+
f"Selected tests file not found: {selected_tests_file}",
117+
file=sys.stderr,
118+
)
24119
return None
25120

26-
with path.open() as f:
27-
selected_files = [line.strip() for line in f if line.strip()]
121+
test_files = [Path(line.strip()) for line in path.read_text().splitlines() if line.strip()]
28122

29-
if not selected_files:
123+
if not test_files:
30124
print("No selected test files, running 0 tests", file=sys.stderr)
31125
return 0
32126

33-
print(f"Counting tests in {len(selected_files)} selected files", file=sys.stderr)
34-
35-
pytest_args = [
36-
"pytest",
37-
# Always pass tests/ directory to ensure proper conftest loading order.
38-
# SELECTED_TESTS_FILE env var triggers filtering in pytest_collection_modifyitems.
39-
"tests",
40-
"--collect-only",
41-
"--quiet",
42-
"--ignore=tests/acceptance",
43-
"--ignore=tests/apidocs",
44-
"--ignore=tests/js",
45-
"--ignore=tests/tools",
46-
]
127+
print(f"Counting tests in {len(test_files)} selected files", file=sys.stderr)
128+
else:
129+
tests_dir = Path("tests")
130+
if not tests_dir.is_dir():
131+
print("tests/ directory not found", file=sys.stderr)
132+
return None
47133

48-
try:
49-
result = subprocess.run(
50-
pytest_args,
51-
capture_output=True,
52-
text=True,
53-
check=False,
134+
test_files = sorted(
135+
p
136+
for p in tests_dir.rglob("test_*.py")
137+
if not any(str(p).startswith(d) for d in IGNORED_DIRS)
54138
)
139+
print(f"Found {len(test_files)} test files", file=sys.stderr)
55140

56-
# Parse output for test count
57-
# Format without deselection: "27000 tests collected in 18.53s"
58-
# Format with deselection: "29/31510 tests collected (31481 deselected) in 18.13s"
59-
output = result.stdout + result.stderr
60-
61-
# Try format with deselection first (selected/total)
62-
match = re.search(r"(\d+)/\d+ tests? collected", output)
63-
if match:
64-
count = int(match.group(1))
65-
print(f"Collected {count} tests", file=sys.stderr)
66-
return count
67-
68-
# Fall back to format without deselection
69-
match = re.search(r"(\d+) tests? collected", output)
70-
if match:
71-
count = int(match.group(1))
72-
print(f"Collected {count} tests", file=sys.stderr)
73-
return count
74-
75-
if result.returncode == 5:
76-
# Exit code 5 indicates no tests collected (https://docs.pytest.org/en/stable/reference/exit-codes.html)
77-
# This can stem from files being deleted in a branch/PR.
78-
print("No tests collected (exit 5)", file=sys.stderr)
79-
return 0
80-
81-
if result.returncode != 0:
82-
print(
83-
f"Pytest collection failed (exit {result.returncode})",
84-
file=sys.stderr,
85-
)
86-
print(result.stderr, file=sys.stderr)
87-
return None
88-
89-
print("No tests collected", file=sys.stderr)
90-
return 0
91-
except Exception as e:
92-
print(f"Error collecting tests: {e}", file=sys.stderr)
93-
return None
141+
total = sum(count_tests_in_file(f) for f in test_files)
142+
print(f"Counted {total} tests via AST analysis", file=sys.stderr)
143+
return total
94144

95145

96146
def calculate_shards(test_count: int | None) -> int:

0 commit comments

Comments
 (0)