#!/bin/sh

# Copyright (C) 2011  Loïc Minier <lool@dooz.org>
# Copyright (C) 2025 Dave Jones <dave.jones@canonical.com>

# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301,
# USA.

# Let the function library know that it is called from the testsuite.
FK_TESTSUITE_RUNNING="yes"

. ./testlib

functions="${FK_CHECKOUT:-$FK_DIR}/functions"
functions_piboot="${FK_CHECKOUT:-$FK_DIR}/functions-piboot"

mock_bootloader_assets() {
    local dir="$1"
    local suffix="${2:-}"
    mkdir -p "$dir"
    touch \
        "$dir"/bootcode.bin"$suffix" \
        "$dir"/start.elf"$suffix" \
        "$dir"/start4.elf"$suffix" \
        "$dir"/fixup.dat"$suffix" \
        "$dir"/fixup4.dat"$suffix"
    # NOTE: this is a legacy config.txt; call mock_new_config (below) to adjust
    # this to the new config.txt, where required
    cat > "$dir"/config.txt"$suffix" << EOF
[all]
kernel=vmlinuz
initramfs initrd.img followkernel
cmdline=cmdline.txt

# Enable serial pins
enable_uart=1

[pi4]
max_framebuffers=2
arm_boost=1

[all]
EOF
}

mock_new_config() {
    local dir="$1"
    local target="${2:-current}"
    # Adjust existing $1/config.txt to point to $2/ (defaults to current)
    awk -v target="$target" '
    BEGIN { found_all=0; }
    { print; }
    /^\[all\]/ && found_all==0 { found_all=1; print "os_prefix=" target "/"; }
    END { if (!found_all) { print "[all]"; print "os_prefix=" target "/"; } }
    ' "$dir"/config.txt > "$dir"/config.new
    mv "$dir"/config.new "$dir"/config.txt
}

mock_kernel_assets() {
    local dir="$1"
    local suffix="${2:-}"
    mkdir -p "$dir"
    touch \
        "$dir"/vmlinuz"$suffix" \
        "$dir"/initrd.img"$suffix" \
        "$dir"/cmdline.txt"$suffix"
}

mock_device_trees() {
    local dir="$1"
    local suffix="${2:-}"
    mkdir -p "$dir"
    touch \
        "$dir"/bcm2710-rpi-2-b.dtb"$suffix" \
        "$dir"/bcm2710-rpi-3-b-plus.dtb"$suffix" \
        "$dir"/bcm2711-rpi-4-b.dtb"$suffix" \
        "$dir"/bcm2712d0-rpi-5-b.dtb"$suffix"
}

mock_device_tree_overlays() {
    local dir="$1"
    local suffix="${2:-}"
    mkdir -p "$dir"/overlays
    touch \
        "$dir"/overlays/gpio-poweroff.dtbo"$suffix" \
        "$dir"/overlays/w1-gpio.dtbo"$suffix" \
        "$dir"/overlays/vc4-kms-v3d.dtbo"$suffix" \
        "$dir"/overlays/overlay_map.dtb"$suffix" \
        "$dir"/overlays/hat_map.dtb"$suffix" \
        "$dir"/overlays/README"$suffix"
}

test_parse_find_opts() {
    (
        . "$functions"
        . "$functions_piboot"
        expected="suffix=.bak foo bar"
        if [ "$(parse_find_opts foo --suffix .bak bar)" != "$expected" ]; then
            echo "parse_find_opts did not output the expected arguments" >&2
            exit 1
        fi
        if [ "$(parse_find_opts --suffix .bak foo bar)" != "$expected" ]; then
            echo "parse_find_opts did not output the expected arguments" >&2
            exit 1
        fi
        if [ "$(parse_find_opts foo bar -s .bak)" != "$expected" ]; then
            echo "parse_find_opts did not output the expected arguments" >&2
            exit 1
        fi
        if [ "$(parse_find_opts foo bar)" != "suffix= foo bar" ]; then
            echo "parse_find_opts did not output the expected arguments" >&2
            exit 1
        fi
    )
}
add_test test_parse_find_opts

test_unique_filenames() {
    (
        . "$functions"
        . "$functions_piboot"
        input="\
/foo/quux
/foo/bar
/bar/quux"
        expected="\
/foo/quux
/foo/bar"
        if [ "$(echo "$input" | unique_filenames)" != "$expected" ]; then
            echo "unique_filenames did not output the expected filenames" >&2
            exit 1
        fi
    )
}
add_test test_unique_filenames

test_find_bootloader_assets() {
    get_tempdir
    bootloader_dir="$last_tempfile"
    mock_bootloader_assets "$bootloader_dir"
    expected="\
$bootloader_dir/bootcode.bin
$bootloader_dir/fixup.dat
$bootloader_dir/fixup4.dat
$bootloader_dir/start.elf
$bootloader_dir/start4.elf"
    (
        . "$functions"
        . "$functions_piboot"
        if [ "$(find_bootloader_assets "$bootloader_dir" | sort)" != "$(echo "$expected" | sort)" ]; then
            echo "find_bootloader_assets did not output the expected list of files" >&2
            exit 1
        fi
    )
}
add_test test_find_bootloader_assets

test_find_bootloader_assets_with_suffix() {
    get_tempdir
    bootloader_dir="$last_tempfile"
    mock_bootloader_assets "$bootloader_dir" ".bak"
    expected="\
$bootloader_dir/bootcode.bin.bak
$bootloader_dir/start.elf.bak
$bootloader_dir/start4.elf.bak
$bootloader_dir/fixup.dat.bak
$bootloader_dir/fixup4.dat.bak"
    (
        . "$functions"
        . "$functions_piboot"
        if [ "$(find_bootloader_assets --suffix .bak "$bootloader_dir" | sort)" != "$(echo "$expected" | sort)" ]; then
            echo "find_bootloader_assets did not output the expected list of files" >&2
            exit 1
        fi
    )
}
add_test test_find_bootloader_assets_with_suffix

test_find_device_trees() {
    get_tempdir
    firmware_dir="$last_tempfile"
    kvers="3.14"
    mkdir -p "$firmware_dir"/"$kvers"/device-tree/broadcom
    mkdir -p "$firmware_dir"/"$kvers"/device-tree/overlays
    mock_device_trees "$firmware_dir"/"$kvers"/device-tree/broadcom
    mock_device_tree_overlays "$firmware_dir"/"$kvers"/device-tree/overlays
    expected="\
$firmware_dir/$kvers/device-tree/broadcom/bcm2710-rpi-2-b.dtb
$firmware_dir/$kvers/device-tree/broadcom/bcm2710-rpi-3-b-plus.dtb
$firmware_dir/$kvers/device-tree/broadcom/bcm2711-rpi-4-b.dtb
$firmware_dir/$kvers/device-tree/broadcom/bcm2712d0-rpi-5-b.dtb"
    (
        . "$functions"
        . "$functions_piboot"
        if [ "$(find_device_trees "$firmware_dir"/"$kvers"/device-tree/broadcom | sort)" != "$(echo "$expected" | sort)" ]; then
            echo "find_device_trees did not output the expected list of files" >&2
            exit 1
        fi
    )
}
add_test test_find_device_trees

test_find_device_tree_overlays() {
    get_tempdir
    firmware_dir="$last_tempfile"
    kvers="3.14"
    mkdir -p "$firmware_dir"/"$kvers"/device-tree/broadcom
    mock_device_trees "$firmware_dir"/"$kvers"/device-tree/broadcom
    mock_device_tree_overlays "$firmware_dir"/"$kvers"/device-tree
    expected="\
$firmware_dir/$kvers/device-tree/overlays/gpio-poweroff.dtbo
$firmware_dir/$kvers/device-tree/overlays/w1-gpio.dtbo
$firmware_dir/$kvers/device-tree/overlays/vc4-kms-v3d.dtbo
$firmware_dir/$kvers/device-tree/overlays/overlay_map.dtb
$firmware_dir/$kvers/device-tree/overlays/hat_map.dtb
$firmware_dir/$kvers/device-tree/overlays/README"
    (
        . "$functions"
        . "$functions_piboot"
        cd "$firmware_dir"
        if [ "$(find_device_tree_overlays "$firmware_dir"/"$kvers"/device-tree | sort)" != "$(echo "$expected" | sort)" ]; then
            echo "find_device_tree_overlays did not output the expected list of files" >&2
            exit 1
        fi
    )
}
add_test test_find_device_tree_overlays

test_find_device_tree_overlays_with_suffix() {
    get_tempdir
    bootloader_dir="$last_tempfile"
    mock_device_tree_overlays "$bootloader_dir" ".bak"
    expected="\
$bootloader_dir/overlays/gpio-poweroff.dtbo.bak
$bootloader_dir/overlays/w1-gpio.dtbo.bak
$bootloader_dir/overlays/vc4-kms-v3d.dtbo.bak
$bootloader_dir/overlays/overlay_map.dtb.bak
$bootloader_dir/overlays/hat_map.dtb.bak
$bootloader_dir/overlays/README.bak"
    (
        . "$functions"
        . "$functions_piboot"
        if [ "$(find_device_tree_overlays --suffix .bak "$bootloader_dir" | sort)" != "$(echo "$expected" | sort)" ]; then
            echo "find_device_tree_overlays did not output the expected list of files" >&2
            exit 1
        fi
    )
}
add_test test_find_device_tree_overlays_with_suffix

test_find_kernel_assets_with_suffix() {
    get_tempdir
    bootloader_dir="$last_tempfile"
    mock_kernel_assets "$bootloader_dir" ".bak"
    expected="\
$bootloader_dir/vmlinuz.bak
$bootloader_dir/initrd.img.bak
$bootloader_dir/cmdline.txt.bak"
    (
        . "$functions"
        . "$functions_piboot"
        if [ "$(find_kernel_assets --suffix .bak "$bootloader_dir" | sort)" != "$(echo "$expected" | sort)" ]; then
            echo "find_kernel_assets did not output the expected list of files" >&2
            exit 1
        fi
    )
}
add_test test_find_kernel_assets_with_suffix

test_atomic_cp() {
    get_tempdir
    test_dir="$last_tempfile"
    echo foo > "$test_dir"/foo
    echo bar > "$test_dir"/bar
    (
        . "$functions"
        . "$functions_piboot"
        if atomic_cp "$test_dir"/foo "$test_dir"/bar; then
            if [ "$(cat "$test_dir"/foo)" != "foo" ]; then
                echo "atomic_cp altered source" >&2
                exit 1
            fi
            if [ "$(cat "$test_dir"/bar)" != "foo" ]; then
                echo "atomic_cp failed to modify destination" >&2
                exit 1
            fi
            if [ "$(ls "$test_dir")" != "$(echo "foo\nbar" | sort)" ]; then
                echo "atomic_cp left temp assets in test_dir" >&2
                exit 1
            fi
        else
            echo "atomic_cp failed unexpectedly" >&2
            exit 1
        fi
    )
}
add_test test_atomic_cp

test_atomic_cp_other_dir() {
    get_tempdir
    test_dir="$last_tempfile"
    mkdir "$test_dir"/foo
    echo bar > "$test_dir"/bar
    echo foo > "$test_dir"/foo/bar
    (
        . "$functions"
        . "$functions_piboot"
        if atomic_cp "$test_dir"/bar "$test_dir"/foo; then
            if [ "$(cat "$test_dir"/bar)" != "bar" ]; then
                echo "atomic_cp altered source" >&2
                exit 1
            fi
            if [ "$(cat "$test_dir"/foo/bar)" != "bar" ]; then
                echo "atomic_cp failed to modify destination" >&2
                exit 1
            fi
            if [ "$(ls "$test_dir")" != "$(echo "foo\nbar" | sort)" ]; then
                echo "atomic_cp left temp assets in test_dir" >&2
                exit 1
            fi
            if [ "$(ls "$test_dir"/foo)" != "bar" ]; then
                echo "atomic_cp left temp assets in test_dir/foo" >&2
                exit 1
            fi
        else
            echo "atomic_cp failed unexpectedly" >&2
            exit 1
        fi
    )
}
add_test test_atomic_cp_other_dir

test_atomic_cp_broken_cp() {
    get_tempdir
    test_dir="$last_tempfile"
    echo foo > "$test_dir"/foo
    echo bar > "$test_dir"/bar
    (
        cp() {
            local src="$1"
            local dest="$2"
            head -c 1 "$src" > "$dest"
            return 1
        }
        . "$functions"
        . "$functions_piboot"
        if atomic_cp "$test_dir"/foo "$test_dir"/bar; then
            echo "atomic_cp did not fail as expected" >&2
            exit 1
        else
            if [ "$(cat "$test_dir"/foo)" != "foo" ]; then
                echo "atomic_cp altered source" >&2
                exit 1
            fi
            if [ "$(cat "$test_dir"/bar)" != "bar" ]; then
                echo "atomic_cp unexpectedly modified destination" >&2
                exit 1
            fi
            if [ "$(ls "$test_dir")" != "$(echo "foo\nbar" | sort)" ]; then
                echo "atomic_cp left temp assets in test_dir" >&2
                exit 1
            fi
        fi
    )
}
add_test test_atomic_cp_broken_cp

test_atomic_cp_broken_mv() {
    get_tempdir
    test_dir="$last_tempfile"
    echo foo > "$test_dir"/foo
    echo bar > "$test_dir"/bar
    (
        mv() {
            return 1
        }
        . "$functions"
        . "$functions_piboot"
        if atomic_cp "$test_dir"/foo "$test_dir"/bar; then
            echo "atomic_cp did not fail as expected" >&2
            exit 1
        else
            if [ "$(cat "$test_dir"/foo)" != "foo" ]; then
                echo "atomic_cp altered source" >&2
                exit 1
            fi
            if [ "$(cat "$test_dir"/bar)" != "bar" ]; then
                echo "atomic_cp unexpectedly modified destination" >&2
                exit 1
            fi
            if [ "$(ls "$test_dir")" != "$(echo "foo\nbar" | sort)" ]; then
                echo "atomic_cp left temp assets in test_dir" >&2
                exit 1
            fi
        fi
    )
}
add_test test_atomic_cp_broken_mv

test_set_state() {
    get_tempdir
    test_dir="$last_tempfile"
    mkdir "$test_dir"/current
    echo good > "$test_dir"/current/state
    (
        . "$functions"
        . "$functions_piboot"
        if set_state "$test_dir" current unknown; then
            if [ "$(cat "$test_dir"/current/state)" != "unknown" ]; then
                echo "set_state failed to alter state" >&2
                exit 1
            fi
        else
            echo "set_state failed unexpectedly" >&2
            exit 1
        fi
    )
}
add_test test_set_state

test_set_state_broken_echo() {
    get_tempdir
    test_dir="$last_tempfile"
    mkdir "$test_dir"/current
    echo good > "$test_dir"/current/state
    (
        bad_echo() {
            return 1
        }
        alias echo=bad_echo
        . "$functions"
        . "$functions_piboot"
        if set_state "$test_dir" current unknown; then
            unalias echo
            echo "set_state did not fail as expected" >&2
            exit 1
        else
            unalias echo
            if [ "$(cat "$test_dir"/current/state)" != "good" ]; then
                echo "set_state altered current/state unexpectedly" >&2
                exit 1
            fi
            if [ "$(ls "$test_dir"/current)" != "state" ]; then
                echo "set_state left temp assets in test_dir" >&2
                exit 1
            fi
        fi
    )
}
add_test test_set_state_broken_echo

test_set_state_broken_mv() {
    get_tempdir
    test_dir="$last_tempfile"
    mkdir "$test_dir"/current
    echo good > "$test_dir"/current/state
    (
        mv() {
            return 1
        }
        . "$functions"
        . "$functions_piboot"
        if set_state "$test_dir" current unknown; then
            echo "set_state did not fail as expected" >&2
            exit 1
        else
            if [ "$(cat "$test_dir"/current/state)" != "good" ]; then
                echo "set_state altered current/state unexpectedly" >&2
                exit 1
            fi
            if [ "$(ls "$test_dir"/current)" != "state" ]; then
                echo "set_state left temp assets in test_dir" >&2
                exit 1
            fi
        fi
        export rc
    )
}
add_test test_set_state_broken_mv

test_needs_migrate() {
    get_tempdir
    bootloader_dir="$last_tempfile"
    # Set up $bootloader_dir like a pre-migration state
    mock_bootloader_assets "$bootloader_dir"
    mock_bootloader_assets "$bootloader_dir" ".bak"
    mock_kernel_assets "$bootloader_dir"
    mock_kernel_assets "$bootloader_dir" ".bak"
    mock_device_trees "$bootloader_dir"
    mock_device_trees "$bootloader_dir" ".bak"
    mock_device_tree_overlays "$bootloader_dir"
    mock_device_tree_overlays "$bootloader_dir" ".bak"
    rm "$bootloader_dir"/config.txt.bak
    rm "$bootloader_dir"/cmdline.txt.bak
    (
        . "$functions"
        . "$functions_piboot"
        if ! needs_migrate "$bootloader_dir"; then
            echo "needs_migrate unexpectedly returned false" >&2
            exit 1
        fi
    )
}
add_test test_needs_migrate

test_migrate() {
    get_tempdir
    bootloader_dir="$last_tempfile"
    # Set up $bootloader_dir like a pre-migration state
    mock_bootloader_assets "$bootloader_dir"
    mock_bootloader_assets "$bootloader_dir" ".bak"
    mock_kernel_assets "$bootloader_dir"
    mock_kernel_assets "$bootloader_dir" ".bak"
    mock_device_trees "$bootloader_dir"
    mock_device_trees "$bootloader_dir" ".bak"
    mock_device_tree_overlays "$bootloader_dir"
    mock_device_tree_overlays "$bootloader_dir" ".bak"
    rm "$bootloader_dir"/config.txt.bak
    rm "$bootloader_dir"/cmdline.txt.bak
    (
        sync() {
            # Keep the tests quick
            return 0
        }
        findmnt() {
            echo /dev/mmcblk0p10
            return 0
        }
        mount() {
            mount_what="$1"
            mount_where="$2"
            cat > "$mount_where"/autoboot.txt << EOF
[all]
partition=1
EOF
            # Both mount and umount will execute in a sub-shell, so we need
            # some we can't just read their variables
            echo "$mount_what" > "$bootloader_dir"/env
            return 0
        }
        umount() {
            mv "$1"/autoboot.txt "$bootloader_dir"
            return 0
        }
        . "$functions"
        . "$functions_piboot"
        migrate "$bootloader_dir" 2>/dev/null
        if [ -e "$bootloader_dir"/*.bak ]; then
            echo "migrate left backup files in the bootloader dir" >&2
            ls "$bootloader_dir"
            exit 1
        fi
        if ! [ -d "$bootloader_dir"/current ]; then
            echo "migrate failed to create current/ directory" >&2
            exit 1
        fi
        if ! grep -q "os_prefix=current/" "$bootloader_dir"/config.txt; then
            echo "migrate failed to update config.txt" >&2
            exit 1
        fi
        if ! grep -q "os_prefix=new/" "$bootloader_dir"/config.txt; then
            echo "migrate failed to update config.txt" >&2
            exit 1
        fi
        mount_what="$(cat "$bootloader_dir"/env)"
        if [ "$mount_what" != "/dev/mmcblk0p1" ]; then
            echo "mount_what = x${mount_what}x" >&2
            echo "autoboot migration failed to mount first partition" >&2
            exit 1
        fi
        if ! grep -q "tryboot_a_b=1" "$bootloader_dir"/autoboot.txt; then
            echo "migrate failed to manipulate autoboot.txt" >&2
            exit 1
        fi
        if needs_migrate "$bootloader_dir"; then
            echo "needs_migrate unexpectedly returned true after migrate" >&2
            exit 1
        fi
    )
}
add_test test_migrate

test_boot_service_reboot() {
    get_tempdir
    bootloader_dir="$last_tempfile"
    # Set up $bootloader_dir like a post-migration state, with new assets to
    # test
    mock_bootloader_assets "$bootloader_dir"
    mock_new_config "$bootloader_dir"
    mock_bootloader_assets "$bootloader_dir"/current
    rm "$bootloader_dir"/current/config.txt
    mock_kernel_assets "$bootloader_dir"/current
    mock_device_trees "$bootloader_dir"/current
    mock_device_tree_overlays "$bootloader_dir"/current
    mock_bootloader_assets "$bootloader_dir"/new
    echo "good" > "$bootloader_dir"/current/state
    rm "$bootloader_dir"/new/config.txt
    mock_kernel_assets "$bootloader_dir"/new
    mock_device_trees "$bootloader_dir"/new
    mock_device_tree_overlays "$bootloader_dir"/new
    echo "unknown" > "$bootloader_dir"/new/state
    # Add some content to the kernel assets to test with
    echo old > "$bootloader_dir"/current/vmlinuz
    echo new > "$bootloader_dir"/new/vmlinuz

    get_tempdir
    FK_PROC_DT_BOOTLOADER="$last_tempfile"
    # Set up /proc/device-tree/chosen/bootloader/tryboot with a "normal boot"
    # state
    printf '\000\000\000\000' > "$FK_PROC_DT_BOOTLOADER"/tryboot
    printf '\000\000\000\012' > "$FK_PROC_DT_BOOTLOADER"/partition
    (
        sync() {
            # Keep the tests quick
            return 0
        }
        reboot() {
            reboot_args="$1"
            return 0
        }
        . "$functions"
        . "$functions_piboot"
        if ! boot_service_reboot "$bootloader_dir" 2>/dev/null; then
            echo "boot_service_reboot failed unexpectedly" >&2
            exit 1
        else
            if [ "$(cat "$bootloader_dir"/new/state)" != "trying" ]; then
                echo "boot_service_reboot did not adjust new/state" >&2
                exit 1
            fi
            if [ "$reboot_args" != "10 tryboot" ]; then
                echo "boot_service_reboot did not call reboot with tryboot" >&2
                exit 1
            fi
            if [ "$(cat "$bootloader_dir"/current/vmlinuz)" != "old" ]; then
                echo "old kernel is not old after boot_service_reboot" >&2
                exit 1
            fi
            if [ "$(cat "$bootloader_dir"/new/vmlinuz)" != "new" ]; then
                echo "new kernel is not new after boot_service_reboot" >&2
                exit 1
            fi
        fi
    )
}
add_test test_boot_service_reboot

test_boot_service_reboot_bad() {
    get_tempdir
    bootloader_dir="$last_tempfile"
    # Set up $bootloader_dir like a post-migration state, with new assets set
    # up as though during a boot test
    mock_bootloader_assets "$bootloader_dir"
    cp "$bootloader_dir"/config.txt "$bootloader_dir"/config.orig
    mock_new_config "$bootloader_dir" "new"
    mv "$bootloader_dir"/config.txt "$bootloader_dir"/tryboot.txt
    mv "$bootloader_dir"/config.orig "$bootloader_dir"/config.txt
    mock_bootloader_assets "$bootloader_dir"/current
    rm "$bootloader_dir"/current/config.txt
    mock_kernel_assets "$bootloader_dir"/current
    mock_device_trees "$bootloader_dir"/current
    mock_device_tree_overlays "$bootloader_dir"/current
    mock_bootloader_assets "$bootloader_dir"/new
    echo "good" > "$bootloader_dir"/current/state
    rm "$bootloader_dir"/new/config.txt
    mock_kernel_assets "$bootloader_dir"/new
    mock_device_trees "$bootloader_dir"/new
    mock_device_tree_overlays "$bootloader_dir"/new
    echo "trying" > "$bootloader_dir"/new/state
    # Add some content to the kernel assets to test with
    echo old > "$bootloader_dir"/current/vmlinuz
    echo new > "$bootloader_dir"/new/vmlinuz

    get_tempdir
    FK_PROC_DT_BOOTLOADER="$last_tempfile"
    # Set up /proc/device-tree/chosen/bootloader/tryboot with a tryboot active
    # state
    printf '\000\000\000\000' > "$FK_PROC_DT_BOOTLOADER"/tryboot
    printf '\000\000\000\001' > "$FK_PROC_DT_BOOTLOADER"/partition
    (
        sync() {
            # Keep the tests quick
            return 0
        }
        reboot() {
            reboot_args="$1"
            # This should not be called by this test
            return 1
        }
        . "$functions"
        . "$functions_piboot"
        # Marking "bad" is actually handled by the early service to avoid a
        # race condition (see comments in boot_service_reboot function)
        if ! boot_service_reboot "$bootloader_dir" 2>/dev/null; then
            echo "boot_service_reboot failed unexpectedly" >&2
            exit 1
        else
            if ! [ -d "$bootloader_dir"/new ]; then
                echo "new directory was moved/removed" >&2
                exit 1
            fi
            if [ -d "$bootloader_dir"/old ]; then
                echo "old directory was created" >&2
                exit 1
            fi
            if [ "$(cat "$bootloader_dir"/new/state)" != "bad" ]; then
                echo "new/state is not good" >&2
                exit 1
            fi
            if [ "$(cat "$bootloader_dir"/current/state)" != "good" ]; then
                echo "current/state is not good" >&2
                exit 1
            fi
            if [ "$reboot_args" != "" ]; then
                echo "boot_service_reboot called reboot unexpectedly" >&2
                exit 1
            fi
        fi
    )
}
add_test test_boot_service_reboot_bad

test_boot_service_validate_good() {
    get_tempdir
    bootloader_dir="$last_tempfile"
    # Set up $bootloader_dir like a post-migration state, with new assets set
    # up as though during a boot test
    mock_bootloader_assets "$bootloader_dir"
    cp "$bootloader_dir"/config.txt "$bootloader_dir"/config.orig
    mock_new_config "$bootloader_dir" "new"
    mv "$bootloader_dir"/config.txt "$bootloader_dir"/tryboot.txt
    mv "$bootloader_dir"/config.orig "$bootloader_dir"/config.txt
    mock_bootloader_assets "$bootloader_dir"/current
    rm "$bootloader_dir"/current/config.txt
    mock_kernel_assets "$bootloader_dir"/current
    mock_device_trees "$bootloader_dir"/current
    mock_device_tree_overlays "$bootloader_dir"/current
    mock_bootloader_assets "$bootloader_dir"/new
    echo "good" > "$bootloader_dir"/current/state
    rm "$bootloader_dir"/new/config.txt
    mock_kernel_assets "$bootloader_dir"/new
    mock_device_trees "$bootloader_dir"/new
    mock_device_tree_overlays "$bootloader_dir"/new
    echo "trying" > "$bootloader_dir"/new/state
    # Add some content to the kernel assets to test with
    echo old > "$bootloader_dir"/current/vmlinuz
    echo new > "$bootloader_dir"/new/vmlinuz

    get_tempdir
    FK_PROC_DT_BOOTLOADER="$last_tempfile"
    FK_ETC_VALIDATE="${FK_CHECKOUT}/etc/piboot-validate"
    # Set up /proc/device-tree/chosen/bootloader/tryboot with a tryboot active
    # state
    printf '\000\000\000\001' > "$FK_PROC_DT_BOOTLOADER"/tryboot
    printf '\000\000\000\001' > "$FK_PROC_DT_BOOTLOADER"/partition
    (
        sync() {
            # Keep the tests quick
            return 0
        }
        reboot() {
            reboot_args="$1"
            # This should not be called by this test
            return 1
        }
        . "$functions"
        . "$functions_piboot"
        if ! boot_service_validate "$bootloader_dir" 2>/dev/null; then
            echo "boot_service_validate failed unexpectedly" >&2
            exit 1
        else
            if [ -d "$bootloader_dir"/new ]; then
                echo "new directory was not moved/removed" >&2
                exit 1
            fi
            if ! [ -d "$bootloader_dir"/old ]; then
                echo "old directory was not created" >&2
                exit 1
            fi
            if [ "$(cat "$bootloader_dir"/old/state)" != "good" ]; then
                echo "old/state is not good" >&2
                exit 1
            fi
            if [ "$(cat "$bootloader_dir"/current/state)" != "good" ]; then
                echo "current/state is not good" >&2
                exit 1
            fi
            if [ "$reboot_args" != "" ]; then
                echo "boot_service_validate called reboot unexpectedly" >&2
                exit 1
            fi
        fi
    )
}
add_test test_boot_service_validate_good

test_piboot_try_help() {
    get_tempfile
    output="$last_tempfile"
    (
        . "$functions"
        . "$functions_piboot"

        piboot_try --help | head -n 1 > "$output"
        if ! grep -q "^Usage:" "$output"; then
            echo "piboot-try --help output did not start with Usage:"
            exit 1
        fi
    )
}
add_test test_piboot_try_help

test_piboot_try_bad_ops() {
    get_tempfile
    (
        . "$functions"
        . "$functions_piboot"

        if piboot_try --test --validate 2>/dev/null; then
            echo "piboot-try succeeded when it should not"
            exit 1
        fi
    )
}

test_main

# vim:syntax=sh
