Files
flatpak/tests/test-unused.sh
Patrick Griffis 8c26798991 update: Make autopruned refs automatically removed
In order to maintain a system over time update automatically removes any EOL runtimes that are unused.

This extends it to also remove any autopruned refs. In practice this means removing no longer used driver versions as the system is updated.

Closes #5261
2024-01-07 18:51:46 -06:00

435 lines
13 KiB
Bash
Executable File

#!/bin/bash
#
# Copyright (C) 2020 Alexander Larsson <alexl@redhat.com>
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.
set -euo pipefail
# We default to use systemdir, so we can also test shadowing from the user dir
export USE_SYSTEMDIR=yes
. $(dirname $0)/libtest.sh
skip_without_bwrap
skip_revokefs_without_fuse
echo "1..3"
setup_empty_repo &> /dev/null > /dev/null
# Manually add the user remote too
$FLATPAK remote-add --user --gpg-import=${FL_GPG_HOMEDIR}/pubring.gpg test-repo "http://127.0.0.1:${port}/test" >&2
# This tests the detection of unused refs. Used refs are any that have
# some kind of dependency from an installed application to the ref.
#
# Possible reasons a ref are used:
# Direct use:
# * App is installed
# * Runtime is marked as pinned and installed
#
# Dependent ref (of a used ref)
# * App $USED uses runtime $DEP
# * App $USED uses runtime $DEP as sdk (so that if you're developing the app we don't remove the sdk)
# * Runtime $USED has extra-data and not NoRuntime, and lists $DEP as "ExtensionOf"
# * Runtime $USED uses runtime $DEP as sdk
#
# Related ref (of a used ref):
# * App $USED uses extension $REL, which is not marked as autoprune-unless
# * Runtime $USED uses extension $REL, which is not marked as autoprune-unless
#
# There are options to affect this:
# * Specific refs can be excluded (never considered used), used to emulate them being uninstalled in a transaction
# * Specify custom new metadata for an installed ref, used to emulate it being updated in a transaction
#
# Additionally, for the system installation, the dependencies are considered from the user installation, such that any dependencies
# in the user installation that are resolved from the system installation (i.e. not in the user installation) are considered used.
# I.E. if an app and its runtime both are installed in the user dir, it is not considered used in the system installation, but if
# only the app is, then it is considered used.
create_runtime () {
local ID="${1}"
local SDK="${2}"
local BRANCH=stable
local DIR=`mktemp -d`
mkdir ${DIR}/files
mkdir ${DIR}/usr
cat > ${DIR}/metadata <<EOF
[Runtime]
name=${ID}
runtime=${ID}/$ARCH/$BRANCH
EOF
if test x$SDK != "x"; then
cat >> ${DIR}/metadata <<EOF
sdk=${SDK}/$ARCH/$BRANCH
EOF
fi
$FLATPAK build-finish $DIR ${finish_args[$ID]:-} &> /dev/null > /dev/null
$FLATPAK build-export -v ${FL_GPGARGS} --disable-sandbox --runtime repos/test ${DIR} ${BRANCH} &> /dev/null > /dev/null
rm -rf ${DIR}
}
create_app () {
local ID="${1}"
local RUNTIME="${2}"
local SDK="${3}"
local BRANCH=stable
local DIR=`mktemp -d`
mkdir ${DIR}/files
cat > ${DIR}/metadata <<EOF
[Application]
name=${ID}
runtime=${RUNTIME}/$ARCH/$BRANCH
EOF
if test x$SDK != "x"; then
cat >> ${DIR}/metadata <<EOF
sdk=${SDK}/$ARCH/$BRANCH
EOF
fi
set -x
$FLATPAK build-finish ${DIR} ${finish_args[$ID]:-} &> /dev/null > /dev/null
$FLATPAK build-export ${FL_GPGARGS} --disable-sandbox repos/test ${DIR} ${BRANCH} &> /dev/null > /dev/null
rm -rf ${DIR}
}
declare -a runtimes
declare -A runtimes_sdk
declare -A runtimes_pinned
declare -a apps
declare -A apps_runtime
declare -A apps_sdk
declare -A finish_args
declare -A installs_in
make_runtime() {
{ set +x; } 2>/dev/null
local ID="org.runtime.${1}"
local SDK="${2:-}"
if test x$SDK != x; then
SDK=org.runtime.$SDK
fi
runtimes+=( "$ID" )
runtimes_sdk["$ID"]="$SDK"
set -x
}
pin_runtime() {
{ set +x; } 2>/dev/null
local ID="org.runtime.${1}"
runtimes_pinned["$ID"]="yes"
set -x
}
install_runtime_in() {
{ set +x; } 2>/dev/null
local ID="org.runtime.${1}"
local WHERE="${2}"
installs_in["$ID"]=$WHERE
set -x
}
make_extension() {
{ set +x; } 2>/dev/null
local ID="org.extension.${1}"
runtimes+=( "$ID" )
set -x
}
pin_extension() {
{ set +x; } 2>/dev/null
local ID="org.extension.${1}"
runtimes_pinned["$ID"]="yes"
set -x
}
install_extension_in() {
{ set +x; } 2>/dev/null
local ID="org.extension.${1}"
local WHERE="${2}"
installs_in["$ID"]=$WHERE
set -x
}
runtime_add_extension() {
{ set +x; } 2>/dev/null
local RUNTIME="org.runtime.${1}"
local EXT="org.extension.${2}"
finish_args["$RUNTIME"]="${finish_args[$RUNTIME]:-} --extension=${EXT}=directory=/ext"
set -x
}
runtime_add_autoprune_extension() {
{ set +x; } 2>/dev/null
local RUNTIME="org.runtime.${1}"
local EXT="org.extension.${2}"
finish_args["$RUNTIME"]="${finish_args[$RUNTIME]:-} --extension=${EXT}=directory=/ext --extension=${EXT}=autoprune-unless=active-gl-driver"
set -x
}
make_runtime_full() {
make_runtime "$1" "${2:-}"
make_extension "$1.Locale"
runtime_add_extension "$1" $1.Locale
make_extension "$1.Debug"
runtime_add_extension "$1" $1.Debug
}
app_add_extension() {
{ set +x; } 2>/dev/null
local APP="org.app.${1}"
local EXT="org.extension.${2}"
finish_args["$APP"]="${finish_args[$APP]:-} --extension=${EXT}=directory=/ext"
set -x
}
install_app_in() {
{ set +x; } 2>/dev/null
local ID="org.app.${1}"
local WHERE="${2}"
installs_in["$ID"]=$WHERE
set -x
}
app_add_autoprune_extension() {
{ set +x; } 2>/dev/null
local APP="org.app.${1}"
local EXT="org.extension.${2}"
finish_args["$RUNTIME"]="${finish_args[$APP]:-} --extension=${EXT}=directory=/ext --extension=${EXT}=autoprune-unless=active-gl-driver"
set -x
}
make_app() {
{ set +x; } 2>/dev/null
local ID="org.app.${1}"
local RUNTIME="org.runtime.${2}"
local SDK="${3:-}"
if test x$SDK != x; then
SDK=org.runtime.$SDK
fi
apps+=( $ID )
apps_runtime[$ID]="$RUNTIME"
apps_sdk[$ID]="$SDK"
set -x
}
make_app_full() {
make_app "$1" "$2" "${3:-}"
make_extension "$1.Locale"
app_add_extension "$1" $1.Locale
make_extension "$1.Debug"
app_add_extension "$1" $1.Debug
}
make_it_happen() {
{ set +x; } 2>/dev/null
for runtime in "${runtimes[@]}"; do
create_runtime "$runtime" "${runtimes_sdk[$runtime]:-}"
done
for app in "${apps[@]}"; do
create_app "$app" "${apps_runtime[$app]}" "${apps_sdk[$app]}"
done
update_repo &> /dev/null > /dev/null
for runtime in "${runtimes[@]}"; do
local in=${installs_in[${runtime}]:-system}
if test $in = system -o $in = both; then
$FLATPAK install -y --system --no-deps --no-related --no-auto-pin test-repo runtime/$runtime/$ARCH/stable &> /dev/null > /dev/null
fi
if test $in = user -o $in = both; then
$FLATPAK install -y --user --no-deps --no-related --no-auto-pin test-repo runtime/$runtime/$ARCH/stable &> /dev/null > /dev/null
fi
if test "x${runtimes_pinned[$runtime]:-}" == "xyes"; then
$FLATPAK pin $runtime//stable
fi
done
for app in "${apps[@]}"; do
local in=${installs_in[$app]:-system}
if test $in = system -o $in = both; then
$FLATPAK install -y --system --no-deps --no-related --no-auto-pin test-repo app/$app/$ARCH/stable &> /dev/null > /dev/null
fi
if test $in = user -o $in = both; then
$FLATPAK install -y --user --no-deps --no-related --no-auto-pin test-repo app/$app/$ARCH/stable &> /dev/null > /dev/null
fi
done
$FLATPAK list --system -a --columns=ref | sort > installed.txt
$FLATPAK list --user -a --columns=ref | sort > user-installed.txt
# No USER_ should be in system dir
assert_not_file_has_content installed.txt USER_
set -x
}
verify_unused() {
comm -23 installed.txt unused.txt > used.txt
comm -12 used.txt unused.txt > used-and-unused.txt
assert_file_empty used-and-unused.txt
#echo ======= UNUSED ==============
#cat unused.txt
#echo ======= USED ==============
#cat used.txt
#echo =============================
assert_not_file_has_content unused.txt "\.USED"
assert_not_file_has_content used.txt "\.UNUSED"
# Also, no app or app extensions are unused
assert_not_file_has_content unused.txt "\.APP"
}
verify_autopruned() {
assert_file_has_content autopruned.txt "\.NONACTIVEGL"
assert_not_file_has_content autopruned.txt "PINNED_.\.NONACTIVEGL"
}
# This is used for the autoprune check
export FLATPAK_GL_DRIVERS=ACTIVEGL
# Runtime for app A, and it has its own sdk
make_runtime_full USED_A USED_A_SDK
make_runtime_full USED_A_SDK
# Sdk for app A, and it has its own sdk
make_runtime USED_A_SDK2 USED_A_SDK3
make_runtime USED_A_SDK3
# App A
make_app_full APP_A USED_A USED_A_SDK2
# Plain unused runtime
make_runtime_full UNUSED_B
# unused runtime with sdk
make_runtime_full UNUSED_C UNUSED_B_SDK
make_runtime_full UNUSED_C_SDK
# Unused extension and dependency
make_extension UNUSED_D
# Pinned runtime
make_runtime_full USED_E
pin_runtime USED_E
# Pinned extension
make_extension USED_F
pin_extension USED_F
# app with runtime with autopruned extension
make_app APP_G USED_G
make_runtime USED_G
make_extension USED_G.ACTIVEGL
runtime_add_autoprune_extension USED_G USED_G.ACTIVEGL
make_extension UNUSED_G.NONACTIVEGL
runtime_add_autoprune_extension USED_G UNUSED_G.NONACTIVEGL
make_extension PINNED_G.NONACTIVEGL
runtime_add_autoprune_extension USED_G PINNED_G.NONACTIVEGL
pin_extension PINNED_G.NONACTIVEGL
# unused runtime with autopruned extension
make_runtime UNUSED_H
make_extension UNUSED_H.ACTIVEGL
runtime_add_autoprune_extension UNUSED_H UNUSED_H.ACTIVEGL
make_extension UNUSED_H.NONACTIVEGL
runtime_add_autoprune_extension UNUSED_H UNUSED_H.NONACTIVEGL
# pinned runtime with autopruned extension
make_runtime USED_I
pin_runtime USED_I
make_extension USED_I.ACTIVEGL
runtime_add_autoprune_extension USED_I USED_I.ACTIVEGL
make_extension UNUSED_I.NONACTIVEGL
runtime_add_autoprune_extension USED_I UNUSED_I.NONACTIVEGL
# System runtime used by user app
make_runtime USED_J
make_app USER_APP_J USED_J
install_app_in USER_APP_J user
# System runtime shadowed by user app and runtime, but with and extension that isn't shadowed and one that is
make_extension USED_EXT_K
make_extension UNUSED_EXT2_K
install_extension_in UNUSED_EXT2_K both
make_runtime UNUSED_K
install_runtime_in UNUSED_K both
runtime_add_extension UNUSED_K USED_EXT_K
runtime_add_extension UNUSED_K UNUSED_EXT2_K
make_app USER_APP_K UNUSED_K
install_app_in USER_APP_K user
make_it_happen
# Verify that the right thing got user-installed
assert_file_has_content user-installed.txt USER_APP_J
assert_file_has_content user-installed.txt USER_APP_K
assert_file_has_content user-installed.txt UNUSED_K
assert_file_has_content user-installed.txt UNUSED_EXT2_K
assert_not_file_has_content user-installed.txt USED_EXT_K
${test_builddir}/list-unused | sed s@^app/@@g | sed s@^runtime/@@g | sort > unused.txt
verify_unused
ok "list unused regular"
${test_builddir}/list-unused --filter-autoprune | sed s@^app/@@g | sed s@^runtime/@@g | sort > autopruned.txt
verify_autopruned
ok "list autopruned"
mv unused.txt old-unused.txt
${test_builddir}/list-unused --exclude "app/org.app.APP_A/$ARCH/stable" | sed s@^app/@@g | sed s@^runtime/@@g | sort > unused.txt
# We don't report the excluded ref itself as unused. It's as if it wasn't even installed
assert_not_file_has_content unused.txt "org.app.APP_A/"
# Excluding a ref should not use more refs
comm -23 old-unused.txt unused.txt > newly-used.txt
assert_file_empty newly-used.txt
# We should add all dependencies from the app, but no more
assert_file_has_content unused.txt "org.extension.APP_A.Debug/"
assert_file_has_content unused.txt "org.extension.APP_A.Locale/"
assert_file_has_content unused.txt "org.runtime.USED_A/"
assert_file_has_content unused.txt "org.extension.USED_A.Debug/"
assert_file_has_content unused.txt "org.extension.USED_A.Locale/"
assert_file_has_content unused.txt "org.runtime.USED_A_SDK/"
assert_file_has_content unused.txt "org.extension.USED_A_SDK.Debug/"
assert_file_has_content unused.txt "org.extension.USED_A_SDK.Locale/"
assert_file_has_content unused.txt "org.runtime.USED_A_SDK2/"
assert_file_has_content unused.txt "org.runtime.USED_A_SDK3/"
comm -13 old-unused.txt unused.txt > newly-unused.txt
if [ $(cat newly-unused.txt | wc -l) -ne 10 ]; then
assert_not_reached "Unexpected unused ref"
fi
ok "list unused exclude"