fix(toolbar): present the connection and database switchers from the … #8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: macOS Tests | |
| on: | |
| # No paths filter here on purpose. A required status check has to report on every pull | |
| # request, and a workflow skipped by a paths filter reports nothing at all, which leaves | |
| # the check pending forever and blocks docs-only work. The filtering moved into the | |
| # changes job below, so the gate job always runs and always reports. | |
| pull_request: | |
| push: | |
| branches: [main] | |
| paths: | |
| - "TablePro/**" | |
| - "Plugins/**" | |
| - "Packages/**" | |
| - "LocalPackages/**" | |
| - "TableProTests/**" | |
| - "TableProUITests/**" | |
| - "Native/**" | |
| - "project.yml" | |
| - "TableProMobile/project.yml" | |
| - "Configs/**" | |
| - "Libs/**" | |
| - "scripts/**" | |
| - "TablePro.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved" | |
| - ".github/actions/**" | |
| - ".github/macos-test-quarantine.txt" | |
| - ".github/macos-ui-test-quarantine.txt" | |
| - ".github/workflows/macos-tests.yml" | |
| workflow_dispatch: | |
| workflow_call: | |
| # Only one run per PR/branch at a time; new pushes cancel pending older ones. | |
| concurrency: | |
| group: macos-tests-${{ github.ref }} | |
| cancel-in-progress: true | |
| # A downgrade or an exact match is always allowed for a called workflow; only asking for more than | |
| # the caller holds fails at run creation. Declaring read here therefore cannot break build.yml's | |
| # workflow_call, and it stops every job in this file from inheriting a write token if the repository | |
| # default is ever flipped. | |
| permissions: | |
| contents: read | |
| env: | |
| XCODE_VERSION: "26.4.1" | |
| XCODE_PROJECT: TablePro.xcodeproj | |
| XCODE_SCHEME: TablePro | |
| TEST_DESTINATION: "platform=macOS" | |
| # Inside the workspace rather than the default location, because the build job tars it and the | |
| # test jobs unpack it. Every path inside the generated .xctestrun is written relative to | |
| # __TESTROOT__, so the products relocate to any directory. | |
| DERIVED_DATA: DerivedData | |
| jobs: | |
| # Reproduces the paths filter this workflow used to carry on `on: pull_request`. Keep this | |
| # list and the `push:` paths above identical, so a pull request and a push to main run the | |
| # same suites. `Native/` is on both because the Dameng steps below build and test the Rust | |
| # bridge that lives there, and a bridge-only change would otherwise compile nowhere. | |
| changes: | |
| name: Detect relevant changes | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| outputs: | |
| run: ${{ steps.decide.outputs.run }} | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 0 | |
| - name: Decide whether the macOS suites need to run | |
| id: decide | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| REF: ${{ github.ref }} | |
| # The reasoning lives in scripts/ci/detect-changed-paths.sh. It was inline here and in | |
| # ios-tests.yml, with a comment asking a human to keep the two copies in step and nothing | |
| # enforcing it. | |
| run: | | |
| set -euo pipefail | |
| RUN=$(scripts/ci/detect-changed-paths.sh \ | |
| --event "$EVENT_NAME" \ | |
| --base "$BASE_SHA" \ | |
| --ref "$REF" \ | |
| --skip-release-commit \ | |
| 'TablePro/' 'Plugins/' 'Packages/' 'LocalPackages/' \ | |
| 'TableProTests/' 'TableProUITests/' 'Native/' 'Configs/' 'Libs/' 'scripts/' \ | |
| '\.github/actions/' '\.github/macos-(ui-)?test-quarantine\.txt$' \ | |
| 'TablePro\.xcodeproj/project\.xcworkspace/xcshareddata/swiftpm/Package\.resolved$' \ | |
| 'TableProMobile/project\.yml$' 'project\.yml$' \ | |
| '\.github/workflows/macos-tests\.yml$') | |
| echo "run=$RUN" >> "$GITHUB_OUTPUT" | |
| packages: | |
| name: Package Tests | |
| needs: changes | |
| if: needs.changes.outputs.run == 'true' | |
| runs-on: macos-26 | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Select Xcode | |
| uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 | |
| with: | |
| xcode-version: ${{ env.XCODE_VERSION }} | |
| - name: Run TableProCore package tests | |
| run: swift test --package-path Packages/TableProCore | |
| # CodeEditSourceEditor is deliberately not run here. `swift test` cannot build it on this | |
| # runner: its CodeEditSymbols dependency fails with "type 'Bundle' has no member 'module'", | |
| # after a SwiftPM cache warning about a missing maintenance.lock. It builds fine inside the | |
| # Xcode project, so the app target still compiles it; only the standalone package test run | |
| # is broken. Its own suites also fail on main (HighlighterTests, TagEditingTests, plus one | |
| # that aborts the runner), so this needs fixing at the package level before it can gate. | |
| - name: Run CodeEditTextView package tests | |
| run: swift test --package-path LocalPackages/CodeEditTextView | |
| # Compiles the app, both test bundles and all 31 plugins once, then hands the products to the | |
| # test jobs. That build used to happen inside the same job that ran the tests, which meant the | |
| # unit step spent 12 of its 15.3 minutes compiling and the UI step could not start until the | |
| # unit step had finished. All 12,662 unit cases execute in 206 seconds, so the build was almost | |
| # all of the cost and none of it was parallel. | |
| build: | |
| name: Build for testing | |
| needs: changes | |
| if: needs.changes.outputs.run == 'true' | |
| runs-on: macos-26 | |
| timeout-minutes: 45 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Select Xcode | |
| uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 | |
| with: | |
| xcode-version: ${{ env.XCODE_VERSION }} | |
| - name: Install xcbeautify | |
| run: brew list xcbeautify &>/dev/null || brew install xcbeautify | |
| - name: Cache static libraries | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: Libs | |
| key: ${{ runner.os }}-libs-${{ hashFiles('Libs/checksums.sha256', 'Plugins/MSSQLDriverPlugin/CFreeTDS/include/sybdb.h') }} | |
| - name: Download static libraries | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: scripts/download-libs.sh | |
| # Compiles a C probe against Libs/libbson and parses every filter document the MongoDB | |
| # query builder can emit. It guarded that invariant and ran nowhere; it needs clang and the | |
| # vendored libraries, so this is the first job where it can run at all. | |
| - name: Check the MongoDB filter shapes against the query builder | |
| run: scripts/check-mongodb-filter-shapes.sh | |
| # Cargo and rustup need access outside Xcode's user-script sandbox. Build the | |
| # native bridge explicitly before compiling the driver target. | |
| - name: Build Dameng native bridge | |
| run: scripts/build-dameng.sh arm64 | |
| # Only the bridge's own FFI tests live here. The DM8 protocol crates are a pinned | |
| # revision of TableProApp/rust-dameng and carry their own suite in that repository. | |
| # Run from the bridge directory so rustup reads its rust-toolchain.toml: rustup resolves | |
| # that file from the working directory, not from --manifest-path, and testing on the | |
| # runner's default Rust would not be the toolchain the shipped staticlib is built with. | |
| - name: Test Dameng native bridge | |
| working-directory: Native/DamengBridge | |
| run: cargo test --locked | |
| # Secrets.xcconfig is gitignored. Tests do not need analytics keys, and the project only | |
| # needs the variable to resolve, so an empty value is enough and no secret is read here. | |
| - name: Create Secrets.xcconfig | |
| run: echo "ANALYTICS_HMAC_SECRET = " > Configs/Secrets.xcconfig | |
| - name: Setup XcodeGen | |
| uses: ./.github/actions/setup-xcodegen | |
| - name: Generate Xcode project | |
| run: scripts/generate-project.sh | |
| # Package.resolved pins every revision and is tracked, which is why it can key the cache. | |
| # build.yml has cached these checkouts for a while; this workflow paid 1.5 minutes a run | |
| # re-resolving them. | |
| - name: Cache Swift package checkouts | |
| uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 | |
| with: | |
| path: ~/.spm-cache | |
| key: ${{ runner.os }}-spm-${{ hashFiles('TablePro.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved') }} | |
| restore-keys: ${{ runner.os }}-spm- | |
| - name: Resolve Swift package dependencies | |
| run: | | |
| xcodebuild -resolvePackageDependencies \ | |
| -project "$XCODE_PROJECT" \ | |
| -scheme "$XCODE_SCHEME" \ | |
| -clonedSourcePackagesDirPath ~/.spm-cache \ | |
| -skipPackagePluginValidation | |
| # The TablePro scheme only builds the 14 bundled plugins, so a change confined to one | |
| # of the 17 registry-only plugins compiles nowhere else in CI. AllPlugins is the only | |
| # target that covers them, and build-plugin.yml does not run until a release tag. | |
| - name: Compile every plugin | |
| run: | | |
| set -o pipefail | |
| xcodebuild build \ | |
| -project "$XCODE_PROJECT" \ | |
| -scheme AllPlugins \ | |
| -destination "$TEST_DESTINATION" \ | |
| -derivedDataPath "$DERIVED_DATA" \ | |
| -clonedSourcePackagesDirPath ~/.spm-cache \ | |
| -skipPackagePluginValidation \ | |
| CODE_SIGNING_ALLOWED=NO \ | |
| | xcbeautify --renderer github-actions | |
| # Builds and signs used to be the whole check: nothing ever tried to load one. "Bundle | |
| # failed to load executable" has shipped twice, and dlopen is exactly the dyld path that | |
| # fails in that case. | |
| - name: Verify every plugin loads | |
| run: scripts/ci/verify-plugin-loads.sh "$DERIVED_DATA/Build/Products/Debug" | |
| - name: Build the app and both test bundles | |
| run: | | |
| set -o pipefail | |
| xcodebuild build-for-testing \ | |
| -project "$XCODE_PROJECT" \ | |
| -scheme "$XCODE_SCHEME" \ | |
| -destination "$TEST_DESTINATION" \ | |
| -derivedDataPath "$DERIVED_DATA" \ | |
| -clonedSourcePackagesDirPath ~/.spm-cache \ | |
| -skipPackagePluginValidation \ | |
| CODE_SIGNING_ALLOWED=NO \ | |
| | xcbeautify --renderer github-actions | |
| # DamengDriverTests is the one plugin-owned unit bundle: its suites import CDameng, so | |
| # they cannot move into TableProTests the way pure-logic plugin tests do. Without this | |
| # step nothing runs them and they rot silently. | |
| - name: Build the Dameng driver tests | |
| run: | | |
| set -o pipefail | |
| xcodebuild build-for-testing \ | |
| -project "$XCODE_PROJECT" \ | |
| -scheme DamengDriverTests \ | |
| -destination "$TEST_DESTINATION" \ | |
| -derivedDataPath "$DERIVED_DATA" \ | |
| -clonedSourcePackagesDirPath ~/.spm-cache \ | |
| -skipPackagePluginValidation \ | |
| CODE_SIGNING_ALLOWED=NO \ | |
| | xcbeautify --renderer github-actions | |
| # Tarred rather than uploaded as a directory. actions/upload-artifact does not preserve | |
| # the executable bit or symlinks, and both matter to an .app bundle: the app would arrive | |
| # unable to run. The archive measures 144 MB against 559 MB on disk. | |
| - name: Pack the built products | |
| run: tar -czf products.tar.gz -C "$DERIVED_DATA/Build" Products | |
| - name: Upload the built products | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: test-products | |
| path: products.tar.gz | |
| retention-days: 1 | |
| compression-level: 0 | |
| unit: | |
| name: Unit tests | |
| needs: [changes, build] | |
| if: needs.changes.outputs.run == 'true' | |
| runs-on: macos-26 | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Select Xcode | |
| uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 | |
| with: | |
| xcode-version: ${{ env.XCODE_VERSION }} | |
| - name: Install xcbeautify | |
| run: brew list xcbeautify &>/dev/null || brew install xcbeautify | |
| - uses: ./.github/actions/unpack-test-products | |
| # Quarantined cases are listed one per line in .github/macos-test-quarantine.txt. Burn that | |
| # list down. The script enumerates the target first and refuses to run if any entry would | |
| # skip nothing, because xcodebuild accepts -skip-testing for a case that does not exist and | |
| # says nothing: that left one line inert for 479 commits, and it is also what happens to a | |
| # Swift Testing case written without its parentheses. | |
| - name: Run unit tests | |
| run: | | |
| set -o pipefail | |
| # A read loop, not mapfile: the macOS runner's /bin/bash is 3.2, which has neither | |
| # mapfile nor readarray, and a `run:` block gets that bash. | |
| SKIP_ARGS=() | |
| while IFS= read -r arg; do | |
| SKIP_ARGS+=("$arg") | |
| done < <(scripts/ci/quarantine-args.sh .github/macos-test-quarantine.txt TableProTests "$XCTESTRUN" "$TEST_DESTINATION") | |
| xcodebuild test-without-building \ | |
| -xctestrun "$XCTESTRUN" \ | |
| -destination "$TEST_DESTINATION" \ | |
| -only-testing:TableProTests \ | |
| "${SKIP_ARGS[@]}" \ | |
| -parallel-testing-enabled NO \ | |
| -resultBundlePath TestResults.xcresult \ | |
| | xcbeautify --renderer github-actions | |
| - name: Run Dameng driver tests | |
| run: | | |
| set -o pipefail | |
| xcodebuild test-without-building \ | |
| -xctestrun "$DAMENG_XCTESTRUN" \ | |
| -destination "$TEST_DESTINATION" \ | |
| -parallel-testing-enabled NO \ | |
| | xcbeautify --renderer github-actions | |
| # Always, including on failure: a red run is when the durations matter most. | |
| - name: Summarise test durations | |
| if: ${{ !cancelled() }} | |
| run: scripts/ci/summarize-xcresult.sh TestResults.xcresult "Unit tests" | |
| - name: Upload test results | |
| if: ${{ !cancelled() }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: macos-test-results | |
| path: TestResults.xcresult | |
| retention-days: 7 | |
| # UI tests drive the real app, so they need the runner's GUI session and cannot run beside | |
| # the unit run. Every case launches the app against a throwaway storage sandbox that | |
| # UITestCase hands it, so nothing here touches a real store. | |
| # | |
| # Sharded across runners rather than parallelised on one. Three things in the suite are shared | |
| # per machine and cannot be made per-worker: the single `com.TablePro.uitest` defaults domain, | |
| # the preferences sweep in UITestCase's class setUp, and the menu bar itself, which belongs to | |
| # whichever app is frontmost. A separate runner per shard has its own of all three. | |
| ui: | |
| name: UI tests ${{ matrix.shard }}/${{ matrix.of }} | |
| needs: [changes, build] | |
| if: needs.changes.outputs.run == 'true' | |
| runs-on: macos-26 | |
| timeout-minutes: 40 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [0, 1, 2] | |
| of: [3] | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| - name: Select Xcode | |
| uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 | |
| with: | |
| xcode-version: ${{ env.XCODE_VERSION }} | |
| - name: Install xcbeautify | |
| run: brew list xcbeautify &>/dev/null || brew install xcbeautify | |
| - uses: ./.github/actions/unpack-test-products | |
| # One retry is allowed. A full local suite flaked once in three runs on a single | |
| # waitForExistence, which without a retry would fail roughly a third of runs and train | |
| # people to re-run until green. A case that fails twice is a real failure. | |
| - name: Run UI tests | |
| env: | |
| SHARD: ${{ matrix.shard }} | |
| SHARD_COUNT: ${{ matrix.of }} | |
| run: | | |
| set -o pipefail | |
| # A read loop, not mapfile: the macOS runner's /bin/bash is 3.2, which has neither. | |
| ONLY_ARGS=() | |
| while IFS= read -r arg; do | |
| ONLY_ARGS+=("$arg") | |
| done < <( | |
| scripts/ci/list-tests.sh \ | |
| --xctestrun "$XCTESTRUN" \ | |
| --target TableProUITests \ | |
| --quarantine .github/macos-ui-test-quarantine.txt \ | |
| --shard "$SHARD/$SHARD_COUNT" | |
| ) | |
| [ "${#ONLY_ARGS[@]}" -gt 0 ] || { echo "::error::shard $SHARD selected no cases"; exit 1; } | |
| echo "shard $SHARD of $SHARD_COUNT runs ${#ONLY_ARGS[@]} cases" | |
| xcodebuild test-without-building \ | |
| -xctestrun "$XCTESTRUN" \ | |
| -destination "$TEST_DESTINATION" \ | |
| "${ONLY_ARGS[@]}" \ | |
| -parallel-testing-enabled NO \ | |
| -test-iterations 2 \ | |
| -retry-tests-on-failure \ | |
| -resultBundlePath UITestResults.xcresult \ | |
| | xcbeautify --renderer github-actions | |
| - name: Summarise UI test durations | |
| if: ${{ !cancelled() }} | |
| run: scripts/ci/summarize-xcresult.sh UITestResults.xcresult "UI tests ${{ matrix.shard }}/${{ matrix.of }}" | |
| - name: Upload UI test results | |
| if: ${{ !cancelled() }} | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: macos-ui-test-results-${{ matrix.shard }} | |
| path: UITestResults.xcresult | |
| retention-days: 7 | |
| # The one check to mark required on main. It reports on every pull request, so a docs-only | |
| # change passes on skipped suites instead of hanging, and a red suite cannot be merged past. | |
| gate: | |
| name: macOS Tests Gate | |
| if: always() | |
| needs: [changes, packages, build, unit, ui] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Require every suite to have passed or been skipped | |
| env: | |
| RESULTS: ${{ join(needs.*.result, ' ') }} | |
| run: | | |
| set -euo pipefail | |
| echo "suite results: $RESULTS" | |
| for result in $RESULTS; do | |
| case "$result" in | |
| success|skipped) ;; | |
| *) | |
| echo "A macOS suite reported '$result'." >&2 | |
| exit 1 | |
| ;; | |
| esac | |
| done |