aboutsummaryrefslogtreecommitdiff
path: root/ci/test.sh
diff options
context:
space:
mode:
Diffstat (limited to 'ci/test.sh')
-rwxr-xr-xci/test.sh152
1 files changed, 122 insertions, 30 deletions
diff --git a/ci/test.sh b/ci/test.sh
index 2e6b14d..e3e728c 100755
--- a/ci/test.sh
+++ b/ci/test.sh
@@ -1,54 +1,146 @@
#!/bin/sh
-# Full checks: source pins, Go formatting/vet/tests, real-rsync integration,
-# and Android JVM tests. Requires the provisioned toolchain and host rsync.
+# Full checks: exact source pins, shell syntax, Go analysis, integration tests,
+# vulnerability scanning, Android JVM tests, and Android lint.
set -eu
-root=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd)
+root=$(CDPATH='' cd -- "$(dirname -- "$0")/.." && pwd)
+# shellcheck source=versions
. "$root/versions"
-command -v rsync >/dev/null 2>&1 || { echo "ci: rsync is required" >&2; exit 1; }
-command -v gradle >/dev/null 2>&1 || { echo "ci: gradle is required" >&2; exit 1; }
+for command in cmp git govulncheck gradle go gofmt rsync sha256sum shellcheck; do
+ command -v "$command" >/dev/null 2>&1 || {
+ echo "ci: $command is required" >&2
+ exit 1
+ }
+done
if [ -f "$root/rsync/rsync-${RSYNC_VERSION}.tar.gz" ]; then
echo "ci: verifying pinned rsync checksum"
( cd "$root/rsync" && sha256sum -c "rsync-${RSYNC_VERSION}.tar.gz.sha256" )
fi
-# The gradle files and go.mod repeat pins from ./versions (gradle cannot source
-# a shell file); fail loud if they drift.
+# These files repeat values from ./versions because Gradle and Go cannot source
+# a POSIX shell file. Exact source lines keep comments from satisfying a pin.
+line() {
+ grep -Fqx -e "$2" "$root/$1" || {
+ echo "ci: $1 does not contain exact pin: $2" >&2
+ exit 1
+ }
+}
+
echo "ci: version pins"
-pin() {
- grep -Fq "$2" "$root/$1" || { echo "ci: $1 does not pin '$2' (see versions)" >&2; exit 1; }
+line build.gradle " id 'com.android.application' version '${AGP_VERSION}' apply false"
+line build.gradle " id 'org.jetbrains.kotlin.android' version '${KOTLIN_VERSION}' apply false"
+line gradle/wrapper/gradle-wrapper.properties "distributionUrl=https\\://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip"
+line gradle/wrapper/gradle-wrapper.properties "distributionSha256Sum=${GRADLE_SHA256}"
+line app/build.gradle " compileSdk ${ANDROID_PLATFORM}"
+line app/build.gradle " buildToolsVersion '${ANDROID_BUILD_TOOLS}'"
+line app/build.gradle " minSdk ${ANDROID_MIN_SDK}"
+line app/build.gradle " targetSdk ${ANDROID_TARGET_SDK}"
+line app/build.gradle " versionCode ${APP_VERSION_CODE}"
+line app/build.gradle " versionName '${APP_VERSION}'"
+line app/build.gradle " implementation 'androidx.core:core-ktx:${ANDROIDX_CORE_VERSION}'"
+line app/build.gradle " implementation 'androidx.appcompat:appcompat:${ANDROIDX_APPCOMPAT_VERSION}'"
+line app/build.gradle " implementation 'androidx.work:work-runtime:${ANDROIDX_WORK_VERSION}'"
+line app/build.gradle " testImplementation 'junit:junit:${JUNIT_VERSION}'"
+line app/build.gradle " testImplementation 'org.json:json:${JSON_VERSION}'"
+line rsh/go.mod "go ${GO_VERSION}"
+line rsh/go.mod "require golang.org/x/crypto v${X_CRYPTO_VERSION}"
+line rsh/go.mod "require golang.org/x/sys v${X_SYS_VERSION} // indirect"
+line THIRD_PARTY "- rsync ${RSYNC_VERSION}, GPLv3. Source: https://download.samba.org/pub/rsync/src/."
+line THIRD_PARTY "- The Go standard library from Go ${GO_VERSION}, BSD 3-clause."
+line THIRD_PARTY "- golang.org/x/crypto ${X_CRYPTO_VERSION}, BSD 3-clause."
+line THIRD_PARTY "APK. golang.org/x/sys ${X_SYS_VERSION} is an indirect module-graph dependency but no"
+line THIRD_PARTY "- AndroidX Core ${ANDROIDX_CORE_VERSION}, AppCompat ${ANDROIDX_APPCOMPAT_VERSION}, and WorkManager ${ANDROIDX_WORK_VERSION},"
+cmp "$root/APACHE-2.0" "$root/app/src/main/assets/APACHE-2.0" || {
+ echo "ci: packaged APACHE-2.0 differs from repository APACHE-2.0" >&2
+ exit 1
+}
+cmp "$root/LICENSE" "$root/app/src/main/assets/LICENSE" || {
+ echo "ci: packaged LICENSE differs from repository LICENSE" >&2
+ exit 1
+}
+cmp "$root/THIRD_PARTY" "$root/app/src/main/assets/THIRD_PARTY" || {
+ echo "ci: packaged THIRD_PARTY differs from repository THIRD_PARTY" >&2
+ exit 1
+}
+[ -s "$root/gradle/verification-metadata.xml" ] || {
+ echo "ci: Gradle dependency verification metadata is missing" >&2
+ exit 1
+}
+tracked_files=$(git -C "$root" ls-files)
+tracked_secrets=
+if tracked_secrets=$(printf '%s\n' "$tracked_files" |
+ grep -E '(^|/)(keystore\.properties|[^/]*\.(jks|keystore))$'); then
+ :
+else
+ status=$?
+ [ "$status" -eq 1 ] || exit "$status"
+fi
+[ -z "$tracked_secrets" ] || {
+ echo "ci: signing secret is tracked:" >&2
+ printf '%s\n' "$tracked_secrets" >&2
+ exit 1
}
-pin build.gradle "version '${AGP_VERSION}'"
-pin build.gradle "version '${KOTLIN_VERSION}'"
-pin gradle/wrapper/gradle-wrapper.properties "gradle-${GRADLE_VERSION}-bin.zip"
-pin app/build.gradle "compileSdk ${ANDROID_PLATFORM}"
-pin app/build.gradle "buildToolsVersion '${ANDROID_BUILD_TOOLS}'"
-pin app/build.gradle "minSdk ${ANDROID_MIN_SDK}"
-pin app/build.gradle "targetSdk ${ANDROID_TARGET_SDK}"
-pin rsh/go.mod "go ${GO_VERSION}"
-pin app/build.gradle "androidx.core:core-ktx:${ANDROIDX_CORE_VERSION}"
-pin app/build.gradle "androidx.appcompat:appcompat:${ANDROIDX_APPCOMPAT_VERSION}"
-pin app/build.gradle "androidx.work:work-runtime-ktx:${ANDROIDX_WORK_VERSION}"
-pin app/build.gradle "junit:junit:${JUNIT_VERSION}"
-pin app/build.gradle "org.json:json:${JSON_VERSION}"
+abi_filters=
+for abi in $ABIS; do
+ if [ -z "$abi_filters" ]; then
+ abi_filters="'$abi'"
+ else
+ abi_filters="$abi_filters, '$abi'"
+ fi
+done
+line app/build.gradle " ndk { abiFilters $abi_filters }"
-echo "ci: gofmt"
-unformatted=$(gofmt -l "$root/rsh")
-if [ -n "$unformatted" ]; then
- echo "ci: gofmt needed for:" >&2
- echo "$unformatted" >&2
+checksum="$root/rsync/rsync-${RSYNC_VERSION}.tar.gz.sha256"
+[ -f "$checksum" ] || { echo "ci: missing $checksum" >&2; exit 1; }
+[ "$(awk 'NF == 2 { print $2 }' "$checksum")" = "rsync-${RSYNC_VERSION}.tar.gz" ] || {
+ echo "ci: checksum filename does not match rsync pin" >&2
+ exit 1
+}
+
+changelog="fastlane/metadata/android/en-US/changelogs/${APP_VERSION_CODE}.txt"
+[ -s "$root/$changelog" ] || { echo "ci: missing $changelog for version ${APP_VERSION}" >&2; exit 1; }
+chars=$(wc -c < "$root/$changelog")
+[ "$chars" -le 500 ] || { echo "ci: $changelog is $chars bytes, over F-Droid's 500 limit" >&2; exit 1; }
+
+echo "ci: shell syntax"
+shell_scripts=$(git -C "$root" ls-files --cached --others --exclude-standard '*.sh')
+printf '%s\n' "$shell_scripts" |
+while IFS= read -r script; do
+ sh -n "$root/$script"
+done
+( cd "$root" && shellcheck -x ci/*.sh rsync/build.sh rsh/build.sh )
+em_dash=$(printf '\342\200\224')
+if git -C "$root" grep --untracked -n -I "$em_dash"; then
+ echo "ci: em dash is not allowed" >&2
exit 1
+else
+ status=$?
+ [ "$status" -eq 1 ] || exit "$status"
fi
+echo "ci: gofmt"
+unformatted=$(gofmt -l "$root/rsh")
+[ -z "$unformatted" ] || { echo "ci: gofmt needed for:$unformatted" >&2; exit 1; }
+
+echo "ci: go module checksums"
+( cd "$root/rsh" && go mod verify )
+
echo "ci: go vet"
( cd "$root/rsh" && go vet ./... )
-echo "ci: go test"
-( cd "$root/rsh" && go test ./... )
+echo "ci: go test (race detector)"
+( cd "$root/rsh" && go test -race ./... )
+
+# govulncheck queries vuln.go.dev and sends module paths, not source code.
+echo "ci: govulncheck"
+( cd "$root/rsh" && govulncheck ./... )
echo "ci: Android unit tests"
-( cd "$root" && gradle :app:testReleaseUnitTest )
+( cd "$root" && gradle --no-daemon :app:testReleaseUnitTest )
+
+echo "ci: Android lint"
+( cd "$root" && gradle --no-daemon :app:lintRelease )
echo "ci: ok"