mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-08 00:22:14 +01:00
469 lines
12 KiB
Go
469 lines
12 KiB
Go
package gc
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"os"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
type ProtoModule struct {
|
|
descriptor func() gotenberg.ModuleDescriptor
|
|
}
|
|
|
|
func (mod ProtoModule) Descriptor() gotenberg.ModuleDescriptor {
|
|
return mod.descriptor()
|
|
}
|
|
|
|
type ProtoValidator struct {
|
|
ProtoModule
|
|
validate func() error
|
|
}
|
|
|
|
func (mod ProtoValidator) Validate() error {
|
|
return mod.validate()
|
|
}
|
|
|
|
type ProtoGarbageCollectorGraceDurationModifier struct {
|
|
ProtoValidator
|
|
graceDuration func() time.Duration
|
|
}
|
|
|
|
func (mod ProtoGarbageCollectorGraceDurationModifier) GraceDuration() time.Duration {
|
|
return mod.graceDuration()
|
|
}
|
|
|
|
type ProtoGarbageCollectorExcludeSubstrModifier struct {
|
|
ProtoValidator
|
|
excludeSubstr func() []string
|
|
}
|
|
|
|
func (mod ProtoGarbageCollectorExcludeSubstrModifier) ExcludeSubstr() []string {
|
|
return mod.excludeSubstr()
|
|
}
|
|
|
|
type ProtoLoggerProvider struct {
|
|
ProtoModule
|
|
logger func(mod gotenberg.Module) (*zap.Logger, error)
|
|
}
|
|
|
|
func (factory ProtoLoggerProvider) Logger(mod gotenberg.Module) (*zap.Logger, error) {
|
|
return factory.logger(mod)
|
|
}
|
|
|
|
func TestGarbageCollector_Descriptor(t *testing.T) {
|
|
descriptor := GarbageCollector{}.Descriptor()
|
|
|
|
actual := reflect.TypeOf(descriptor.New())
|
|
expect := reflect.TypeOf(new(GarbageCollector))
|
|
|
|
if actual != expect {
|
|
t.Errorf("expected '%s' but got '%s'", expect, actual)
|
|
}
|
|
}
|
|
|
|
func TestGarbageCollector_Provision(t *testing.T) {
|
|
for i, tc := range []struct {
|
|
ctx *gotenberg.Context
|
|
expectGraceDuration time.Duration
|
|
expectExcludeSubstr []string
|
|
expectErr bool
|
|
}{
|
|
{
|
|
ctx: func() *gotenberg.Context {
|
|
mod := struct {
|
|
ProtoGarbageCollectorGraceDurationModifier
|
|
}{}
|
|
mod.descriptor = func() gotenberg.ModuleDescriptor {
|
|
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return mod }}
|
|
}
|
|
mod.validate = func() error { return errors.New("foo") }
|
|
|
|
return gotenberg.NewContext(gotenberg.ParsedFlags{}, []gotenberg.ModuleDescriptor{
|
|
mod.Descriptor(),
|
|
})
|
|
}(),
|
|
expectErr: true,
|
|
},
|
|
{
|
|
ctx: func() *gotenberg.Context {
|
|
mod := struct {
|
|
ProtoGarbageCollectorExcludeSubstrModifier
|
|
}{}
|
|
mod.descriptor = func() gotenberg.ModuleDescriptor {
|
|
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return mod }}
|
|
}
|
|
mod.validate = func() error { return errors.New("foo") }
|
|
|
|
return gotenberg.NewContext(gotenberg.ParsedFlags{}, []gotenberg.ModuleDescriptor{
|
|
mod.Descriptor(),
|
|
})
|
|
}(),
|
|
expectErr: true,
|
|
},
|
|
{
|
|
ctx: gotenberg.NewContext(gotenberg.ParsedFlags{}, make([]gotenberg.ModuleDescriptor, 0)),
|
|
expectErr: true,
|
|
},
|
|
{
|
|
ctx: func() *gotenberg.Context {
|
|
mod := struct {
|
|
ProtoLoggerProvider
|
|
}{}
|
|
mod.descriptor = func() gotenberg.ModuleDescriptor {
|
|
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return mod }}
|
|
}
|
|
mod.logger = func(mod gotenberg.Module) (*zap.Logger, error) { return nil, errors.New("foo") }
|
|
|
|
return gotenberg.NewContext(gotenberg.ParsedFlags{}, []gotenberg.ModuleDescriptor{
|
|
mod.Descriptor(),
|
|
})
|
|
}(),
|
|
expectErr: true,
|
|
},
|
|
{
|
|
ctx: func() *gotenberg.Context {
|
|
mod := struct {
|
|
ProtoLoggerProvider
|
|
}{}
|
|
mod.descriptor = func() gotenberg.ModuleDescriptor {
|
|
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return mod }}
|
|
}
|
|
mod.logger = func(mod gotenberg.Module) (*zap.Logger, error) { return zap.NewNop(), nil }
|
|
|
|
return gotenberg.NewContext(gotenberg.ParsedFlags{}, []gotenberg.ModuleDescriptor{
|
|
mod.Descriptor(),
|
|
})
|
|
}(),
|
|
},
|
|
{
|
|
ctx: func() *gotenberg.Context {
|
|
mod1 := struct {
|
|
ProtoGarbageCollectorGraceDurationModifier
|
|
}{}
|
|
mod1.descriptor = func() gotenberg.ModuleDescriptor {
|
|
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return mod1 }}
|
|
}
|
|
mod1.graceDuration = func() time.Duration { return time.Duration(10) * time.Second }
|
|
mod1.validate = func() error { return nil }
|
|
|
|
mod2 := struct {
|
|
ProtoGarbageCollectorGraceDurationModifier
|
|
}{}
|
|
mod2.descriptor = func() gotenberg.ModuleDescriptor {
|
|
return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod2 }}
|
|
}
|
|
mod2.graceDuration = func() time.Duration { return time.Duration(20) * time.Second }
|
|
mod2.validate = func() error { return nil }
|
|
|
|
mod3 := struct {
|
|
ProtoLoggerProvider
|
|
}{}
|
|
mod3.descriptor = func() gotenberg.ModuleDescriptor {
|
|
return gotenberg.ModuleDescriptor{ID: "baz", New: func() gotenberg.Module { return mod3 }}
|
|
}
|
|
mod3.logger = func(mod gotenberg.Module) (*zap.Logger, error) { return zap.NewNop(), nil }
|
|
|
|
return gotenberg.NewContext(gotenberg.ParsedFlags{}, []gotenberg.ModuleDescriptor{
|
|
mod1.Descriptor(),
|
|
mod2.Descriptor(),
|
|
mod3.Descriptor(),
|
|
})
|
|
}(),
|
|
expectGraceDuration: time.Duration(20) * time.Second,
|
|
},
|
|
{
|
|
ctx: func() *gotenberg.Context {
|
|
mod1 := struct {
|
|
ProtoGarbageCollectorExcludeSubstrModifier
|
|
}{}
|
|
mod1.descriptor = func() gotenberg.ModuleDescriptor {
|
|
return gotenberg.ModuleDescriptor{ID: "foo", New: func() gotenberg.Module { return mod1 }}
|
|
}
|
|
mod1.excludeSubstr = func() []string { return []string{"foo"} }
|
|
mod1.validate = func() error { return nil }
|
|
|
|
mod2 := struct {
|
|
ProtoGarbageCollectorExcludeSubstrModifier
|
|
}{}
|
|
mod2.descriptor = func() gotenberg.ModuleDescriptor {
|
|
return gotenberg.ModuleDescriptor{ID: "bar", New: func() gotenberg.Module { return mod2 }}
|
|
}
|
|
mod2.excludeSubstr = func() []string { return []string{"bar"} }
|
|
mod2.validate = func() error { return nil }
|
|
|
|
mod3 := struct {
|
|
ProtoLoggerProvider
|
|
}{}
|
|
mod3.descriptor = func() gotenberg.ModuleDescriptor {
|
|
return gotenberg.ModuleDescriptor{ID: "baz", New: func() gotenberg.Module { return mod3 }}
|
|
}
|
|
mod3.logger = func(mod gotenberg.Module) (*zap.Logger, error) { return zap.NewNop(), nil }
|
|
|
|
return gotenberg.NewContext(gotenberg.ParsedFlags{}, []gotenberg.ModuleDescriptor{
|
|
mod1.Descriptor(),
|
|
mod2.Descriptor(),
|
|
mod3.Descriptor(),
|
|
})
|
|
}(),
|
|
expectExcludeSubstr: func() []string {
|
|
expect := strings.Split(os.Getenv("GC_EXCLUDE_SUBSTR"), ",")
|
|
return append(expect, "foo", "bar")
|
|
}(),
|
|
},
|
|
} {
|
|
mod := new(GarbageCollector)
|
|
err := mod.Provision(tc.ctx)
|
|
|
|
if tc.expectErr && err == nil {
|
|
t.Errorf("test %d: expected error but got: %v", i, err)
|
|
}
|
|
|
|
if !tc.expectErr && err != nil {
|
|
t.Errorf("test %d: expected no error but got: %v", i, err)
|
|
}
|
|
|
|
if tc.expectGraceDuration != 0 && tc.expectGraceDuration != mod.graceDuration {
|
|
t.Errorf("test %d: expected grace duration of '%s' but got '%s'", i, tc.expectGraceDuration, mod.graceDuration)
|
|
}
|
|
|
|
if tc.expectExcludeSubstr != nil && !reflect.DeepEqual(tc.expectExcludeSubstr, mod.excludeSubstr) {
|
|
t.Errorf("test %d: expected exclude substr '%s' but got '%s'", i, tc.expectExcludeSubstr, mod.excludeSubstr)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGarbageCollector_Start(t *testing.T) {
|
|
mod := new(GarbageCollector)
|
|
mod.logger = zap.NewNop()
|
|
|
|
path, err := gotenberg.MkdirAll()
|
|
if err != nil {
|
|
t.Fatalf("expected no error but got: %v", err)
|
|
}
|
|
|
|
mod.rootPath = path
|
|
|
|
err = mod.Start()
|
|
if err != nil {
|
|
t.Errorf("expected no error but got: %v", err)
|
|
}
|
|
|
|
time.Sleep(time.Duration(2) * time.Second)
|
|
mod.ticker.Stop()
|
|
mod.done <- true
|
|
}
|
|
|
|
func TestGarbageCollector_collect(t *testing.T) {
|
|
for i, tc := range []struct {
|
|
gc *GarbageCollector
|
|
expectNotExists []string
|
|
expectExists []string
|
|
force bool
|
|
}{
|
|
{
|
|
gc: func() *GarbageCollector {
|
|
mod := new(GarbageCollector)
|
|
mod.logger = zap.NewNop()
|
|
|
|
path, err := gotenberg.MkdirAll()
|
|
if err != nil {
|
|
t.Fatalf("expected no error but got: %v", err)
|
|
}
|
|
|
|
mod.rootPath = path
|
|
|
|
err = os.WriteFile(path+"/foo", []byte{1}, 0755)
|
|
if err != nil {
|
|
t.Fatalf("expected no error but got: %v", err)
|
|
}
|
|
|
|
mod.excludeSubstr = []string{
|
|
"foo",
|
|
}
|
|
|
|
return mod
|
|
}(),
|
|
expectExists: []string{
|
|
"/foo",
|
|
},
|
|
},
|
|
{
|
|
gc: func() *GarbageCollector {
|
|
mod := new(GarbageCollector)
|
|
mod.logger = zap.NewNop()
|
|
|
|
path, err := gotenberg.MkdirAll()
|
|
if err != nil {
|
|
t.Fatalf("expected no error but got: %v", err)
|
|
}
|
|
|
|
mod.rootPath = path
|
|
|
|
err = os.WriteFile(path+"/foo", []byte{1}, 0755)
|
|
if err != nil {
|
|
t.Fatalf("expected no error but got: %v", err)
|
|
}
|
|
|
|
err = os.MkdirAll(path+"/bar", 0755)
|
|
if err != nil {
|
|
t.Fatalf("expected no error but got: %v", err)
|
|
}
|
|
|
|
return mod
|
|
}(),
|
|
expectNotExists: []string{
|
|
"/foo",
|
|
"/bar",
|
|
},
|
|
force: true,
|
|
},
|
|
{
|
|
gc: func() *GarbageCollector {
|
|
mod := new(GarbageCollector)
|
|
mod.logger = zap.NewNop()
|
|
mod.graceDuration = time.Duration(10) * time.Second
|
|
|
|
path, err := gotenberg.MkdirAll()
|
|
if err != nil {
|
|
t.Fatalf("expected no error but got: %v", err)
|
|
}
|
|
|
|
mod.rootPath = path
|
|
|
|
err = os.WriteFile(path+"/foo", []byte{1}, 0755)
|
|
if err != nil {
|
|
t.Fatalf("expected no error but got: %v", err)
|
|
}
|
|
|
|
newTime := time.Now().Add(-time.Duration(20) * time.Second)
|
|
err = os.Chtimes(path+"/foo", newTime, newTime)
|
|
|
|
if err != nil {
|
|
t.Fatalf("expected no error but got: %v", err)
|
|
}
|
|
|
|
err = os.WriteFile(path+"/bar", []byte{1}, 0755)
|
|
if err != nil {
|
|
t.Fatalf("expected no error but got: %v", err)
|
|
}
|
|
|
|
newTime = time.Now().Add(time.Duration(10) * time.Second)
|
|
err = os.Chtimes(path+"/bar", newTime, newTime)
|
|
|
|
if err != nil {
|
|
t.Fatalf("expected no error but got: %v", err)
|
|
}
|
|
|
|
return mod
|
|
}(),
|
|
expectNotExists: []string{
|
|
"/foo",
|
|
},
|
|
expectExists: []string{
|
|
"/bar",
|
|
},
|
|
},
|
|
} {
|
|
tc.gc.collect(tc.force)
|
|
|
|
for _, name := range tc.expectNotExists {
|
|
path := tc.gc.rootPath + name
|
|
_, err := os.Stat(path)
|
|
if !os.IsNotExist(err) {
|
|
t.Errorf("test %d: expected '%s' not to exist but got: %v", i, path, err)
|
|
}
|
|
}
|
|
|
|
for _, name := range tc.expectExists {
|
|
path := tc.gc.rootPath + name
|
|
_, err := os.Stat(path)
|
|
if os.IsNotExist(err) {
|
|
t.Errorf("test %d: expected '%s' to exist but got: %v", i, path, err)
|
|
}
|
|
}
|
|
|
|
err := os.RemoveAll(tc.gc.rootPath)
|
|
if err != nil {
|
|
t.Fatalf("test %d: expected no error but got: %v", i, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGarbageCollector_StartupMessage(t *testing.T) {
|
|
actual := new(GarbageCollector).StartupMessage()
|
|
expect := ""
|
|
|
|
if actual != expect {
|
|
t.Errorf("expected '%s' but got '%s'", expect, actual)
|
|
}
|
|
}
|
|
|
|
func TestGarbageCollector_Stop(t *testing.T) {
|
|
for i, tc := range []struct {
|
|
timeout time.Duration
|
|
expectErr bool
|
|
}{
|
|
{
|
|
expectErr: true,
|
|
},
|
|
{
|
|
timeout: time.Duration(1) * time.Nanosecond,
|
|
},
|
|
} {
|
|
func() {
|
|
mod := new(GarbageCollector)
|
|
mod.logger = zap.NewNop()
|
|
|
|
path, err := gotenberg.MkdirAll()
|
|
if err != nil {
|
|
t.Fatalf("test %d: expected no error but got: %v", i, err)
|
|
}
|
|
|
|
mod.rootPath = path
|
|
|
|
err = mod.Start()
|
|
if err != nil {
|
|
t.Fatalf("test %d: expected no error but got: %v", i, err)
|
|
}
|
|
|
|
if tc.timeout == 0 {
|
|
err = mod.Stop(context.TODO())
|
|
} else {
|
|
ctx, cancel := context.WithTimeout(context.Background(), tc.timeout)
|
|
defer cancel()
|
|
|
|
err = mod.Stop(ctx)
|
|
}
|
|
|
|
if tc.expectErr && err == nil {
|
|
t.Errorf("test %d: expected error but got: %v", i, err)
|
|
}
|
|
|
|
if !tc.expectErr && err != nil {
|
|
t.Errorf("test %d: expected no error but got: %v", i, err)
|
|
}
|
|
}()
|
|
}
|
|
}
|
|
|
|
// Interface guards.
|
|
var (
|
|
_ gotenberg.Module = (*ProtoModule)(nil)
|
|
_ gotenberg.Validator = (*ProtoValidator)(nil)
|
|
_ GarbageCollectorGraceDurationModifier = (*ProtoGarbageCollectorGraceDurationModifier)(nil)
|
|
_ gotenberg.Module = (*ProtoGarbageCollectorGraceDurationModifier)(nil)
|
|
_ gotenberg.Validator = (*ProtoGarbageCollectorGraceDurationModifier)(nil)
|
|
_ GarbageCollectorExcludeSubstrModifier = (*ProtoGarbageCollectorExcludeSubstrModifier)(nil)
|
|
_ gotenberg.Module = (*ProtoGarbageCollectorExcludeSubstrModifier)(nil)
|
|
_ gotenberg.Validator = (*ProtoGarbageCollectorExcludeSubstrModifier)(nil)
|
|
_ gotenberg.LoggerProvider = (*ProtoLoggerProvider)(nil)
|
|
_ gotenberg.Module = (*ProtoLoggerProvider)(nil)
|
|
)
|