Skip to content
Merged
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
27 changes: 4 additions & 23 deletions .github/workflows/_base-server-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ on:
enable-coverage:
required: false
type: boolean
default: false
default: true


jobs:
Expand Down Expand Up @@ -100,7 +100,6 @@ jobs:
python-version: ${{ inputs.python-version }}
node-version: ${{ inputs.node-version }}
disable-socketio: true
enable-coverage: ${{ inputs.enable-coverage }}
db-root-password: ${{ env.DB_ROOT_PASSWORD }}
db: ${{ matrix.db }}
env:
Expand All @@ -110,29 +109,11 @@ jobs:
run: |
bench --site test_site \
run-parallel-tests \
--with-coverage \
--app "${{ github.event.repository.name }}" \
--total-builds ${{ inputs.parallel-runs }} \
--build-number ${{ matrix.index }} 2> >(tee -a stderr.log >&2)

# Process warnings and create annotations
if [ -s stderr.log ] && [ "$DB" == "mariadb" ]; then
echo "Processing deprecation warnings..."
grep -E "DeprecationWarning" stderr.log | sort -u | while read -r warning; do
# Extract file path, line number, and warning type
file_info=$(echo "$warning" | grep -oP '^.*?:\d+:')
file_path=$(echo "$file_info" | cut -d':' -f1)
line_number=$(echo "$file_info" | cut -d':' -f2)
warning_type=$(echo "$warning" | grep -oP '\w+Warning')

# Extract the actual warning message
message=$(echo "$warning" | sed -E "s/^.*$warning_type: //")
--build-number ${{ matrix.index }}

# Create the annotation
echo "::warning file=${file_path},line=${line_number}::${warning_type}: ${message}"
done
else
echo "No deprecation warnings found."
fi
env:
DB: ${{ matrix.db }}
# consumed by bench run-parallel-tests
Expand All @@ -144,7 +125,7 @@ jobs:
if: inputs.enable-coverage
with:
name: coverage-${{ matrix.db }}-${{ matrix.index }}
path: ./sites/*-coverage*.xml
path: ./sites/*coverage*.xml

- name: Setup tmate session
uses: mxschmitt/action-tmate@v3
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/server-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ jobs:
enable-postgres: ${{ needs.checkrun.outputs.run_postgres == 'true' }} # This enables PostgreSQL to run tests
enable-sqlite: false # This will test against both MariaDB and SQLite if enabled
parallel-runs: 2
enable-coverage: ${{ github.event_name != 'pull_request' }}
fake-success: ${{ needs.checkrun.outputs.build != 'strawberry' }}
needs: checkrun
secrets: inherit
Expand All @@ -67,7 +66,6 @@ jobs:
name: Coverage Wrap Up
needs: [test, checkrun]
runs-on: ubuntu-latest
if: ${{ github.event_name != 'pull_request' }}
steps:
- name: Clone
uses: actions/checkout@v6
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ui-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,14 @@ jobs:
uses: ./.github/workflows/_base-ui-tests.yml
with:
parallel-runs: 3
enable-coverage: ${{ github.event_name != 'pull_request' }}
enable-coverage: false
fake-success: ${{ needs.checkrun.outputs.build != 'strawberry' }}
needs: checkrun

coverage:
name: Coverage Wrap Up
needs: [test, checkrun]
if: ${{ github.event_name != 'pull_request' }}
if: ${{ needs.checkrun.outputs.build == 'strawberry' }}
runs-on: ubuntu-latest
steps:
- name: Clone
Expand Down
4 changes: 0 additions & 4 deletions codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ flags:
paths:
- "**/*.py"
carryforward: true
ui-tests:
paths:
- "**/*.js"
carryforward: true
server-ui:
paths:
- "**/*.py"
Expand Down
1 change: 0 additions & 1 deletion frappe/commands/testing.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ def main(
verbosity=2 if testing_module_logger.getEffectiveLevel() < logging.INFO else 1,
tb_locals=testing_module_logger.getEffectiveLevel() <= logging.INFO,
cfg=test_config,
buffer=not debug, # unfortunate as it messes up stdout/stderr output order
)

if doctype or doctype_list_path:
Expand Down
14 changes: 11 additions & 3 deletions frappe/core/doctype/user/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import re
from collections.abc import Iterable
from datetime import timedelta
from functools import cached_property
from functools import cached_property, lru_cache
from typing import Any

import frappe
Expand Down Expand Up @@ -894,9 +894,14 @@ def validate_ip_addr(self):

@frappe.whitelist()
def get_timezones():
import zoneinfo
return {"timezones": _get_timezones()}

return {"timezones": zoneinfo.available_timezones()}

@lru_cache(maxsize=1)
def _get_timezones():
import pytz

return sorted(pytz.common_timezones)


@frappe.whitelist()
Expand Down Expand Up @@ -1038,6 +1043,9 @@ def has_email_account(email: str):

@frappe.whitelist(allow_guest=False)
def get_email_awaiting(user: str):
if user != frappe.session.user:
frappe.has_permission("User", "read", doc=user, throw=True)

return frappe.get_all(
"User Email",
fields=["email_account", "email_id"],
Expand Down
8 changes: 7 additions & 1 deletion frappe/coverage.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"*/node_modules/*",
"*/doctype/*/*_dashboard.py",
"*/patches/*",
"*/.github/*",
]

# tested via commands' test suite
Expand Down Expand Up @@ -78,7 +79,12 @@ def __enter__(self):
if self.app == "frappe":
omit.extend(FRAPPE_EXCLUSIONS)

self.coverage = Coverage(source=[source_path], omit=omit, include=STANDARD_INCLUSIONS)
self.coverage = Coverage(
source=[source_path],
omit=omit,
include=STANDARD_INCLUSIONS,
data_suffix=True,
)
self.coverage.start()
return self

Expand Down
2 changes: 0 additions & 2 deletions frappe/parallel_test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import frappe
from frappe.tests.utils import make_test_records, toggle_test_mode

from .testing.environment import _decorate_all_methods_and_functions_with_type_checker
from .testing.result import TestResult

click_ctx = click.get_current_context(True)
Expand Down Expand Up @@ -61,7 +60,6 @@ def setup_test_site(self):
frappe.clear_cache()
frappe.utils.scheduler.disable_scheduler()
if not self.lightmode:
_decorate_all_methods_and_functions_with_type_checker()
self.before_test_setup()

def before_test_setup(self):
Expand Down
2 changes: 1 addition & 1 deletion frappe/patches.txt
Original file line number Diff line number Diff line change
Expand Up @@ -260,4 +260,4 @@ execute:from frappe.email.doctype.notification.notification import install_notif
execute:frappe.db.set_value("Email Account", {}, "add_x_original_from", 1)
frappe.patches.v16_0.fix_myanmar_language_name
execute:frappe.db.set_value("Email Account", {}, "add_reply_to_header", 1)
frappe.patches.v16_0.set_reply_to_header
frappe.patches.v16_0.set_reply_to_header
4 changes: 2 additions & 2 deletions frappe/public/js/frappe/ui/page.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
</svg>
</span>
</button>
<div class="flex fill-width title-area ellipsis">
<div class="flex title-area ellipsis">
<ul class="nav d-sm-flex navbar-breadcrumbs ellipsis {{frappe.is_mobile() ? "mobile-no-divider" : ""}} "></ul>
<button class="btn btn-default more-button hide">
<svg class="icon icon-sm">
<use href="#icon-dot-horizontal">
</use>
</svg>
</button>
<span class="indicator-pill whitespace-nowrap"></span>
<span class="indicator-pill whitespace-nowrap page-indicator-pill"></span>
</div>
</div>
<div class="align-center flex standard-items-section">
Expand Down
11 changes: 11 additions & 0 deletions frappe/public/scss/desk/list.scss
Original file line number Diff line number Diff line change
Expand Up @@ -698,4 +698,15 @@ input.list-header-checkbox {
}
}
}
.page-indicator-pill {
display: none;
}
}

@media screen and (max-width: 375px) {
.navbar-breadcrumbs li:last-child > a {
max-width: 105px;
overflow: hidden;
text-overflow: ellipsis;
}
}
91 changes: 0 additions & 91 deletions frappe/testing/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,6 @@ def _initialize_test_environment(site, config):
frappe.flags.print_messages = logger.getEffectiveLevel() < logging.INFO
frappe.flags.tests_verbose = logger.getEffectiveLevel() < logging.INFO

_decorate_all_methods_and_functions_with_type_checker()


def _cleanup_after_tests():
"""Perform cleanup operations after running tests"""
Expand All @@ -83,95 +81,6 @@ def _disable_scheduler_if_needed():
frappe.utils.scheduler.disable_scheduler()


@debug_timer
def _decorate_all_methods_and_functions_with_type_checker():
from frappe.utils.typing_validations import validate_argument_types

def _get_config_from_pyproject(app_path):
try:
with open(f"{app_path}/pyproject.toml", "rb") as f:
config = tomllib.load(f)
return (
config.get("tool", {})
.get("frappe", {})
.get("testing", {})
.get("function_type_validation", {})
)
except FileNotFoundError:
return {}
except tomllib.TOMLDecodeError:
logger.warning(f"Failed to parse pyproject.toml for app {app_path}")
return {}

def _decorate_callable(obj, parent_module):
# whitelisted methods are already checked, see frappe.whitelist
if getattr(obj, "__func__", obj) in frappe.whitelisted:
return obj
# Check if the function is already decorated
elif hasattr(obj, "_is_decorated_for_validate_argument_types"):
return obj
elif module := getattr(obj, "__module__", ""):
# ensure that the origin skip list is honored on imports; but not the origin
# max_depth because they are reimported thus attached to a different namespace
app = module.split(".", 1)[0]
config = _get_config_from_pyproject(frappe.get_app_source_path(app))
skip_namespaces = config.get("skip_namespaces", [])
if any(module.startswith(n) for n in skip_namespaces):
return obj

@functools.wraps(obj)
def wrapper(*args, **kwargs):
return validate_argument_types(obj)(*args, **kwargs)

wrapper._is_decorated_for_validate_argument_types = True

if obj.__module__ != parent_module.__name__:
logger.debug(f"... patching {obj.__module__}.{obj.__name__} (inside {parent_module.__name__})")
else:
logger.debug(f"... patching {obj.__module__}.{obj.__name__}")

return wrapper

def _decorate_module(app, module, apps, current_depth, max_depth):
if current_depth > max_depth:
return
for name in dir(module):
obj = getattr(module, name)
if inspect.isfunction(obj):
if not getattr(obj, "__annotations__", None):
continue
# never cross the apps (plural!) boundary for functions
if obj.__module__.split(".", 1)[0] not in apps:
continue
setattr(module, name, _decorate_callable(obj, module))
elif inspect.ismodule(obj):
# never cross the app (singular!) boundary for modules
if obj.__name__.split(".", 1)[0] != app:
continue
if hasattr(obj, "_is_decorated_for_validate_argument_types"):
continue
obj._is_decorated_for_validate_argument_types = True
_decorate_module(app, obj, apps, current_depth + 1, max_depth)

for app in (apps := frappe.get_installed_apps()):
config = _get_config_from_pyproject(frappe.get_app_source_path(app))
max_depth = config.get("max_module_depth", 0)
skip_namespaces = config.get("skip_namespaces", [])
logger.info(f"Adding type validator in {app!r} (up to level {max_depth})...")
pkg = frappe.get_module(app)
_decorate_module(app, pkg, apps, 1, max_depth)

for _, submodule_name, _ in pkgutil.walk_packages(path=pkg.__path__, prefix=pkg.__name__ + "."):
current_depth = len(submodule_name.split("."))
if current_depth > max_depth:
continue
if any(submodule_name.startswith(n) for n in skip_namespaces):
continue

submodule = frappe.get_module(submodule_name)
_decorate_module(app, submodule, apps, current_depth, max_depth)


class IntegrationTestPreparation:
def __init__(self, cfg):
self.cfg = cfg
Expand Down
2 changes: 1 addition & 1 deletion frappe/testing/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def __init__(
descriptions=True,
verbosity=1,
failfast=False,
buffer=True,
buffer=False,
resultclass=None,
warnings="module",
*,
Expand Down
1 change: 1 addition & 0 deletions frappe/tests/classes/context_managers.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import faulthandler
import logging
from collections.abc import Callable
from contextlib import contextmanager
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ dev = [
]
test = [
"unittest-xml-reporting~=3.2.0",
"coverage~=7.10.0",
"coverage~=7.13.5",
"hypothesis~=6.77.0",
"freezegun~=1.5.1",
]
Expand All @@ -146,7 +146,7 @@ skip_namespaces = [
]

[tool.bench.dev-dependencies]
coverage = "~=7.10.0"
coverage = "~=7.13.5"
pyngrok = "~=7.5.0"
unittest-xml-reporting = "~=3.2.0"
watchdog = "~=6.0.0"
Expand Down
Loading