Files
syft/internal/unknown/coordinate_error_test.go
T
Alex Goodman 10af8342c0 fix: don't fail the whole SBOM when a cataloger panics on one file (#5342)
* fix(generic): recover from parser and processor panics

- a panic while parsing one file becomes an unknown for that location instead of failing the whole SBOM
- a panic in a package processor is logged as a warning and the unprocessed results are kept
- stacks go to the trace log, not into the unknown text

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix(unknown): keep errors that unwrap to nil

`sync.PanicError` with a non-error panic value (e.g. `panic("boom")`) unwraps to nil, and was being dropped entirely from the error graph instead of being kept as a leaf.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix(task): don't put the stack trace in task panic errors

Task panics are still fatal, but the error now reads `panic in task "<name>": <value>` and the stack goes to the debug log.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* test(generic): cover chained processors when a later one panics

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

* fix(file): recover from panics in the executable and file digest catalogers

A panic while reading one file is now recorded as an unknown for that file instead of failing the whole SBOM, the same as generic catalogers.

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>

---------

Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
2026-09-29 09:21:03 -04:00

246 lines
5.6 KiB
Go

package unknown
import (
"errors"
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/anchore/go-sync"
"github.com/anchore/syft/syft/file"
)
func Test_visitErrors(t *testing.T) {
tests := []struct {
name string
in error
transform func(error) error
expected string
}{
{
name: "return",
in: fmt.Errorf("err1"),
transform: func(e error) error {
return e
},
expected: "err1",
},
{
name: "omit",
in: fmt.Errorf("err1"),
transform: func(_ error) error {
return nil
},
expected: "<nil>",
},
{
name: "wrapped return",
in: fmt.Errorf("wrapped: %w", fmt.Errorf("err1")),
transform: func(e error) error {
return e
},
expected: "wrapped: err1",
},
{
name: "wrapped omit",
in: fmt.Errorf("wrapped: %w", fmt.Errorf("err1")),
transform: func(e error) error {
if e.Error() == "err1" {
return nil
}
return e
},
expected: "<nil>",
},
{
name: "joined return",
in: errors.Join(fmt.Errorf("err1"), fmt.Errorf("err2")),
transform: func(e error) error {
return e
},
expected: "err1\nerr2",
},
{
name: "joined omit",
in: errors.Join(fmt.Errorf("err1"), fmt.Errorf("err2")),
transform: func(_ error) error {
return nil
},
expected: "<nil>",
},
{
name: "joined omit first",
in: errors.Join(fmt.Errorf("err1"), fmt.Errorf("err2")),
transform: func(e error) error {
if e.Error() == "err1" {
return nil
}
return e
},
expected: "err2",
},
{
name: "joined wrapped return",
in: errors.Join(fmt.Errorf("wrapped: %w", fmt.Errorf("err1")), fmt.Errorf("err2")),
transform: func(e error) error {
return e
},
expected: "wrapped: err1\nerr2",
},
{
name: "joined wrapped omit first",
in: errors.Join(fmt.Errorf("wrapped: %w", fmt.Errorf("err1")), fmt.Errorf("err2")),
transform: func(e error) error {
if e.Error() == "err1" {
return nil
}
return e
},
expected: "err2",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
gotErr := visitErrors(test.in, test.transform)
got := fmt.Sprintf("%v", gotErr)
require.Equal(t, test.expected, got)
})
}
}
func Test_Join(t *testing.T) {
err1 := fmt.Errorf("err1")
err2 := fmt.Errorf("err2")
tests := []struct {
name string ``
in []error
expected string
}{
{
name: "basic",
in: []error{fmt.Errorf("err")},
expected: "err",
},
{
name: "wrapped",
in: []error{fmt.Errorf("outer: %w", fmt.Errorf("err"))},
expected: "outer: err",
},
{
name: "wrapped joined",
in: []error{errors.Join(fmt.Errorf("outer: %w", fmt.Errorf("err1")), fmt.Errorf("err2"))},
expected: "outer: err1\nerr2",
},
{
name: "duplicates",
in: []error{err1, err1, err2},
expected: "err1\nerr2",
},
{
name: "nested duplicates",
in: []error{errors.Join(err1, err2), err1, err2},
expected: "err1\nerr2",
},
{
name: "nested duplicates coords",
in: []error{New(file.NewLocation("l1"), errors.Join(fmt.Errorf("err1"), fmt.Errorf("err2"))), fmt.Errorf("err1"), fmt.Errorf("err2")},
expected: "l1: err1\nl1: err2\nerr1\nerr2",
},
{
name: "all nil",
in: []error{nil, nil, nil},
expected: "",
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got := Join(test.in...)
if test.expected == "" {
require.Nil(t, got)
return
}
require.NotNil(t, got)
require.Equal(t, test.expected, got.Error())
})
}
}
func Test_flatten(t *testing.T) {
coords := file.Coordinates{
RealPath: "real/path",
}
e1 := fmt.Errorf("e1")
e2 := fmt.Errorf("e2")
c1 := New(coords, fmt.Errorf("c1"))
c2 := New(coords, fmt.Errorf("c2"))
tests := []struct {
name string ``
in error
expected string
}{
{
name: "basic",
in: errors.Join(e1, e2),
expected: "e1//e2",
},
{
name: "coords",
in: New(coords, e1),
expected: "real/path: e1",
},
{
name: "coords with joined children",
in: New(coords, errors.Join(e1, e2)),
expected: "real/path: e1//real/path: e2",
},
{
name: "very nested",
in: errors.Join(errors.Join(errors.Join(errors.Join(e1, c1), e2), c2), e2),
expected: "e1//real/path: c1//e2//real/path: c2//e2",
},
}
toString := func(errs ...error) string {
var parts []string
for _, e := range errs {
parts = append(parts, e.Error())
}
return strings.Join(parts, "//")
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got := flatten(test.in)
require.NotNil(t, got)
require.Equal(t, test.expected, toString(got...))
})
}
}
func Test_Append(t *testing.T) {
e1 := New(file.NewLocation("l1"), fmt.Errorf("e1"))
e2 := Append(e1, file.NewLocation("l2"), fmt.Errorf("e2"))
e3 := Appendf(e2, file.NewLocation("l3"), "%s", "e3")
require.Equal(t, "l1: e1\nl2: e2\nl3: e3", e3.Error())
e1 = New(file.NewLocation("l1"), nil)
require.Nil(t, e1)
e2 = Append(e1, file.NewLocation("l2"), fmt.Errorf("e2"))
e3 = Appendf(e2, file.NewLocation("l3"), "%s", "e3")
require.Equal(t, "l2: e2\nl3: e3", e3.Error())
e1 = New(file.NewLocation("l1"), fmt.Errorf("e1"))
e2 = Append(e1, file.NewLocation("l2"), nil)
e3 = Appendf(e2, file.NewLocation("l3"), "%s", "e3")
require.Equal(t, "l1: e1\nl3: e3", e3.Error())
}
func Test_ExtractCoordinateErrors_keepsNilUnwrap(t *testing.T) {
// sync.PanicError unwraps to nil when the panic value is not an error
err := sync.PanicError{Value: "x"}
unknowns, remaining := ExtractCoordinateErrors(err)
require.Empty(t, unknowns)
require.Equal(t, err, remaining)
}