Files
Abue Ammar 4935e937b0 Install extensions from GitHub; search only the Raycast Store (#1262)
* Install extensions from GitHub; search only the Raycast Store

Search now queries the Raycast Store alone: no GitHub search, no merged
results, and no registries or store toggle. A new "Install from GitHub"
row builds one extension from a repository or folder link with the
chosen package manager, keeps only the build, and deletes the source and
dependencies with the install's workspace. Closing the panel cancels a
running build.

* Refactor ExtensionStorePanel layout and improve StoreRow details

* Check store extensions for updates when Settings opens

Record the store's commit for each store install, and an unknown version
for each Raycast import that the first check adopts. Opening Settings ›
Extensions looks each tracked extension up by handle and name and offers
an Update per row and an Update All above the list. Folder and GitHub
installs are never checked. Uninstall moves out of the expanded settings
to a trash icon on the row, with Update to its left.

* Stop a cancelled install's whole process group and release its timeout
2026-10-02 01:43:47 +06:00

759 lines
50 KiB
Bash
Executable File

#!/bin/bash
# The test suite. There is no XCTest target: each harness compiles the shipped sources it guards,
# so a harness that stops compiling means a decision leaked out of a pure layer. See docs/testing.md.
#
# Never join a compile and its run with `&&`: `set -e` ignores a failure in a non-final AND-OR list
# member, which is how CI reported success over a harness that had not compiled since phase 10.
set -uo pipefail
# Absolute: the workers re-enter this script after the cd, where a relative $0 would not resolve.
SELF="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")"
cd "$(dirname "$0")/.." || exit 1
BIN="${TMPDIR:-/tmp}/tinycast-harness"
mkdir -p "$BIN"
# `--exec` is the worker half: xargs re-enters here once per queued harness.
if [ "${1:-}" = "--exec" ]; then
shift
name=$1 opt=$2
shift 2
: > "$BIN/$name.running"
trap 'rm -f "$BIN/$name.running" "$BIN/$name.time"' EXIT
fail() {
printf '\033[31mFAIL\033[0m %-25s %s\n' "$name" "$1"
: > "$BIN/$name.failed"
exit 0
}
TIMEFORMAT=%1R
if ! compiled=$( { time swiftc -swift-version 6 "$opt" "$@" "Tests/$name.swift" -o "$BIN/$name" > "$BIN/$name.log" 2>&1; } 2>&1 ); then
fail "did not compile"
fi
{ time "$BIN/$name" > "$BIN/$name.log" 2>&1; } 2> "$BIN/$name.time" &
pid=$!
# macOS ships no `timeout`, so the worker polls; a wedged harness must fail, not stall the suite.
ticks=0
while kill -0 "$pid" 2>/dev/null; do
if [ "$ticks" -ge $((TINYCAST_TEST_TIMEOUT * 5)) ]; then
{ pkill -KILL -P "$pid"; kill -KILL "$pid"; wait "$pid"; } 2>/dev/null
printf '\n[run-tests] killed after %ss without finishing\n' "$TINYCAST_TEST_TIMEOUT" >> "$BIN/$name.log"
fail "timed out after ${TINYCAST_TEST_TIMEOUT}s"
fi
ticks=$((ticks + 1))
sleep 0.2
done
wait "$pid"
status=$?
took=$(< "$BIN/$name.time")
if [ "$status" -gt 128 ]; then fail "crashed (signal $((status - 128))) after ${took}s"; fi
if [ "$status" -ne 0 ]; then fail "assertion failed after ${took}s"; fi
printf '\033[32mok\033[0m %-25s %5ss \033[2m(compile %ss)\033[0m\n' "$name" "$took" "$compiled"
exit 0
fi
QUEUE="$BIN/queue"
: > "$QUEUE"
rm -f "$BIN"/*.failed "$BIN"/*.running
failed=()
ran=0
only="${1:-}"
# `--index` merges each harness's compile command into .compile instead of running anything.
# xcodebuild never compiles the harnesses, so without this nothing in Tests/ resolves in an editor.
# The source lists below are the only copy, which is why this lives here rather than in its own script.
emit_db=0
DB="${TMPDIR:-/tmp}/tinycast-compile-db.json"
if [ "$only" = "--index" ]; then
emit_db=1
only=""
printf '[' > "$DB"
fi
# run [slow] [-O] [index] <name> <source...> — queue the harness. `slow` dispatches it in the first
# wave; `index` claims editor flags for a harness that is compiled by hand rather than by the suite.
run() {
local opt=-Onone pri=1 index_only=0
while :; do
case "$1" in
slow) pri=0; shift;;
-O) opt=-O; shift;;
index) index_only=1; shift;;
*) break;;
esac
done
local name=$1
shift
if [ -n "$only" ] && [ "$name" != "$only" ]; then return 0; fi
if [ "$index_only" -eq 1 ] && [ "$emit_db" -eq 0 ]; then return 0; fi
ran=$((ran + 1))
# Absolute paths throughout: sourcekit-lsp resolves the command itself and does not apply
# `directory` to relative arguments, so a relative path there silently yields no index.
if [ "$emit_db" -eq 1 ]; then
local sources=()
for source in "$@" "Tests/$name.swift"; do sources+=("$PWD/$source"); done
[ "$ran" -gt 1 ] && printf ',' >> "$DB"
printf '{"directory":"%s","command":"swiftc -swift-version 6 -sdk %s' \
"$PWD" "$(xcrun --show-sdk-path --sdk macosx)" >> "$DB"
printf ' %s' "${sources[@]}" >> "$DB"
# Claim every file under `Tests/`: the harness and any helper compiled beside it. A shipped
# source stays unclaimed, because it would get this short command instead of the app's full
# one and `.compile` is last-wins — but the app never compiles anything in `Tests/`.
local claimed=""
for source in "${sources[@]}"; do
case "$source" in *"/Tests/"*) claimed="$claimed${claimed:+,}\"$source\"";; esac
done
printf '","files":[%s]}' "$claimed" >> "$DB"
return 0
fi
# xargs splits the queue on whitespace, so no harness source path may contain a space.
printf '%s %s %s %s\n' "$pri" "$name" "$opt" "$*" >> "$QUEUE"
}
L=Tinycast/Features/Launcher/Model
run slow -O fuzz-test $L/SearchRelevance.swift $L/ScriptRomanization.swift \
$L/LauncherMatch.swift $L/EntryNaming.swift $L/LauncherOrder.swift \
$L/LauncherRankingStore.swift $L/LauncherSuggestions.swift
run file-search-test $L/SearchRelevance.swift \
Tinycast/Features/FileSearch/Model/*.swift
run file-search-session-test Tinycast/Platform/Signposts.swift \
$L/SearchRelevance.swift \
Tinycast/Features/FileSearch/Model/*.swift \
Tinycast/Features/FileSearch/Service/*.swift
run menu-search-test $L/SearchRelevance.swift \
Tinycast/Features/MenuSearch/Model/*.swift \
Tinycast/Features/MenuSearch/Service/*.swift
run window-switch-test $L/SearchRelevance.swift \
Tinycast/Features/WindowSwitcher/Model/*.swift
run index file-search-performance Tinycast/Platform/Signposts.swift \
$L/SearchRelevance.swift \
Tinycast/Features/FileSearch/Model/*.swift \
Tinycast/Features/FileSearch/Service/FileSearchService.swift
run ranking-test $L/SearchRelevance.swift $L/ScriptRomanization.swift \
$L/LauncherMatch.swift $L/LauncherRankingStore.swift
run scopes-test $L/SearchScopes.swift
run app-name-test Tinycast/Platform/AppDisplayName.swift \
Tinycast/Platform/BundleLocalization.swift \
$L/SearchRelevance.swift
run favorites-test $L/FavoriteSlots.swift
run apple-shortcut-test Tinycast/Features/AppleShortcuts/Model/*.swift
run calc-test Tinycast/Features/Calculator/Model/*.swift
run index calc-performance Tinycast/Features/Calculator/Model/*.swift
run calendar-test Tinycast/Features/Calendar/Model/*.swift
run clipboard-test Tinycast/Features/Clipboard/Model/ClipboardStore.swift \
Tinycast/Features/Clipboard/Model/ClipboardFilter.swift \
Tinycast/Features/Clipboard/Model/ClipboardFileKind.swift \
Tinycast/Features/Clipboard/Model/ColorValue.swift \
Tinycast/Features/Clipboard/Model/ColorFormat.swift \
Tinycast/Features/Clipboard/Model/ColorSpaces.swift
# `Q` is the URL detector a drag payload builds its link with, rather than a second one.
Q=Tinycast/Features/Quicklinks/Model/QuicklinkDestination.swift
run clipboard-search-test Tinycast/Features/Clipboard/Model/*.swift $Q
run paste-sequence-test Tinycast/Features/Clipboard/Model/*.swift $Q
run clipboard-text-test Tinycast/Features/Clipboard/Model/*.swift $Q \
Tinycast/Features/Clipboard/Service/ClipboardTextExtractor.swift \
Tinycast/Features/Clipboard/Service/ClipboardTextIndexer.swift \
Tinycast/Features/Clipboard/Service/ClipboardTextWorker.swift \
Tinycast/Platform/ProcessExit.swift
run pasteboard-test Tinycast/Platform/PasteboardFiles.swift \
Tinycast/Features/Clipboard/Model/ClipboardStore.swift \
Tinycast/Features/Clipboard/Model/ClipboardFilter.swift \
Tinycast/Features/Clipboard/Model/ClipboardFileKind.swift \
Tinycast/Features/Clipboard/Model/ColorValue.swift \
Tinycast/Features/Clipboard/Model/ColorFormat.swift \
Tinycast/Features/Clipboard/Model/ColorSpaces.swift \
Tinycast/Features/Clipboard/Service/ClipboardManager.swift \
Tinycast/Features/Clipboard/Service/Paster.swift
run index clipboard-file-performance \
Tinycast/Platform/PasteboardFiles.swift \
Tinycast/Features/Clipboard/Model/ClipboardStore.swift \
Tinycast/Features/Clipboard/Model/ClipboardFilter.swift \
Tinycast/Features/Clipboard/Model/ClipboardFileKind.swift \
Tinycast/Features/Clipboard/Model/ColorValue.swift \
Tinycast/Features/Clipboard/Model/ColorFormat.swift \
Tinycast/Features/Clipboard/Model/ColorSpaces.swift \
Tinycast/Features/Clipboard/Service/ClipboardManager.swift
run emoji-test Tinycast/Features/Emoji/Model/EmojiCatalog.swift \
Tinycast/Features/Emoji/Model/EmojiGridGeometry.swift \
Tinycast/Features/Emoji/Model/EmojiData.generated.swift
run emoji-search-test Tinycast/Features/Emoji/Model/EmojiCatalog.swift \
Tinycast/Features/Emoji/Model/EmojiData.generated.swift \
Tinycast/Features/Emoji/Service/EmojiIndex.swift \
Tinycast/Features/Emoji/Service/FrequentEmojiStore.swift \
Tinycast/Features/Emoji/Service/PinnedEmojiStore.swift \
Tinycast/Features/Launcher/Model/SearchRelevance.swift \
Tinycast/Platform/AppPaths.swift Tinycast/Platform/Memo.swift
run index emoji-search-performance \
Tinycast/Features/Emoji/Model/EmojiCatalog.swift \
Tinycast/Features/Emoji/Model/EmojiData.generated.swift \
Tinycast/Features/Emoji/Service/EmojiIndex.swift \
Tinycast/Features/Emoji/Service/FrequentEmojiStore.swift \
Tinycast/Features/Launcher/Model/SearchRelevance.swift \
Tinycast/Platform/AppPaths.swift Tinycast/Platform/Memo.swift
run palette-selection-test Tinycast/Features/PaletteRowIndex.swift \
Tinycast/Features/Emoji/Model/EmojiGridGeometry.swift
run appearance-test Tinycast/Platform/Appearance.swift \
Tinycast/DesignSystem/Theme.swift \
Tinycast/DesignSystem/InterfaceMetrics.swift \
Tinycast/Features/Settings/AppAppearance.swift
run interface-size-test Tinycast/Platform/Appearance.swift \
Tinycast/DesignSystem/Theme.swift \
Tinycast/DesignSystem/InterfaceMetrics.swift \
Tinycast/Features/Settings/InterfaceSize.swift \
Tinycast/Features/Extensions/Model/ExtensionFormMetrics.swift
run palette-placement-test Tinycast/Platform/Appearance.swift \
Tinycast/DesignSystem/Theme.swift \
Tinycast/DesignSystem/InterfaceMetrics.swift \
Tinycast/Features/Settings/InterfaceSize.swift \
Tinycast/Palette/PalettePlacement.swift
run scroll-reveal-test Tinycast/DesignSystem/Scrolling/SelectionReveal.swift
run redaction-test Tinycast/DesignSystem/RedactedPlaceholder.swift
run keyboard-focus-test Tinycast/DesignSystem/Interaction/KeyboardFocus.swift
run ai-instructions-test Tinycast/Features/AI/Model/AIInstructions.swift \
Tinycast/Features/AI/Model/AIPreamble.swift
run hover-arming-test Tinycast/Palette/HoverArming.swift \
Tinycast/Palette/PaletteState.swift \
Tinycast/Palette/PaletteMode.swift \
Tinycast/Features/Emoji/Model/EmojiCatalog.swift \
Tinycast/Features/Clipboard/Model/ClipboardStore.swift \
Tinycast/Features/Clipboard/Model/ClipboardFilter.swift \
Tinycast/Features/Clipboard/Model/ClipboardFileKind.swift \
Tinycast/Features/FileSearch/Model/FileSearchFilter.swift \
Tinycast/Features/Clipboard/Model/ColorValue.swift \
Tinycast/Features/Clipboard/Model/ColorFormat.swift \
Tinycast/Features/Clipboard/Model/ColorSpaces.swift \
Tinycast/Features/Quicklinks/Model/Quicklink.swift \
Tinycast/Features/Quicklinks/Model/QuicklinkDestination.swift \
Tinycast/Features/CustomCommands/Model/CustomCommand.swift
run palette-escape-test Tinycast/Palette/PaletteMode.swift \
Tinycast/Palette/PaletteEscapeAction.swift \
Tinycast/Palette/CommandEscapeTap.swift \
Tinycast/Features/Settings/EscapeKeyBehavior.swift \
Tinycast/Features/Quicklinks/Model/Quicklink.swift \
Tinycast/Features/Quicklinks/Model/QuicklinkDestination.swift \
Tinycast/Features/CustomCommands/Model/CustomCommand.swift
run palette-navigation-test Tinycast/Palette/PaletteState.swift \
Tinycast/Palette/PaletteMode.swift \
Tinycast/Palette/HoverArming.swift \
Tinycast/Features/Emoji/Model/EmojiCatalog.swift \
Tinycast/Features/Clipboard/Model/ClipboardStore.swift \
Tinycast/Features/Clipboard/Model/ClipboardFilter.swift \
Tinycast/Features/Clipboard/Model/ClipboardFileKind.swift \
Tinycast/Features/FileSearch/Model/FileSearchFilter.swift \
Tinycast/Features/Clipboard/Model/ColorValue.swift \
Tinycast/Features/Clipboard/Model/ColorFormat.swift \
Tinycast/Features/Clipboard/Model/ColorSpaces.swift \
Tinycast/Features/Quicklinks/Model/Quicklink.swift \
Tinycast/Features/Quicklinks/Model/QuicklinkDestination.swift \
Tinycast/Features/CustomCommands/Model/CustomCommand.swift
run palette-filter-test Tinycast/Palette/PaletteMode.swift \
Tinycast/Palette/PaletteFilterAction.swift \
Tinycast/Features/Quicklinks/Model/Quicklink.swift \
Tinycast/Features/Quicklinks/Model/QuicklinkDestination.swift \
Tinycast/Features/CustomCommands/Model/CustomCommand.swift
run action-menu-search-test Tinycast/Palette/ActionMenuSearchQuery.swift \
Tinycast/Features/Launcher/Model/SearchRelevance.swift
run palette-shortcut-test Tinycast/Palette/PaletteShortcut.swift
run ascii-layout-test Tinycast/Platform/ASCIIKeyboardLayout.swift
run palette-tab-test Tinycast/Palette/PaletteMode.swift \
Tinycast/Palette/PaletteTabAction.swift \
Tinycast/Features/Quicklinks/Model/Quicklink.swift \
Tinycast/Features/Quicklinks/Model/QuicklinkDestination.swift \
Tinycast/Features/CustomCommands/Model/CustomCommand.swift
run fallback-test Tinycast/Features/Launcher/Model/Fallback.swift \
Tinycast/Features/Launcher/Model/CommandID.swift \
Tinycast/Features/HotKeys/Model/HotKeyAction.swift \
Tinycast/Features/QuickActions/Model/QuickAction.swift \
Tinycast/Features/QuickActions/Model/BuiltInQuickAction.swift \
Tinycast/Features/QuickActions/Model/CustomQuickAction.swift \
Tinycast/Features/Quicklinks/Model/Quicklink.swift \
Tinycast/Features/Quicklinks/Model/QuicklinkDestination.swift \
Tinycast/Features/SystemActions/Model/SystemAction.swift \
Tinycast/Features/WindowManagement/Model/WindowCommand.swift \
Tinycast/Features/Snippets/Model/Snippet.swift
run dictionary-test Tinycast/Features/Dictionary/Model/DictionaryEntry.swift \
Tinycast/Features/Dictionary/Model/DictionaryMarkup.swift
run hotkey-test Tinycast/Features/HotKeys/Model/DoubleTapModifier.swift \
Tinycast/Features/HotKeys/Model/DoubleTapDetector.swift \
Tinycast/Features/HotKeys/Model/GlobeTapDetector.swift \
Tinycast/Features/HotKeys/Model/HotKeyBinding.swift \
Tinycast/Features/HotKeys/Model/HotKeySpelling.swift \
Tinycast/Features/HotKeys/Model/HyperKey.swift \
Tinycast/Platform/ASCIIKeyboardLayout.swift \
Tinycast/Features/HotKeys/Service/KeyShortcut.swift \
Tinycast/Features/HotKeys/Model/HotKeyAction.swift \
Tinycast/Features/QuickActions/Model/QuickAction.swift \
Tinycast/Features/QuickActions/Model/BuiltInQuickAction.swift \
Tinycast/Features/QuickActions/Model/CustomQuickAction.swift \
Tinycast/Features/Launcher/Model/CommandID.swift \
Tinycast/Features/Quicklinks/Model/Quicklink.swift \
Tinycast/Features/Quicklinks/Model/QuicklinkDestination.swift \
Tinycast/Features/SystemActions/Model/SystemAction.swift \
Tinycast/Features/WindowManagement/Model/WindowCommand.swift \
Tinycast/Features/Snippets/Model/Snippet.swift
run callout-test Tinycast/Platform/Appearance.swift \
Tinycast/DesignSystem/Theme.swift \
Tinycast/DesignSystem/InterfaceMetrics.swift \
Tinycast/Features/HotKeys/UI/CalloutPlacement.swift
run icon-cache-test Tinycast/Platform/Appearance.swift \
Tinycast/Platform/Images/IconCache.swift
run entry-icon-test Tinycast/Platform/Appearance.swift \
Tinycast/Platform/Images/IconCache.swift \
Tinycast/Platform/Images/FileIconStamp.swift
run ext-icon-test Tinycast/Platform/Appearance.swift \
Tinycast/Platform/AppDisplayName.swift \
Tinycast/Platform/Images/IconCache.swift \
Tinycast/Platform/Compression/Zlib.swift \
Tinycast/DesignSystem/Theme.swift \
Tinycast/DesignSystem/InterfaceMetrics.swift \
Tinycast/Features/Extensions/Model/ExtensionBootConfig.swift \
Tinycast/Features/Extensions/Model/ExtensionLaunchType.swift \
Tinycast/Features/Extensions/Model/ExtensionManifest.swift \
Tinycast/Features/Extensions/Model/ExtensionRefreshPolicy.swift \
Tinycast/Features/Extensions/Model/ExtensionRefreshState.swift \
Tinycast/Features/Extensions/Model/RenderNode.swift \
Tinycast/Features/Extensions/Service/ExtensionCatalog.swift \
Tinycast/Features/Extensions/Service/ExtensionFetcher.swift \
Tinycast/Platform/ProcessExit.swift \
Tinycast/Features/Extensions/Service/ExtensionNodeShims.swift \
Tinycast/Features/Extensions/Service/ExtensionOAuthKeychain.swift \
Tinycast/Features/Extensions/Service/ExtensionOAuthSession.swift \
Tinycast/Features/Extensions/Service/ExtensionRuntime.swift \
Tinycast/Features/Extensions/Service/ExtensionIconCache.swift \
Tinycast/Features/Extensions/UI/ExtensionAnimatedImage.swift \
Tinycast/Features/Extensions/UI/ExtensionImage.swift \
Tinycast/Features/Clipboard/Model/ColorValue.swift \
Tinycast/Features/Clipboard/Model/ColorSpaces.swift
run system-action-test Tinycast/Features/SystemActions/Model/SystemAction.swift
run volume-test Tinycast/Features/SystemActions/Model/VolumeLevel.swift
run window-command-test Tinycast/Features/WindowManagement/Model/WindowCommand.swift \
Tinycast/Features/WindowManagement/Model/WindowCycle.swift \
Tinycast/Features/WindowManagement/Model/WindowPlacementEngine.swift \
Tinycast/Features/WindowManagement/Model/WindowActionMemory.swift
run window-preset-test Tinycast/Features/WindowManagement/Model/WindowCommand.swift \
Tinycast/Features/WindowManagement/Model/WindowShortcutPreset.swift \
Tinycast/Features/HotKeys/Model/DoubleTapModifier.swift \
Tinycast/Features/HotKeys/Model/HotKeyBinding.swift \
Tinycast/Features/HotKeys/Model/HyperKey.swift \
Tinycast/Platform/ASCIIKeyboardLayout.swift \
Tinycast/Features/HotKeys/Service/KeyShortcut.swift
run space-gesture-test Tinycast/Features/WindowManagement/Model/WindowCommand.swift \
Tinycast/Features/WindowManagement/Model/SpaceGesture.swift
run window-layout-test Tinycast/Features/WindowManagement/Model/WindowCommand.swift \
Tinycast/Features/WindowManagement/Model/WindowCycle.swift \
Tinycast/Features/WindowManagement/Model/WindowPlacementEngine.swift \
Tinycast/Features/WindowManagement/Model/WindowLayoutAnchor.swift \
Tinycast/Features/WindowManagement/Model/WindowLayoutDisplay.swift \
Tinycast/Features/WindowManagement/Model/WindowLayout.swift \
Tinycast/Features/WindowManagement/Model/WindowLayoutGeometry.swift \
Tinycast/Features/WindowManagement/Model/WindowLayoutPlan.swift \
Tinycast/Features/WindowManagement/Model/WindowLayoutStore.swift \
Tinycast/Features/WindowManagement/Model/CustomWindowSize.swift \
Tinycast/Features/WindowManagement/Model/CustomWindowSizeStore.swift
run window-room-test Tinycast/Features/WindowManagement/Model/WindowCommand.swift \
Tinycast/Features/WindowManagement/Model/WindowCycle.swift \
Tinycast/Features/WindowManagement/Model/WindowPlacementEngine.swift \
Tinycast/Features/WindowManagement/Model/WindowLayoutAnchor.swift \
Tinycast/Features/WindowManagement/Model/WindowLayoutDisplay.swift \
Tinycast/Features/WindowManagement/Model/WindowLayout.swift \
Tinycast/Features/WindowManagement/Model/WindowLayoutGeometry.swift \
Tinycast/Features/WindowManagement/Model/WindowLayoutPlan.swift \
Tinycast/Features/WindowManagement/Model/RoomLayoutKind.swift \
Tinycast/Features/WindowManagement/Model/RoomLayoutEngine.swift \
Tinycast/Features/WindowManagement/Model/RoomGrid.swift \
Tinycast/Features/WindowManagement/Model/RoomWindow.swift \
Tinycast/Features/WindowManagement/Model/Room.swift \
Tinycast/Features/WindowManagement/Model/RoomWindowMatcher.swift \
Tinycast/Features/WindowManagement/Model/RoomParking.swift \
Tinycast/Features/WindowManagement/Model/RoomPlan.swift \
Tinycast/Features/WindowManagement/Model/RoomArrangement.swift \
Tinycast/Features/WindowManagement/Model/RoomStore.swift \
Tinycast/Features/WindowManagement/Model/RoomMinimumSizeStore.swift \
Tinycast/Features/WindowManagement/Model/RoomParkingLedger.swift
run window-file-test Tinycast/Features/WindowManagement/Model/WindowCommand.swift \
Tinycast/Features/WindowManagement/Model/WindowCycle.swift \
Tinycast/Features/WindowManagement/Model/WindowPlacementEngine.swift \
Tinycast/Features/WindowManagement/Model/WindowLayoutAnchor.swift \
Tinycast/Features/WindowManagement/Model/WindowLayoutDisplay.swift \
Tinycast/Features/WindowManagement/Model/WindowLayout.swift \
Tinycast/Features/WindowManagement/Model/WindowLayoutGeometry.swift \
Tinycast/Features/WindowManagement/Model/CustomWindowSize.swift \
Tinycast/Features/WindowManagement/Model/Room.swift \
Tinycast/Features/WindowManagement/Model/RoomWindow.swift \
Tinycast/Features/WindowManagement/Model/RoomLayoutKind.swift \
Tinycast/Features/WindowManagement/Model/RoomGrid.swift \
Tinycast/Features/WindowManagement/Model/RoomLayoutEngine.swift \
Tinycast/Features/WindowManagement/Model/WindowManagementFileFormat.swift \
Tinycast/Features/Settings/Model/SettingsFileJSON.swift \
Tinycast/Features/Settings/Model/SettingsFileIdentity.swift
run custom-command-test Tinycast/Platform/PseudoTerminal.swift \
Tinycast/Platform/ProcessExit.swift \
Tinycast/Features/CustomCommands/Model/CustomCommand.swift \
Tinycast/Features/CustomCommands/Model/RaycastScriptImport.swift \
Tinycast/Features/CustomCommands/Service/ShellCommandRunner.swift
run uninstall-test Tinycast/Features/Uninstall/Model/UninstallTarget.swift \
Tinycast/Features/Uninstall/Model/UninstallSearchRoot.swift \
Tinycast/Features/Uninstall/Model/UninstallRules.swift \
Tinycast/Features/Uninstall/Model/UninstallProtection.swift \
Tinycast/Features/Uninstall/Model/UninstallPlan.swift
run quicklink-test Tinycast/Features/Quicklinks/Model/Quicklink.swift \
Tinycast/Features/Quicklinks/Model/QuicklinkDestination.swift \
Tinycast/Features/Quicklinks/Model/QuicklinkStore.swift \
Tinycast/Features/Quicklinks/Model/QuicklinkArchive.swift \
Tinycast/Features/Quicklinks/Model/RaycastQuicklinkImport.swift
run slow snippets-test Tinycast/Platform/NotificationToken.swift \
Tinycast/Platform/HealthTicker.swift \
Tinycast/Platform/AccessibilityText.swift \
Tinycast/Features/Snippets/Model/*.swift \
Tinycast/Features/Snippets/Service/*.swift \
Tinycast/Features/TextInjection/Service/*.swift
run notes-test Tinycast/Platform/Signposts.swift \
$L/SearchRelevance.swift \
Tinycast/Features/Notes/Model/*.swift \
Tinycast/Features/Notes/Service/*.swift
run notes-editor-test Tinycast/Platform/Signposts.swift \
Tinycast/Platform/Appearance.swift \
Tinycast/DesignSystem/Theme.swift \
Tinycast/DesignSystem/InterfaceMetrics.swift \
Tinycast/Platform/NotificationToken.swift \
Tinycast/Features/TextInjection/Service/InjectableTextView.swift \
Tinycast/Features/Notes/Model/NoteDocument.swift \
Tinycast/Features/Notes/Model/NoteMarkdown.swift \
Tinycast/Features/Notes/Model/NoteMarkdownParser.swift \
Tinycast/Features/Notes/Model/NoteInlineScanner.swift \
Tinycast/Features/Notes/Model/NoteEditPlan.swift \
Tinycast/Features/Notes/Model/NoteEditAction.swift \
Tinycast/Features/Notes/Model/NoteFormatting.swift \
Tinycast/Features/Notes/Model/NoteMarkdownEditing.swift \
Tinycast/Features/Notes/Model/NoteRevealPolicy.swift \
Tinycast/Features/Notes/UI/NoteMarkdownTypography.swift \
Tinycast/Features/Notes/UI/NoteBlockDecoration.swift \
Tinycast/Features/Notes/UI/NoteMarkdownStyler.swift \
Tinycast/Features/Notes/UI/NoteMarkdownRenderer.swift \
Tinycast/Features/Notes/UI/NoteCheckboxGeometry.swift \
Tinycast/Features/Notes/UI/NoteBlockLayoutFragment.swift \
Tinycast/Features/Notes/UI/NoteLayoutFragmentProvider.swift \
Tinycast/Features/Notes/UI/NoteTextViewEditing.swift \
Tinycast/Features/Notes/UI/NoteTextView.swift \
Tinycast/Features/Notes/UI/NoteEditorView.swift
run -O index notes-editor-performance \
Tinycast/Platform/Signposts.swift \
Tinycast/Platform/Appearance.swift \
Tinycast/DesignSystem/Theme.swift \
Tinycast/DesignSystem/InterfaceMetrics.swift \
Tinycast/Platform/NotificationToken.swift \
Tinycast/Features/TextInjection/Service/InjectableTextView.swift \
Tinycast/Features/Notes/Model/NoteDocument.swift \
Tinycast/Features/Notes/Model/NoteMarkdown.swift \
Tinycast/Features/Notes/Model/NoteMarkdownParser.swift \
Tinycast/Features/Notes/Model/NoteInlineScanner.swift \
Tinycast/Features/Notes/Model/NoteEditPlan.swift \
Tinycast/Features/Notes/Model/NoteEditAction.swift \
Tinycast/Features/Notes/Model/NoteFormatting.swift \
Tinycast/Features/Notes/Model/NoteMarkdownEditing.swift \
Tinycast/Features/Notes/Model/NoteRevealPolicy.swift \
Tinycast/Features/Notes/UI/NoteMarkdownTypography.swift \
Tinycast/Features/Notes/UI/NoteBlockDecoration.swift \
Tinycast/Features/Notes/UI/NoteMarkdownStyler.swift \
Tinycast/Features/Notes/UI/NoteMarkdownRenderer.swift \
Tinycast/Features/Notes/UI/NoteCheckboxGeometry.swift \
Tinycast/Features/Notes/UI/NoteBlockLayoutFragment.swift \
Tinycast/Features/Notes/UI/NoteLayoutFragmentProvider.swift \
Tinycast/Features/Notes/UI/NoteTextViewEditing.swift \
Tinycast/Features/Notes/UI/NoteTextView.swift \
Tinycast/Features/Notes/UI/NoteEditorView.swift
run slow -O raycast-test Tinycast/Features/Backup/Model/RaycastImportError.swift \
Tinycast/Features/Backup/Service/RaycastDecoder.swift \
Tinycast/Features/Backup/Service/Scrypt.swift \
Tinycast/Platform/Compression/Zlib.swift
run settings-backup-test Tinycast/Features/Settings/AppSettingsKey.swift \
Tinycast/Features/Backup/Model/SettingsBackupCoverage.swift
run settings-file-test Tinycast/Features/Settings/Model/*.swift \
Tinycast/Features/Settings/Service/SettingsFileMonitor.swift \
Tinycast/Features/Settings/Service/SettingsFileRepository.swift \
Tinycast/Platform/AppPaths.swift
run backup-archive-test Tinycast/Platform/AppPaths.swift \
Tinycast/Features/Backup/Model/BackupArchive.swift \
Tinycast/Features/Backup/Model/BackupBundle.swift \
Tinycast/Features/Backup/Model/BackupCategory.swift \
Tinycast/Features/Backup/Model/BackupClipboardItem.swift \
Tinycast/Features/Backup/Model/BackupManifest.swift \
Tinycast/Features/Backup/Service/BackupStaging.swift
E=Tinycast/Features/Extensions
run symbols-test $E/Service/SymbolCatalog.swift
run ext-cleanup-test $E/Service/ExtensionCleanup.swift \
$E/Service/ExtensionCatalog.swift \
Tinycast/Platform/AppDisplayName.swift \
$E/Model/ExtensionManifest.swift \
$E/Model/ExtensionLaunchType.swift \
$E/Model/ExtensionRefreshPolicy.swift \
$E/Model/ExtensionRefreshState.swift
run ext-refresh-test $E/Model/ExtensionManifest.swift \
Tinycast/Platform/AppDisplayName.swift \
$E/Model/ExtensionLaunchType.swift \
$E/Model/ExtensionRefreshPolicy.swift \
$E/Model/ExtensionRefreshState.swift
run ext-metadata-test $E/Model/ExtensionCommandMetadata.swift \
$E/Model/ExtensionMenuBarSnapshot.swift \
$E/Service/ExtensionCommandMetadataStore.swift
run ext-version-test $E/Model/ExtensionListing.swift \
$E/Service/ExtensionVersionStore.swift
run ext-store-test $E/Model/ExtensionGitHubSource.swift \
$E/Model/ExtensionListing.swift \
$E/Model/ExtensionPackageManager.swift \
$E/Model/ExtensionStoreResponse.swift
run ext-form-test $E/Model/ExtensionFormMetrics.swift \
$E/Model/ExtensionFormField.swift \
$E/UI/ExtensionFormKey.swift \
$E/Model/ExtensionDateExpression.swift \
$E/UI/ExtensionListKey.swift \
Tests/ext-list-key-test.swift
run ext-image-size-test $E/Model/ExtensionImageSize.swift
run ext-accessory-test $E/Model/RenderNode.swift \
$E/Model/ExtensionPickerItem.swift \
$E/Model/ExtensionSearchAccessory.swift \
$E/Service/ExtensionStorage.swift
run slow ext-test -parse-as-library \
Tests/ext-menu-bar-test.swift \
Tests/ext-fetch-test.swift \
$E/Model/ExtensionLaunchError.swift \
$E/Model/ExtensionMenuBarSnapshot.swift \
$E/Service/ExtensionStorage.swift \
$E/Service/ExtensionMenuBarManager.swift \
$E/Model/ExtensionCommandMetadata.swift \
$E/Service/ExtensionCommandMetadataStore.swift \
$E/UI/ExtensionMenuBarController.swift \
$E/UI/ExtensionMenuBarImage.swift \
Tinycast/Platform/Appearance.swift \
Tinycast/Platform/AppDisplayName.swift \
Tinycast/Platform/Images/IconCache.swift \
Tinycast/DesignSystem/Theme.swift \
Tinycast/DesignSystem/InterfaceMetrics.swift \
$E/Model/ExtensionBootConfig.swift \
$E/Model/ExtensionDeepLink.swift \
$E/Model/ExtensionLaunchType.swift \
$E/Model/ExtensionFormField.swift \
$E/Model/ExtensionGridLayout.swift \
$E/Model/ExtensionManifest.swift \
$E/Model/ExtensionRefreshPolicy.swift \
$E/Model/ExtensionRefreshState.swift \
$E/Model/RenderNode.swift \
$E/Model/ExtensionPickerItem.swift \
$E/Model/ExtensionSearchAccessory.swift \
$E/Service/ExtensionCatalog.swift \
$E/Service/ExtensionFetcher.swift \
Tinycast/Platform/ProcessExit.swift \
$E/Service/ExtensionIconCache.swift \
$E/Service/ExtensionNodeShims.swift \
$E/Service/ExtensionOAuthKeychain.swift \
$E/Service/ExtensionOAuthSession.swift \
$E/Service/ExtensionRuntime.swift \
$E/Service/ExtensionNameResolver.swift \
$E/Service/ExtensionWebSocketBridge.swift \
$E/UI/ExtensionAnimatedImage.swift \
$E/UI/ExtensionImage.swift \
$E/UI/ExtensionScreen.swift \
$L/SearchRelevance.swift \
Tinycast/Platform/Compression/Zlib.swift \
Tinycast/Features/Clipboard/Model/ColorValue.swift \
Tinycast/Features/Clipboard/Model/ColorSpaces.swift
run settings-history-test Tinycast/Features/Settings/SettingsTab.swift \
Tinycast/Features/Settings/SettingsHistory.swift \
Tinycast/Features/Settings/SettingsAnchor.swift \
Tinycast/Features/Settings/SettingsNavigationState.swift \
Tinycast/Features/Settings/SettingsSearchCatalog.swift \
$L/SearchRelevance.swift
run updates-test Tinycast/Features/Updates/Model/*.swift \
Tinycast/Features/Updates/Service/BundleSignature.swift
run support-test Tinycast/Features/Support/Model/*.swift
run ai-provider-test Tinycast/Features/Settings/AppSettingsKey.swift \
Tinycast/Features/AI/Model/*.swift \
Tinycast/Features/AI/Settings/AISettingsStore.swift
run ai-chat-test Tinycast/Features/AI/Model/AIRequest.swift \
Tinycast/Features/AI/Model/AIConnection.swift \
Tinycast/Features/AI/Model/AppleIntelligence.swift \
Tinycast/Features/AI/Model/AIAttachmentPolicy.swift \
Tinycast/Features/AI/Model/AIRetention.swift \
Tinycast/Features/AI/Model/AITool.swift \
Tinycast/Features/AI/Model/JSONValue.swift \
Tinycast/Features/AI/Model/ChatMessage.swift \
Tinycast/Features/AI/Model/ChatSession.swift \
Tinycast/Features/AI/Model/ChatChoices.swift \
Tinycast/Features/AI/Model/ChatReferences.swift \
Tinycast/Features/AI/Model/ChatTitle.swift \
Tinycast/Features/AI/Model/ChatFind.swift \
Tinycast/Features/AI/Model/ChatCitations.swift \
Tinycast/Features/AI/Model/ChatToolScope.swift \
Tinycast/Features/AI/Model/MarkdownBlock.swift \
Tinycast/Features/AI/Model/MarkdownMath.swift \
Tinycast/Features/AI/Model/MathFormula.swift \
Tinycast/Features/AI/Model/MathNode.swift \
Tinycast/Features/AI/Model/MathSymbolCatalog.swift \
Tinycast/Features/AI/Service/AIProvider.swift \
Tinycast/Features/AI/Service/ChatHistoryStore.swift \
Tinycast/Features/AI/Service/AIToolLoopProvider.swift \
Tinycast/Features/AI/UI/AIChatState.swift \
Tinycast/Features/AI/UI/AIChatSurfacesState.swift \
Tinycast/Features/AI/UI/ChatFindState.swift
run chat-markdown-test Tinycast/Platform/Appearance.swift \
Tinycast/DesignSystem/Theme.swift \
Tinycast/DesignSystem/InterfaceMetrics.swift \
Tinycast/Features/Settings/InterfaceSize.swift \
Tinycast/Features/AI/Model/AIRequest.swift \
Tinycast/Features/AI/Model/AITool.swift \
Tinycast/Features/AI/Model/JSONValue.swift \
Tinycast/Features/AI/Model/ChatMessage.swift \
Tinycast/Features/AI/Model/ChatChoices.swift \
Tinycast/Features/AI/Model/ChatReferences.swift \
Tinycast/Features/AI/Model/ChatCitations.swift \
Tinycast/Features/AI/Model/ChatFind.swift \
Tinycast/Features/AI/Model/MarkdownBlock.swift \
Tinycast/Features/AI/Model/MarkdownMath.swift \
Tinycast/Features/AI/Model/MathFormula.swift \
Tinycast/Features/AI/Model/MathNode.swift \
Tinycast/Features/AI/Model/MathSymbolCatalog.swift \
Tinycast/Features/AI/UI/ChatTextHighlight.swift \
Tinycast/Features/AI/UI/ChatMarkdownRenderer.swift \
Tinycast/Features/AI/UI/MathAttachmentCell.swift \
Tinycast/Features/AI/UI/MathBox.swift \
Tinycast/Features/AI/UI/MathFont.swift \
Tinycast/Features/AI/UI/MathLayoutEngine.swift
run mcp-test Tinycast/Features/Settings/AppSettingsKey.swift \
Tinycast/Features/AI/Model/AIConnection.swift \
Tinycast/Features/AI/Model/AppleIntelligence.swift \
Tinycast/Features/AI/Model/AITool.swift \
Tinycast/Features/AI/Model/AIToolServer.swift \
Tinycast/Features/AI/Model/JSONValue.swift \
Tinycast/Features/MCP/Model/*.swift \
Tinycast/Features/MCP/Settings/MCPSettingsStore.swift
run -O text-diff-test Tinycast/Features/QuickActions/Model/TextDiffEngine.swift
run index text-diff-performance Tinycast/Features/QuickActions/Model/TextDiffEngine.swift
run quick-action-test Tinycast/Features/Settings/AppSettingsKey.swift \
Tinycast/Features/AI/Model/AIConnection.swift \
Tinycast/Features/AI/Model/AppleIntelligence.swift \
Tinycast/Features/AI/Model/ChatGPTSubscription.swift \
Tinycast/Features/AI/Model/InstalledAI.swift \
Tinycast/Features/QuickActions/Model/*.swift \
Tinycast/Features/QuickActions/Settings/QuickActionSettingsStore.swift
run apple-intelligence-test Tinycast/Features/Settings/AppSettingsKey.swift \
Tinycast/Features/AI/Model/*.swift \
Tinycast/Features/AI/Service/AIProvider.swift \
Tinycast/Features/AI/Service/AppleIntelligenceProvider.swift
run mcp-oauth-test Tinycast/Platform/ExecutableLocator.swift \
Tinycast/Platform/ProcessExit.swift \
Tinycast/Platform/KeychainSecretStore.swift \
Tinycast/Features/Settings/AppSettingsKey.swift \
Tinycast/Features/AI/Model/AIConnection.swift \
Tinycast/Features/AI/Model/AppleIntelligence.swift \
Tinycast/Features/AI/Model/AITool.swift \
Tinycast/Features/AI/Model/AIToolServer.swift \
Tinycast/Features/AI/Model/AIStreamDecoder.swift \
Tinycast/Features/AI/Model/AIThinkTagDecoder.swift \
Tinycast/Features/AI/Model/AIRequest.swift \
Tinycast/Features/AI/Model/JSONValue.swift \
Tinycast/Features/MCP/Model/*.swift \
Tinycast/Features/MCP/Service/*.swift
run slow mcp-stdio-test Tinycast/Platform/ExecutableLocator.swift \
Tinycast/Platform/ProcessExit.swift \
Tinycast/Platform/KeychainSecretStore.swift \
Tinycast/Features/Settings/AppSettingsKey.swift \
Tinycast/Features/AI/Model/AIConnection.swift \
Tinycast/Features/AI/Model/AppleIntelligence.swift \
Tinycast/Features/AI/Model/AITool.swift \
Tinycast/Features/AI/Model/AIToolServer.swift \
Tinycast/Features/AI/Model/AIStreamDecoder.swift \
Tinycast/Features/AI/Model/AIThinkTagDecoder.swift \
Tinycast/Features/AI/Model/AIRequest.swift \
Tinycast/Features/AI/Model/JSONValue.swift \
Tinycast/Features/MCP/Model/*.swift \
Tinycast/Features/MCP/Service/*.swift
run slow codex-turn-test Tinycast/Platform/AppPaths.swift \
Tinycast/Features/AI/Model/*.swift \
Tinycast/Features/AI/Service/AIProvider.swift \
Tinycast/Features/AI/Service/ChatGPTSubscriptionManager.swift \
Tinycast/Features/AI/Service/CodexAppServerClient.swift \
Tinycast/Features/AI/Service/InstalledAIProbe.swift \
Tinycast/Platform/ExecutableLocator.swift \
Tinycast/Platform/ProcessExit.swift \
Tinycast/Features/AI/Service/CodexTurnRunner.swift
run installed-ai-test Tinycast/Features/AI/Model/*.swift \
Tinycast/Features/AI/Service/AIProvider.swift \
Tinycast/Platform/AppPaths.swift \
Tinycast/Platform/ExecutableLocator.swift \
Tinycast/Platform/ProcessExit.swift \
Tinycast/Features/AI/Service/InstalledCLIProvider.swift \
Tinycast/Features/AI/Service/InstalledAIProbe.swift \
Tinycast/Features/AI/Service/InstalledAIManager.swift
if [ "$emit_db" -eq 1 ]; then
printf ']\n' >> "$DB"
[ -f .compile ] || echo '[]' > .compile
node -e '
const fs = require("node:fs");
const [comp, db] = process.argv.slice(1);
const existing = JSON.parse(fs.readFileSync(comp, "utf8"));
const harnesses = JSON.parse(fs.readFileSync(db, "utf8"));
const kept = existing.filter((e) => !(e.files || []).some((f) => f.includes("/Tests/")));
fs.writeFileSync(comp, JSON.stringify([...kept, ...harnesses], null, 1));
console.log(harnesses.length + " harness entries indexed into .compile");
' .compile "$DB"
exit 0
fi
if [ "$ran" -eq 0 ]; then
echo "No harness named '$only'." >&2
exit 2
fi
# `sort -s` is stable, so the slow harnesses lead and everything else keeps its declaration order.
JOBS="${TINYCAST_TEST_JOBS:-$(sysctl -n hw.ncpu)}"
export TINYCAST_TEST_TIMEOUT="${TINYCAST_TEST_TIMEOUT:-300}"
started=$SECONDS
# Numbers each result, and names what is still running whenever the output goes quiet.
report() {
local finished=0 line asked running file
while :; do
asked=$SECONDS
if IFS= read -r -t 15 line; then
case "$line" in "dispatch "*) return "${line#dispatch }";; esac
finished=$((finished + 1))
printf '[%*d/%d] %s\n' "${#ran}" "$finished" "$ran" "$line"
continue
fi
# Bash 3.2 returns the same status for a timeout and EOF; only EOF comes back at once.
if [ $((SECONDS - asked)) -lt 10 ]; then return 1; fi
running=""
for file in "$BIN"/*.running; do
[ -e "$file" ] && running="$running $(basename "$file" .running)"
done
printf ' \033[2mstill running after %ds:%s\033[0m\n' $((SECONDS - started)) "$running"
done
}
# Without this the suite reports "all passed" whenever dispatch itself dies and no harness ran.
if ! { sort -s -k1,1n "$QUEUE" | cut -d' ' -f2- | xargs -P "$JOBS" -L1 "$SELF" --exec; echo "dispatch $?"; } | report; then
echo "harness dispatch failed; no result below can be trusted" >&2
exit 1
fi
elapsed=$((SECONDS - started))
# A compiler diagnostic is far longer than PIPE_BUF, so the workers log it and it is replayed here.
while read -r _ name _; do
if [ -f "$BIN/$name.failed" ]; then failed+=("$name"); fi
done < "$QUEUE"
if [ ${#failed[@]} -gt 0 ]; then
for name in "${failed[@]}"; do
printf '\n\033[31m--- %s ---\033[0m\n' "$name"
cat "$BIN/$name.log"
done
printf '\n\033[31mFAILED\033[0m %d of %d harness(es) failed in %ds: %s\n' \
"${#failed[@]}" "$ran" "$elapsed" "${failed[*]}" >&2
exit 1
fi
printf '\n\033[32mPASSED\033[0m All %d harness(es) passed in %ds.\n' "$ran" "$elapsed"