Version 5.22 introduced a new option to /etc/containers/policy.json called
keyPaths, see
https://github.com/containers/image/pull/1609
EL9 immediately took advantage of this new feature and started using it, see
04645c4a84
This quickly became an issue in our code: The go library (containers/image)
parses the configuration file very strictly and refuses to create a client
when policy.json with an unknown key is present on the filesystem. As we
used 5.21.1 that doesn't know the new key, our unit tests started to
failing when containers-common was present.
Reproducer:
podman run --pull=always --rm -it centos:stream9
dnf install -y dnf-plugins-core
dnf config-manager --set-enabled crb
dnf install -y gpgme-devel libassuan-devel krb5-devel golang git-core
git clone https://github.com/osbuild/osbuild-composer
cd osbuild-composer
# install the new containers-common and run the test
dnf install -y https://kojihub.stream.centos.org/kojifiles/packages/containers-common/1/44.el9/x86_64/containers-common-1-44.el9.x86_64.rpm
go test -count 1 ./...
# this returns:
--- FAIL: TestClientResolve (0.00s)
client_test.go:31:
Error Trace: client_test.go:31
Error: Received unexpected error:
Unknown key "keyPaths"
invalid policy in "/etc/containers/policy.json"
github.com/containers/image/v5/signature.NewPolicyFromFile
/osbuild-composer/vendor/github.com/containers/image/v5/signature/policy_config.go:88
github.com/osbuild/osbuild-composer/internal/container.NewClient
/osbuild-composer/internal/container/client.go:123
github.com/osbuild/osbuild-composer/internal/container_test.TestClientResolve
/osbuild-composer/internal/container/client_test.go:29
testing.tRunner
/usr/lib/golang/src/testing/testing.go:1439
runtime.goexit
/usr/lib/golang/src/runtime/asm_amd64.s:1571
Test: TestClientResolve
client_test.go:32:
Error Trace: client_test.go:32
Error: Expected value not to be nil.
Test: TestClientResolve
When run with an older containers-common, it succeeds:
dnf install -y https://kojihub.stream.centos.org/kojifiles/packages/containers-common/1/40.el9/x86_64/containers-common-1-40.el9.x86_64.rpm
go test -count 1 ./...
PASS
To sum it up, I had to upgrade github.com/containers/image/v5 to v5.22.0.
Unfortunately, this wasn't so simple, see
go get github.com/containers/image/v5@latest
go: github.com/containers/image/v5@v5.22.0 requires
github.com/letsencrypt/boulder@v0.0.0-20220331220046-b23ab962616e requires
github.com/honeycombio/beeline-go@v1.1.1 requires
github.com/gobuffalo/pop/v5@v5.3.1 requires
github.com/mattn/go-sqlite3@v2.0.3+incompatible: reading github.com/mattn/go-sqlite3/go.mod at revision v2.0.3: unknown revision v2.0.3
It turns out that github.com/mattn/go-sqlite3@v2.0.3+incompatible has been
recently retracted https://github.com/mattn/go-sqlite3/pull/998 and this
broke a ton of packages depending on it. I was able to fix it by adding
exclude github.com/mattn/go-sqlite3 v2.0.3+incompatible
to our go.mod, see
https://github.com/mattn/go-sqlite3/issues/975#issuecomment-955661657
After adding it,
go get github.com/containers/image/v5@latest
succeeded and tools/prepare-source.sh took care of the rest.
Signed-off-by: Ondřej Budai <ondrej@budai.cz>
226 lines
5.2 KiB
Go
226 lines
5.2 KiB
Go
package suite
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"reflect"
|
|
"regexp"
|
|
"runtime/debug"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var allTestsFilter = func(_, _ string) (bool, error) { return true, nil }
|
|
var matchMethod = flag.String("testify.m", "", "regular expression to select tests of the testify suite to run")
|
|
|
|
// Suite is a basic testing suite with methods for storing and
|
|
// retrieving the current *testing.T context.
|
|
type Suite struct {
|
|
*assert.Assertions
|
|
mu sync.RWMutex
|
|
require *require.Assertions
|
|
t *testing.T
|
|
}
|
|
|
|
// T retrieves the current *testing.T context.
|
|
func (suite *Suite) T() *testing.T {
|
|
suite.mu.RLock()
|
|
defer suite.mu.RUnlock()
|
|
return suite.t
|
|
}
|
|
|
|
// SetT sets the current *testing.T context.
|
|
func (suite *Suite) SetT(t *testing.T) {
|
|
suite.mu.Lock()
|
|
defer suite.mu.Unlock()
|
|
suite.t = t
|
|
suite.Assertions = assert.New(t)
|
|
suite.require = require.New(t)
|
|
}
|
|
|
|
// Require returns a require context for suite.
|
|
func (suite *Suite) Require() *require.Assertions {
|
|
suite.mu.Lock()
|
|
defer suite.mu.Unlock()
|
|
if suite.require == nil {
|
|
suite.require = require.New(suite.T())
|
|
}
|
|
return suite.require
|
|
}
|
|
|
|
// Assert returns an assert context for suite. Normally, you can call
|
|
// `suite.NoError(expected, actual)`, but for situations where the embedded
|
|
// methods are overridden (for example, you might want to override
|
|
// assert.Assertions with require.Assertions), this method is provided so you
|
|
// can call `suite.Assert().NoError()`.
|
|
func (suite *Suite) Assert() *assert.Assertions {
|
|
suite.mu.Lock()
|
|
defer suite.mu.Unlock()
|
|
if suite.Assertions == nil {
|
|
suite.Assertions = assert.New(suite.T())
|
|
}
|
|
return suite.Assertions
|
|
}
|
|
|
|
func recoverAndFailOnPanic(t *testing.T) {
|
|
r := recover()
|
|
failOnPanic(t, r)
|
|
}
|
|
|
|
func failOnPanic(t *testing.T, r interface{}) {
|
|
if r != nil {
|
|
t.Errorf("test panicked: %v\n%s", r, debug.Stack())
|
|
t.FailNow()
|
|
}
|
|
}
|
|
|
|
// Run provides suite functionality around golang subtests. It should be
|
|
// called in place of t.Run(name, func(t *testing.T)) in test suite code.
|
|
// The passed-in func will be executed as a subtest with a fresh instance of t.
|
|
// Provides compatibility with go test pkg -run TestSuite/TestName/SubTestName.
|
|
func (suite *Suite) Run(name string, subtest func()) bool {
|
|
oldT := suite.T()
|
|
defer suite.SetT(oldT)
|
|
return oldT.Run(name, func(t *testing.T) {
|
|
suite.SetT(t)
|
|
subtest()
|
|
})
|
|
}
|
|
|
|
// Run takes a testing suite and runs all of the tests attached
|
|
// to it.
|
|
func Run(t *testing.T, suite TestingSuite) {
|
|
defer recoverAndFailOnPanic(t)
|
|
|
|
suite.SetT(t)
|
|
|
|
var suiteSetupDone bool
|
|
|
|
var stats *SuiteInformation
|
|
if _, ok := suite.(WithStats); ok {
|
|
stats = newSuiteInformation()
|
|
}
|
|
|
|
tests := []testing.InternalTest{}
|
|
methodFinder := reflect.TypeOf(suite)
|
|
suiteName := methodFinder.Elem().Name()
|
|
|
|
for i := 0; i < methodFinder.NumMethod(); i++ {
|
|
method := methodFinder.Method(i)
|
|
|
|
ok, err := methodFilter(method.Name)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "testify: invalid regexp for -m: %s\n", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if !suiteSetupDone {
|
|
if stats != nil {
|
|
stats.Start = time.Now()
|
|
}
|
|
|
|
if setupAllSuite, ok := suite.(SetupAllSuite); ok {
|
|
setupAllSuite.SetupSuite()
|
|
}
|
|
|
|
suiteSetupDone = true
|
|
}
|
|
|
|
test := testing.InternalTest{
|
|
Name: method.Name,
|
|
F: func(t *testing.T) {
|
|
parentT := suite.T()
|
|
suite.SetT(t)
|
|
defer recoverAndFailOnPanic(t)
|
|
defer func() {
|
|
r := recover()
|
|
|
|
if stats != nil {
|
|
passed := !t.Failed() && r == nil
|
|
stats.end(method.Name, passed)
|
|
}
|
|
|
|
if afterTestSuite, ok := suite.(AfterTest); ok {
|
|
afterTestSuite.AfterTest(suiteName, method.Name)
|
|
}
|
|
|
|
if tearDownTestSuite, ok := suite.(TearDownTestSuite); ok {
|
|
tearDownTestSuite.TearDownTest()
|
|
}
|
|
|
|
suite.SetT(parentT)
|
|
failOnPanic(t, r)
|
|
}()
|
|
|
|
if setupTestSuite, ok := suite.(SetupTestSuite); ok {
|
|
setupTestSuite.SetupTest()
|
|
}
|
|
if beforeTestSuite, ok := suite.(BeforeTest); ok {
|
|
beforeTestSuite.BeforeTest(methodFinder.Elem().Name(), method.Name)
|
|
}
|
|
|
|
if stats != nil {
|
|
stats.start(method.Name)
|
|
}
|
|
|
|
method.Func.Call([]reflect.Value{reflect.ValueOf(suite)})
|
|
},
|
|
}
|
|
tests = append(tests, test)
|
|
}
|
|
if suiteSetupDone {
|
|
defer func() {
|
|
if tearDownAllSuite, ok := suite.(TearDownAllSuite); ok {
|
|
tearDownAllSuite.TearDownSuite()
|
|
}
|
|
|
|
if suiteWithStats, measureStats := suite.(WithStats); measureStats {
|
|
stats.End = time.Now()
|
|
suiteWithStats.HandleStats(suiteName, stats)
|
|
}
|
|
}()
|
|
}
|
|
|
|
runTests(t, tests)
|
|
}
|
|
|
|
// Filtering method according to set regular expression
|
|
// specified command-line argument -m
|
|
func methodFilter(name string) (bool, error) {
|
|
if ok, _ := regexp.MatchString("^Test", name); !ok {
|
|
return false, nil
|
|
}
|
|
return regexp.MatchString(*matchMethod, name)
|
|
}
|
|
|
|
func runTests(t testing.TB, tests []testing.InternalTest) {
|
|
if len(tests) == 0 {
|
|
t.Log("warning: no tests to run")
|
|
return
|
|
}
|
|
|
|
r, ok := t.(runner)
|
|
if !ok { // backwards compatibility with Go 1.6 and below
|
|
if !testing.RunTests(allTestsFilter, tests) {
|
|
t.Fail()
|
|
}
|
|
return
|
|
}
|
|
|
|
for _, test := range tests {
|
|
r.Run(test.Name, test.F)
|
|
}
|
|
}
|
|
|
|
type runner interface {
|
|
Run(name string, f func(t *testing.T)) bool
|
|
}
|