Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 5 additions & 32 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -205,16 +205,16 @@ jobs:
run: |
pytest posthog/test/mcp --verbose --timeout=30

mutation-tests:
name: Targeted mutation tests
utils-quality:
name: Utils quality
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0

- name: Check targeted mutation inputs changed
- name: Check utils quality inputs changed
id: changes
shell: bash
run: |
Expand All @@ -235,11 +235,11 @@ jobs:

if [[ -n "$changed_files" ]]; then
echo "changed=true" >> "$GITHUB_OUTPUT"
echo "Targeted mutation inputs changed:"
echo "Utils quality inputs changed:"
echo "$changed_files"
else
echo "changed=false" >> "$GITHUB_OUTPUT"
echo "Skipping targeted mutation tests: inputs unchanged."
echo "Skipping utils quality check: inputs unchanged."
fi

- name: Set up Python 3.11
Expand All @@ -263,33 +263,6 @@ jobs:
UV_PROJECT_ENVIRONMENT=$pythonLocation uv run --extra test --with pytest-crap --with pytest-cov pytest posthog/test/test_utils.py posthog/test/test_size_limited_dict.py --timeout=30 --cov=posthog.utils --crap --crap-threshold=10 --crap-top-n=40 -q
UV_PROJECT_ENVIRONMENT=$pythonLocation uv run --extra test --with pytest-crap --with pytest-cov python .github/scripts/check_crap_threshold.py posthog/utils.py --max-crap 10

- name: Restore mutmut cache
id: mutmut-cache
if: steps.changes.outputs.changed == 'true'
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: mutants
key: mutmut-${{ runner.os }}-py311-${{ hashFiles('posthog/utils.py', 'posthog/test/test_utils.py', 'posthog/test/test_size_limited_dict.py') }}
restore-keys: |
mutmut-${{ runner.os }}-py311-

- name: Skip mutation tests on exact cache hit
if: steps.changes.outputs.changed == 'true' && steps.mutmut-cache.outputs.cache-hit == 'true'
run: |
echo "Skipping targeted mutation tests: exact mutmut cache hit."

- name: Run targeted mutation tests
if: steps.changes.outputs.changed == 'true' && steps.mutmut-cache.outputs.cache-hit != 'true'
shell: bash
run: |
set -euo pipefail
UV_PROJECT_ENVIRONMENT=$pythonLocation uv run --extra test --with mutmut mutmut run --max-children 1
results="$(UV_PROJECT_ENVIRONMENT=$pythonLocation uv run --extra test --with mutmut mutmut results)"
if [[ -n "$results" ]]; then
echo "$results"
exit 1
fi

import-check:
name: Python ${{ matrix.python-version }} import check
runs-on: ubuntu-latest
Expand Down
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,3 @@ pyrightconfig.json
.DS_Store
posthog-python-references.json
.claude/settings.local.json
mutants/
16 changes: 7 additions & 9 deletions posthog/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

def is_naive(dt: datetime) -> bool:
"""Determines if a given datetime.datetime is naive."""
return dt.tzinfo is None or dt.tzinfo.utcoffset(dt) is None # pragma: no mutate
return dt.tzinfo is None or dt.tzinfo.utcoffset(dt) is None


def total_seconds(delta: timedelta) -> float:
Expand All @@ -36,14 +36,12 @@ def guess_timezone(dt: datetime) -> datetime:
# attempts to guess the datetime.datetime.now() local timezone
# case, and then defaults to utc
delta = total_seconds(datetime.now() - dt)
if 0 <= delta < 5: # pragma: no mutate
if 0 <= delta < 5:
# this was created using datetime.datetime.now(),
# so use the current system local timezone
# Equivalent to converting the naive value directly because this path
# only handles values created in the current local timezone.
dt = dt.replace(
tzinfo=datetime.now().astimezone().tzinfo
) # pragma: no mutate
dt = dt.replace(tzinfo=datetime.now().astimezone().tzinfo)
else:
# at this point, the best we can do is guess UTC
dt = dt.replace(tzinfo=timezone.utc)
Expand All @@ -62,7 +60,7 @@ def _normalize_timestamp(timestamp: Union[datetime, str]) -> str:
try:
# Python 3.10 needs the replacements; on 3.11+ fromisoformat accepts Z
# and compact UTC offsets such as +0530.
normalized_timestamp = timestamp.replace("Z", "+00:00") # pragma: no mutate
normalized_timestamp = timestamp.replace("Z", "+00:00")
normalized_timestamp = re.sub(
r"([+-]\d{2})(\d{2})$", r"\1:\2", normalized_timestamp
)
Expand Down Expand Up @@ -162,7 +160,7 @@ def _coerce_unicode(cmplx: Any) -> Optional[str]:
item = None
try:
if isinstance(cmplx, bytes):
item = cmplx.decode("utf-8", "strict") # pragma: no mutate
item = cmplx.decode("utf-8", "strict")
elif isinstance(cmplx, str):
item = cmplx
except Exception as exception:
Expand Down Expand Up @@ -569,7 +567,7 @@ def invalidate_version(self, old_version):
self._delete_keys_with_version(keys, old_version)

if cursor == 0:
break # pragma: no mutate
break

except Exception:
# Redis error - silently fail
Expand Down Expand Up @@ -641,7 +639,7 @@ def clear(self):
if keys:
self.redis.delete(*keys)
if cursor == 0:
break # pragma: no mutate
break
except Exception:
# Redis error - silently fail
pass
Expand Down
7 changes: 0 additions & 7 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -173,13 +173,6 @@ asyncio_default_fixture_loop_scope = "function"
testpaths = ["posthog/test"]
norecursedirs = ["integration_tests"]

[tool.mutmut]
paths_to_mutate = ["posthog/utils.py"]
do_not_mutate = ["posthog/test/*"]
tests_dir = ["posthog/test/test_utils.py", "posthog/test/test_size_limited_dict.py"]
also_copy = ["posthog"]
pytest_add_cli_args = ["--timeout=30"]

[dependency-groups]
dev = [
"claude-agent-sdk>=0.1.50",
Expand Down
Loading