fix: LibreOffice newer versions stability (#697)

This commit is contained in:
Julien Neuhart
2023-10-23 17:05:10 +02:00
committed by GitHub
parent 9cc8e16d64
commit 258876d13f
59 changed files with 870 additions and 1383 deletions

View File

@@ -8,8 +8,9 @@ import (
"sync"
"time"
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
"go.uber.org/zap"
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
)
type listener interface {
@@ -42,15 +43,18 @@ type libreOfficeListener struct {
queueLength int
queueLengthMu sync.RWMutex
lockChan chan struct{}
logger *zap.Logger
fs *gotenberg.FileSystem
logger *zap.Logger
}
func newLibreOfficeListener(logger *zap.Logger, binPath string, startTimeout time.Duration, threshold int) listener {
func newLibreOfficeListener(logger *zap.Logger, fs *gotenberg.FileSystem, binPath string, startTimeout time.Duration, threshold int) listener {
return &libreOfficeListener{
binPath: binPath,
startTimeout: startTimeout,
threshold: threshold,
lockChan: make(chan struct{}, 1),
fs: fs,
logger: logger.Named("listener"),
}
}
@@ -65,10 +69,7 @@ func (listener *libreOfficeListener) start(logger *zap.Logger) error {
return fmt.Errorf("get free port: %w", err)
}
// Good to know: the garbage collector might delete the next directory
// while it is still running. It does seem to cause any issue though.
userProfileDirPath := gotenberg.NewDirPath()
userProfileDirPath := listener.fs.NewDirPath()
args := []string{
"--headless",
"--invisible",
@@ -158,6 +159,12 @@ func (listener *libreOfficeListener) start(logger *zap.Logger) error {
if err != nil {
logger.Debug(fmt.Sprintf("kill LibreOffice listener process: %v", err))
}
// And the user profile directory is deleted.
err = os.RemoveAll(userProfileDirPath)
if err != nil {
logger.Debug(fmt.Sprintf("remove user profile directory: %v", err))
}
}()
logger.Debug("waiting for the LibreOffice listener socket to be available...")
@@ -185,9 +192,21 @@ func (listener *libreOfficeListener) stop(logger *zap.Logger) error {
defer func() {
defer listener.cfgMu.RUnlock()
if listener.userProfileDirPath == "" {
return
}
err := os.RemoveAll(listener.userProfileDirPath)
if err != nil {
logger.Error(fmt.Sprintf("remove LibreOffice listener user profile directory: %v", err))
logger.Error(fmt.Sprintf("remove LibreOffice listener's user profile directory: %v", err))
}
logger.Debug(fmt.Sprintf("'%s' LibreOffice listener's user profile directory removed", listener.userProfileDirPath))
// Also remove listener specific files in the temporary directory.
err = gotenberg.GarbageCollect(logger, os.TempDir(), []string{"OSL_PIPE", ".tmp"})
if err != nil {
logger.Error(err.Error())
}
}()
@@ -317,7 +336,7 @@ func (listener *libreOfficeListener) unlock(logger *zap.Logger) error {
}
listener.usage += 1
if listener.usage < listener.threshold {
if listener.threshold > 0 && listener.usage < listener.threshold {
return nil
}

View File

@@ -7,6 +7,8 @@ import (
"time"
"go.uber.org/zap"
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
)
func TestListener_start(t *testing.T) {
@@ -17,11 +19,11 @@ func TestListener_start(t *testing.T) {
}{
{
name: "nominal behavior",
listener: newLibreOfficeListener(zap.NewNop(), os.Getenv("LIBREOFFICE_BIN_PATH"), time.Duration(10)*time.Second, 10),
listener: newLibreOfficeListener(zap.NewNop(), gotenberg.NewFileSystem(), os.Getenv("LIBREOFFICE_BIN_PATH"), time.Duration(10)*time.Second, 10),
},
{
name: "non-exit code 81 on first start",
listener: newLibreOfficeListener(zap.NewNop(), "foo", time.Duration(10)*time.Second, 10),
listener: newLibreOfficeListener(zap.NewNop(), gotenberg.NewFileSystem(), "foo", time.Duration(10)*time.Second, 10),
expectStartErr: true,
},
}
@@ -57,6 +59,7 @@ func TestListener_start(t *testing.T) {
func TestListener_stop(t *testing.T) {
listener := newLibreOfficeListener(
zap.NewNop(),
gotenberg.NewFileSystem(),
os.Getenv("LIBREOFFICE_BIN_PATH"),
time.Duration(10)*time.Second,
10,
@@ -76,6 +79,7 @@ func TestListener_stop(t *testing.T) {
func TestListener_restart(t *testing.T) {
listener := newLibreOfficeListener(
zap.NewNop(),
gotenberg.NewFileSystem(),
os.Getenv("LIBREOFFICE_BIN_PATH"),
time.Duration(10)*time.Second,
10,
@@ -107,7 +111,7 @@ func TestListener_lock(t *testing.T) {
{
name: "nominal behavior",
listener: func() listener {
listener := newLibreOfficeListener(zap.NewNop(), os.Getenv("LIBREOFFICE_BIN_PATH"), time.Duration(10)*time.Second, 10)
listener := newLibreOfficeListener(zap.NewNop(), gotenberg.NewFileSystem(), os.Getenv("LIBREOFFICE_BIN_PATH"), time.Duration(10)*time.Second, 10)
err := listener.start(zap.NewNop())
if err != nil {
@@ -123,7 +127,7 @@ func TestListener_lock(t *testing.T) {
},
{
name: "first start",
listener: newLibreOfficeListener(zap.NewNop(), os.Getenv("LIBREOFFICE_BIN_PATH"), time.Duration(10)*time.Second, 10),
listener: newLibreOfficeListener(zap.NewNop(), gotenberg.NewFileSystem(), os.Getenv("LIBREOFFICE_BIN_PATH"), time.Duration(10)*time.Second, 10),
ctx: context.Background(),
teardown: func(listener listener) error {
return listener.stop(zap.NewNop())
@@ -132,7 +136,7 @@ func TestListener_lock(t *testing.T) {
{
name: "unhealthy listener",
listener: func() listener {
listener := newLibreOfficeListener(zap.NewNop(), os.Getenv("LIBREOFFICE_BIN_PATH"), time.Duration(10)*time.Second, 10)
listener := newLibreOfficeListener(zap.NewNop(), gotenberg.NewFileSystem(), os.Getenv("LIBREOFFICE_BIN_PATH"), time.Duration(10)*time.Second, 10)
err := listener.start(zap.NewNop())
if err != nil {
@@ -154,7 +158,7 @@ func TestListener_lock(t *testing.T) {
{
name: "context done",
listener: func() listener {
listener := newLibreOfficeListener(zap.NewNop(), os.Getenv("LIBREOFFICE_BIN_PATH"), time.Duration(10)*time.Second, 10)
listener := newLibreOfficeListener(zap.NewNop(), gotenberg.NewFileSystem(), os.Getenv("LIBREOFFICE_BIN_PATH"), time.Duration(10)*time.Second, 10)
err := listener.start(zap.NewNop())
if err != nil {
@@ -212,7 +216,7 @@ func TestListener_unlock(t *testing.T) {
{
name: "nominal behavior",
listener: func() listener {
listener := newLibreOfficeListener(zap.NewNop(), os.Getenv("LIBREOFFICE_BIN_PATH"), time.Duration(10)*time.Second, 10)
listener := newLibreOfficeListener(zap.NewNop(), gotenberg.NewFileSystem(), os.Getenv("LIBREOFFICE_BIN_PATH"), time.Duration(10)*time.Second, 10)
err := listener.start(zap.NewNop())
if err != nil {
@@ -233,7 +237,7 @@ func TestListener_unlock(t *testing.T) {
{
name: "unhealthy listener",
listener: func() listener {
listener := newLibreOfficeListener(zap.NewNop(), os.Getenv("LIBREOFFICE_BIN_PATH"), time.Duration(10)*time.Second, 10)
listener := newLibreOfficeListener(zap.NewNop(), gotenberg.NewFileSystem(), os.Getenv("LIBREOFFICE_BIN_PATH"), time.Duration(10)*time.Second, 10)
err := listener.start(zap.NewNop())
if err != nil {
@@ -259,7 +263,7 @@ func TestListener_unlock(t *testing.T) {
{
name: "threshold reached",
listener: func() listener {
listener := newLibreOfficeListener(zap.NewNop(), os.Getenv("LIBREOFFICE_BIN_PATH"), time.Duration(10)*time.Second, 1)
listener := newLibreOfficeListener(zap.NewNop(), gotenberg.NewFileSystem(), os.Getenv("LIBREOFFICE_BIN_PATH"), time.Duration(10)*time.Second, 1)
err := listener.start(zap.NewNop())
if err != nil {
@@ -299,6 +303,7 @@ func TestListener_unlock(t *testing.T) {
func TestListener_port(t *testing.T) {
listener := newLibreOfficeListener(
zap.NewNop(),
gotenberg.NewFileSystem(),
os.Getenv("LIBREOFFICE_BIN_PATH"),
time.Duration(10)*time.Second,
10,
@@ -323,6 +328,7 @@ func TestListener_port(t *testing.T) {
func TestListener_queue(t *testing.T) {
listener := newLibreOfficeListener(
zap.NewNop(),
gotenberg.NewFileSystem(),
os.Getenv("LIBREOFFICE_BIN_PATH"),
time.Duration(10)*time.Second,
10,
@@ -395,6 +401,7 @@ func TestListener_healthy(t *testing.T) {
startTimeout: time.Duration(10) * time.Second,
threshold: 10,
lockChan: make(chan struct{}, 1),
fs: gotenberg.NewFileSystem(),
logger: zap.NewNop(),
}

View File

@@ -9,11 +9,12 @@ import (
"time"
"github.com/alexliesenfeld/health"
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
flag "github.com/spf13/pflag"
"go.uber.org/multierr"
"go.uber.org/zap"
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
"github.com/gotenberg/gotenberg/v7/pkg/modules/api"
)
func init() {
@@ -82,10 +83,10 @@ func (UNO) Descriptor() gotenberg.ModuleDescriptor {
FlagSet: func() *flag.FlagSet {
fs := flag.NewFlagSet("uno", flag.ExitOnError)
fs.Duration("uno-listener-start-timeout", time.Duration(10)*time.Second, "Time limit for restarting the LibreOffice listener")
fs.Int("uno-listener-restart-threshold", 10, "Conversions limit after which the LibreOffice listener is restarted - 0 means no long-running LibreOffice listener")
fs.Int("uno-listener-restart-threshold", 10, "Conversions limit after which the LibreOffice listener is restarted - 0 means no restart")
fs.Bool("unoconv-disable-listener", false, "Do not start a long-running listener - save resources in detriment of unitary performance")
err := fs.MarkDeprecated("unoconv-disable-listener", "use uno-listener-restart-threshold with 0 instead")
err := fs.MarkDeprecated("unoconv-disable-listener", "listener cannot be disabled")
if err != nil {
panic(fmt.Errorf("create deprecated flags for the uno module: %v", err))
}
@@ -134,8 +135,10 @@ func (mod *UNO) Provision(ctx *gotenberg.Context) error {
mod.logger = logger
// Listener.
mod.listener = newLibreOfficeListener(
mod.logger,
gotenberg.NewFileSystem(),
mod.libreOfficeBinPath,
mod.libreOfficeStartTimeout,
mod.libreOfficeRestartThreshold,
@@ -170,19 +173,11 @@ func (mod UNO) Start() error {
// StartupMessage returns a custom startup message.
func (mod UNO) StartupMessage() string {
if mod.libreOfficeRestartThreshold == 0 {
return "long-running LibreOffice listener disabled"
}
return "long-running LibreOffice listener ready to start"
}
// Stop stops the long-running LibreOffice Listener if it exists.
// Stop stops the long-running LibreOffice Listener.
func (mod UNO) Stop(ctx context.Context) error {
if mod.libreOfficeRestartThreshold == 0 {
return nil
}
// Block until the context is done so that other module may gracefully stop
// before we do a shutdown cleanup.
mod.logger.Debug("wait for the end of grace duration")
@@ -194,7 +189,7 @@ func (mod UNO) Stop(ctx context.Context) error {
return nil
}
return fmt.Errorf("stop long-running LibreOffice listener")
return fmt.Errorf("stop long-running LibreOffice listener: %w", err)
}
// Metrics returns the metrics.
@@ -284,14 +279,8 @@ func (mod UNO) Checks() ([]health.CheckerOption, error) {
}, nil
}
// PDF converts a document to PDF.
//
// If there is no long-running LibreOffice listener, it creates a dedicated
// LibreOffice instance for the conversion. Substantial calls to this method
// may increase CPU and memory usage drastically
//
// If there is a long-running LibreOffice listener, the conversion performance
// improves substantially. However, it cannot perform parallel operations.
// PDF converts a document to PDF. Be cautious when making multiple concurrent
// calls, as it might lead to reaching the context's deadline.
func (mod UNO) PDF(ctx context.Context, logger *zap.Logger, inputPath, outputPath string, options Options) error {
args := []string{
"--no-launch",
@@ -299,45 +288,26 @@ func (mod UNO) PDF(ctx context.Context, logger *zap.Logger, inputPath, outputPat
"pdf",
}
switch mod.libreOfficeRestartThreshold {
case 0:
listener := newLibreOfficeListener(logger, mod.libreOfficeBinPath, mod.libreOfficeStartTimeout, 0)
err := mod.listener.lock(ctx, logger)
if err != nil {
return fmt.Errorf("lock long-running LibreOffice listener: %w", err)
}
err := listener.start(logger)
if err != nil {
return fmt.Errorf("start LibreOffice listener: %w", err)
}
defer func() {
err := listener.stop(logger)
defer func() {
go func() {
err := mod.listener.unlock(logger)
if err != nil {
logger.Error(fmt.Sprintf("stop LibreOffice listener: %v", err))
mod.logger.Error(fmt.Sprintf("unlock long-running LibreOffice listener: %v", err))
return
}
}()
}()
args = append(args, "--port", fmt.Sprintf("%d", listener.port()))
default:
err := mod.listener.lock(ctx, logger)
if err != nil {
return fmt.Errorf("lock long-running LibreOffice listener: %w", err)
}
defer func() {
go func() {
err := mod.listener.unlock(logger)
if err != nil {
mod.logger.Error(fmt.Sprintf("unlock long-running LibreOffice listener: %v", err))
return
}
}()
}()
// If the LibreOffice listener is restarting while acquiring the lock,
// the port will change. It's therefore important to add the port args
// after we acquire the lock.
args = append(args, "--port", fmt.Sprintf("%d", mod.listener.port()))
}
// If the LibreOffice listener is restarting while acquiring the lock,
// the port will change. It's therefore important to add the port args
// after we acquire the lock.
args = append(args, "--port", fmt.Sprintf("%d", mod.listener.port()))
checkedEntry := logger.Check(zap.DebugLevel, "check for debug level before setting high verbosity")
if checkedEntry != nil {

View File

@@ -9,9 +9,10 @@ import (
"time"
"github.com/alexliesenfeld/health"
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
flag "github.com/spf13/pflag"
"go.uber.org/zap"
"github.com/gotenberg/gotenberg/v7/pkg/gotenberg"
)
func TestUNO_Descriptor(t *testing.T) {
@@ -78,7 +79,6 @@ func TestUNO_Provision(t *testing.T) {
FlagSet: func() *flag.FlagSet {
fs := new(UNO).Descriptor().FlagSet
err := fs.Parse([]string{"--unoconv-disable-listener=true"})
if err != nil {
t.Fatalf("expected no error from fs.Parse(), but got: %v", err)
}
@@ -205,35 +205,11 @@ func TestUNO_Start(t *testing.T) {
}
func TestUNO_StartupMessage(t *testing.T) {
tests := []struct {
name string
mod UNO
expectMessage string
}{
{
name: "long-running LibreOffice listener ready to start",
mod: UNO{
libreOfficeRestartThreshold: 10,
},
expectMessage: "long-running LibreOffice listener ready to start",
},
{
name: "long-running LibreOffice listener disabled",
mod: UNO{
libreOfficeRestartThreshold: 0,
},
expectMessage: "long-running LibreOffice listener disabled",
},
}
actual := new(UNO).StartupMessage()
expect := "long-running LibreOffice listener ready to start"
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
actual := tc.mod.StartupMessage()
if tc.expectMessage != actual {
t.Errorf("expected '%s' from mod.StartupMessage(), but got '%s'", tc.expectMessage, actual)
}
})
if actual != expect {
t.Errorf("expected '%s' but got '%s'", expect, actual)
}
}
@@ -255,13 +231,6 @@ func TestUNO_Stop(t *testing.T) {
logger: zap.NewNop(),
},
},
{
name: "no long-running LibreOffice listener",
mod: UNO{
libreOfficeRestartThreshold: 0,
logger: zap.NewNop(),
},
},
{
name: "stop error",
mod: UNO{
@@ -468,7 +437,7 @@ func TestUNO_Checks(t *testing.T) {
func TestUNO_PDF(t *testing.T) {
tests := []struct {
name string
scenario string
mod UNO
ctx context.Context
logger *zap.Logger
@@ -478,19 +447,7 @@ func TestUNO_PDF(t *testing.T) {
teardown func(mod UNO) error
}{
{
name: "nominal behavior with no long-running LibreOffice listener",
mod: UNO{
unoconvBinPath: os.Getenv("UNOCONV_BIN_PATH"),
libreOfficeBinPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
libreOfficeStartTimeout: time.Duration(10) * time.Second,
libreOfficeRestartThreshold: 0,
},
ctx: context.Background(),
logger: zap.NewNop(),
inputPath: "/tests/test/testdata/libreoffice/sample1.docx",
},
{
name: "nominal behavior with a long-running LibreOffice listener",
scenario: "nominal behavior with a long-running LibreOffice listener",
mod: func() UNO {
mod := UNO{
unoconvBinPath: os.Getenv("UNOCONV_BIN_PATH"),
@@ -501,6 +458,7 @@ func TestUNO_PDF(t *testing.T) {
}
mod.listener = newLibreOfficeListener(
mod.logger,
gotenberg.NewFileSystem(),
mod.libreOfficeBinPath,
mod.libreOfficeStartTimeout,
mod.libreOfficeRestartThreshold,
@@ -518,56 +476,122 @@ func TestUNO_PDF(t *testing.T) {
return mod.Stop(ctx)
},
},
//{
// scenario: "convert with a debug logger",
// mod: func() UNO {
// mod := UNO{
// unoconvBinPath: os.Getenv("UNOCONV_BIN_PATH"),
// libreOfficeBinPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
// libreOfficeStartTimeout: time.Duration(10) * time.Second,
// libreOfficeRestartThreshold: 10,
// logger: zap.NewNop(),
// }
// mod.listener = newLibreOfficeListener(
// mod.logger,
// gotenberg.NewFileSystem(),
// mod.libreOfficeBinPath,
// mod.libreOfficeStartTimeout,
// mod.libreOfficeRestartThreshold,
// )
//
// return mod
// }(),
// ctx: context.Background(),
// logger: zap.NewExample(),
// inputPath: "/tests/test/testdata/libreoffice/sample1.docx",
// teardown: func(mod UNO) error {
// ctx, cancel := context.WithCancel(context.Background())
// cancel()
//
// return mod.Stop(ctx)
// },
//},
{
name: "convert with a debug logger",
mod: UNO{
unoconvBinPath: os.Getenv("UNOCONV_BIN_PATH"),
libreOfficeBinPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
libreOfficeStartTimeout: time.Duration(10) * time.Second,
libreOfficeRestartThreshold: 0,
},
ctx: context.Background(),
logger: zap.NewExample(),
inputPath: "/tests/test/testdata/libreoffice/sample1.docx",
},
{
name: "convert with landscape",
mod: UNO{
unoconvBinPath: os.Getenv("UNOCONV_BIN_PATH"),
libreOfficeBinPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
libreOfficeStartTimeout: time.Duration(10) * time.Second,
libreOfficeRestartThreshold: 0,
},
scenario: "convert with landscape",
mod: func() UNO {
mod := UNO{
unoconvBinPath: os.Getenv("UNOCONV_BIN_PATH"),
libreOfficeBinPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
libreOfficeStartTimeout: time.Duration(10) * time.Second,
libreOfficeRestartThreshold: 10,
logger: zap.NewNop(),
}
mod.listener = newLibreOfficeListener(
mod.logger,
gotenberg.NewFileSystem(),
mod.libreOfficeBinPath,
mod.libreOfficeStartTimeout,
mod.libreOfficeRestartThreshold,
)
return mod
}(),
ctx: context.Background(),
logger: zap.NewNop(),
inputPath: "/tests/test/testdata/libreoffice/sample1.docx",
options: Options{
Landscape: true,
},
teardown: func(mod UNO) error {
ctx, cancel := context.WithCancel(context.Background())
cancel()
return mod.Stop(ctx)
},
},
{
name: "convert with page ranges",
mod: UNO{
unoconvBinPath: os.Getenv("UNOCONV_BIN_PATH"),
libreOfficeBinPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
libreOfficeStartTimeout: time.Duration(10) * time.Second,
libreOfficeRestartThreshold: 0,
},
scenario: "convert with page ranges",
mod: func() UNO {
mod := UNO{
unoconvBinPath: os.Getenv("UNOCONV_BIN_PATH"),
libreOfficeBinPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
libreOfficeStartTimeout: time.Duration(10) * time.Second,
libreOfficeRestartThreshold: 10,
logger: zap.NewNop(),
}
mod.listener = newLibreOfficeListener(
mod.logger,
gotenberg.NewFileSystem(),
mod.libreOfficeBinPath,
mod.libreOfficeStartTimeout,
mod.libreOfficeRestartThreshold,
)
return mod
}(),
ctx: context.Background(),
logger: zap.NewNop(),
inputPath: "/tests/test/testdata/libreoffice/sample1.docx",
options: Options{
PageRanges: "1-2",
},
teardown: func(mod UNO) error {
ctx, cancel := context.WithCancel(context.Background())
cancel()
return mod.Stop(ctx)
},
},
{
name: "convert with invalid page ranges",
mod: UNO{
unoconvBinPath: os.Getenv("UNOCONV_BIN_PATH"),
libreOfficeBinPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
libreOfficeStartTimeout: time.Duration(10) * time.Second,
libreOfficeRestartThreshold: 0,
},
scenario: "convert with invalid page ranges",
mod: func() UNO {
mod := UNO{
unoconvBinPath: os.Getenv("UNOCONV_BIN_PATH"),
libreOfficeBinPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
libreOfficeStartTimeout: time.Duration(10) * time.Second,
libreOfficeRestartThreshold: 10,
logger: zap.NewNop(),
}
mod.listener = newLibreOfficeListener(
mod.logger,
gotenberg.NewFileSystem(),
mod.libreOfficeBinPath,
mod.libreOfficeStartTimeout,
mod.libreOfficeRestartThreshold,
)
return mod
}(),
ctx: context.Background(),
logger: zap.NewNop(),
inputPath: "/tests/test/testdata/libreoffice/sample1.docx",
@@ -575,60 +599,132 @@ func TestUNO_PDF(t *testing.T) {
PageRanges: "foo",
},
expectPDFErr: true,
teardown: func(mod UNO) error {
ctx, cancel := context.WithCancel(context.Background())
cancel()
return mod.Stop(ctx)
},
},
{
name: "convert to PDF/A-1a",
mod: UNO{
unoconvBinPath: os.Getenv("UNOCONV_BIN_PATH"),
libreOfficeBinPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
libreOfficeStartTimeout: time.Duration(10) * time.Second,
libreOfficeRestartThreshold: 0,
},
scenario: "convert to PDF/A-1a",
mod: func() UNO {
mod := UNO{
unoconvBinPath: os.Getenv("UNOCONV_BIN_PATH"),
libreOfficeBinPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
libreOfficeStartTimeout: time.Duration(10) * time.Second,
libreOfficeRestartThreshold: 10,
logger: zap.NewNop(),
}
mod.listener = newLibreOfficeListener(
mod.logger,
gotenberg.NewFileSystem(),
mod.libreOfficeBinPath,
mod.libreOfficeStartTimeout,
mod.libreOfficeRestartThreshold,
)
return mod
}(),
ctx: context.Background(),
logger: zap.NewNop(),
inputPath: "/tests/test/testdata/libreoffice/sample1.docx",
options: Options{
PDFformat: gotenberg.FormatPDFA1a,
},
teardown: func(mod UNO) error {
ctx, cancel := context.WithCancel(context.Background())
cancel()
return mod.Stop(ctx)
},
},
{
name: "convert to PDF/A-2b",
mod: UNO{
unoconvBinPath: os.Getenv("UNOCONV_BIN_PATH"),
libreOfficeBinPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
libreOfficeStartTimeout: time.Duration(10) * time.Second,
libreOfficeRestartThreshold: 0,
},
scenario: "convert to PDF/A-2b",
mod: func() UNO {
mod := UNO{
unoconvBinPath: os.Getenv("UNOCONV_BIN_PATH"),
libreOfficeBinPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
libreOfficeStartTimeout: time.Duration(10) * time.Second,
libreOfficeRestartThreshold: 10,
logger: zap.NewNop(),
}
mod.listener = newLibreOfficeListener(
mod.logger,
gotenberg.NewFileSystem(),
mod.libreOfficeBinPath,
mod.libreOfficeStartTimeout,
mod.libreOfficeRestartThreshold,
)
return mod
}(),
ctx: context.Background(),
logger: zap.NewNop(),
inputPath: "/tests/test/testdata/libreoffice/sample1.docx",
options: Options{
PDFformat: gotenberg.FormatPDFA2b,
},
teardown: func(mod UNO) error {
ctx, cancel := context.WithCancel(context.Background())
cancel()
return mod.Stop(ctx)
},
},
{
name: "convert to PDF/A-3b",
mod: UNO{
unoconvBinPath: os.Getenv("UNOCONV_BIN_PATH"),
libreOfficeBinPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
libreOfficeStartTimeout: time.Duration(10) * time.Second,
libreOfficeRestartThreshold: 0,
},
scenario: "convert to PDF/A-3b",
mod: func() UNO {
mod := UNO{
unoconvBinPath: os.Getenv("UNOCONV_BIN_PATH"),
libreOfficeBinPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
libreOfficeStartTimeout: time.Duration(10) * time.Second,
libreOfficeRestartThreshold: 10,
logger: zap.NewNop(),
}
mod.listener = newLibreOfficeListener(
mod.logger,
gotenberg.NewFileSystem(),
mod.libreOfficeBinPath,
mod.libreOfficeStartTimeout,
mod.libreOfficeRestartThreshold,
)
return mod
}(),
ctx: context.Background(),
logger: zap.NewNop(),
inputPath: "/tests/test/testdata/libreoffice/sample1.docx",
options: Options{
PDFformat: gotenberg.FormatPDFA3b,
},
teardown: func(mod UNO) error {
ctx, cancel := context.WithCancel(context.Background())
cancel()
return mod.Stop(ctx)
},
},
{
name: "convert to invalid PDF format",
mod: UNO{
unoconvBinPath: os.Getenv("UNOCONV_BIN_PATH"),
libreOfficeBinPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
libreOfficeStartTimeout: time.Duration(10) * time.Second,
libreOfficeRestartThreshold: 0,
},
scenario: "convert to invalid PDF format",
mod: func() UNO {
mod := UNO{
unoconvBinPath: os.Getenv("UNOCONV_BIN_PATH"),
libreOfficeBinPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
libreOfficeStartTimeout: time.Duration(10) * time.Second,
libreOfficeRestartThreshold: 10,
logger: zap.NewNop(),
}
mod.listener = newLibreOfficeListener(
mod.logger,
gotenberg.NewFileSystem(),
mod.libreOfficeBinPath,
mod.libreOfficeStartTimeout,
mod.libreOfficeRestartThreshold,
)
return mod
}(),
ctx: context.Background(),
logger: zap.NewNop(),
inputPath: "/tests/test/testdata/libreoffice/sample1.docx",
@@ -636,28 +732,33 @@ func TestUNO_PDF(t *testing.T) {
PDFformat: "foo",
},
expectPDFErr: true,
teardown: func(mod UNO) error {
ctx, cancel := context.WithCancel(context.Background())
cancel()
return mod.Stop(ctx)
},
},
{
name: "nil context",
mod: UNO{
unoconvBinPath: os.Getenv("UNOCONV_BIN_PATH"),
libreOfficeBinPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
libreOfficeStartTimeout: time.Duration(10) * time.Second,
libreOfficeRestartThreshold: 0,
},
ctx: nil,
logger: zap.NewNop(),
inputPath: "/tests/test/testdata/libreoffice/sample1.docx",
expectPDFErr: true,
},
{
name: "expired context",
mod: UNO{
unoconvBinPath: os.Getenv("UNOCONV_BIN_PATH"),
libreOfficeBinPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
libreOfficeStartTimeout: time.Duration(10) * time.Second,
libreOfficeRestartThreshold: 0,
},
scenario: "expired context",
mod: func() UNO {
mod := UNO{
unoconvBinPath: os.Getenv("UNOCONV_BIN_PATH"),
libreOfficeBinPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
libreOfficeStartTimeout: time.Duration(10) * time.Second,
libreOfficeRestartThreshold: 10,
logger: zap.NewNop(),
}
mod.listener = newLibreOfficeListener(
mod.logger,
gotenberg.NewFileSystem(),
mod.libreOfficeBinPath,
mod.libreOfficeStartTimeout,
mod.libreOfficeRestartThreshold,
)
return mod
}(),
ctx: func() context.Context {
ctx, cancel := context.WithCancel(context.Background())
cancel()
@@ -669,7 +770,7 @@ func TestUNO_PDF(t *testing.T) {
expectPDFErr: true,
},
{
name: "cannot lock long-running LibreOffice listener",
scenario: "cannot lock long-running LibreOffice listener",
mod: UNO{
unoconvBinPath: os.Getenv("UNOCONV_BIN_PATH"),
libreOfficeBinPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
@@ -688,7 +789,7 @@ func TestUNO_PDF(t *testing.T) {
expectPDFErr: true,
},
{
name: "cannot unlock long-running LibreOffice listener",
scenario: "cannot unlock long-running LibreOffice listener",
mod: UNO{
unoconvBinPath: os.Getenv("UNOCONV_BIN_PATH"),
libreOfficeBinPath: os.Getenv("LIBREOFFICE_BIN_PATH"),
@@ -715,7 +816,7 @@ func TestUNO_PDF(t *testing.T) {
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Run(tc.scenario, func(t *testing.T) {
defer func() {
if tc.teardown == nil {
return
@@ -727,15 +828,16 @@ func TestUNO_PDF(t *testing.T) {
}
}()
outputDir, err := gotenberg.MkdirAll()
fs := gotenberg.NewFileSystem()
outputDir, err := fs.MkdirAll()
if err != nil {
t.Fatalf("expected no error from gotenberg.MkdirAll(), but got: %v", err)
t.Fatalf("test %s: expected error but got: %v", tc.scenario, err)
}
defer func() {
err := os.RemoveAll(outputDir)
err := os.RemoveAll(fs.WorkingDirPath())
if err != nil {
t.Errorf("expected no error from os.RemoveAll(), but got: %v", err)
t.Fatalf("test %s: expected no error while cleaning up but got: %v", tc.scenario, err)
}
}()