diff --git a/.github/workflows/raylib_ui_preview.yaml b/.github/workflows/raylib_ui_preview.yaml
index ff9655d155f827..40f01072791280 100644
--- a/.github/workflows/raylib_ui_preview.yaml
+++ b/.github/workflows/raylib_ui_preview.yaml
@@ -2,9 +2,16 @@ name: "raylib ui preview"
on:
push:
branches:
- - master
- pull_request_target:
- types: [assigned, opened, synchronize, reopened, edited]
+ - 'master'
+ # pull_request_target:
+ # types: [assigned, opened, synchronize, reopened, edited]
+ # branches:
+ # - '**'
+ # paths:
+ # - 'selfdrive/ui/**'
+ # - 'system/ui/**'
+ pull_request:
+ types: [opened, synchronize, reopened, edited]
branches:
- 'master'
paths:
@@ -17,10 +24,11 @@ env:
REPORT_NAME: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && 'master' || github.event.number }}
SHA: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.sha || github.event.pull_request.head.sha }}
BRANCH_NAME: "openpilot/pr-${{ github.event.number }}-raylib-ui"
+ CI_ARTIFACTS_OWNER: ${{ github.repository_owner }}
+ CI_ARTIFACTS_REPO: openpilot-ci-artifacts
jobs:
preview:
- if: github.repository == 'commaai/openpilot'
name: preview
runs-on: ubuntu-latest
timeout-minutes: 20
@@ -59,13 +67,13 @@ jobs:
- name: Getting master ui
uses: actions/checkout@v4
with:
- repository: commaai/ci-artifacts
+ repository: ${{ env.CI_ARTIFACTS_OWNER }}/${{ env.CI_ARTIFACTS_REPO }}
ssh-key: ${{ secrets.CI_ARTIFACTS_DEPLOY_KEY }}
path: ${{ github.workspace }}/master_ui_raylib
ref: openpilot_master_ui_raylib
- name: Saving new master ui
- if: github.ref == 'refs/heads/master' && github.event_name == 'push'
+ if: github.event_name == 'push'
working-directory: ${{ github.workspace }}/master_ui_raylib
run: |
git checkout --orphan=new_master_ui_raylib
@@ -80,12 +88,12 @@ jobs:
git push origin openpilot_master_ui_raylib --force
- name: Finding diff
- if: github.event_name == 'pull_request_target'
+ if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target'
id: find_diff
run: >-
sudo apt-get update && sudo apt-get install -y imagemagick
- scenes=$(find ${{ github.workspace }}/pr_ui/*.png -type f -printf "%f\n" | cut -d '.' -f 1 | grep -v 'pair_device')
+ scenes=$(find ${{ github.workspace }}/pr_ui/*.png -type f -printf "%f\n" | cut -d '.' -f 1)
A=($scenes)
DIFF=""
@@ -102,7 +110,7 @@ jobs:
DIFF="${DIFF}
"
DIFF="${DIFF}"
- DIFF="${DIFF} | "
+ DIFF="${DIFF} | "
DIFF="${DIFF}
"
DIFF="${DIFF}
"
@@ -119,13 +127,13 @@ jobs:
DIFF="${DIFF}"
DIFF="${DIFF}"
- DIFF="${DIFF} master | "
- DIFF="${DIFF} proposed | "
+ DIFF="${DIFF} master | "
+ DIFF="${DIFF} proposed | "
DIFF="${DIFF}
"
DIFF="${DIFF}"
- DIFF="${DIFF} diff | "
- DIFF="${DIFF} composite diff | "
+ DIFF="${DIFF} diff | "
+ DIFF="${DIFF} composite diff | "
DIFF="${DIFF}
"
DIFF="${DIFF}
"
@@ -138,7 +146,7 @@ jobs:
if [[ $INDEX -eq 0 ]]; then
TABLE="${TABLE}"
fi
- TABLE="${TABLE} | "
+ TABLE="${TABLE} | "
if [[ $INDEX -eq 1 || $(($i + 1)) -eq ${#A[*]} ]]; then
TABLE="${TABLE}
"
fi
@@ -149,7 +157,7 @@ jobs:
echo "DIFF=$DIFF$TABLE" >> "$GITHUB_OUTPUT"
- name: Saving proposed ui
- if: github.event_name == 'pull_request_target'
+ if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target'
working-directory: ${{ github.workspace }}/master_ui_raylib
run: |
git config user.name "GitHub Actions Bot"
@@ -162,7 +170,7 @@ jobs:
git push origin ${{ env.BRANCH_NAME }} --force
- name: Comment Screenshots on PR
- if: github.event_name == 'pull_request_target'
+ if: github.event_name == 'pull_request' || github.event_name == 'pull_request_target'
uses: thollander/actions-comment-pull-request@v2
with:
message: |
@@ -171,4 +179,4 @@ jobs:
${{ steps.find_diff.outputs.DIFF }}
comment_tag: run_id_screenshots_raylib
pr_number: ${{ github.event.number }}
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
\ No newline at end of file
diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml
index 35ced1e38b4e7c..35c03f4957a0e6 100644
--- a/.github/workflows/selfdrive_tests.yaml
+++ b/.github/workflows/selfdrive_tests.yaml
@@ -32,12 +32,7 @@ env:
jobs:
build_release:
name: build release
- runs-on: ${{
- (github.repository == 'commaai/openpilot') &&
- ((github.event_name != 'pull_request') ||
- (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
- && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
- || fromJSON('["ubuntu-24.04"]') }}
+ runs-on: ubuntu-24.04
env:
STRIPPED_DIR: /tmp/releasepilot
steps:
@@ -65,23 +60,17 @@ jobs:
cd $STRIPPED_DIR
${{ env.RUN }} "release/check-dirty.sh"
- name: Check submodules
- if: github.repository == 'commaai/openpilot'
timeout-minutes: 3
run: release/check-submodules.sh
build:
- runs-on: ${{
- (github.repository == 'commaai/openpilot') &&
- ((github.event_name != 'pull_request') ||
- (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
- && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
- || fromJSON('["ubuntu-24.04"]') }}
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
submodules: true
- name: Setup docker push
- if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot'
+ if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request'
run: |
echo "PUSH_IMAGE=true" >> "$GITHUB_ENV"
$DOCKER_LOGIN
@@ -91,7 +80,7 @@ jobs:
build_mac:
name: build macOS
- runs-on: ${{ ((github.repository == 'commaai/openpilot') && ((github.event_name != 'pull_request') || (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))) && 'namespace-profile-macos-8x14' || 'macos-latest' }}
+ runs-on: macos-latest
steps:
- uses: actions/checkout@v4
with:
@@ -105,6 +94,10 @@ jobs:
restore-keys: |
brew-macos-${{ env.CACHE_COMMIT_DATE }}
brew-macos
+ # - name: Force relink openssl@3
+ # run: |
+ # brew unlink openssl@3 || true
+ # brew link --overwrite openssl@3
- name: Install dependencies
run: ./tools/mac_setup.sh
env:
@@ -124,12 +117,7 @@ jobs:
static_analysis:
name: static analysis
- runs-on: ${{
- (github.repository == 'commaai/openpilot') &&
- ((github.event_name != 'pull_request') ||
- (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
- && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
- || fromJSON('["ubuntu-24.04"]') }}
+ runs-on: ubuntu-24.04
env:
PYTHONWARNINGS: default
steps:
@@ -143,12 +131,7 @@ jobs:
unit_tests:
name: unit tests
- runs-on: ${{
- (github.repository == 'commaai/openpilot') &&
- ((github.event_name != 'pull_request') ||
- (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
- && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
- || fromJSON('["ubuntu-24.04"]') }}
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
@@ -170,12 +153,7 @@ jobs:
process_replay:
name: process replay
- runs-on: ${{
- (github.repository == 'commaai/openpilot') &&
- ((github.event_name != 'pull_request') ||
- (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
- && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
- || fromJSON('["ubuntu-24.04"]') }}
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
@@ -219,12 +197,7 @@ jobs:
simulator_driving:
name: simulator driving
- runs-on: ${{
- (github.repository == 'commaai/openpilot') &&
- ((github.event_name != 'pull_request') ||
- (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
- && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
- || fromJSON('["ubuntu-24.04"]') }}
+ runs-on: ubuntu-24.04
if: false # FIXME: Started to timeout recently
steps:
- uses: actions/checkout@v4
@@ -245,12 +218,7 @@ jobs:
create_ui_report:
# This job name needs to be the same as UI_JOB_NAME in ui_preview.yaml
name: Create UI Report
- runs-on: ${{
- (github.repository == 'commaai/openpilot') &&
- ((github.event_name != 'pull_request') ||
- (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
- && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
- || fromJSON('["ubuntu-24.04"]') }}
+ runs-on: ubuntu-24.04
if: false # FIXME: FrameReader is broken on CI runners
steps:
- uses: actions/checkout@v4
@@ -280,12 +248,7 @@ jobs:
create_raylib_ui_report:
name: Create raylib UI Report
- runs-on: ${{
- (github.repository == 'commaai/openpilot') &&
- ((github.event_name != 'pull_request') ||
- (github.event.pull_request.head.repo.full_name == 'commaai/openpilot'))
- && fromJSON('["namespace-profile-amd64-8x16", "namespace-experiments:docker.builds.local-cache=separate"]')
- || fromJSON('["ubuntu-24.04"]') }}
+ runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
with:
@@ -294,7 +257,7 @@ jobs:
- name: Build openpilot
run: ${{ env.RUN }} "scons -j$(nproc)"
- name: Create raylib UI Report
- run: >
+ run: |
${{ env.RUN }} "PYTHONWARNINGS=ignore &&
source selfdrive/test/setup_xvfb.sh &&
python3 selfdrive/ui/tests/test_ui/raylib_screenshots.py"
diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py
index 66341db37b1dae..140debe20743b5 100644
--- a/selfdrive/ui/layouts/settings/device.py
+++ b/selfdrive/ui/layouts/settings/device.py
@@ -167,8 +167,7 @@ def _update_calib_description(self):
cloudlog.exception("invalid LiveTorqueParameters")
desc += "
"
- desc += ("openpilot is continuously calibrating, resetting is rarely required. " +
- "Resetting calibration will restart openpilot if the car is powered on.")
+ desc += "openpilot is continuously calibrating, resetting is rarely required. " + "Resetting calibration will restart openpilot if the car is powered on."
self._reset_calib_btn.set_description(desc)
@@ -208,7 +207,9 @@ def _on_regulatory(self):
def _on_review_training_guide(self):
if not self._training_guide:
+
def completed_callback():
gui_app.set_modal_overlay(None)
+
self._training_guide = TrainingGuide(completed_callback=completed_callback)
gui_app.set_modal_overlay(self._training_guide)
diff --git a/selfdrive/ui/layouts/settings/software.py b/selfdrive/ui/layouts/settings/software.py
index 0c17a54fbe4ae0..856463611218d6 100644
--- a/selfdrive/ui/layouts/settings/software.py
+++ b/selfdrive/ui/layouts/settings/software.py
@@ -45,7 +45,7 @@ def __init__(self):
self._onroad_label = ListItem(title="Updates are only downloaded while the car is off.")
self._version_item = text_item("Current Version", ui_state.params.get("UpdaterCurrentDescription") or "")
- self._download_btn = button_item("Download", "CHECK", callback=self._on_download_update)
+ self._download_btn = button_item("DOWNLOAD", "CHECK", callback=self._on_download_update)
# Install button is initially hidden
self._install_btn = button_item("Install Update", "INSTALL", callback=self._on_install_update)
diff --git a/selfdrive/ui/tests/test_ui/raylib_screenshots.py b/selfdrive/ui/tests/test_ui/raylib_screenshots.py
index bd40b89624831a..137b90991d86e9 100755
--- a/selfdrive/ui/tests/test_ui/raylib_screenshots.py
+++ b/selfdrive/ui/tests/test_ui/raylib_screenshots.py
@@ -5,11 +5,13 @@
import time
import pathlib
from collections import namedtuple
+from collections.abc import Callable
+from typing import NotRequired, TypedDict
import pyautogui
-import pywinctl
+from PIL import ImageChops
-from cereal import log
+from cereal import car, log
from cereal import messaging
from cereal.messaging import PubMaster
from openpilot.common.basedir import BASEDIR
@@ -23,6 +25,10 @@
TEST_OUTPUT_DIR = TEST_DIR / "raylib_report"
SCREENSHOTS_DIR = TEST_OUTPUT_DIR / "screenshots"
UI_DELAY = 0.2
+SCROLL_DELAY = 1.5 # Delay screenshot by this many seconds after scrolling (to allow scroll to settle)
+DEFAULT_SCROLL_AMOUNT = -20 # Good for most full screen scrollers
+MAX_SCREENSHOTS_PER_CASE = 8 # Maximum screenshots to generate while scrolling
+
# Offroad alerts to test
OFFROAD_ALERTS = ['Offroad_IsTakingSnapshot']
@@ -36,83 +42,109 @@ def put_update_params(params: Params):
params.put("UpdaterNewDescription", description)
-def setup_homescreen(click, pm: PubMaster):
+def setup_homescreen(click, scroll, pm: PubMaster):
pass
-def setup_settings(click, pm: PubMaster):
+def setup_settings(click, scroll, pm: PubMaster):
click(100, 100)
-def close_settings(click, pm: PubMaster):
+def close_settings(click, scroll, pm: PubMaster):
click(240, 216)
-def setup_settings_network(click, pm: PubMaster):
- setup_settings(click, pm)
+def setup_settings_network(click, scroll, pm: PubMaster):
+ setup_settings(click, scroll, pm)
click(278, 450)
-def setup_settings_toggles(click, pm: PubMaster):
- setup_settings(click, pm)
+def setup_settings_toggles(click, scroll, pm: PubMaster):
+ setup_settings(click, scroll, pm)
click(278, 600)
-def setup_settings_software(click, pm: PubMaster):
+def setup_settings_software(click, scroll, pm: PubMaster):
put_update_params(Params())
- setup_settings(click, pm)
+ setup_settings(click, scroll, pm)
click(278, 720)
-def setup_settings_firehose(click, pm: PubMaster):
- setup_settings(click, pm)
+def setup_settings_firehose(click, scroll, pm: PubMaster):
+ setup_settings(click, scroll, pm)
click(278, 845)
-def setup_settings_developer(click, pm: PubMaster):
- setup_settings(click, pm)
+def setup_settings_developer(click, scroll, pm: PubMaster):
+ CP = car.CarParams()
+ CP.alphaLongitudinalAvailable = True # show alpha long control toggle
+ Params().put("CarParamsPersistent", CP.to_bytes())
+
+ setup_settings(click, scroll, pm)
click(278, 950)
-def setup_keyboard(click, pm: PubMaster):
- setup_settings_developer(click, pm)
+def setup_keyboard(click, scroll, pm: PubMaster):
+ setup_settings_developer(click, scroll, pm)
click(1930, 470)
-def setup_pair_device(click, pm: PubMaster):
+def setup_openpilot_long_control_confirmation_dialog(click, scroll, pm: PubMaster):
+ setup_settings_developer(click, scroll, pm)
+ click(2000, 960) # toggle openpilot longitudinal control
+
+
+def setup_pair_device(click, scroll, pm: PubMaster):
click(1950, 800)
-def setup_offroad_alert(click, pm: PubMaster):
+def setup_offroad_alert(click, scroll, pm: PubMaster):
set_offroad_alert("Offroad_TemperatureTooHigh", True, extra_text='99C')
set_offroad_alert("Offroad_ExcessiveActuation", True, extra_text='longitudinal')
for alert in OFFROAD_ALERTS:
set_offroad_alert(alert, True)
- setup_settings(click, pm)
- close_settings(click, pm)
+ setup_settings(click, scroll, pm)
+ close_settings(click, scroll, pm)
-def setup_confirmation_dialog(click, pm: PubMaster):
- setup_settings(click, pm)
+def setup_confirmation_dialog(click, scroll, pm: PubMaster):
+ setup_settings(click, scroll, pm)
click(1985, 791) # reset calibration
-def setup_homescreen_update_available(click, pm: PubMaster):
+def setup_homescreen_update_available(click, scroll, pm: PubMaster):
params = Params()
params.put_bool("UpdateAvailable", True)
put_update_params(params)
- setup_settings(click, pm)
- close_settings(click, pm)
+ setup_settings(click, scroll, pm)
+ close_settings(click, scroll, pm)
-def setup_software_release_notes(click, pm: PubMaster):
- setup_settings(click, pm)
- setup_settings_software(click, pm)
+def setup_software_release_notes(click, scroll, pm: PubMaster):
+ setup_settings(click, scroll, pm)
+ setup_settings_software(click, scroll, pm)
click(588, 110) # expand description for current version
-CASES = {
+def setup_experimental_mode_description(click, scroll, pm: PubMaster):
+ setup_settings(click, scroll, pm)
+ setup_settings_toggles(click, scroll, pm)
+ click(1200, 280) # expand description for experimental mode
+ scroll(-4) # scroll down to show more of the description
+ time.sleep(1)
+
+
+class CaseConfig(TypedDict):
+ scroll_amount: NotRequired[int]
+ scroll_enabled: NotRequired[bool]
+
+
+SetupFunction = Callable[[Callable[..., None], Callable[..., None], PubMaster], None]
+CaseValue = SetupFunction | tuple[SetupFunction, CaseConfig | None]
+
+# Value can be the setup function, or tuple of (setup func, config)
+CASES: dict[str, CaseValue] = {
"homescreen": setup_homescreen,
"settings_device": setup_settings,
"settings_network": setup_settings_network,
@@ -120,12 +152,17 @@ def setup_software_release_notes(click, pm: PubMaster):
"settings_software": setup_settings_software,
"settings_firehose": setup_settings_firehose,
"settings_developer": setup_settings_developer,
- "keyboard": setup_keyboard,
+ "keyboard": (setup_keyboard, {"scroll_enabled": False}), # The blinking cursor makes it think there was a change when scrolling
"pair_device": setup_pair_device,
- "offroad_alert": setup_offroad_alert,
- "homescreen_update_available": setup_homescreen_update_available,
+ "offroad_alert": (setup_offroad_alert, {"scroll_amount": -12}), # smaller scrollable area
+ "homescreen_update_available": (setup_homescreen_update_available, {"scroll_amount": -12}), # smaller scrollable area
"confirmation_dialog": setup_confirmation_dialog,
"software_release_notes": setup_software_release_notes,
+ "experimental_mode_description": (
+ setup_experimental_mode_description,
+ {"scroll_enabled": False},
+ ),
+ "openpilot_long_control_confirmation_dialog": setup_openpilot_long_control_confirmation_dialog,
}
@@ -144,28 +181,76 @@ def setup(self):
ds.clear_write_flag()
time.sleep(0.05)
time.sleep(0.5)
- try:
- self.ui = pywinctl.getWindowsWithTitle("UI")[0]
- except Exception as e:
- print(f"failed to find ui window, assuming that it's in the top left (for Xvfb) {e}")
- self.ui = namedtuple("bb", ["left", "top", "width", "height"])(0, 0, 2160, 1080)
+ self.ui = namedtuple("bb", ["left", "top", "width", "height"])(0, 0, 2160, 1080)
- def screenshot(self, name: str):
+ def screenshot(self):
full_screenshot = pyautogui.screenshot()
cropped = full_screenshot.crop((self.ui.left, self.ui.top, self.ui.left + self.ui.width, self.ui.top + self.ui.height))
- cropped.save(SCREENSHOTS_DIR / f"{name}.png")
+ return cropped
+
+ def screenshot_and_save(self, name: str):
+ screenshot = self.screenshot()
+ screenshot.save(SCREENSHOTS_DIR / f"{name}.png")
+ return screenshot
+
+ def capture_scrollable(self, name: str, scroll_clicks: int, max_screenshots=MAX_SCREENSHOTS_PER_CASE):
+ # Take first screenshot
+ prev = self.screenshot_and_save(name)
+
+ # Scroll until there are no more changes or we reach the limit
+ for i in range(1, max_screenshots):
+ self.scroll(scroll_clicks)
+ time.sleep(SCROLL_DELAY)
+ curr = self.screenshot()
+
+ # Check for difference
+ try:
+ # TODO: This might need to be more robust to allow for small pixel diffs in case scrolling isn't consistent, but so far it seems to work
+ diff = ImageChops.difference(prev.convert('RGB'), curr.convert('RGB'))
+ if diff.getbbox() is None:
+ # no changes -> reached end
+ break
+ except Exception as e:
+ print(f"error comparing screenshots: {e}")
+ break
+
+ # Save the current page
+ curr.save(SCREENSHOTS_DIR / f"{name}_{i}.png")
+
+ prev = curr
def click(self, x: int, y: int, *args, **kwargs):
pyautogui.mouseDown(self.ui.left + x, self.ui.top + y, *args, **kwargs)
time.sleep(0.01)
pyautogui.mouseUp(self.ui.left + x, self.ui.top + y, *args, **kwargs)
+ def scroll(self, clicks: int, *args, **kwargs):
+ if clicks == 0:
+ return
+ click = -1 if clicks < 0 else 1 # -1 = down, 1 = up
+ for _ in range(abs(clicks)):
+ pyautogui.scroll(click, *args, **kwargs) # scroll for individual clicks since we need to delay between clicks
+ time.sleep(0.01) # small delay between scroll clicks to work properly in xvfb
+
@with_processes(["ui"])
- def test_ui(self, name, setup_case):
+ def test_ui(self, name: str, setup_case: SetupFunction, config: CaseConfig | None = None):
self.setup()
- time.sleep(UI_DELAY) # wait for UI to start
- setup_case(self.click, self.pm)
- self.screenshot(name)
+ time.sleep(UI_DELAY) # Wait for UI to start
+ setup_case(self.click, self.scroll, self.pm)
+ config = config or {}
+
+ # Just take a screenshot if scrolling is disabled
+ scroll_enabled = config.get("scroll_enabled", True)
+ if not scroll_enabled:
+ self.screenshot_and_save(name)
+ return
+
+ try:
+ scroll_clicks = config.get("scroll_amount", DEFAULT_SCROLL_AMOUNT)
+ self.capture_scrollable(name, scroll_clicks=scroll_clicks)
+ except Exception as e:
+ print(f"failed capturing scrollable page, falling back to single screenshot: {e}")
+ self.screenshot_and_save(name)
def create_screenshots():
@@ -178,7 +263,8 @@ def create_screenshots():
params = Params()
params.put("DongleId", "123456789012345")
for name, setup in CASES.items():
- t.test_ui(name, setup)
+ setup_fn, cfg = setup if isinstance(setup, tuple) else (setup, None)
+ t.test_ui(name, setup_fn, cfg)
if __name__ == "__main__":
diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh
index 0ae0b35359e6e8..f6bc2be3e5b851 100755
--- a/tools/mac_setup.sh
+++ b/tools/mac_setup.sh
@@ -32,6 +32,10 @@ else
brew up
fi
+# Relink openssl
+brew unlink openssl@3 || true
+brew link overwrite openssl@3
+
brew bundle --file=- <<-EOS
brew "git-lfs"
brew "capnp"