debian-forge-cli/pkg/progress/progress_test.go
Michael Vogt 958f95f634 progress: set --cache-max-size in osbuild
This commit allows controlling the `osbuild --cache-max-size`
option. By default it will set the cache to 20GiB but allows
this to be controlled by the user.

Thanks to Simon for raising this.
2025-04-01 11:30:44 +00:00

283 lines
7.4 KiB
Go

package progress_test
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"reflect"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/osbuild/image-builder-cli/pkg/progress"
)
func TestProgressNew(t *testing.T) {
for _, tc := range []struct {
typ string
expected interface{}
expectedErr string
}{
{"term", &progress.TerminalProgressBar{}, ""},
{"debug", &progress.DebugProgressBar{}, ""},
{"verbose", &progress.VerboseProgressBar{}, ""},
// unknown progress type
{"bad", nil, `unknown progress type: "bad"`},
} {
pb, err := progress.New(tc.typ)
if tc.expectedErr == "" {
assert.NoError(t, err)
assert.Equal(t, reflect.TypeOf(pb), reflect.TypeOf(tc.expected), fmt.Sprintf("[%v] %T not the expected %T", tc.typ, pb, tc.expected))
} else {
assert.EqualError(t, err, tc.expectedErr)
}
}
}
func TestVerboseProgress(t *testing.T) {
var buf bytes.Buffer
restore := progress.MockOsStderr(&buf)
defer restore()
// verbose progress never generates progress output
pbar, err := progress.NewVerboseProgressBar()
assert.NoError(t, err)
err = pbar.SetProgress(0, "set-progress", 1, 100)
assert.NoError(t, err)
assert.Equal(t, "", buf.String())
// but it shows the messages
pbar.SetPulseMsgf("pulse")
assert.Equal(t, "pulse\n", buf.String())
buf.Reset()
pbar.SetMessagef("message")
assert.Equal(t, "message\n", buf.String())
buf.Reset()
pbar.Start()
assert.Equal(t, "", buf.String())
pbar.Stop()
assert.Equal(t, "", buf.String())
}
func TestDebugProgress(t *testing.T) {
var buf bytes.Buffer
restore := progress.MockOsStderr(&buf)
defer restore()
pbar, err := progress.NewDebugProgressBar()
assert.NoError(t, err)
err = pbar.SetProgress(0, "set-progress-msg", 1, 100)
assert.NoError(t, err)
assert.Equal(t, "[1 / 100] set-progress-msg\n", buf.String())
buf.Reset()
pbar.SetPulseMsgf("pulse-msg")
assert.Equal(t, "pulse: pulse-msg\n", buf.String())
buf.Reset()
pbar.SetMessagef("some-message")
assert.Equal(t, "msg: some-message\n", buf.String())
buf.Reset()
pbar.Start()
assert.Equal(t, "Start progressbar\n", buf.String())
buf.Reset()
pbar.Stop()
assert.Equal(t, "Stop progressbar\n", buf.String())
buf.Reset()
}
func TestTermProgress(t *testing.T) {
var buf bytes.Buffer
restore := progress.MockOsStderr(&buf)
defer restore()
pbar, err := progress.NewTerminalProgressBar()
assert.NoError(t, err)
pbar.Start()
pbar.SetPulseMsgf("pulse-msg")
pbar.SetMessagef("some-message")
err = pbar.SetProgress(0, "set-progress-msg", 0, 5)
assert.NoError(t, err)
pbar.Stop()
assert.NoError(t, pbar.(*progress.TerminalProgressBar).Err())
assert.Contains(t, buf.String(), "[1 / 6] set-progress-msg")
assert.Contains(t, buf.String(), "[|] pulse-msg\n")
assert.Contains(t, buf.String(), "Message: some-message\n")
// check shutdown
assert.Contains(t, buf.String(), progress.CURSOR_SHOW)
}
func TestProgressNewAutoselect(t *testing.T) {
for _, tc := range []struct {
onTerm bool
expected interface{}
}{
{false, &progress.VerboseProgressBar{}},
{true, &progress.TerminalProgressBar{}},
} {
restore := progress.MockIsattyIsTerminal(func(uintptr) bool {
return tc.onTerm
})
defer restore()
pb, err := progress.New("auto")
assert.NoError(t, err)
assert.Equal(t, reflect.TypeOf(pb), reflect.TypeOf(tc.expected), fmt.Sprintf("[%v] %T not the expected %T", tc.onTerm, pb, tc.expected))
}
}
func makeFakeOsbuild(t *testing.T, content string) string {
p := filepath.Join(t.TempDir(), "fake-osbuild")
err := os.WriteFile(p, []byte("#!/bin/sh\n"+content), 0755)
assert.NoError(t, err)
return p
}
func TestRunOSBuildWithProgressErrorReporting(t *testing.T) {
restore := progress.MockOsStderr(io.Discard)
defer restore()
restore = progress.MockOsbuildCmd(makeFakeOsbuild(t, `
>&3 echo '{"message": "osbuild-stage-message"}'
echo osbuild-stdout-output
>&2 echo osbuild-stderr-output
exit 112
`))
defer restore()
pbar, err := progress.New("debug")
assert.NoError(t, err)
err = progress.RunOSBuild(pbar, []byte(`{"fake":"manifest"}`), nil, nil)
assert.EqualError(t, err, `error running osbuild: exit status 112
BuildLog:
osbuild-stage-message
Output:
osbuild-stdout-output
osbuild-stderr-output
`)
}
func TestRunOSBuildWithProgressIncorrectJSON(t *testing.T) {
signalDeliveredMarkerPath := filepath.Join(t.TempDir(), "sigint-delivered")
restore := progress.MockOsbuildCmd(makeFakeOsbuild(t, fmt.Sprintf(`
trap 'touch "%s";exit 2' INT
>&3 echo invalid-json
# we cannot sleep infinity here or the shell script trap is never run
while true; do
sleep 0.1
done
`, signalDeliveredMarkerPath)))
defer restore()
pbar, err := progress.New("debug")
assert.NoError(t, err)
err = progress.RunOSBuild(pbar, []byte(`{"fake":"manifest"}`), nil, nil)
assert.EqualError(t, err, `error parsing osbuild status, please report a bug and try with "--progress=verbose": cannot scan line "invalid-json": invalid character 'i' looking for beginning of value`)
// ensure the SIGINT got delivered
var pathExists = func(p string) bool {
_, err := os.Stat(p)
return err == nil
}
for i := 0; i < 20; i++ {
time.Sleep(100 * time.Millisecond)
if pathExists(signalDeliveredMarkerPath) {
break
}
}
assert.True(t, pathExists(signalDeliveredMarkerPath))
}
func TestRunOSBuildWithBuildlogTerm(t *testing.T) {
restore := progress.MockOsbuildCmd(makeFakeOsbuild(t, `
echo osbuild-stdout-output
>&2 echo osbuild-stderr-output
# without the sleep this is racy as two different go routines poll
# this does not matter (much) in practise because osbuild output and
# stage output are using the syncedMultiWriter so output is not garbled
sleep 0.1
>&3 echo '{"message": "osbuild-stage-message"}'
`))
defer restore()
var fakeStdout, fakeStderr bytes.Buffer
restore = progress.MockOsStdout(&fakeStdout)
defer restore()
restore = progress.MockOsStderr(&fakeStderr)
defer restore()
pbar, err := progress.New("term")
assert.NoError(t, err)
var buildLog bytes.Buffer
opts := &progress.OSBuildOptions{
BuildLog: &buildLog,
}
err = progress.RunOSBuild(pbar, []byte(`{"fake":"manifest"}`), nil, opts)
assert.NoError(t, err)
expectedOutput := `osbuild-stdout-output
osbuild-stderr-output
osbuild-stage-message
`
assert.Equal(t, expectedOutput, buildLog.String())
}
func TestRunOSBuildWithBuildlogVerbose(t *testing.T) {
restore := progress.MockOsbuildCmd(makeFakeOsbuild(t, `
echo osbuild-stdout-output
>&2 echo osbuild-stderr-output
`))
defer restore()
var fakeStdout, fakeStderr bytes.Buffer
restore = progress.MockOsStdout(&fakeStdout)
defer restore()
restore = progress.MockOsStderr(&fakeStderr)
defer restore()
pbar, err := progress.New("verbose")
assert.NoError(t, err)
var buildLog bytes.Buffer
opts := &progress.OSBuildOptions{
BuildLog: &buildLog,
}
err = progress.RunOSBuild(pbar, []byte(`{"fake":"manifest"}`), nil, opts)
assert.NoError(t, err)
expectedOutput := `osbuild-stdout-output
osbuild-stderr-output
`
assert.Equal(t, expectedOutput, buildLog.String())
}
func TestRunOSBuildCacheMaxSize(t *testing.T) {
fakeOsbuildBinary := makeFakeOsbuild(t, `echo "$@" > "$0".cmdline`)
restore := progress.MockOsbuildCmd(fakeOsbuildBinary)
defer restore()
pbar, err := progress.New("debug")
assert.NoError(t, err)
osbuildOpts := &progress.OSBuildOptions{
CacheMaxSize: 77,
}
err = progress.RunOSBuild(pbar, []byte(`{"fake":"manifest"}`), nil, osbuildOpts)
assert.NoError(t, err)
cmdline, err := os.ReadFile(fakeOsbuildBinary + ".cmdline")
assert.NoError(t, err)
assert.Contains(t, string(cmdline), "--cache-max-size=77")
}