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
10 changes: 9 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
SHELL := /bin/bash
SWIFT_FLAGS ?=

.PHONY: help build test release run clean
.PHONY: help build test coverage mutation-test release run clean

help:
@printf "%s\n" \
"make build - build the Swift package" \
"make test - run Swift tests" \
"make coverage - run source coverage gate" \
"make mutation-test - run mutation smoke checks" \
"make release - build dist/icloud-cli and checksum" \
"make run ARGS=... - run the debug CLI" \
"make clean - remove SwiftPM build outputs"
Expand All @@ -17,6 +19,12 @@ build:
test:
swift test $(SWIFT_FLAGS)

coverage:
bash scripts/ci/check-coverage.sh

mutation-test:
bash scripts/ci/run-mutation-smoke.sh

release:
swift build $(SWIFT_FLAGS) -c release
mkdir -p dist
Expand Down
8 changes: 8 additions & 0 deletions Tests/ICloudCLICoreTests/CLIParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ import Testing
#expect(options.safariDirectory.path == "/tmp/safari-fixture")
}

@Test func parsesHelpAndVersionShortcuts() throws {
#expect(try CLIParser().parse(arguments: ["icloud-cli"]) == .help)
#expect(try CLIParser().parse(arguments: ["icloud-cli", "-h"]) == .help)
#expect(try CLIParser().parse(arguments: ["icloud-cli", "--help"]) == .help)
#expect(try CLIParser().parse(arguments: ["icloud-cli", "-V"]) == .version)
#expect(try CLIParser().parse(arguments: ["icloud-cli", "--version"]) == .version)
}

@Test func rejectsInvalidSource() throws {
#expect(throws: CLIParseError.invalidSource("icloud")) {
try CLIParser().parse(arguments: ["icloud-cli", "safari", "tabs", "--source", "icloud"])
Expand Down
29 changes: 29 additions & 0 deletions Tests/ICloudCLICoreTests/SafariTabsReaderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,35 @@ import Testing
#expect(tabs[1].url == "https://openclaw.ai/docs")
}

@Test func parserKeepsHttpTabsAndFiltersNonWebUrls() {
let plist: [String: Any] = [
"Nested": [
"Tabs": [
[
"url": "http://example.org/plain-http",
"title": "Plain HTTP",
],
[
"TabURL": "ftp://example.org/not-web",
"TabTitle": "FTP",
],
],
],
]

let tabs = SafariSessionPlistParser(sourceName: "fixture").parse(plist)

#expect(tabs == [
SafariTab(
url: "http://example.org/plain-http",
title: "Plain HTTP",
windowIndex: nil,
tabIndex: 0,
source: "fixture"
),
])
}

@Test func readerLoadsCurrentSessionPlist() throws {
let tempRoot = try temporaryDirectory()
defer { try? FileManager.default.removeItem(at: tempRoot) }
Expand Down
2 changes: 2 additions & 0 deletions project.bootstrap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ ci:
fastChecks:
- build
- test
- coverage
- mutation
- secrets
release:
enabled: false
Expand Down
43 changes: 43 additions & 0 deletions scripts/ci/check-coverage.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
set -euo pipefail

minimum_line_coverage="${COVERAGE_MIN_LINES:-68}"
module_cache="${CLANG_MODULE_CACHE_PATH:-$PWD/.build/module-cache}"
mkdir -p "$module_cache"
export CLANG_MODULE_CACHE_PATH="$module_cache"
export SWIFTPM_MODULECACHE_OVERRIDE="${SWIFTPM_MODULECACHE_OVERRIDE:-$module_cache}"

swift_args=()
if [[ -n "${SWIFT_FLAGS:-}" ]]; then
# Intentional word splitting lets callers pass SwiftPM flags such as
# SWIFT_FLAGS="--disable-sandbox --scratch-path /tmp/icloud-cli-build".
read -r -a swift_args <<< "$SWIFT_FLAGS"
fi

if [[ "${#swift_args[@]}" -gt 0 ]]; then
swift test "${swift_args[@]}" --enable-code-coverage
coverage_json="$(swift test "${swift_args[@]}" --show-codecov-path)"
else
swift test --enable-code-coverage
coverage_json="$(swift test --show-codecov-path)"
fi

source_line_coverage="$(
jq -r --arg prefix "$PWD/Sources/ICloudCLICore/" '
[.data[0].files[]
| select(.filename | startswith($prefix))
| .summary.lines]
| {count: (map(.count) | add), covered: (map(.covered) | add)}
| (.covered * 100 / .count)
' "$coverage_json"
)"

printf 'Source line coverage: %.2f%% (minimum %.2f%%)\n' "$source_line_coverage" "$minimum_line_coverage"

awk -v actual="$source_line_coverage" -v minimum="$minimum_line_coverage" '
BEGIN {
if (actual + 0 < minimum + 0) {
exit 1
}
}
'
2 changes: 2 additions & 0 deletions scripts/ci/run-fast-checks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ bash scripts/ci/check-shell-syntax.sh
bash scripts/check-detect-secrets.sh --all-files
bash scripts/check-privacy-fixtures.sh
swift test
bash scripts/ci/check-coverage.sh
bash scripts/ci/run-mutation-smoke.sh
swift build
swift build -c release
swift run icloud-cli --help
86 changes: 86 additions & 0 deletions scripts/ci/run-mutation-smoke.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
#!/usr/bin/env bash
set -euo pipefail

module_cache="${CLANG_MODULE_CACHE_PATH:-$PWD/.build/module-cache}"
mkdir -p "$module_cache"
export CLANG_MODULE_CACHE_PATH="$module_cache"
export SWIFTPM_MODULECACHE_OVERRIDE="${SWIFTPM_MODULECACHE_OVERRIDE:-$module_cache}"

swift_args=()
if [[ -n "${SWIFT_FLAGS:-}" ]]; then
read -r -a swift_args <<< "$SWIFT_FLAGS"
fi

run_swift_test() {
if [[ "${#swift_args[@]}" -gt 0 ]]; then
swift test "${swift_args[@]}"
else
swift test
fi
}

mutation_file=""
backup_file=""

restore_mutation() {
if [[ -n "$mutation_file" && -n "$backup_file" && -f "$backup_file" ]]; then
cp "$backup_file" "$mutation_file"
fi
if [[ -n "$backup_file" && -f "$backup_file" ]]; then
rm -f "$backup_file"
fi
mutation_file=""
backup_file=""
}

trap restore_mutation EXIT

run_mutant() {
local name="$1"
local file="$2"
local needle="$3"
local replacement="$4"

mutation_file="$file"
backup_file="$(mktemp)"
cp "$file" "$backup_file"

NEEDLE="$needle" REPLACEMENT="$replacement" perl -0pi -e '
BEGIN { $needle = $ENV{"NEEDLE"}; $replacement = $ENV{"REPLACEMENT"}; }
$count = s/\Q$needle\E/$replacement/;
END { exit($count == 1 ? 0 : 2); }
' "$file"

if run_swift_test >/tmp/icloud-cli-mutant.log 2>&1; then
echo "Mutation survived: $name" >&2
cat /tmp/icloud-cli-mutant.log >&2
exit 1
fi

echo "Mutation killed: $name"
restore_mutation
}

run_mutant \
"drop-http-url-support" \
"Sources/ICloudCLICore/SafariTabs.swift" \
'return ["http", "https"].contains(scheme)' \
'return ["https"].contains(scheme)'

run_mutant \
"drop-tab-title-text-rendering" \
"Sources/ICloudCLICore/CommandRunner.swift" \
'if let title = tab.title {' \
'if false, let title = tab.title {'

run_mutant \
"wrong-missing-cloud-tabs-failure-mode" \
"Sources/ICloudCLICore/CloudTabs.swift" \
'return "cloud-tabs-store-missing"' \
'return "cloud-tabs-store-unreadable"'

run_mutant \
"disable-help-shortcut" \
"Sources/ICloudCLICore/CommandLine.swift" \
'if tokens.isEmpty || tokens.contains("--help") || tokens.contains("-h") {' \
'if tokens.isEmpty {'
Loading