247 lines
9.8 KiB
Go
247 lines
9.8 KiB
Go
|
|
package main
|
||
|
|
|
||
|
|
import (
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestVerifyFlashDestinationForImageFilesAndBlockDevices(t *testing.T) {
|
||
|
|
t.Run("image file success and helpers", func(t *testing.T) {
|
||
|
|
tools := fakeCommandDir(t, map[string]string{
|
||
|
|
"sfdisk": `cat <<'JSON'
|
||
|
|
{"partitiontable":{"partitions":[{"type":"ef00"},{"type":"8300"}]}}
|
||
|
|
JSON`,
|
||
|
|
})
|
||
|
|
t.Setenv("PATH", tools+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||
|
|
|
||
|
|
result, err := verifyFlashDestination(filepath.Join(t.TempDir(), "titan-15.img"))
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("verifyFlashDestination(image): %v", err)
|
||
|
|
}
|
||
|
|
if !result.Verified || result.VerificationKind != "image-file" {
|
||
|
|
t.Fatalf("unexpected image verification result: %#v", result)
|
||
|
|
}
|
||
|
|
if !isBootPartitionType("ef00") || !isLinuxPartitionType("8300") {
|
||
|
|
t.Fatal("expected helper partition type recognizers to accept GPT aliases")
|
||
|
|
}
|
||
|
|
if got := firstNonEmpty("", " writable ", "ignored"); got != "writable" {
|
||
|
|
t.Fatalf("firstNonEmpty = %q", got)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("image file missing writable partition", func(t *testing.T) {
|
||
|
|
tools := fakeCommandDir(t, map[string]string{
|
||
|
|
"sfdisk": `cat <<'JSON'
|
||
|
|
{"partitiontable":{"partitions":[{"type":"ef00"}]}}
|
||
|
|
JSON`,
|
||
|
|
})
|
||
|
|
t.Setenv("PATH", tools+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||
|
|
|
||
|
|
if _, err := verifyImageFileFlash(filepath.Join(t.TempDir(), "broken.img")); err == nil || !strings.Contains(err.Error(), "boot and writable partitions") {
|
||
|
|
t.Fatalf("expected missing partition error, got %v", err)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("image file missing boot partition", func(t *testing.T) {
|
||
|
|
tools := fakeCommandDir(t, map[string]string{
|
||
|
|
"sfdisk": `cat <<'JSON'
|
||
|
|
{"partitiontable":{"partitions":[{"type":"8300"}]}}
|
||
|
|
JSON`,
|
||
|
|
})
|
||
|
|
t.Setenv("PATH", tools+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||
|
|
|
||
|
|
if _, err := verifyImageFileFlash(filepath.Join(t.TempDir(), "broken-boot.img")); err == nil || !strings.Contains(err.Error(), "boot and writable partitions") {
|
||
|
|
t.Fatalf("expected missing boot partition error, got %v", err)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("block device success", func(t *testing.T) {
|
||
|
|
tools := fakeCommandDir(t, map[string]string{
|
||
|
|
"lsblk": `cat <<'JSON'
|
||
|
|
{"blockdevices":[{"path":"/dev/sdk","children":[{"path":"/dev/sdk1","type":"part","fstype":"vfat","label":"system-boot"},{"path":"/dev/sdk2","type":"part","fstype":"ext4","label":"writable"}]}]}
|
||
|
|
JSON`,
|
||
|
|
"mount": `mount_dir="${4:-}"
|
||
|
|
mkdir -p "${mount_dir}"
|
||
|
|
printf 'ok' > "${mount_dir}/config.txt"
|
||
|
|
printf 'console=ttyAMA0' > "${mount_dir}/cmdline.txt"
|
||
|
|
printf 'boot script' > "${mount_dir}/boot.scr"`,
|
||
|
|
"umount": `exit 0`,
|
||
|
|
"blockdev": `exit 0`,
|
||
|
|
"partprobe": `exit 0`,
|
||
|
|
"udevadm": `exit 0`,
|
||
|
|
})
|
||
|
|
t.Setenv("PATH", tools+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||
|
|
|
||
|
|
result, err := verifyFlashDestination("/dev/sdk")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("verifyFlashDestination(block): %v", err)
|
||
|
|
}
|
||
|
|
if !result.Verified || result.VerificationKind != "block-device" {
|
||
|
|
t.Fatalf("unexpected block-device verification result: %#v", result)
|
||
|
|
}
|
||
|
|
if result.BootPartition != "/dev/sdk1" || result.RootPartition != "/dev/sdk2" {
|
||
|
|
t.Fatalf("unexpected partition details: %#v", result)
|
||
|
|
}
|
||
|
|
if len(result.CheckedFiles) != 3 {
|
||
|
|
t.Fatalf("expected checked boot files, got %#v", result.CheckedFiles)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("block device missing boot file", func(t *testing.T) {
|
||
|
|
tools := fakeCommandDir(t, map[string]string{
|
||
|
|
"lsblk": `cat <<'JSON'
|
||
|
|
{"blockdevices":[{"path":"/dev/sdk","children":[{"path":"/dev/sdk1","type":"part","fstype":"vfat","label":"system-boot"},{"path":"/dev/sdk2","type":"part","fstype":"ext4","label":"writable"}]}]}
|
||
|
|
JSON`,
|
||
|
|
"mount": `mount_dir="${4:-}"
|
||
|
|
mkdir -p "${mount_dir}"
|
||
|
|
printf 'ok' > "${mount_dir}/config.txt"
|
||
|
|
printf 'console=ttyAMA0' > "${mount_dir}/cmdline.txt"`,
|
||
|
|
"umount": `exit 0`,
|
||
|
|
"blockdev": `exit 0`,
|
||
|
|
"partprobe": `exit 0`,
|
||
|
|
"udevadm": `exit 0`,
|
||
|
|
})
|
||
|
|
t.Setenv("PATH", tools+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||
|
|
|
||
|
|
if _, err := verifyBlockDeviceFlash("/dev/sdk"); err == nil || !strings.Contains(err.Error(), "missing boot.scr") {
|
||
|
|
t.Fatalf("expected missing boot file error, got %v", err)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestFlushFlashTargetAndClassifyHelpers(t *testing.T) {
|
||
|
|
logPath := filepath.Join(t.TempDir(), "commands.log")
|
||
|
|
tools := fakeCommandDir(t, map[string]string{
|
||
|
|
"sync": `printf 'sync %s\n' "$*" >> "` + logPath + `"`,
|
||
|
|
"blockdev": `printf 'blockdev %s\n' "$*" >> "` + logPath + `"`,
|
||
|
|
"partprobe": `printf 'partprobe %s\n' "$*" >> "` + logPath + `"`,
|
||
|
|
"udevadm": `printf 'udevadm %s\n' "$*" >> "` + logPath + `"`,
|
||
|
|
})
|
||
|
|
t.Setenv("PATH", tools+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||
|
|
|
||
|
|
flushFlashTarget("/tmp/test.img", "titan-15")
|
||
|
|
flushFlashTarget("/dev/sdk", "titan-15")
|
||
|
|
|
||
|
|
logged, err := os.ReadFile(logPath)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("read flush log: %v", err)
|
||
|
|
}
|
||
|
|
text := string(logged)
|
||
|
|
if strings.Count(text, "sync ") != 2 {
|
||
|
|
t.Fatalf("expected sync for file and block targets, got %q", text)
|
||
|
|
}
|
||
|
|
for _, want := range []string{"blockdev --flushbufs /dev/sdk", "blockdev --rereadpt /dev/sdk", "partprobe /dev/sdk", "udevadm settle --timeout=10"} {
|
||
|
|
if !strings.Contains(text, want) {
|
||
|
|
t.Fatalf("expected %q in %q", want, text)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if _, _, err := classifyFlashPartitions([]flashBlockDevice{{Path: "/dev/sdk1", Type: "part", FSType: "vfat", Label: "system-boot"}}); err == nil || !strings.Contains(err.Error(), "writable ext4") {
|
||
|
|
t.Fatalf("expected classify failure, got %v", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestReadFlashPartitionTableAndBlockPartitionErrors(t *testing.T) {
|
||
|
|
t.Run("partition table decode error", func(t *testing.T) {
|
||
|
|
tools := fakeCommandDir(t, map[string]string{"sfdisk": `printf '{'`})
|
||
|
|
t.Setenv("PATH", tools+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||
|
|
if _, err := readFlashPartitionTable(filepath.Join(t.TempDir(), "broken.img")); err == nil || !strings.Contains(err.Error(), "decode partition table") {
|
||
|
|
t.Fatalf("expected partition table decode error, got %v", err)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("lsblk decode error", func(t *testing.T) {
|
||
|
|
tools := fakeCommandDir(t, map[string]string{"lsblk": `printf '{'`})
|
||
|
|
t.Setenv("PATH", tools+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||
|
|
if _, err := readBlockDevicePartitions("/dev/sdk"); err == nil || !strings.Contains(err.Error(), "decode lsblk output") {
|
||
|
|
t.Fatalf("expected lsblk decode error, got %v", err)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("lsblk finds explicit device match", func(t *testing.T) {
|
||
|
|
tools := fakeCommandDir(t, map[string]string{
|
||
|
|
"lsblk": `cat <<'JSON'
|
||
|
|
{"blockdevices":[{"path":"/dev/other","children":[{"path":"/dev/other1","type":"part"}]},{"path":"/dev/sdk","children":[{"path":"/dev/sdk1","type":"part","fstype":"vfat","label":"system-boot"}]}]}
|
||
|
|
JSON`,
|
||
|
|
})
|
||
|
|
t.Setenv("PATH", tools+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||
|
|
parts, err := readBlockDevicePartitions("/dev/sdk")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("readBlockDevicePartitions match: %v", err)
|
||
|
|
}
|
||
|
|
if len(parts) != 1 || parts[0].Path != "/dev/sdk1" {
|
||
|
|
t.Fatalf("unexpected matching partitions: %#v", parts)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("lsblk missing device match", func(t *testing.T) {
|
||
|
|
tools := fakeCommandDir(t, map[string]string{
|
||
|
|
"lsblk": `cat <<'JSON'
|
||
|
|
{"blockdevices":[{"path":"/dev/other","children":[]},{"path":"/dev/another","children":[]}]}
|
||
|
|
JSON`,
|
||
|
|
})
|
||
|
|
t.Setenv("PATH", tools+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||
|
|
if _, err := readBlockDevicePartitions("/dev/sdk"); err == nil || !strings.Contains(err.Error(), "did not report partitions") {
|
||
|
|
t.Fatalf("expected missing block-device match error, got %v", err)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestVerifyBootPartitionFilesMountFailure(t *testing.T) {
|
||
|
|
tools := fakeCommandDir(t, map[string]string{
|
||
|
|
"mount": `printf 'mount failed' >&2; exit 9`,
|
||
|
|
})
|
||
|
|
t.Setenv("PATH", tools+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||
|
|
if _, err := verifyBootPartitionFiles("/dev/sdk1"); err == nil || !strings.Contains(err.Error(), "mount /dev/sdk1 read-only") {
|
||
|
|
t.Fatalf("expected mount failure, got %v", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestRemoteFlashHelperAdditionalFailureBranches(t *testing.T) {
|
||
|
|
t.Run("partition table command failure", func(t *testing.T) {
|
||
|
|
tools := fakeCommandDir(t, map[string]string{"sfdisk": `printf 'bad disk' >&2; exit 7`})
|
||
|
|
t.Setenv("PATH", tools+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||
|
|
if _, err := readFlashPartitionTable(filepath.Join(t.TempDir(), "broken.img")); err == nil || !strings.Contains(err.Error(), "sfdisk -J") {
|
||
|
|
t.Fatalf("expected sfdisk failure, got %v", err)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("lsblk command failure", func(t *testing.T) {
|
||
|
|
tools := fakeCommandDir(t, map[string]string{"lsblk": `printf 'lsblk failed' >&2; exit 4`})
|
||
|
|
t.Setenv("PATH", tools+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||
|
|
if _, err := readBlockDevicePartitions("/dev/sdk"); err == nil || !strings.Contains(err.Error(), "lsblk /dev/sdk") {
|
||
|
|
t.Fatalf("expected lsblk command failure, got %v", err)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("boot partition classification failure", func(t *testing.T) {
|
||
|
|
if _, _, err := classifyFlashPartitions([]flashBlockDevice{{Path: "/dev/sdk2", Type: "part", FSType: "ext4", Label: "writable"}}); err == nil || !strings.Contains(err.Error(), "boot partition") {
|
||
|
|
t.Fatalf("expected missing boot partition error, got %v", err)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("firstNonEmpty returns empty when every value is blank", func(t *testing.T) {
|
||
|
|
if got := firstNonEmpty("", " "); got != "" {
|
||
|
|
t.Fatalf("expected empty firstNonEmpty result, got %q", got)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestReadBlockDevicePartitionsFallsBackToSingleReturnedDevice(t *testing.T) {
|
||
|
|
tools := fakeCommandDir(t, map[string]string{
|
||
|
|
"lsblk": `cat <<'JSON'
|
||
|
|
{"blockdevices":[{"path":"/dev/mmcblk0","children":[{"path":"/dev/mmcblk0p1","type":"part","fstype":"vfat","label":"system-boot"}]}]}
|
||
|
|
JSON`,
|
||
|
|
})
|
||
|
|
t.Setenv("PATH", tools+string(os.PathListSeparator)+os.Getenv("PATH"))
|
||
|
|
parts, err := readBlockDevicePartitions("/dev/sdk")
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("readBlockDevicePartitions fallback: %v", err)
|
||
|
|
}
|
||
|
|
if len(parts) != 1 || parts[0].Path != "/dev/mmcblk0p1" {
|
||
|
|
t.Fatalf("unexpected fallback partitions: %#v", parts)
|
||
|
|
}
|
||
|
|
}
|