"""Benchmark tests for apps with varying component numbers.""" from __future__ import annotations import functools import time from typing import Generator import pytest from benchmarks import WINDOWS_SKIP_REASON from reflex import constants from reflex.compiler import utils from reflex.testing import AppHarness, chdir from reflex.utils import build from reflex.utils.prerequisites import get_web_dir web_pages = get_web_dir() / constants.Dirs.PAGES def render_component(num: int): """Generate a number of components based on num. Args: num: number of components to produce. Returns: The rendered number of components. """ import reflex as rx return [ rx.fragment( rx.box( rx.accordion.root( rx.accordion.item( header="Full Ingredients", content="Yes. It's built with accessibility in mind.", font_size="3em", ), rx.accordion.item( header="Applications", content="Yes. It's unstyled by default, giving you freedom over the look and feel.", ), collapsible=True, variant="ghost", width="25rem", ), padding_top="20px", ), rx.box( rx.drawer.root( rx.drawer.trigger( rx.button("Open Drawer with snap points"), as_child=True ), rx.drawer.overlay(), rx.drawer.portal( rx.drawer.content( rx.flex( rx.drawer.title("Drawer Content"), rx.drawer.description("Drawer description"), rx.drawer.close( rx.button("Close Button"), as_child=True, ), direction="column", margin="5em", align_items="center", ), top="auto", height="100%", flex_direction="column", background_color="var(--green-3)", ), ), snap_points=["148px", "355px", 1], ), ), rx.box( rx.callout( "You will need admin privileges to install and access this application.", icon="info", size="3", ), ), rx.box( rx.table.root( rx.table.header( rx.table.row( rx.table.column_header_cell("Full name"), rx.table.column_header_cell("Email"), rx.table.column_header_cell("Group"), ), ), rx.table.body( rx.table.row( rx.table.row_header_cell("Danilo Sousa"), rx.table.cell("danilo@example.com"), rx.table.cell("Developer"), ), rx.table.row( rx.table.row_header_cell("Zahra Ambessa"), rx.table.cell("zahra@example.com"), rx.table.cell("Admin"), ), rx.table.row( rx.table.row_header_cell("Jasper Eriksson"), rx.table.cell("jasper@example.com"), rx.table.cell("Developer"), ), ), ) ), ) ] * num def AppWithTenComponentsOnePage(): """A reflex app with roughly 10 components on one page.""" import reflex as rx def index() -> rx.Component: return rx.center(rx.vstack(*render_component(1))) app = rx.App(_state=rx.State) app.add_page(index) def AppWithHundredComponentOnePage(): """A reflex app with roughly 100 components on one page.""" import reflex as rx def index() -> rx.Component: return rx.center(rx.vstack(*render_component(100))) app = rx.App(_state=rx.State) app.add_page(index) def AppWithThousandComponentsOnePage(): """A reflex app with roughly 1000 components on one page.""" import reflex as rx def index() -> rx.Component: return rx.center(rx.vstack(*render_component(1000))) app = rx.App(_state=rx.State) app.add_page(index) @pytest.fixture(scope="session") def app_with_10_components( tmp_path_factory, ) -> Generator[AppHarness, None, None]: """Start Blank Template app at tmp_path via AppHarness. Args: tmp_path_factory: pytest tmp_path_factory fixture Yields: running AppHarness instance """ root = tmp_path_factory.mktemp("app10components") yield AppHarness.create( root=root, app_source=functools.partial( AppWithTenComponentsOnePage, render_component=render_component, # pyright: ignore [reportCallIssue] ), ) @pytest.fixture(scope="session") def app_with_100_components( tmp_path_factory, ) -> Generator[AppHarness, None, None]: """Start Blank Template app at tmp_path via AppHarness. Args: tmp_path_factory: pytest tmp_path_factory fixture Yields: running AppHarness instance """ root = tmp_path_factory.mktemp("app100components") yield AppHarness.create( root=root, app_source=functools.partial( AppWithHundredComponentOnePage, render_component=render_component, # pyright: ignore [reportCallIssue] ), ) @pytest.fixture(scope="session") def app_with_1000_components( tmp_path_factory, ) -> Generator[AppHarness, None, None]: """Create an app with 1000 components at tmp_path via AppHarness. Args: tmp_path_factory: pytest tmp_path_factory fixture Yields: an AppHarness instance """ root = tmp_path_factory.mktemp("app1000components") yield AppHarness.create( root=root, app_source=functools.partial( AppWithThousandComponentsOnePage, render_component=render_component, # pyright: ignore [reportCallIssue] ), ) @pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON) @pytest.mark.benchmark( group="Compile time of varying component numbers", timer=time.perf_counter, disable_gc=True, warmup=False, ) def test_app_10_compile_time_cold(benchmark, app_with_10_components): """Test the compile time on a cold start for an app with roughly 10 components. Args: benchmark: The benchmark fixture. app_with_10_components: The app harness. """ def setup(): with chdir(app_with_10_components.app_path): utils.empty_dir(web_pages, ["_app.js"]) app_with_10_components._initialize_app() build.setup_frontend(app_with_10_components.app_path) def benchmark_fn(): with chdir(app_with_10_components.app_path): app_with_10_components.app_instance._compile() benchmark.pedantic(benchmark_fn, setup=setup, rounds=10) @pytest.mark.benchmark( group="Compile time of varying component numbers", min_rounds=5, timer=time.perf_counter, disable_gc=True, warmup=False, ) def test_app_10_compile_time_warm(benchmark, app_with_10_components): """Test the compile time on a warm start for an app with roughly 10 components. Args: benchmark: The benchmark fixture. app_with_10_components: The app harness. """ with chdir(app_with_10_components.app_path): app_with_10_components._initialize_app() build.setup_frontend(app_with_10_components.app_path) def benchmark_fn(): with chdir(app_with_10_components.app_path): app_with_10_components.app_instance._compile() benchmark(benchmark_fn) @pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON) @pytest.mark.benchmark( group="Compile time of varying component numbers", timer=time.perf_counter, disable_gc=True, warmup=False, ) def test_app_100_compile_time_cold(benchmark, app_with_100_components): """Test the compile time on a cold start for an app with roughly 100 components. Args: benchmark: The benchmark fixture. app_with_100_components: The app harness. """ def setup(): with chdir(app_with_100_components.app_path): utils.empty_dir(web_pages, ["_app.js"]) app_with_100_components._initialize_app() build.setup_frontend(app_with_100_components.app_path) def benchmark_fn(): with chdir(app_with_100_components.app_path): app_with_100_components.app_instance._compile() benchmark.pedantic(benchmark_fn, setup=setup, rounds=5) @pytest.mark.benchmark( group="Compile time of varying component numbers", min_rounds=5, timer=time.perf_counter, disable_gc=True, warmup=False, ) def test_app_100_compile_time_warm(benchmark, app_with_100_components): """Test the compile time on a warm start for an app with roughly 100 components. Args: benchmark: The benchmark fixture. app_with_100_components: The app harness. """ with chdir(app_with_100_components.app_path): app_with_100_components._initialize_app() build.setup_frontend(app_with_100_components.app_path) def benchmark_fn(): with chdir(app_with_100_components.app_path): app_with_100_components.app_instance._compile() benchmark(benchmark_fn) @pytest.mark.skipif(constants.IS_WINDOWS, reason=WINDOWS_SKIP_REASON) @pytest.mark.benchmark( group="Compile time of varying component numbers", timer=time.perf_counter, disable_gc=True, warmup=False, ) def test_app_1000_compile_time_cold(benchmark, app_with_1000_components): """Test the compile time on a cold start for an app with roughly 1000 components. Args: benchmark: The benchmark fixture. app_with_1000_components: The app harness. """ def setup(): with chdir(app_with_1000_components.app_path): utils.empty_dir(web_pages, keep_files=["_app.js"]) app_with_1000_components._initialize_app() build.setup_frontend(app_with_1000_components.app_path) def benchmark_fn(): with chdir(app_with_1000_components.app_path): app_with_1000_components.app_instance._compile() benchmark.pedantic(benchmark_fn, setup=setup, rounds=5) @pytest.mark.benchmark( group="Compile time of varying component numbers", min_rounds=5, timer=time.perf_counter, disable_gc=True, warmup=False, ) def test_app_1000_compile_time_warm(benchmark, app_with_1000_components): """Test the compile time on a warm start for an app with roughly 1000 components. Args: benchmark: The benchmark fixture. app_with_1000_components: The app harness. """ with chdir(app_with_1000_components.app_path): app_with_1000_components._initialize_app() build.setup_frontend(app_with_1000_components.app_path) def benchmark_fn(): with chdir(app_with_1000_components.app_path): app_with_1000_components.app_instance._compile() benchmark(benchmark_fn)