mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-11-11 02:51:10 +00:00
ci: build and test on root self-hosted runners (#2237)
This commit is contained in:
1 parent
8fa30b99f9
commit
d29a1a2dc2
10 files changed
+59
-5
No files matched your search
@@ -0,0 +1,16 @@
|
||||
package com.droiddeck.launcher
|
||||
|
||||
import java.nio.file.Files
|
||||
import org.junit.Assume.assumeTrue
|
||||
|
||||
/** Root ignores file mode bits, so tests that rely on a denied read or write cannot run as root or without POSIX permissions. */
|
||||
fun assumeFilePermissionsEnforced() {
|
||||
val probe = Files.createTempDirectory("permission-probe").toFile()
|
||||
try {
|
||||
assumeTrue("no POSIX file permissions", Files.getFileStore(probe.toPath()).supportsFileAttributeView("posix"))
|
||||
assumeTrue("file permissions are not enforced for this user", probe.setReadable(false, false) && !probe.canRead())
|
||||
} finally {
|
||||
probe.setReadable(true, false)
|
||||
probe.delete()
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.droiddeck.launcher.files
|
||||
|
||||
import com.droiddeck.launcher.assumeFilePermissionsEnforced
|
||||
import java.io.File
|
||||
import java.util.concurrent.CancellationException
|
||||
import java.nio.file.Files
|
||||
@@ -114,6 +115,7 @@ class FileOpsCopyTest {
|
||||
}
|
||||
|
||||
@Test fun anOverwriteStillRefusesAReadOnlyDestination() {
|
||||
assumeFilePermissionsEnforced()
|
||||
assumeTrue(Files.getFileStore(tmp.root.toPath()).supportsFileAttributeView("posix"))
|
||||
val src = tmp.newFile("source").apply { writeText("replacement") }
|
||||
val dst = tmp.newFile("dest").apply { writeText("original") }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.droiddeck.launcher.frontend
|
||||
|
||||
import com.droiddeck.launcher.assumeFilePermissionsEnforced
|
||||
import android.os.Environment
|
||||
import com.droiddeck.launcher.session.SessionPrefs
|
||||
import java.io.File
|
||||
@@ -308,6 +309,7 @@ class AddedExesTest {
|
||||
}
|
||||
|
||||
@Test fun anExeInAFolderTheAppCannotReadIsNotAdded() {
|
||||
assumeFilePermissionsEnforced()
|
||||
noGamesFolders()
|
||||
val outside = exe(File(tmp.newFolder("elsewhere"), "Game/Game.exe"))
|
||||
outside.parentFile!!.setReadable(false)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.droiddeck.launcher.runtime
|
||||
|
||||
import com.droiddeck.launcher.assumeFilePermissionsEnforced
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.LinkOption.NOFOLLOW_LINKS
|
||||
@@ -128,6 +129,7 @@ class RuntimeRemovalTest {
|
||||
}
|
||||
|
||||
@Test fun aPartialRemovalStaysQuarantinedAndRetryable() {
|
||||
assumeFilePermissionsEnforced()
|
||||
val root = runtime()
|
||||
try {
|
||||
val removal = LinuxRuntimeInstaller.beginUninstall(null, root)!!
|
||||
@@ -146,6 +148,7 @@ class RuntimeRemovalTest {
|
||||
}
|
||||
|
||||
@Test fun reportsFailureWhenTheRuntimeCannotBeQuarantinedAndAllowsRetry() {
|
||||
assumeFilePermissionsEnforced()
|
||||
val root = runtime()
|
||||
Files.setPosixFilePermissions(dir.toPath(), PosixFilePermissions.fromString("r-xr-xr-x"))
|
||||
try {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.droiddeck.launcher.runtime
|
||||
|
||||
import com.droiddeck.launcher.assumeFilePermissionsEnforced
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.nio.file.Files
|
||||
@@ -155,6 +156,7 @@ class RuntimeUserDataCarryTest {
|
||||
}
|
||||
|
||||
@Test fun anUnreadableAppImageDirectoryAbortsBeforeMovingHome() {
|
||||
assumeFilePermissionsEnforced()
|
||||
assumeTrue(Files.getFileStore(tmp.root.toPath()).supportsFileAttributeView("posix"))
|
||||
val old = userTree()
|
||||
val images = File(old, "opt/appimages")
|
||||
@@ -264,6 +266,7 @@ class RuntimeUserDataCarryTest {
|
||||
}
|
||||
|
||||
@Test fun anInaccessibleLiveHomeIsNeverTreatedAsMissing() {
|
||||
assumeFilePermissionsEnforced()
|
||||
assumeTrue(Files.getFileStore(tmp.root.toPath()).supportsFileAttributeView("posix"))
|
||||
val root = tmp.newFolder("runtime")
|
||||
val staging = tmp.newFolder("runtime.new")
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.droiddeck.launcher.session
|
||||
|
||||
import com.droiddeck.launcher.assumeFilePermissionsEnforced
|
||||
import android.content.Intent
|
||||
import org.json.JSONObject
|
||||
import org.junit.Assert.assertEquals
|
||||
@@ -403,6 +404,7 @@ class EnhancedSessionLogShareTest {
|
||||
}
|
||||
|
||||
@Test fun unreadableDiscoverySubtreeKeepsOtherLogsAndMarksTheManifestPartial() {
|
||||
assumeFilePermissionsEnforced()
|
||||
assumeFalse(System.getProperty("os.name").startsWith("Windows"))
|
||||
val session = tmp.newFolder("2026-11-04-12-partial-discovery")
|
||||
session.resolve("session.log").writeText("safe\n")
|
||||
|
||||
@@ -10,9 +10,13 @@ if [ "${#missing[@]}" = 0 ]; then
|
||||
echo "Ubuntu build dependencies are already installed."
|
||||
exit 0
|
||||
fi
|
||||
options=(
|
||||
-o Dir::Etc::sourcelist=sources.list.d/ubuntu.sources
|
||||
-o Dir::Etc::sourceparts=-
|
||||
options=()
|
||||
# Runners without the deb822 Ubuntu sources file keep their own apt configuration.
|
||||
ubuntu_sources=${UBUNTU_SOURCES_FILE:-/etc/apt/sources.list.d/ubuntu.sources}
|
||||
if [ -f "$ubuntu_sources" ]; then
|
||||
options+=(-o "Dir::Etc::sourcelist=$ubuntu_sources" -o Dir::Etc::sourceparts=-)
|
||||
fi
|
||||
options+=(
|
||||
-o Acquire::Retries=1
|
||||
-o Acquire::http::Timeout=15
|
||||
-o Acquire::https::Timeout=15
|
||||
@@ -28,13 +32,14 @@ if install_packages; then
|
||||
exit 0
|
||||
fi
|
||||
echo "::warning::Runner mirror failed or timed out; retrying with the official Ubuntu mirror."
|
||||
[ -f "$ubuntu_sources" ] || exit 1
|
||||
sources=$(mktemp --suffix=.sources)
|
||||
trap 'rm -f "$sources"' EXIT
|
||||
mirror=https://archive.ubuntu.com/ubuntu
|
||||
case "$(dpkg --print-architecture)" in
|
||||
arm64) mirror=https://ports.ubuntu.com/ubuntu-ports ;;
|
||||
esac
|
||||
sed -E "s@^URIs:.*@URIs: $mirror@" /etc/apt/sources.list.d/ubuntu.sources > "$sources"
|
||||
sed -E "s@^URIs:.*@URIs: $mirror@" "$ubuntu_sources" > "$sources"
|
||||
chmod 644 "$sources"
|
||||
options[1]="Dir::Etc::sourcelist=$sources"
|
||||
install_packages
|
||||
@@ -139,6 +139,9 @@
|
||||
"app/src/main/jniLibs/arm64-v8a/libproot.so",
|
||||
"app/src/main/jniLibs/arm64-v8a/libproot-loader.so"
|
||||
],
|
||||
"packages": [
|
||||
"gawk"
|
||||
],
|
||||
"ndk": true
|
||||
},
|
||||
"comet": {
|
||||
|
||||
@@ -52,6 +52,13 @@ cp "$HERE/talloc-config.h" "$WORK/talloc/config/config.h"
|
||||
"$TOOLCHAIN/llvm-ar" rcs "$WORK/libtalloc.a" "$WORK/talloc.o"
|
||||
mkdir -p "$WORK/bin"
|
||||
ln -s "$TOOLCHAIN/llvm-readelf" "$WORK/bin/readelf"
|
||||
# loader-info.awk needs strtonum, which mawk (the default awk on some runners) lacks.
|
||||
if command -v gawk >/dev/null; then
|
||||
ln -s "$(command -v gawk)" "$WORK/bin/awk"
|
||||
elif ! awk 'BEGIN { exit !(strtonum("0x10") == 16) }' 2>/dev/null; then
|
||||
echo "gawk, or an awk with strtonum, is required to build the proot loader" >&2
|
||||
exit 1
|
||||
fi
|
||||
PATH="$WORK/bin:$PATH" make -C "$WORK/proot/src" -j"$(getconf _NPROCESSORS_ONLN)" \
|
||||
CC="$CC" STRIP="$TOOLCHAIN/llvm-strip" OBJCOPY="$TOOLCHAIN/llvm-objcopy" OBJDUMP="$TOOLCHAIN/llvm-objdump" \
|
||||
CPPFLAGS="-D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE -I. -I$WORK/talloc -DARG_MAX=131072 -Wno-error=implicit-function-declaration" \
|
||||
|
||||
@@ -64,7 +64,10 @@ class CiChecksTest(unittest.TestCase):
|
||||
query.write_text('#!/bin/sh\nexit 1\n')
|
||||
query.chmod(0o755)
|
||||
log = root / 'apt.log'
|
||||
env = dict(os.environ, PATH=str(root) + os.pathsep + os.environ['PATH'], APT_LOG=str(log))
|
||||
sources = root / 'ubuntu.sources'
|
||||
sources.write_text('URIs: http://mirror.invalid/ubuntu\n')
|
||||
env = dict(os.environ, PATH=str(root) + os.pathsep + os.environ['PATH'], APT_LOG=str(log),
|
||||
UBUNTU_SOURCES_FILE=str(sources))
|
||||
installer = ROOT / 'tools/ci/install-ubuntu-packages.sh'
|
||||
result = subprocess.run(['bash', str(installer), 'qemu-user'], env=dict(env, APT_RESULT='17'))
|
||||
self.assertEqual(result.returncode, 17)
|
||||
@@ -74,11 +77,19 @@ class CiChecksTest(unittest.TestCase):
|
||||
update, install = log.read_text().splitlines()
|
||||
for command in (update, install):
|
||||
self.assertIn('Dir::Etc::sourceparts=-', command)
|
||||
self.assertIn(f'Dir::Etc::sourcelist={sources}', command)
|
||||
self.assertIn('Acquire::https::Timeout=15', command)
|
||||
self.assertIn('Acquire::Retries=1', command)
|
||||
self.assertIn('APT::Update::Error-Mode=any', update)
|
||||
self.assertIn('install -y -q --no-install-recommends qemu-user', install)
|
||||
|
||||
log.unlink()
|
||||
absent = dict(env, UBUNTU_SOURCES_FILE=str(root / 'missing.sources'), APT_RESULT='17')
|
||||
result = subprocess.run(['bash', str(installer), 'qemu-user'], env=absent)
|
||||
self.assertEqual(result.returncode, 1)
|
||||
self.assertEqual(len(log.read_text().splitlines()), 1)
|
||||
self.assertNotIn('Dir::Etc::sourceparts', log.read_text())
|
||||
|
||||
def test_required_status_rejects_failed_cancelled_or_skipped_test_jobs(self):
|
||||
jobs = yaml.safe_load((ROOT / '.github/workflows/build.yml').read_text())['jobs']
|
||||
gate = jobs['checks']
|
||||
|
||||
Reference in new issue
Block a user