apptest/tests: only flush component logs on test failure (#9491)

Update tests to only print component output in integration tests if the
test case is failing.

An example of pipeline failure:
https://github.com/VictoriaMetrics/VictoriaMetrics/actions/runs/16473633711/job/46569913168?pr=9491

---------

Signed-off-by: Zakhar Bessarab <z.bessarab@victoriametrics.com>
This commit is contained in:
Zakhar Bessarab
2025-08-01 16:21:14 +04:00
committed by GitHub
parent c1cde61f20
commit 8f6b7c2e8c
11 changed files with 87 additions and 27 deletions

View File

@@ -32,6 +32,7 @@ type app struct {
flags []string
process *os.Process
wait bool
output io.Writer
}
// appOptions holds the optional configuration of an app, such as default flags
@@ -40,6 +41,7 @@ type appOptions struct {
defaultFlags map[string]string
extractREs []*regexp.Regexp
wait bool
output io.Writer
}
// startApp starts an instance of an app using the app binary file path and
@@ -70,15 +72,21 @@ func startApp(instance string, binary string, flags []string, opts *appOptions)
return nil, nil, err
}
output := opts.output
if output == nil {
output = os.Stderr
}
app := &app{
instance: instance,
binary: binary,
flags: flags,
process: cmd.Process,
wait: opts.wait,
output: output,
}
go app.processOutput("stdout", stdout, app.writeToStderr)
go app.processOutput("stdout", stdout, app.writeToOutput)
lineProcessors := make([]lineProcessor, len(opts.extractREs))
reExtractors := make([]*reExtractor, len(opts.extractREs))
@@ -87,7 +95,7 @@ func startApp(instance string, binary string, flags []string, opts *appOptions)
reExtractors[i] = newREExtractor(re, timeout)
lineProcessors[i] = reExtractors[i].extractRE
}
go app.processOutput("stderr", stderr, append(lineProcessors, app.writeToStderr)...)
go app.processOutput("stderr", stderr, append(lineProcessors, app.writeToOutput)...)
extracts, err := extractREs(reExtractors, timeout)
if err != nil {
@@ -177,11 +185,11 @@ func (app *app) processOutput(outputName string, output io.Reader, lps ...linePr
}
}
// writeToStderr is a line processor that writes the line to the stderr.
// writeToOutput is a lineProcessor that writes the line to the output.
// The function always returns false to indicate its caller that each line must
// be written to the stderr.
func (app *app) writeToStderr(line string) bool {
fmt.Fprintf(os.Stderr, "%s %s\n", app.instance, line)
func (app *app) writeToOutput(line string) bool {
fmt.Fprintf(app.output, "%s %s\n", app.instance, line)
return false
}

View File

@@ -2,8 +2,11 @@ package apptest
import (
"fmt"
"io"
"os"
"path"
"path/filepath"
"sync"
"testing"
"time"
@@ -18,6 +21,7 @@ type TestCase struct {
t *testing.T
cli *Client
output *outputProcessor
startedApps map[string]Stopper
}
@@ -29,7 +33,15 @@ type Stopper interface {
// NewTestCase creates a new test case.
func NewTestCase(t *testing.T) *TestCase {
t.Parallel()
return &TestCase{t, NewClient(), make(map[string]Stopper)}
tc := &TestCase{t, NewClient(), &outputProcessor{make([]byte, 0), sync.Mutex{}}, make(map[string]Stopper)}
tc.t.Cleanup(func() {
if tc.t.Failed() || testing.Verbose() {
tc.output.FlushOutput()
}
})
return tc
}
// T returns the test state.
@@ -87,7 +99,7 @@ func (tc *TestCase) MustStartVmsingle(instance string, flags []string) *Vmsingle
func (tc *TestCase) MustStartVmsingleAt(instance, binary string, flags []string) *Vmsingle {
tc.t.Helper()
app, err := StartVmsingleAt(instance, binary, flags, tc.cli)
app, err := StartVmsingleAt(instance, binary, flags, tc.cli, tc.output)
if err != nil {
tc.t.Fatalf("Could not start %s: %v", instance, err)
}
@@ -108,7 +120,7 @@ func (tc *TestCase) MustStartVmstorage(instance string, flags []string) *Vmstora
func (tc *TestCase) MustStartVmstorageAt(instance string, binary string, flags []string) *Vmstorage {
tc.t.Helper()
app, err := StartVmstorageAt(instance, binary, flags, tc.cli)
app, err := StartVmstorageAt(instance, binary, flags, tc.cli, tc.output)
if err != nil {
tc.t.Fatalf("Could not start %s: %v", instance, err)
}
@@ -121,7 +133,7 @@ func (tc *TestCase) MustStartVmstorageAt(instance string, binary string, flags [
func (tc *TestCase) MustStartVmselect(instance string, flags []string) *Vmselect {
tc.t.Helper()
app, err := StartVmselect(instance, flags, tc.cli)
app, err := StartVmselect(instance, flags, tc.cli, tc.output)
if err != nil {
tc.t.Fatalf("Could not start %s: %v", instance, err)
}
@@ -134,7 +146,7 @@ func (tc *TestCase) MustStartVmselect(instance string, flags []string) *Vmselect
func (tc *TestCase) MustStartVminsert(instance string, flags []string) *Vminsert {
tc.t.Helper()
app, err := StartVminsert(instance, flags, tc.cli)
app, err := StartVminsert(instance, flags, tc.cli, tc.output)
if err != nil {
tc.t.Fatalf("Could not start %s: %v", instance, err)
}
@@ -149,7 +161,7 @@ func (tc *TestCase) MustStartVmagent(instance string, flags []string, promScrape
promScrapeConfigFilePath := path.Join(tc.t.TempDir(), "prometheus.yml")
fs.MustWriteSync(promScrapeConfigFilePath, []byte(promScrapeConfigFileYAML))
app, err := StartVmagent(instance, flags, tc.cli, promScrapeConfigFilePath)
app, err := StartVmagent(instance, flags, tc.cli, promScrapeConfigFilePath, tc.output)
if err != nil {
tc.t.Fatalf("Could not start %s: %v", instance, err)
}
@@ -193,7 +205,7 @@ func (tc *TestCase) MustStartVmauth(instance string, flags []string, configFileY
configFilePath := path.Join(tc.t.TempDir(), "config.yaml")
fs.MustWriteSync(configFilePath, []byte(configFileYAML))
app, err := StartVmauth(instance, flags, tc.cli, configFilePath)
app, err := StartVmauth(instance, flags, tc.cli, configFilePath, tc.output)
if err != nil {
tc.t.Fatalf("Could not start %s: %v", instance, err)
}
@@ -207,7 +219,7 @@ func (tc *TestCase) MustStartVmauth(instance string, flags []string, configFileY
func (tc *TestCase) MustStartVmbackup(instance, storageDataPath, snapshotCreateURL, dst string) {
tc.t.Helper()
if err := StartVmbackup(instance, storageDataPath, snapshotCreateURL, dst); err != nil {
if err := StartVmbackup(instance, storageDataPath, snapshotCreateURL, dst, tc.output); err != nil {
tc.t.Fatalf("vmbackup %q failed to start or exited with non-zero code: %v", instance, err)
}
@@ -222,7 +234,7 @@ func (tc *TestCase) MustStartVmbackup(instance, storageDataPath, snapshotCreateU
func (tc *TestCase) MustStartVmrestore(instance, src, storageDataPath string) {
tc.t.Helper()
if err := StartVmrestore(instance, src, storageDataPath); err != nil {
if err := StartVmrestore(instance, src, storageDataPath, tc.output); err != nil {
tc.t.Fatalf("vmrestore %q failed to start or exited with non-zero code: %v", instance, err)
}
@@ -307,7 +319,7 @@ func (tc *TestCase) MustStartCluster(opts *ClusterOptions) *Vmcluster {
func (tc *TestCase) MustStartVmctl(instance string, flags []string) {
tc.t.Helper()
err := StartVmctl(instance, flags)
err := StartVmctl(instance, flags, tc.output)
if err != nil {
tc.t.Fatalf("Could not start %s: %v", instance, err)
}
@@ -429,3 +441,25 @@ func (tc *TestCase) Assert(opts *AssertOptions) {
tc.t.Error(msg)
}
}
var _ io.Writer = &outputProcessor{}
type outputProcessor struct {
entries []byte
entriesLock sync.Mutex
}
func (op *outputProcessor) Write(p []byte) (n int, err error) {
op.entriesLock.Lock()
defer op.entriesLock.Unlock()
op.entries = append(op.entries, p...)
return len(p), nil
}
func (op *outputProcessor) FlushOutput() {
op.entriesLock.Lock()
defer op.entriesLock.Unlock()
os.Stderr.Write(op.entries)
op.entries = nil
}

View File

@@ -2,6 +2,7 @@ package apptest
import (
"fmt"
"io"
"net/http"
"os"
"regexp"
@@ -23,7 +24,7 @@ type Vmagent struct {
// StartVmagent starts an instance of vmagent with the given flags. It also
// sets the default flags and populates the app instance state with runtime
// values extracted from the application log (such as httpListenAddr)
func StartVmagent(instance string, flags []string, cli *Client, promScrapeConfigFilePath string) (*Vmagent, error) {
func StartVmagent(instance string, flags []string, cli *Client, promScrapeConfigFilePath string, output io.Writer) (*Vmagent, error) {
extractREs := []*regexp.Regexp{
httpListenAddrRE,
}
@@ -35,6 +36,7 @@ func StartVmagent(instance string, flags []string, cli *Client, promScrapeConfig
"-remoteWrite.tmpDataPath": fmt.Sprintf("%s/%s-%d", os.TempDir(), instance, time.Now().UnixNano()),
},
extractREs: extractREs,
output: output,
})
if err != nil {
return nil, err

View File

@@ -2,6 +2,7 @@ package apptest
import (
"fmt"
"io"
"regexp"
"syscall"
"testing"
@@ -26,7 +27,7 @@ type Vmauth struct {
// StartVmauth starts an instance of vmauth with the given flags. It also
// sets the default flags and populates the app instance state with runtime
// values extracted from the application log (such as httpListenAddr)
func StartVmauth(instance string, flags []string, cli *Client, configFilePath string) (*Vmauth, error) {
func StartVmauth(instance string, flags []string, cli *Client, configFilePath string, output io.Writer) (*Vmauth, error) {
extractREs := []*regexp.Regexp{
httpBuilitinListenAddrRE,
}
@@ -37,6 +38,7 @@ func StartVmauth(instance string, flags []string, cli *Client, configFilePath st
"-auth.config": configFilePath,
},
extractREs: extractREs,
output: output,
})
if err != nil {
return nil, err

View File

@@ -1,13 +1,15 @@
package apptest
import "io"
// StartVmbackup starts an instance of vmbackup with the given flags and waits
// until it exits.
func StartVmbackup(instance, storageDataPath, snapshotCreateURL, dst string) error {
func StartVmbackup(instance, storageDataPath, snapshotCreateURL, dst string, output io.Writer) error {
flags := []string{
"-storageDataPath=" + storageDataPath,
"-snapshot.createURL=" + snapshotCreateURL,
"-dst=" + dst,
}
_, _, err := startApp(instance, "../../bin/vmbackup", flags, &appOptions{wait: true})
_, _, err := startApp(instance, "../../bin/vmbackup", flags, &appOptions{wait: true, output: output})
return err
}

View File

@@ -1,7 +1,9 @@
package apptest
import "io"
// StartVmctl starts an instance of vmctl cli with the given flags
func StartVmctl(instance string, flags []string) error {
_, _, err := startApp(instance, "../../bin/vmctl", flags, &appOptions{wait: true})
func StartVmctl(instance string, flags []string, output io.Writer) error {
_, _, err := startApp(instance, "../../bin/vmctl", flags, &appOptions{wait: true, output: output})
return err
}

View File

@@ -2,6 +2,7 @@ package apptest
import (
"fmt"
"io"
"net/http"
"regexp"
"strings"
@@ -41,7 +42,7 @@ func storageNodes(flags []string) []string {
// StartVminsert starts an instance of vminsert with the given flags. It also
// sets the default flags and populates the app instance state with runtime
// values extracted from the application log (such as httpListenAddr)
func StartVminsert(instance string, flags []string, cli *Client) (*Vminsert, error) {
func StartVminsert(instance string, flags []string, cli *Client, output io.Writer) (*Vminsert, error) {
extractREs := []*regexp.Regexp{
httpListenAddrRE,
vminsertClusterNativeAddrRE,
@@ -63,6 +64,7 @@ func StartVminsert(instance string, flags []string, cli *Client) (*Vminsert, err
"-opentsdbListenAddr": "127.0.0.1:0",
},
extractREs: extractREs,
output: output,
})
if err != nil {
return nil, err

View File

@@ -1,12 +1,14 @@
package apptest
import "io"
// StartVmrestore starts an instance of vmrestore with the given flags and waits
// until it exits.
func StartVmrestore(instance, src, storageDataPath string) error {
func StartVmrestore(instance, src, storageDataPath string, output io.Writer) error {
flags := []string{
"-src=" + src,
"-storageDataPath=" + storageDataPath,
}
_, _, err := startApp(instance, "../../bin/vmrestore", flags, &appOptions{wait: true})
_, _, err := startApp(instance, "../../bin/vmrestore", flags, &appOptions{wait: true, output: output})
return err
}

View File

@@ -3,6 +3,7 @@ package apptest
import (
"encoding/json"
"fmt"
"io"
"net/http"
"regexp"
"testing"
@@ -22,7 +23,7 @@ type Vmselect struct {
// StartVmselect starts an instance of vmselect with the given flags. It also
// sets the default flags and populates the app instance state with runtime
// values extracted from the application log (such as httpListenAddr)
func StartVmselect(instance string, flags []string, cli *Client) (*Vmselect, error) {
func StartVmselect(instance string, flags []string, cli *Client, output io.Writer) (*Vmselect, error) {
app, stderrExtracts, err := startApp(instance, "../../bin/vmselect", flags, &appOptions{
defaultFlags: map[string]string{
"-httpListenAddr": "127.0.0.1:0",
@@ -32,6 +33,7 @@ func StartVmselect(instance string, flags []string, cli *Client) (*Vmselect, err
httpListenAddrRE,
vmselectAddrRE,
},
output: output,
})
if err != nil {
return nil, err

View File

@@ -3,6 +3,7 @@ package apptest
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"regexp"
@@ -46,7 +47,7 @@ type Vmsingle struct {
// StartVmsingleAt starts an instance of vmsingle with the given flags. It also
// sets the default flags and populates the app instance state with runtime
// values extracted from the application log (such as httpListenAddr).
func StartVmsingleAt(instance, binary string, flags []string, cli *Client) (*Vmsingle, error) {
func StartVmsingleAt(instance, binary string, flags []string, cli *Client, output io.Writer) (*Vmsingle, error) {
app, stderrExtracts, err := startApp(instance, binary, flags, &appOptions{
defaultFlags: map[string]string{
"-storageDataPath": fmt.Sprintf("%s/%s-%d", os.TempDir(), instance, time.Now().UnixNano()),
@@ -60,6 +61,7 @@ func StartVmsingleAt(instance, binary string, flags []string, cli *Client) (*Vms
graphiteListenAddrRE,
openTSDBListenAddrRE,
},
output: output,
})
if err != nil {
return nil, err

View File

@@ -3,6 +3,7 @@ package apptest
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"regexp"
@@ -25,7 +26,7 @@ type Vmstorage struct {
// StartVmstorageAt starts an instance of vmstorage with the given flags. It also
// sets the default flags and populates the app instance state with runtime
// values extracted from the application log (such as httpListenAddr)
func StartVmstorageAt(instance, binary string, flags []string, cli *Client) (*Vmstorage, error) {
func StartVmstorageAt(instance, binary string, flags []string, cli *Client, output io.Writer) (*Vmstorage, error) {
app, stderrExtracts, err := startApp(instance, binary, flags, &appOptions{
defaultFlags: map[string]string{
"-storageDataPath": fmt.Sprintf("%s/%s-%d", os.TempDir(), instance, time.Now().UnixNano()),
@@ -39,6 +40,7 @@ func StartVmstorageAt(instance, binary string, flags []string, cli *Client) (*Vm
vminsertAddrRE,
vmselectAddrRE,
},
output: output,
})
if err != nil {
return nil, err