mirror of
https://github.com/gotenberg/gotenberg.git
synced 2026-08-17 12:42:16 +01:00
feat(api): add debug route
This commit is contained in:
2
Makefile
2
Makefile
@@ -37,6 +37,7 @@ API-DOWNLOAD-FROM-DENY-LIST=
|
|||||||
API-DOWNLOAD-FROM-FROM-MAX-RETRY=4
|
API-DOWNLOAD-FROM-FROM-MAX-RETRY=4
|
||||||
API-DISABLE-DOWNLOAD-FROM=false
|
API-DISABLE-DOWNLOAD-FROM=false
|
||||||
API_DISABLE_HEALTH_CHECK_LOGGING=false
|
API_DISABLE_HEALTH_CHECK_LOGGING=false
|
||||||
|
API_ENABLE_DEBUG_ROUTE=false
|
||||||
CHROMIUM_RESTART_AFTER=10
|
CHROMIUM_RESTART_AFTER=10
|
||||||
CHROMIUM_MAX_QUEUE_SIZE=0
|
CHROMIUM_MAX_QUEUE_SIZE=0
|
||||||
CHROMIUM_AUTO_START=false
|
CHROMIUM_AUTO_START=false
|
||||||
@@ -107,6 +108,7 @@ run: ## Start a Gotenberg container
|
|||||||
--api-download-from-max-retry=$(API-DOWNLOAD-FROM-FROM-MAX-RETRY) \
|
--api-download-from-max-retry=$(API-DOWNLOAD-FROM-FROM-MAX-RETRY) \
|
||||||
--api-disable-download-from=$(API-DISABLE-DOWNLOAD-FROM) \
|
--api-disable-download-from=$(API-DISABLE-DOWNLOAD-FROM) \
|
||||||
--api-disable-health-check-logging=$(API_DISABLE_HEALTH_CHECK_LOGGING) \
|
--api-disable-health-check-logging=$(API_DISABLE_HEALTH_CHECK_LOGGING) \
|
||||||
|
--api-enable-debug-route=$(API_ENABLE_DEBUG_ROUTE) \
|
||||||
--chromium-restart-after=$(CHROMIUM_RESTART_AFTER) \
|
--chromium-restart-after=$(CHROMIUM_RESTART_AFTER) \
|
||||||
--chromium-auto-start=$(CHROMIUM_AUTO_START) \
|
--chromium-auto-start=$(CHROMIUM_AUTO_START) \
|
||||||
--chromium-max-queue-size=$(CHROMIUM_MAX_QUEUE_SIZE) \
|
--chromium-max-queue-size=$(CHROMIUM_MAX_QUEUE_SIZE) \
|
||||||
|
|||||||
@@ -108,6 +108,9 @@ func Run() {
|
|||||||
}(l.(gotenberg.SystemLogger))
|
}(l.(gotenberg.SystemLogger))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build the debug data.
|
||||||
|
gotenberg.BuildDebug(ctx)
|
||||||
|
|
||||||
quit := make(chan os.Signal, 1)
|
quit := make(chan os.Signal, 1)
|
||||||
|
|
||||||
// We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C) or SIGTERM (Kubernetes).
|
// We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C) or SIGTERM (Kubernetes).
|
||||||
|
|||||||
@@ -1,4 +1,65 @@
|
|||||||
package gotenberg
|
package gotenberg
|
||||||
|
|
||||||
// Version is the... version of the Gotenberg application.
|
import (
|
||||||
var Version = "snapshot"
|
"runtime"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
flag "github.com/spf13/pflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DebugInfo gathers data for debugging.
|
||||||
|
type DebugInfo struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Architecture string `json:"architecture"`
|
||||||
|
Modules []string `json:"modules"`
|
||||||
|
ModulesAdditionalData map[string]map[string]interface{} `json:"modules_additional_data"`
|
||||||
|
Flags map[string]interface{} `json:"flags"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildDebug builds the debug data from modules.
|
||||||
|
func BuildDebug(ctx *Context) {
|
||||||
|
debugMu.Lock()
|
||||||
|
defer debugMu.Unlock()
|
||||||
|
|
||||||
|
debug = &DebugInfo{
|
||||||
|
Version: Version,
|
||||||
|
Architecture: runtime.GOARCH,
|
||||||
|
Modules: make([]string, len(ctx.moduleInstances)),
|
||||||
|
ModulesAdditionalData: make(map[string]map[string]interface{}),
|
||||||
|
Flags: make(map[string]interface{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for ID, mod := range ctx.moduleInstances {
|
||||||
|
debug.Modules[i] = ID
|
||||||
|
i++
|
||||||
|
|
||||||
|
debuggable, ok := mod.(Debuggable)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
debug.ModulesAdditionalData[ID] = debuggable.Debug()
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.ParsedFlags().VisitAll(func(f *flag.Flag) {
|
||||||
|
debug.Flags[f.Name] = f.Value.String()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug returns the debug data.
|
||||||
|
func Debug() DebugInfo {
|
||||||
|
debugMu.Lock()
|
||||||
|
defer debugMu.Unlock()
|
||||||
|
|
||||||
|
if debug == nil {
|
||||||
|
return DebugInfo{}
|
||||||
|
}
|
||||||
|
|
||||||
|
return *debug
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
debug *DebugInfo
|
||||||
|
debugMu sync.Mutex
|
||||||
|
)
|
||||||
|
|||||||
72
pkg/gotenberg/debug_test.go
Normal file
72
pkg/gotenberg/debug_test.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package gotenberg
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
flag "github.com/spf13/pflag"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuildDebug(t *testing.T) {
|
||||||
|
if !reflect.DeepEqual(Debug(), DebugInfo{}) {
|
||||||
|
t.Errorf("Debug() should return empty debug data")
|
||||||
|
}
|
||||||
|
|
||||||
|
fs := flag.NewFlagSet("gotenberg", flag.ExitOnError)
|
||||||
|
fs.String("foo", "bar", "Set foo")
|
||||||
|
ctx := NewContext(ParsedFlags{
|
||||||
|
FlagSet: fs,
|
||||||
|
}, func() []ModuleDescriptor {
|
||||||
|
mod1 := &struct {
|
||||||
|
ModuleMock
|
||||||
|
}{}
|
||||||
|
mod1.DescriptorMock = func() ModuleDescriptor {
|
||||||
|
return ModuleDescriptor{ID: "foo", New: func() Module { return mod1 }}
|
||||||
|
}
|
||||||
|
mod2 := &struct {
|
||||||
|
ModuleMock
|
||||||
|
DebuggableMock
|
||||||
|
}{}
|
||||||
|
mod2.DescriptorMock = func() ModuleDescriptor {
|
||||||
|
return ModuleDescriptor{ID: "bar", New: func() Module { return mod2 }}
|
||||||
|
}
|
||||||
|
mod2.DebugMock = func() map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return []ModuleDescriptor{mod1.Descriptor(), mod2.Descriptor()}
|
||||||
|
}())
|
||||||
|
|
||||||
|
// Load modules.
|
||||||
|
_, err := ctx.Modules(new(Module))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("expected no error but got: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build debug data.
|
||||||
|
BuildDebug(ctx)
|
||||||
|
|
||||||
|
expect := DebugInfo{
|
||||||
|
Version: Version,
|
||||||
|
Architecture: runtime.GOARCH,
|
||||||
|
Modules: []string{
|
||||||
|
"foo",
|
||||||
|
"bar",
|
||||||
|
},
|
||||||
|
ModulesAdditionalData: map[string]map[string]interface{}{
|
||||||
|
"bar": {
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Flags: map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(expect, Debug()) {
|
||||||
|
t.Errorf("expected '%+v', bug got '%+v'", expect, Debug())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,6 +34,14 @@ func (mod *ValidatorMock) Validate() error {
|
|||||||
return mod.ValidateMock()
|
return mod.ValidateMock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DebuggableMock struct {
|
||||||
|
DebugMock func() map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mod *DebuggableMock) Debug() map[string]interface{} {
|
||||||
|
return mod.DebugMock()
|
||||||
|
}
|
||||||
|
|
||||||
// PdfEngineMock is a mock for the [PdfEngine] interface.
|
// PdfEngineMock is a mock for the [PdfEngine] interface.
|
||||||
//
|
//
|
||||||
//nolint:dupl
|
//nolint:dupl
|
||||||
|
|||||||
@@ -48,6 +48,21 @@ func TestValidatorMock(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDebuggableMock(t *testing.T) {
|
||||||
|
mock := &DebuggableMock{
|
||||||
|
DebugMock: func() map[string]interface{} {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
d := mock.Debug()
|
||||||
|
if d == nil {
|
||||||
|
t.Errorf("expected debug data, but got nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPDFEngineMock(t *testing.T) {
|
func TestPDFEngineMock(t *testing.T) {
|
||||||
mock := &PdfEngineMock{
|
mock := &PdfEngineMock{
|
||||||
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
MergeMock: func(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
||||||
|
|||||||
@@ -75,6 +75,12 @@ type SystemLogger interface {
|
|||||||
SystemMessages() []string
|
SystemMessages() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debuggable is a module interface for modules which want to provide
|
||||||
|
// additional debug data.
|
||||||
|
type Debuggable interface {
|
||||||
|
Debug() map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
// MustRegisterModule registers a module.
|
// MustRegisterModule registers a module.
|
||||||
//
|
//
|
||||||
// To register a module, create an init() method in the module main go file:
|
// To register a module, create an init() method in the module main go file:
|
||||||
|
|||||||
4
pkg/gotenberg/version.go
Normal file
4
pkg/gotenberg/version.go
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
package gotenberg
|
||||||
|
|
||||||
|
// Version is the... version of the Gotenberg application.
|
||||||
|
var Version = "snapshot"
|
||||||
@@ -42,6 +42,7 @@ type Api struct {
|
|||||||
basicAuthPassword string
|
basicAuthPassword string
|
||||||
downloadFromCfg downloadFromConfig
|
downloadFromCfg downloadFromConfig
|
||||||
disableHealthCheckLogging bool
|
disableHealthCheckLogging bool
|
||||||
|
enableDebugRoute bool
|
||||||
|
|
||||||
routes []Route
|
routes []Route
|
||||||
externalMiddlewares []Middleware
|
externalMiddlewares []Middleware
|
||||||
@@ -187,6 +188,7 @@ func (a *Api) Descriptor() gotenberg.ModuleDescriptor {
|
|||||||
fs.Int("api-download-from-max-retry", 4, "Set the maximum number of retries for the download from feature")
|
fs.Int("api-download-from-max-retry", 4, "Set the maximum number of retries for the download from feature")
|
||||||
fs.Bool("api-disable-download-from", false, "Disable the download from feature")
|
fs.Bool("api-disable-download-from", false, "Disable the download from feature")
|
||||||
fs.Bool("api-disable-health-check-logging", false, "Disable health check logging")
|
fs.Bool("api-disable-health-check-logging", false, "Disable health check logging")
|
||||||
|
fs.Bool("api-enable-debug-route", false, "Enable the debug route")
|
||||||
return fs
|
return fs
|
||||||
}(),
|
}(),
|
||||||
New: func() gotenberg.Module { return new(Api) },
|
New: func() gotenberg.Module { return new(Api) },
|
||||||
@@ -212,6 +214,7 @@ func (a *Api) Provision(ctx *gotenberg.Context) error {
|
|||||||
disable: flags.MustBool("api-disable-download-from"),
|
disable: flags.MustBool("api-disable-download-from"),
|
||||||
}
|
}
|
||||||
a.disableHealthCheckLogging = flags.MustBool("api-disable-health-check-logging")
|
a.disableHealthCheckLogging = flags.MustBool("api-disable-health-check-logging")
|
||||||
|
a.enableDebugRoute = flags.MustBool("api-enable-debug-route")
|
||||||
|
|
||||||
// Port from env?
|
// Port from env?
|
||||||
portEnvVar := flags.MustString("api-port-from-env")
|
portEnvVar := flags.MustString("api-port-from-env")
|
||||||
@@ -365,9 +368,10 @@ func (a *Api) Validate() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
routesMap := make(map[string]string, len(a.routes)+2)
|
routesMap := make(map[string]string, len(a.routes)+3)
|
||||||
routesMap["/health"] = "/health"
|
routesMap["/health"] = "/health"
|
||||||
routesMap["/version"] = "/version"
|
routesMap["/version"] = "/version"
|
||||||
|
routesMap["/debug"] = "/debug"
|
||||||
|
|
||||||
for _, route := range a.routes {
|
for _, route := range a.routes {
|
||||||
if route.Path == "" {
|
if route.Path == "" {
|
||||||
@@ -529,7 +533,7 @@ func (a *Api) Start() error {
|
|||||||
hardTimeoutMiddleware(hardTimeout),
|
hardTimeoutMiddleware(hardTimeout),
|
||||||
)
|
)
|
||||||
|
|
||||||
// ...and the version route.
|
// ...the version route.
|
||||||
a.srv.GET(
|
a.srv.GET(
|
||||||
fmt.Sprintf("%s%s", a.rootPath, "version"),
|
fmt.Sprintf("%s%s", a.rootPath, "version"),
|
||||||
func(c echo.Context) error {
|
func(c echo.Context) error {
|
||||||
@@ -538,6 +542,17 @@ func (a *Api) Start() error {
|
|||||||
securityMiddleware,
|
securityMiddleware,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ...and the debug route.
|
||||||
|
if a.enableDebugRoute {
|
||||||
|
a.srv.GET(
|
||||||
|
fmt.Sprintf("%s%s", a.rootPath, "debug"),
|
||||||
|
func(c echo.Context) error {
|
||||||
|
return c.JSONPretty(http.StatusOK, gotenberg.Debug(), " ")
|
||||||
|
},
|
||||||
|
securityMiddleware,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for all modules to be ready.
|
// Wait for all modules to be ready.
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), a.startTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), a.startTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|||||||
@@ -786,6 +786,7 @@ func TestApi_Start(t *testing.T) {
|
|||||||
mod.basicAuthUsername = "foo"
|
mod.basicAuthUsername = "foo"
|
||||||
mod.basicAuthPassword = "bar"
|
mod.basicAuthPassword = "bar"
|
||||||
mod.disableHealthCheckLogging = true
|
mod.disableHealthCheckLogging = true
|
||||||
|
mod.enableDebugRoute = true
|
||||||
mod.routes = []Route{
|
mod.routes = []Route{
|
||||||
{
|
{
|
||||||
Method: http.MethodPost,
|
Method: http.MethodPost,
|
||||||
@@ -908,6 +909,15 @@ func TestApi_Start(t *testing.T) {
|
|||||||
t.Errorf("expected %d status code but got %d", http.StatusOK, recorder.Code)
|
t.Errorf("expected %d status code but got %d", http.StatusOK, recorder.Code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// debug request.
|
||||||
|
recorder = httptest.NewRecorder()
|
||||||
|
debugRequest := httptest.NewRequest(http.MethodGet, "/debug", nil)
|
||||||
|
debugRequest.SetBasicAuth(mod.basicAuthUsername, mod.basicAuthPassword)
|
||||||
|
mod.srv.ServeHTTP(recorder, debugRequest)
|
||||||
|
if recorder.Code != http.StatusOK {
|
||||||
|
t.Errorf("expected %d status code but got %d", http.StatusOK, recorder.Code)
|
||||||
|
}
|
||||||
|
|
||||||
// "multipart/form-data" request.
|
// "multipart/form-data" request.
|
||||||
multipartRequest := func(url string) *http.Request {
|
multipartRequest := func(url string) *http.Request {
|
||||||
body := &bytes.Buffer{}
|
body := &bytes.Buffer{}
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alexliesenfeld/health"
|
"github.com/alexliesenfeld/health"
|
||||||
@@ -484,6 +487,23 @@ func (mod *Chromium) Stop(ctx context.Context) error {
|
|||||||
return fmt.Errorf("stop Chromium: %w", err)
|
return fmt.Errorf("stop Chromium: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug returns additional debug data.
|
||||||
|
func (mod *Chromium) Debug() map[string]interface{} {
|
||||||
|
debug := make(map[string]interface{})
|
||||||
|
|
||||||
|
cmd := exec.Command(mod.args.binPath, "--version") //nolint:gosec
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||||
|
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
debug["version"] = err.Error()
|
||||||
|
return debug
|
||||||
|
}
|
||||||
|
|
||||||
|
debug["version"] = strings.TrimSpace(string(output))
|
||||||
|
return debug
|
||||||
|
}
|
||||||
|
|
||||||
// Metrics returns the metrics.
|
// Metrics returns the metrics.
|
||||||
func (mod *Chromium) Metrics() ([]gotenberg.Metric, error) {
|
func (mod *Chromium) Metrics() ([]gotenberg.Metric, error) {
|
||||||
return []gotenberg.Metric{
|
return []gotenberg.Metric{
|
||||||
@@ -593,6 +613,7 @@ var (
|
|||||||
_ gotenberg.Provisioner = (*Chromium)(nil)
|
_ gotenberg.Provisioner = (*Chromium)(nil)
|
||||||
_ gotenberg.Validator = (*Chromium)(nil)
|
_ gotenberg.Validator = (*Chromium)(nil)
|
||||||
_ gotenberg.App = (*Chromium)(nil)
|
_ gotenberg.App = (*Chromium)(nil)
|
||||||
|
_ gotenberg.Debuggable = (*Chromium)(nil)
|
||||||
_ gotenberg.MetricsProvider = (*Chromium)(nil)
|
_ gotenberg.MetricsProvider = (*Chromium)(nil)
|
||||||
_ api.HealthChecker = (*Chromium)(nil)
|
_ api.HealthChecker = (*Chromium)(nil)
|
||||||
_ api.Router = (*Chromium)(nil)
|
_ api.Router = (*Chromium)(nil)
|
||||||
|
|||||||
@@ -336,6 +336,54 @@ func TestChromium_Stop(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestChromium_Debug(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
scenario string
|
||||||
|
mod *Chromium
|
||||||
|
expect map[string]interface{}
|
||||||
|
doNotExpect map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
scenario: "cannot determine version",
|
||||||
|
mod: &Chromium{
|
||||||
|
args: browserArguments{
|
||||||
|
binPath: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: map[string]interface{}{
|
||||||
|
"version": `exec: "foo": executable file not found in $PATH`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "success",
|
||||||
|
mod: &Chromium{
|
||||||
|
args: browserArguments{
|
||||||
|
binPath: "echo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
doNotExpect: map[string]interface{}{
|
||||||
|
"version": `exec: "echo": executable file not found in $PATH`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
|
d := tc.mod.Debug()
|
||||||
|
|
||||||
|
if tc.expect != nil {
|
||||||
|
if !reflect.DeepEqual(d, tc.expect) {
|
||||||
|
t.Errorf("expected '%v' but got '%v'", tc.expect, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.doNotExpect != nil {
|
||||||
|
if reflect.DeepEqual(d, tc.doNotExpect) {
|
||||||
|
t.Errorf("did not expect '%v'", d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestChromium_Metrics(t *testing.T) {
|
func TestChromium_Metrics(t *testing.T) {
|
||||||
mod := new(Chromium)
|
mod := new(Chromium)
|
||||||
mod.supervisor = &gotenberg.ProcessSupervisorMock{
|
mod.supervisor = &gotenberg.ProcessSupervisorMock{
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/barasher/go-exiftool"
|
"github.com/barasher/go-exiftool"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@@ -53,6 +56,23 @@ func (engine *ExifTool) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug returns additional debug data.
|
||||||
|
func (engine *ExifTool) Debug() map[string]interface{} {
|
||||||
|
debug := make(map[string]interface{})
|
||||||
|
|
||||||
|
cmd := exec.Command(engine.binPath, "-ver") //nolint:gosec
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||||
|
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
debug["version"] = err.Error()
|
||||||
|
return debug
|
||||||
|
}
|
||||||
|
|
||||||
|
debug["version"] = strings.TrimSpace(string(output))
|
||||||
|
return debug
|
||||||
|
}
|
||||||
|
|
||||||
// Merge is not available in this implementation.
|
// Merge is not available in this implementation.
|
||||||
func (engine *ExifTool) Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
func (engine *ExifTool) Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
||||||
return fmt.Errorf("merge PDFs with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
return fmt.Errorf("merge PDFs with ExifTool: %w", gotenberg.ErrPdfEngineMethodNotSupported)
|
||||||
@@ -161,5 +181,6 @@ var (
|
|||||||
_ gotenberg.Module = (*ExifTool)(nil)
|
_ gotenberg.Module = (*ExifTool)(nil)
|
||||||
_ gotenberg.Provisioner = (*ExifTool)(nil)
|
_ gotenberg.Provisioner = (*ExifTool)(nil)
|
||||||
_ gotenberg.Validator = (*ExifTool)(nil)
|
_ gotenberg.Validator = (*ExifTool)(nil)
|
||||||
|
_ gotenberg.Debuggable = (*ExifTool)(nil)
|
||||||
_ gotenberg.PdfEngine = (*ExifTool)(nil)
|
_ gotenberg.PdfEngine = (*ExifTool)(nil)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -73,6 +73,50 @@ func TestExifTool_Validate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExifTool_Debug(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
scenario string
|
||||||
|
engine *ExifTool
|
||||||
|
expect map[string]interface{}
|
||||||
|
doNotExpect map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
scenario: "cannot determine version",
|
||||||
|
engine: &ExifTool{
|
||||||
|
binPath: "foo",
|
||||||
|
},
|
||||||
|
expect: map[string]interface{}{
|
||||||
|
"version": `exec: "foo": executable file not found in $PATH`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "success",
|
||||||
|
engine: &ExifTool{
|
||||||
|
binPath: "echo",
|
||||||
|
},
|
||||||
|
doNotExpect: map[string]interface{}{
|
||||||
|
"version": `exec: "echo": executable file not found in $PATH`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
|
d := tc.engine.Debug()
|
||||||
|
|
||||||
|
if tc.expect != nil {
|
||||||
|
if !reflect.DeepEqual(d, tc.expect) {
|
||||||
|
t.Errorf("expected '%v' but got '%v'", tc.expect, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.doNotExpect != nil {
|
||||||
|
if reflect.DeepEqual(d, tc.doNotExpect) {
|
||||||
|
t.Errorf("did not expect '%v'", d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestExiftool_Merge(t *testing.T) {
|
func TestExiftool_Merge(t *testing.T) {
|
||||||
engine := new(ExifTool)
|
engine := new(ExifTool)
|
||||||
err := engine.Merge(context.Background(), zap.NewNop(), nil, "")
|
err := engine.Merge(context.Background(), zap.NewNop(), nil, "")
|
||||||
|
|||||||
@@ -5,6 +5,9 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/alexliesenfeld/health"
|
"github.com/alexliesenfeld/health"
|
||||||
@@ -305,6 +308,23 @@ func (a *Api) Stop(ctx context.Context) error {
|
|||||||
return fmt.Errorf("stop LibreOffice: %w", err)
|
return fmt.Errorf("stop LibreOffice: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug returns additional debug data.
|
||||||
|
func (a *Api) Debug() map[string]interface{} {
|
||||||
|
debug := make(map[string]interface{})
|
||||||
|
|
||||||
|
cmd := exec.Command(a.args.binPath, "--version") //nolint:gosec
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||||
|
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
debug["version"] = err.Error()
|
||||||
|
return debug
|
||||||
|
}
|
||||||
|
|
||||||
|
debug["version"] = strings.TrimSpace(string(output))
|
||||||
|
return debug
|
||||||
|
}
|
||||||
|
|
||||||
// Metrics returns the metrics.
|
// Metrics returns the metrics.
|
||||||
func (a *Api) Metrics() ([]gotenberg.Metric, error) {
|
func (a *Api) Metrics() ([]gotenberg.Metric, error) {
|
||||||
return []gotenberg.Metric{
|
return []gotenberg.Metric{
|
||||||
@@ -536,6 +556,7 @@ var (
|
|||||||
_ gotenberg.Provisioner = (*Api)(nil)
|
_ gotenberg.Provisioner = (*Api)(nil)
|
||||||
_ gotenberg.Validator = (*Api)(nil)
|
_ gotenberg.Validator = (*Api)(nil)
|
||||||
_ gotenberg.App = (*Api)(nil)
|
_ gotenberg.App = (*Api)(nil)
|
||||||
|
_ gotenberg.Debuggable = (*Api)(nil)
|
||||||
_ gotenberg.MetricsProvider = (*Api)(nil)
|
_ gotenberg.MetricsProvider = (*Api)(nil)
|
||||||
_ api.HealthChecker = (*Api)(nil)
|
_ api.HealthChecker = (*Api)(nil)
|
||||||
_ Uno = (*Api)(nil)
|
_ Uno = (*Api)(nil)
|
||||||
|
|||||||
@@ -277,6 +277,54 @@ func TestApi_Stop(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPdfTk_Debug(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
scenario string
|
||||||
|
a *Api
|
||||||
|
expect map[string]interface{}
|
||||||
|
doNotExpect map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
scenario: "cannot determine version",
|
||||||
|
a: &Api{
|
||||||
|
args: libreOfficeArguments{
|
||||||
|
binPath: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expect: map[string]interface{}{
|
||||||
|
"version": `exec: "foo": executable file not found in $PATH`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "success",
|
||||||
|
a: &Api{
|
||||||
|
args: libreOfficeArguments{
|
||||||
|
binPath: "echo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
doNotExpect: map[string]interface{}{
|
||||||
|
"version": `exec: "echo": executable file not found in $PATH`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
|
d := tc.a.Debug()
|
||||||
|
|
||||||
|
if tc.expect != nil {
|
||||||
|
if !reflect.DeepEqual(d, tc.expect) {
|
||||||
|
t.Errorf("expected '%v' but got '%v'", tc.expect, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.doNotExpect != nil {
|
||||||
|
if reflect.DeepEqual(d, tc.doNotExpect) {
|
||||||
|
t.Errorf("did not expect '%v'", d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestApi_Metrics(t *testing.T) {
|
func TestApi_Metrics(t *testing.T) {
|
||||||
a := new(Api)
|
a := new(Api)
|
||||||
a.supervisor = &gotenberg.ProcessSupervisorMock{
|
a.supervisor = &gotenberg.ProcessSupervisorMock{
|
||||||
|
|||||||
@@ -5,7 +5,10 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
@@ -52,6 +55,32 @@ func (engine *PdfCpu) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug returns additional debug data.
|
||||||
|
func (engine *PdfCpu) Debug() map[string]interface{} {
|
||||||
|
debug := make(map[string]interface{})
|
||||||
|
|
||||||
|
cmd := exec.Command(engine.binPath, "version") //nolint:gosec
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||||
|
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
debug["version"] = err.Error()
|
||||||
|
return debug
|
||||||
|
}
|
||||||
|
|
||||||
|
debug["version"] = "Unable to determine pdfcpu version"
|
||||||
|
|
||||||
|
lines := strings.Split(string(output), "\n")
|
||||||
|
for _, line := range lines {
|
||||||
|
if strings.HasPrefix(line, "pdfcpu:") {
|
||||||
|
debug["version"] = strings.TrimSpace(strings.TrimPrefix(line, "pdfcpu:"))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return debug
|
||||||
|
}
|
||||||
|
|
||||||
// Merge combines multiple PDFs into a single PDF.
|
// Merge combines multiple PDFs into a single PDF.
|
||||||
func (engine *PdfCpu) Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
func (engine *PdfCpu) Merge(ctx context.Context, logger *zap.Logger, inputPaths []string, outputPath string) error {
|
||||||
var args []string
|
var args []string
|
||||||
@@ -132,5 +161,6 @@ var (
|
|||||||
_ gotenberg.Module = (*PdfCpu)(nil)
|
_ gotenberg.Module = (*PdfCpu)(nil)
|
||||||
_ gotenberg.Provisioner = (*PdfCpu)(nil)
|
_ gotenberg.Provisioner = (*PdfCpu)(nil)
|
||||||
_ gotenberg.Validator = (*PdfCpu)(nil)
|
_ gotenberg.Validator = (*PdfCpu)(nil)
|
||||||
|
_ gotenberg.Debuggable = (*PdfCpu)(nil)
|
||||||
_ gotenberg.PdfEngine = (*PdfCpu)(nil)
|
_ gotenberg.PdfEngine = (*PdfCpu)(nil)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -71,6 +71,59 @@ func TestPdfCpu_Validate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPdfCpu_Debug(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
scenario string
|
||||||
|
engine *PdfCpu
|
||||||
|
expect map[string]interface{}
|
||||||
|
doNotExpect map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
scenario: "cannot determine version (command error)",
|
||||||
|
engine: &PdfCpu{
|
||||||
|
binPath: "foo",
|
||||||
|
},
|
||||||
|
expect: map[string]interface{}{
|
||||||
|
"version": `exec: "foo": executable file not found in $PATH`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "cannot determine version (no pdfcpu)",
|
||||||
|
engine: &PdfCpu{
|
||||||
|
binPath: "echo",
|
||||||
|
},
|
||||||
|
expect: map[string]interface{}{
|
||||||
|
"version": "Unable to determine pdfcpu version",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "success",
|
||||||
|
engine: &PdfCpu{
|
||||||
|
binPath: "pdfcpu",
|
||||||
|
},
|
||||||
|
doNotExpect: map[string]interface{}{
|
||||||
|
"version": "Unable to determine pdfcpu version",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
|
d := tc.engine.Debug()
|
||||||
|
|
||||||
|
if tc.expect != nil {
|
||||||
|
if !reflect.DeepEqual(d, tc.expect) {
|
||||||
|
t.Errorf("expected '%v' but got '%v'", tc.expect, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.doNotExpect != nil {
|
||||||
|
if reflect.DeepEqual(d, tc.doNotExpect) {
|
||||||
|
t.Errorf("did not expect '%v'", d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPdfCpu_Merge(t *testing.T) {
|
func TestPdfCpu_Merge(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
scenario string
|
scenario string
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package pdftk
|
package pdftk
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
@@ -52,6 +55,29 @@ func (engine *PdfTk) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug returns additional debug data.
|
||||||
|
func (engine *PdfTk) Debug() map[string]interface{} {
|
||||||
|
debug := make(map[string]interface{})
|
||||||
|
|
||||||
|
cmd := exec.Command(engine.binPath, "--version") //nolint:gosec
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||||
|
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
debug["version"] = err.Error()
|
||||||
|
return debug
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := bytes.SplitN(output, []byte("\n"), 2)
|
||||||
|
if len(lines) > 0 {
|
||||||
|
debug["version"] = string(lines[0])
|
||||||
|
} else {
|
||||||
|
debug["version"] = "Unable to determine PDFtk version"
|
||||||
|
}
|
||||||
|
|
||||||
|
return debug
|
||||||
|
}
|
||||||
|
|
||||||
// Split splits a given PDF file.
|
// Split splits a given PDF file.
|
||||||
func (engine *PdfTk) Split(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
func (engine *PdfTk) Split(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
var args []string
|
var args []string
|
||||||
@@ -124,5 +150,6 @@ var (
|
|||||||
_ gotenberg.Module = (*PdfTk)(nil)
|
_ gotenberg.Module = (*PdfTk)(nil)
|
||||||
_ gotenberg.Provisioner = (*PdfTk)(nil)
|
_ gotenberg.Provisioner = (*PdfTk)(nil)
|
||||||
_ gotenberg.Validator = (*PdfTk)(nil)
|
_ gotenberg.Validator = (*PdfTk)(nil)
|
||||||
|
_ gotenberg.Debuggable = (*PdfTk)(nil)
|
||||||
_ gotenberg.PdfEngine = (*PdfTk)(nil)
|
_ gotenberg.PdfEngine = (*PdfTk)(nil)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -71,6 +71,50 @@ func TestPdfTk_Validate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPdfTk_Debug(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
scenario string
|
||||||
|
engine *PdfTk
|
||||||
|
expect map[string]interface{}
|
||||||
|
doNotExpect map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
scenario: "cannot determine version",
|
||||||
|
engine: &PdfTk{
|
||||||
|
binPath: "foo",
|
||||||
|
},
|
||||||
|
expect: map[string]interface{}{
|
||||||
|
"version": `exec: "foo": executable file not found in $PATH`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "success",
|
||||||
|
engine: &PdfTk{
|
||||||
|
binPath: "echo",
|
||||||
|
},
|
||||||
|
doNotExpect: map[string]interface{}{
|
||||||
|
"version": `exec: "echo": executable file not found in $PATH`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
|
d := tc.engine.Debug()
|
||||||
|
|
||||||
|
if tc.expect != nil {
|
||||||
|
if !reflect.DeepEqual(d, tc.expect) {
|
||||||
|
t.Errorf("expected '%v' but got '%v'", tc.expect, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.doNotExpect != nil {
|
||||||
|
if reflect.DeepEqual(d, tc.doNotExpect) {
|
||||||
|
t.Errorf("did not expect '%v'", d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestPdfTk_Merge(t *testing.T) {
|
func TestPdfTk_Merge(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
scenario string
|
scenario string
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package qpdf
|
package qpdf
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
|
|
||||||
@@ -52,6 +55,29 @@ func (engine *QPdf) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Debug returns additional debug data.
|
||||||
|
func (engine *QPdf) Debug() map[string]interface{} {
|
||||||
|
debug := make(map[string]interface{})
|
||||||
|
|
||||||
|
cmd := exec.Command(engine.binPath, "--version") //nolint:gosec
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||||
|
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
debug["version"] = err.Error()
|
||||||
|
return debug
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := bytes.SplitN(output, []byte("\n"), 2)
|
||||||
|
if len(lines) > 0 {
|
||||||
|
debug["version"] = string(lines[0])
|
||||||
|
} else {
|
||||||
|
debug["version"] = "Unable to determine QPDF version"
|
||||||
|
}
|
||||||
|
|
||||||
|
return debug
|
||||||
|
}
|
||||||
|
|
||||||
// Split splits a given PDF file.
|
// Split splits a given PDF file.
|
||||||
func (engine *QPdf) Split(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
func (engine *QPdf) Split(ctx context.Context, logger *zap.Logger, mode gotenberg.SplitMode, inputPath, outputDirPath string) ([]string, error) {
|
||||||
var args []string
|
var args []string
|
||||||
@@ -142,5 +168,6 @@ var (
|
|||||||
_ gotenberg.Module = (*QPdf)(nil)
|
_ gotenberg.Module = (*QPdf)(nil)
|
||||||
_ gotenberg.Provisioner = (*QPdf)(nil)
|
_ gotenberg.Provisioner = (*QPdf)(nil)
|
||||||
_ gotenberg.Validator = (*QPdf)(nil)
|
_ gotenberg.Validator = (*QPdf)(nil)
|
||||||
|
_ gotenberg.Debuggable = (*QPdf)(nil)
|
||||||
_ gotenberg.PdfEngine = (*QPdf)(nil)
|
_ gotenberg.PdfEngine = (*QPdf)(nil)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -73,6 +73,50 @@ func TestQPdf_Validate(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestQPdf_Debug(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
scenario string
|
||||||
|
engine *QPdf
|
||||||
|
expect map[string]interface{}
|
||||||
|
doNotExpect map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
scenario: "cannot determine version",
|
||||||
|
engine: &QPdf{
|
||||||
|
binPath: "foo",
|
||||||
|
},
|
||||||
|
expect: map[string]interface{}{
|
||||||
|
"version": `exec: "foo": executable file not found in $PATH`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
scenario: "success",
|
||||||
|
engine: &QPdf{
|
||||||
|
binPath: "echo",
|
||||||
|
},
|
||||||
|
doNotExpect: map[string]interface{}{
|
||||||
|
"version": `exec: "echo": executable file not found in $PATH`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.scenario, func(t *testing.T) {
|
||||||
|
d := tc.engine.Debug()
|
||||||
|
|
||||||
|
if tc.expect != nil {
|
||||||
|
if !reflect.DeepEqual(d, tc.expect) {
|
||||||
|
t.Errorf("expected '%v' but got '%v'", tc.expect, d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if tc.doNotExpect != nil {
|
||||||
|
if reflect.DeepEqual(d, tc.doNotExpect) {
|
||||||
|
t.Errorf("did not expect '%v'", d)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestQPdf_Merge(t *testing.T) {
|
func TestQPdf_Merge(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
scenario string
|
scenario string
|
||||||
|
|||||||
Reference in New Issue
Block a user