chore: minor refactor of gotenberg pkg

This commit is contained in:
Julien Neuhart
2023-11-20 22:10:18 +01:00
parent 793e65bac0
commit b56cde47ca
10 changed files with 554 additions and 442 deletions

View File

@@ -25,11 +25,11 @@ type Cmd struct {
// children without creating orphans. // children without creating orphans.
// //
// See https://medium.com/@felixge/killing-a-child-process-and-all-of-its-children-in-go-54079af94773. // See https://medium.com/@felixge/killing-a-child-process-and-all-of-its-children-in-go-54079af94773.
func Command(logger *zap.Logger, binPath string, args ...string) Cmd { func Command(logger *zap.Logger, binPath string, args ...string) *Cmd {
cmd := exec.Command(binPath, args...) cmd := exec.Command(binPath, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
return Cmd{ return &Cmd{
ctx: nil, ctx: nil,
logger: logger.Named(strings.ReplaceAll(binPath, "/", "")), logger: logger.Named(strings.ReplaceAll(binPath, "/", "")),
process: cmd, process: cmd,
@@ -41,15 +41,15 @@ func Command(logger *zap.Logger, binPath string, args ...string) Cmd {
// children without creating orphans. // children without creating orphans.
// //
// See https://medium.com/@felixge/killing-a-child-process-and-all-of-its-children-in-go-54079af94773. // See https://medium.com/@felixge/killing-a-child-process-and-all-of-its-children-in-go-54079af94773.
func CommandContext(ctx context.Context, logger *zap.Logger, binPath string, args ...string) (Cmd, error) { func CommandContext(ctx context.Context, logger *zap.Logger, binPath string, args ...string) (*Cmd, error) {
if ctx == nil { if ctx == nil {
return Cmd{}, errors.New("nil context") return nil, errors.New("nil context")
} }
cmd := exec.CommandContext(ctx, binPath, args...) cmd := exec.CommandContext(ctx, binPath, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
return Cmd{ return &Cmd{
ctx: ctx, ctx: ctx,
logger: logger.Named(strings.ReplaceAll(binPath, "/", "")), logger: logger.Named(strings.ReplaceAll(binPath, "/", "")),
process: cmd, process: cmd,
@@ -57,7 +57,7 @@ func CommandContext(ctx context.Context, logger *zap.Logger, binPath string, arg
} }
// Start starts the command but does not wait for its completion. // Start starts the command but does not wait for its completion.
func (cmd Cmd) Start() error { func (cmd *Cmd) Start() error {
err := cmd.pipeOutput() err := cmd.pipeOutput()
if err != nil { if err != nil {
return fmt.Errorf("pipe unix process output: %w", err) return fmt.Errorf("pipe unix process output: %w", err)
@@ -75,7 +75,7 @@ func (cmd Cmd) Start() error {
// Wait waits for the command to complete. It should be called when using the // Wait waits for the command to complete. It should be called when using the
// Start method, so that the command does not leak zombies. // Start method, so that the command does not leak zombies.
func (cmd Cmd) Wait() error { func (cmd *Cmd) Wait() error {
err := cmd.process.Wait() err := cmd.process.Wait()
if err != nil { if err != nil {
return fmt.Errorf("wait for unix process: %w", err) return fmt.Errorf("wait for unix process: %w", err)
@@ -86,7 +86,7 @@ func (cmd Cmd) Wait() error {
// Exec executes the command and wait for its completion or until the context // Exec executes the command and wait for its completion or until the context
// is done. In any case, it kills the unix process and all its children. // is done. In any case, it kills the unix process and all its children.
func (cmd Cmd) Exec() (int, error) { func (cmd *Cmd) Exec() (int, error) {
if cmd.ctx == nil { if cmd.ctx == nil {
return 10, errors.New("nil context") return 10, errors.New("nil context")
} }
@@ -134,7 +134,7 @@ func (cmd Cmd) Exec() (int, error) {
// pipeOutput creates logs entries according to the process stdout and stderr. // pipeOutput creates logs entries according to the process stdout and stderr.
// It does nothing if the logging level is not debug. // It does nothing if the logging level is not debug.
func (cmd Cmd) pipeOutput() error { func (cmd *Cmd) pipeOutput() error {
checkedEntry := cmd.logger.Check(zap.DebugLevel, "check for debug level before piping unix process output") checkedEntry := cmd.logger.Check(zap.DebugLevel, "check for debug level before piping unix process output")
if checkedEntry == nil { if checkedEntry == nil {
return nil return nil
@@ -154,7 +154,12 @@ func (cmd Cmd) pipeOutput() error {
// (either stdout or stderr). // (either stdout or stderr).
logCommandOutput := func(logger *zap.Logger, reader io.ReadCloser) { logCommandOutput := func(logger *zap.Logger, reader io.ReadCloser) {
r := bufio.NewReader(reader) r := bufio.NewReader(reader)
defer reader.Close() defer func(reader io.ReadCloser) {
err := reader.Close()
if err != nil {
logger.Error(fmt.Sprintf("close reader: %s", err))
}
}(reader)
for { for {
line, _, err := r.ReadLine() line, _, err := r.ReadLine()
@@ -181,7 +186,7 @@ func (cmd Cmd) pipeOutput() error {
// Kill kills the unix process and all its children without creating orphans. // Kill kills the unix process and all its children without creating orphans.
// //
// See https://medium.com/@felixge/killing-a-child-process-and-all-of-its-children-in-go-54079af94773. // See https://medium.com/@felixge/killing-a-child-process-and-all-of-its-children-in-go-54079af94773.
func (cmd Cmd) Kill() error { func (cmd *Cmd) Kill() error {
if cmd.process == nil { if cmd.process == nil {
// We cannot use the logger here, because for whatever reason using it // We cannot use the logger here, because for whatever reason using it
// result to a panic. // result to a panic.

View File

@@ -10,7 +10,6 @@ import (
func TestCommand(t *testing.T) { func TestCommand(t *testing.T) {
cmd := Command(zap.NewNop(), "foo") cmd := Command(zap.NewNop(), "foo")
if !cmd.process.SysProcAttr.Setpgid { if !cmd.process.SysProcAttr.Setpgid {
t.Error("expected cmd.process.SysProcAttr.Setpgid to be true") t.Error("expected cmd.process.SysProcAttr.Setpgid to be true")
} }
@@ -18,34 +17,36 @@ func TestCommand(t *testing.T) {
func TestCommandContext(t *testing.T) { func TestCommandContext(t *testing.T) {
tests := []struct { tests := []struct {
name string scenario string
ctx context.Context ctx context.Context
expectCommandContextErr bool expectCommandContextError bool
}{ }{
{ {
name: "nominal behavior", scenario: "nominal behavior",
ctx: context.Background(), ctx: context.Background(),
expectCommandContextError: false,
}, },
{ {
name: "nil context", scenario: "nil context",
expectCommandContextErr: true, ctx: nil,
expectCommandContextError: true,
}, },
} }
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.scenario, func(t *testing.T) {
cmd, err := CommandContext(tc.ctx, zap.NewNop(), "foo") cmd, err := CommandContext(tc.ctx, zap.NewNop(), "foo")
if err == nil && !cmd.process.SysProcAttr.Setpgid { if err == nil && !cmd.process.SysProcAttr.Setpgid {
t.Fatal("expected cmd.process.SysProcAttr.Setpgid to be true") t.Fatal("expected cmd.process.SysProcAttr.Setpgid to be true")
} }
if tc.expectCommandContextErr && err == nil { if !tc.expectCommandContextError && err != nil {
t.Error("expected error from CommandContext(), but got none") t.Fatalf("expected no error but got: %v", err)
} }
if !tc.expectCommandContextErr && err != nil { if tc.expectCommandContextError && err == nil {
t.Errorf("expected no error from CommandContext(), but got: %v", err) t.Fatal("expected error but got none")
} }
}) })
} }
@@ -53,31 +54,32 @@ func TestCommandContext(t *testing.T) {
func TestCmd_Start(t *testing.T) { func TestCmd_Start(t *testing.T) {
tests := []struct { tests := []struct {
name string scenario string
cmd Cmd cmd *Cmd
expectStartErr bool expectStartError bool
}{ }{
{ {
name: "nominal behavior", scenario: "nominal behavior",
cmd: Command(zap.NewNop(), "echo", "Hello", "World"), cmd: Command(zap.NewNop(), "echo", "Hello", "World"),
expectStartError: false,
}, },
{ {
name: "start error", scenario: "start error",
cmd: Command(zap.NewNop(), "foo"), cmd: Command(zap.NewNop(), "foo"),
expectStartErr: true, expectStartError: true,
}, },
} }
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.scenario, func(t *testing.T) {
err := tc.cmd.Start() err := tc.cmd.Start()
if tc.expectStartErr && err == nil { if !tc.expectStartError && err != nil {
t.Error("expected error from cmd.Start(), but got none") t.Fatalf("expected no error but got: %v", err)
} }
if !tc.expectStartErr && err != nil { if tc.expectStartError && err == nil {
t.Errorf("expected no error from cmd.Start(), but got: %v", err) t.Fatal("expected error but got none")
} }
}) })
} }
@@ -85,54 +87,50 @@ func TestCmd_Start(t *testing.T) {
func TestCmd_Wait(t *testing.T) { func TestCmd_Wait(t *testing.T) {
tests := []struct { tests := []struct {
name string scenario string
cmd Cmd cmd *Cmd
expectWaitErr bool expectWaitError bool
}{ }{
{ {
name: "nominal behavior", scenario: "nominal behavior",
cmd: func() Cmd { cmd: func() *Cmd {
cmd := Command(zap.NewNop(), "echo", "Hello", "World") cmd := Command(zap.NewNop(), "echo", "Hello", "World")
err := cmd.Start() err := cmd.Start()
if err != nil { if err != nil {
t.Fatalf("expected no error from cmd.Start(), but got: %v", err) t.Fatalf("expected no error but got: %v", err)
} }
return cmd return cmd
}(), }(),
expectWaitError: false,
}, },
{ {
name: "wait error", scenario: "wait error",
cmd: func() Cmd { cmd: func() *Cmd {
cmd := Command(zap.NewNop(), "echo", "Hello", "World") cmd := Command(zap.NewNop(), "echo", "Hello", "World")
err := cmd.Start() err := cmd.Start()
if err != nil { if err != nil {
t.Fatalf("expected no error from cmd.Start(), but got: %v", err) t.Fatalf("expected no error but got: %v", err)
} }
err = cmd.Kill() err = cmd.Kill()
if err != nil { if err != nil {
t.Fatalf("expected no error from cmd.Kill(), but got: %v", err) t.Fatalf("expected no error but got: %v", err)
} }
return cmd return cmd
}(), }(),
expectWaitErr: true, expectWaitError: true,
}, },
} }
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.scenario, func(t *testing.T) {
err := tc.cmd.Wait() err := tc.cmd.Wait()
if tc.expectWaitErr && err == nil { if !tc.expectWaitError && err != nil {
t.Error("expected error from cmd.Wait(), but got none") t.Fatalf("expected no error but got: %v", err)
} }
if !tc.expectWaitErr && err != nil { if tc.expectWaitError && err == nil {
t.Errorf("expected no error from cmd.Wait(), but got: %v", err) t.Fatal("expected error but got none")
} }
}) })
} }
@@ -140,49 +138,48 @@ func TestCmd_Wait(t *testing.T) {
func TestCmd_Exec(t *testing.T) { func TestCmd_Exec(t *testing.T) {
tests := []struct { tests := []struct {
name string scenario string
cmd Cmd cmd *Cmd
timeout time.Duration timeout time.Duration
expectExecErr bool expectExecError bool
}{ }{
{ {
name: "nominal behavior", scenario: "nominal behavior",
cmd: func() Cmd { cmd: func() *Cmd {
cmd, err := CommandContext(context.Background(), zap.NewNop(), "echo", "Hello", "World") cmd, err := CommandContext(context.Background(), zap.NewNop(), "echo", "Hello", "World")
if err != nil { if err != nil {
t.Fatalf("expected no error from CommandContext(), but got: %v", err) t.Fatalf("expected no error from CommandContext(), but got: %v", err)
} }
return cmd return cmd
}(), }(),
expectExecError: false,
}, },
{ {
name: "nil context", scenario: "nil context",
cmd: Command(zap.NewNop(), "echo", "Hello", "World"), cmd: Command(zap.NewNop(), "echo", "Hello", "World"),
expectExecErr: true, expectExecError: true,
}, },
{ {
name: "start error", scenario: "start error",
cmd: func() Cmd { cmd: func() *Cmd {
cmd, err := CommandContext(context.Background(), zap.NewNop(), "foo") cmd, err := CommandContext(context.Background(), zap.NewNop(), "foo")
if err != nil { if err != nil {
t.Fatalf("expected no error from CommandContext(), but got: %v", err) t.Fatalf("expected no error but got: %v", err)
} }
return cmd return cmd
}(), }(),
expectExecErr: true, expectExecError: true,
}, },
{ {
name: "context done", scenario: "context done",
cmd: Command(zap.NewNop(), "sleep", "2"), cmd: Command(zap.NewNop(), "sleep", "2"),
timeout: time.Duration(1) * time.Second, timeout: time.Duration(1) * time.Second,
expectExecErr: true, expectExecError: true,
}, },
} }
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.scenario, func(t *testing.T) {
if tc.timeout > 0 { if tc.timeout > 0 {
ctx, cancel := context.WithTimeout(context.TODO(), tc.timeout) ctx, cancel := context.WithTimeout(context.TODO(), tc.timeout)
defer cancel() defer cancel()
@@ -192,12 +189,12 @@ func TestCmd_Exec(t *testing.T) {
_, err := tc.cmd.Exec() _, err := tc.cmd.Exec()
if tc.expectExecErr && err == nil { if !tc.expectExecError && err != nil {
t.Error("expected error from cmd.Exec(), but got none") t.Fatalf("expected no error but got: %v", err)
} }
if !tc.expectExecErr && err != nil { if tc.expectExecError && err == nil {
t.Errorf("expected no error from cmd.Exec(), but got: %v", err) t.Fatal("expected error but got none")
} }
}) })
} }
@@ -205,67 +202,68 @@ func TestCmd_Exec(t *testing.T) {
func TestCmd_pipeOutput(t *testing.T) { func TestCmd_pipeOutput(t *testing.T) {
tests := []struct { tests := []struct {
name string scenario string
cmd Cmd cmd *Cmd
run bool run bool
expectPipeOutputErr bool expectPipeOutputError bool
}{ }{
{ {
name: "nominal behavior", scenario: "nominal behavior",
cmd: Command(zap.NewExample(), "echo", "Hello", "World"), cmd: Command(zap.NewExample(), "echo", "Hello", "World"),
run: true, run: true,
expectPipeOutputError: false,
}, },
{ {
name: "no debug, no pipe", scenario: "no debug, no pipe",
cmd: Command(zap.NewNop(), "echo", "Hello", "World"), cmd: Command(zap.NewNop(), "echo", "Hello", "World"),
run: false,
expectPipeOutputError: false,
}, },
{ {
name: "stdout already piped", scenario: "stdout already piped",
cmd: func() Cmd { cmd: func() *Cmd {
cmd := Command(zap.NewExample(), "echo", "Hello", "World") cmd := Command(zap.NewExample(), "echo", "Hello", "World")
_, err := cmd.process.StdoutPipe() _, err := cmd.process.StdoutPipe()
if err != nil { if err != nil {
t.Fatalf("expected no error from cmd.process.StdoutPipe(), but got: %v", err) t.Fatalf("expected no error but got: %v", err)
} }
return cmd return cmd
}(), }(),
expectPipeOutputErr: true, run: false,
expectPipeOutputError: true,
}, },
{ {
name: "stderr already piped", scenario: "stderr already piped",
cmd: func() Cmd { cmd: func() *Cmd {
cmd := Command(zap.NewExample(), "echo", "Hello", "World") cmd := Command(zap.NewExample(), "echo", "Hello", "World")
_, err := cmd.process.StderrPipe() _, err := cmd.process.StderrPipe()
if err != nil { if err != nil {
t.Fatalf("expected no error from cmd.process.StderrPipe(), but got: %v", err) t.Fatalf("expected no error but got: %v", err)
} }
return cmd return cmd
}(), }(),
expectPipeOutputErr: true, run: false,
expectPipeOutputError: true,
}, },
} }
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.scenario, func(t *testing.T) {
err := tc.cmd.pipeOutput() err := tc.cmd.pipeOutput()
if tc.run { if tc.run {
errStart := tc.cmd.process.Start() errStart := tc.cmd.process.Start()
if errStart != nil { if errStart != nil {
t.Fatalf("expected no error from tc.cmd.process.Start(), but got: %v", errStart) t.Fatalf("expected no error but got: %v", err)
} }
} }
if tc.expectPipeOutputErr && err == nil { if !tc.expectPipeOutputError && err != nil {
t.Error("expected error from cmd.pipeOutput(), but got none") t.Fatalf("expected no error but got: %v", err)
} }
if !tc.expectPipeOutputErr && err != nil { if tc.expectPipeOutputError && err == nil {
t.Errorf("expected no error from cmd.pipeOutput(), but got: %v", err) t.Fatal("expected error but got none")
} }
}) })
} }
@@ -273,51 +271,46 @@ func TestCmd_pipeOutput(t *testing.T) {
func TestCmd_Kill(t *testing.T) { func TestCmd_Kill(t *testing.T) {
tests := []struct { tests := []struct {
name string scenario string
cmd Cmd cmd *Cmd
}{ }{
{ {
name: "nominal behavior", scenario: "nominal behavior",
cmd: func() Cmd { cmd: func() *Cmd {
cmd := Command(zap.NewNop(), "sleep", "60") cmd := Command(zap.NewNop(), "sleep", "60")
err := cmd.process.Start() err := cmd.process.Start()
if err != nil { if err != nil {
t.Fatalf("expected no error from cmd.process.Start(), but got: %v", err) t.Fatalf("expected no error but got: %v", err)
} }
return cmd return cmd
}(), }(),
}, },
{ {
name: "no process", scenario: "no process",
cmd: Cmd{logger: zap.NewNop()}, cmd: &Cmd{logger: zap.NewNop()},
}, },
{ {
name: "process already killed", scenario: "process already killed",
cmd: func() Cmd { cmd: func() *Cmd {
cmd := Command(zap.NewNop(), "sleep", "60") cmd := Command(zap.NewNop(), "sleep", "60")
err := cmd.process.Start() err := cmd.process.Start()
if err != nil { if err != nil {
t.Fatalf("expected no error from cmd.process.Start(), but got: %v", err) t.Fatalf("expected no error but got: %v", err)
} }
err = cmd.Kill() err = cmd.Kill()
if err != nil { if err != nil {
t.Fatalf("expected no error from cmd.Kill(), but got: %v", err) t.Fatalf("expected no error but got: %v", err)
} }
return cmd return cmd
}(), }(),
}, },
} }
for _, tc := range tests { for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.scenario, func(t *testing.T) {
err := tc.cmd.Kill() err := tc.cmd.Kill()
if err != nil { if err != nil {
t.Errorf("expected no error from cmd.Kill(), but got: %v", err) t.Fatalf("expected no error but got: %v", err)
} }
}) })
} }

View File

@@ -23,173 +23,212 @@ func TestContext_ParsedFlags(t *testing.T) {
} }
func TestContext_Module(t *testing.T) { func TestContext_Module(t *testing.T) {
for i, tc := range []struct { for _, tc := range []struct {
mods []ModuleDescriptor scenario string
kind interface{} mods []ModuleDescriptor
expectErr bool kind interface{}
expectError bool
}{ }{
{ {
scenario: "module with error on provision",
mods: func() []ModuleDescriptor { mods: func() []ModuleDescriptor {
mod := struct{ ProtoProvisioner }{} mod := &struct {
mod.descriptor = func() ModuleDescriptor { ModuleMock
ProvisionerMock
}{}
mod.DescriptorMock = func() ModuleDescriptor {
return ModuleDescriptor{ID: "foo", New: func() Module { return mod }} return ModuleDescriptor{ID: "foo", New: func() Module { return mod }}
} }
mod.provision = func(ctx *Context) error { return errors.New("foo") } mod.ProvisionMock = func(ctx *Context) error { return errors.New("foo") }
return []ModuleDescriptor{mod.Descriptor()} return []ModuleDescriptor{mod.Descriptor()}
}(), }(),
kind: new(Provisioner), kind: new(Provisioner),
expectErr: true, expectError: true,
}, },
{ {
scenario: "two modules instead of one",
mods: func() []ModuleDescriptor { mods: func() []ModuleDescriptor {
mod := struct{ ProtoProvisioner }{} mod := &struct {
mod.descriptor = func() ModuleDescriptor { ModuleMock
ProvisionerMock
}{}
mod.DescriptorMock = func() ModuleDescriptor {
return ModuleDescriptor{ID: "foo", New: func() Module { return mod }} return ModuleDescriptor{ID: "foo", New: func() Module { return mod }}
} }
mod.provision = func(ctx *Context) error { return nil } mod.ProvisionMock = func(ctx *Context) error { return nil }
return []ModuleDescriptor{mod.Descriptor(), mod.Descriptor()} return []ModuleDescriptor{mod.Descriptor(), mod.Descriptor()}
}(), }(),
kind: new(Provisioner), kind: new(Provisioner),
expectErr: true, expectError: true,
}, },
{ {
scenario: "success",
mods: func() []ModuleDescriptor { mods: func() []ModuleDescriptor {
mod := struct{ ProtoProvisioner }{} mod := &struct {
mod.descriptor = func() ModuleDescriptor { ModuleMock
ProvisionerMock
}{}
mod.DescriptorMock = func() ModuleDescriptor {
return ModuleDescriptor{ID: "foo", New: func() Module { return mod }} return ModuleDescriptor{ID: "foo", New: func() Module { return mod }}
} }
mod.provision = func(ctx *Context) error { return nil } mod.ProvisionMock = func(ctx *Context) error { return nil }
return []ModuleDescriptor{mod.Descriptor()} return []ModuleDescriptor{mod.Descriptor()}
}(), }(),
kind: new(Provisioner), kind: new(Provisioner),
expectError: false,
}, },
} { } {
t.Run(tc.scenario, func(t *testing.T) {
ctx := NewContext(ParsedFlags{}, tc.mods)
_, err := ctx.Module(tc.kind)
ctx := NewContext(ParsedFlags{}, tc.mods) if !tc.expectError && err != nil {
_, err := ctx.Module(tc.kind) t.Fatalf("expected no error but got: %v", err)
}
if tc.expectErr && err == nil { if tc.expectError && err == nil {
t.Errorf("test %d: expected error but got: %v", i, err) t.Fatal("expected error but got none")
} }
})
if !tc.expectErr && err != nil {
t.Errorf("test %d: expected no error but got: %v", i, err)
}
} }
} }
func TestContext_Modules(t *testing.T) { func TestContext_Modules(t *testing.T) {
for i, tc := range []struct { for _, tc := range []struct {
mods []ModuleDescriptor scenario string
kind interface{} mods []ModuleDescriptor
expectErr bool kind interface{}
expectError bool
}{ }{
{ {
scenario: "module with error on provision",
mods: func() []ModuleDescriptor { mods: func() []ModuleDescriptor {
mod := struct{ ProtoProvisioner }{} mod := &struct {
mod.descriptor = func() ModuleDescriptor { ModuleMock
ProvisionerMock
}{}
mod.DescriptorMock = func() ModuleDescriptor {
return ModuleDescriptor{ID: "foo", New: func() Module { return mod }} return ModuleDescriptor{ID: "foo", New: func() Module { return mod }}
} }
mod.provision = func(ctx *Context) error { return errors.New("foo") } mod.ProvisionMock = func(ctx *Context) error { return errors.New("foo") }
return []ModuleDescriptor{mod.Descriptor()} return []ModuleDescriptor{mod.Descriptor()}
}(), }(),
kind: new(Provisioner), kind: new(Provisioner),
expectErr: true, expectError: true,
}, },
{ {
scenario: "success (module)",
mods: func() []ModuleDescriptor { mods: func() []ModuleDescriptor {
mod := struct{ ProtoProvisioner }{} mod := &struct {
mod.descriptor = func() ModuleDescriptor { ModuleMock
ProvisionerMock
}{}
mod.DescriptorMock = func() ModuleDescriptor {
return ModuleDescriptor{ID: "foo", New: func() Module { return mod }} return ModuleDescriptor{ID: "foo", New: func() Module { return mod }}
} }
mod.provision = func(ctx *Context) error { return nil } mod.ProvisionMock = func(ctx *Context) error { return nil }
return []ModuleDescriptor{mod.Descriptor(), mod.Descriptor()} return []ModuleDescriptor{mod.Descriptor(), mod.Descriptor()}
}(), }(),
kind: new(Provisioner), kind: new(Provisioner),
expectError: false,
}, },
{ {
scenario: "success (one module)",
mods: func() []ModuleDescriptor { mods: func() []ModuleDescriptor {
mod := struct{ ProtoProvisioner }{} mod := &struct {
mod.descriptor = func() ModuleDescriptor { ModuleMock
ProvisionerMock
}{}
mod.DescriptorMock = func() ModuleDescriptor {
return ModuleDescriptor{ID: "foo", New: func() Module { return mod }} return ModuleDescriptor{ID: "foo", New: func() Module { return mod }}
} }
mod.provision = func(ctx *Context) error { return nil } mod.ProvisionMock = func(ctx *Context) error { return nil }
return []ModuleDescriptor{mod.Descriptor()} return []ModuleDescriptor{mod.Descriptor()}
}(), }(),
kind: new(Provisioner), kind: new(Provisioner),
expectError: false,
}, },
} { } {
t.Run(tc.scenario, func(t *testing.T) {
ctx := NewContext(ParsedFlags{}, tc.mods)
_, err := ctx.Modules(tc.kind)
ctx := NewContext(ParsedFlags{}, tc.mods) if !tc.expectError && err != nil {
_, err := ctx.Modules(tc.kind) t.Fatalf("expected no error but got: %v", err)
}
if tc.expectErr && err == nil { if tc.expectError && err == nil {
t.Errorf("test %d: expected error but got: %v", i, err) t.Fatal("expected error but got none")
} }
})
if !tc.expectErr && err != nil {
t.Errorf("test %d: expected no error but got: %v", i, err)
}
} }
} }
func TestContext_loadModule(t *testing.T) { func TestContext_loadModule(t *testing.T) {
for i, tc := range []struct { for _, tc := range []struct {
instance interface{} scenario string
expectErr bool instance interface{}
expectError bool
}{ }{
{ {
scenario: "module with error on provision",
instance: func() interface{} { instance: func() interface{} {
mod := struct{ ProtoProvisioner }{} mod := &struct {
mod.descriptor = func() ModuleDescriptor { ModuleMock
ProvisionerMock
}{}
mod.DescriptorMock = func() ModuleDescriptor {
return ModuleDescriptor{ID: "foo", New: func() Module { return mod }} return ModuleDescriptor{ID: "foo", New: func() Module { return mod }}
} }
mod.provision = func(ctx *Context) error { return errors.New("foo") } mod.ProvisionMock = func(ctx *Context) error { return errors.New("foo") }
return mod return mod
}(), }(),
expectErr: true, expectError: true,
}, },
{ {
scenario: "module with error on validation",
instance: func() interface{} { instance: func() interface{} {
mod := struct{ ProtoValidator }{} mod := &struct {
mod.descriptor = func() ModuleDescriptor { ModuleMock
ValidatorMock
}{}
mod.DescriptorMock = func() ModuleDescriptor {
return ModuleDescriptor{ID: "foo", New: func() Module { return mod }} return ModuleDescriptor{ID: "foo", New: func() Module { return mod }}
} }
mod.validate = func() error { return errors.New("foo") } mod.ValidateMock = func() error { return errors.New("foo") }
return mod return mod
}(), }(),
expectErr: true, expectError: true,
}, },
{ {
scenario: "success",
instance: func() interface{} { instance: func() interface{} {
mod := struct{ ProtoValidator }{} mod := &struct {
mod.descriptor = func() ModuleDescriptor { ModuleMock
ValidatorMock
}{}
mod.DescriptorMock = func() ModuleDescriptor {
return ModuleDescriptor{ID: "foo", New: func() Module { return mod }} return ModuleDescriptor{ID: "foo", New: func() Module { return mod }}
} }
mod.validate = func() error { return nil } mod.ValidateMock = func() error { return nil }
return mod return mod
}(), }(),
expectError: false,
}, },
} { } {
t.Run(tc.scenario, func(t *testing.T) {
ctx := NewContext(ParsedFlags{}, nil)
err := ctx.loadModule("foo", tc.instance)
ctx := NewContext(ParsedFlags{}, nil) if !tc.expectError && err != nil {
err := ctx.loadModule("foo", tc.instance) t.Fatalf("expected no error but got: %v", err)
}
if tc.expectErr && err == nil { if tc.expectError && err == nil {
t.Errorf("test %d: expected error but got: %v", i, err) t.Fatal("expected error but got none")
} }
})
if !tc.expectErr && err != nil {
t.Errorf("test %d: expected no error but got: %v", i, err)
}
} }
} }

View File

@@ -20,23 +20,27 @@ func TestParsedFlags_MustString(t *testing.T) {
parsedFlags := ParsedFlags{FlagSet: fs} parsedFlags := ParsedFlags{FlagSet: fs}
for i, tc := range []struct { for _, tc := range []struct {
scenario string
name string name string
expectPanic bool expectPanic bool
}{ }{
{ {
name: "foo", scenario: "success",
name: "foo",
expectPanic: false,
}, },
{ {
scenario: "non-existing flag",
name: "bar", name: "bar",
expectPanic: true, expectPanic: true,
}, },
} { } {
func() { t.Run(tc.scenario, func(t *testing.T) {
if tc.expectPanic { if tc.expectPanic {
defer func() { defer func() {
if r := recover(); r == nil { if r := recover(); r == nil {
t.Errorf("test %d: expected panic but got none", i) t.Fatal("expected panic but got none")
} }
}() }()
} }
@@ -44,49 +48,55 @@ func TestParsedFlags_MustString(t *testing.T) {
if !tc.expectPanic { if !tc.expectPanic {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
t.Errorf("test %d: expected no panic but got: %v", i, r) t.Fatalf("expected no panic but got: %v", r)
} }
}() }()
} }
parsedFlags.MustString(tc.name) parsedFlags.MustString(tc.name)
}() })
} }
} }
func TestParsedFlags_MustDeprecatedString(t *testing.T) { func TestParsedFlags_MustDeprecatedString(t *testing.T) {
for i, tc := range []struct { for _, tc := range []struct {
scenario string
rawFlags []string rawFlags []string
expectValue string expectValue string
}{ }{
{ {
scenario: "deprecated flag value",
rawFlags: []string{"--foo=foo"}, rawFlags: []string{"--foo=foo"},
expectValue: "foo", expectValue: "foo",
}, },
{ {
scenario: "non-deprecated flag value",
rawFlags: []string{"--bar=bar"}, rawFlags: []string{"--bar=bar"},
expectValue: "bar", expectValue: "bar",
}, },
{ {
scenario: "deprecated flag value > non-deprecated flag value",
rawFlags: []string{"--foo=foo", "--bar=bar"}, rawFlags: []string{"--foo=foo", "--bar=bar"},
expectValue: "foo", expectValue: "foo",
}, },
} { } {
fs := flag.NewFlagSet("tests", flag.ContinueOnError) t.Run(tc.scenario, func(t *testing.T) {
fs.String("foo", "", "") fs := flag.NewFlagSet("tests", flag.ContinueOnError)
fs.String("bar", "", "") fs.String("foo", "", "")
fs.String("bar", "", "")
parsedFlags := ParsedFlags{FlagSet: fs} parsedFlags := ParsedFlags{FlagSet: fs}
err := parsedFlags.Parse(tc.rawFlags) err := parsedFlags.Parse(tc.rawFlags)
if err != nil { if err != nil {
t.Fatalf("test %d: expected no error but got: %v", i, err) t.Fatalf("expected no error but got: %v", err)
} }
actual := parsedFlags.MustDeprecatedString("foo", "bar") actual := parsedFlags.MustDeprecatedString("foo", "bar")
if actual != tc.expectValue { if actual != tc.expectValue {
t.Errorf("test %d: expected '%s' but got '%s'", i, tc.expectValue, actual) t.Errorf("expected '%s' but got '%s'", tc.expectValue, actual)
} }
})
} }
} }
@@ -101,23 +111,27 @@ func TestParsedFlags_MustStringSlice(t *testing.T) {
parsedFlags := ParsedFlags{FlagSet: fs} parsedFlags := ParsedFlags{FlagSet: fs}
for i, tc := range []struct { for _, tc := range []struct {
scenario string
name string name string
expectPanic bool expectPanic bool
}{ }{
{ {
name: "foo", scenario: "success",
name: "foo",
expectPanic: false,
}, },
{ {
scenario: "non-existing flag",
name: "bar", name: "bar",
expectPanic: true, expectPanic: true,
}, },
} { } {
func() { t.Run(tc.scenario, func(t *testing.T) {
if tc.expectPanic { if tc.expectPanic {
defer func() { defer func() {
if r := recover(); r == nil { if r := recover(); r == nil {
t.Errorf("test %d: expected panic but got none", i) t.Fatal("expected panic but got none")
} }
}() }()
} }
@@ -125,49 +139,55 @@ func TestParsedFlags_MustStringSlice(t *testing.T) {
if !tc.expectPanic { if !tc.expectPanic {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
t.Errorf("test %d: expected no panic but got: %v", i, r) t.Fatalf("expected no panic but got: %v", r)
} }
}() }()
} }
parsedFlags.MustStringSlice(tc.name) parsedFlags.MustStringSlice(tc.name)
}() })
} }
} }
func TestParsedFlags_MustDeprecatedStringSlice(t *testing.T) { func TestParsedFlags_MustDeprecatedStringSlice(t *testing.T) {
for i, tc := range []struct { for _, tc := range []struct {
scenario string
rawFlags []string rawFlags []string
expectValue []string expectValue []string
}{ }{
{ {
scenario: "deprecated flag value",
rawFlags: []string{"--foo=foo"}, rawFlags: []string{"--foo=foo"},
expectValue: []string{"foo"}, expectValue: []string{"foo"},
}, },
{ {
scenario: "non-deprecated flag value",
rawFlags: []string{"--bar=bar"}, rawFlags: []string{"--bar=bar"},
expectValue: []string{"bar"}, expectValue: []string{"bar"},
}, },
{ {
scenario: "deprecated flag value > non-deprecated flag value",
rawFlags: []string{"--foo=foo", "--bar=bar"}, rawFlags: []string{"--foo=foo", "--bar=bar"},
expectValue: []string{"foo"}, expectValue: []string{"foo"},
}, },
} { } {
fs := flag.NewFlagSet("tests", flag.ContinueOnError) t.Run(tc.scenario, func(t *testing.T) {
fs.StringSlice("foo", make([]string, 0), "") fs := flag.NewFlagSet("tests", flag.ContinueOnError)
fs.StringSlice("bar", make([]string, 0), "") fs.StringSlice("foo", make([]string, 0), "")
fs.StringSlice("bar", make([]string, 0), "")
parsedFlags := ParsedFlags{FlagSet: fs} parsedFlags := ParsedFlags{FlagSet: fs}
err := parsedFlags.Parse(tc.rawFlags) err := parsedFlags.Parse(tc.rawFlags)
if err != nil { if err != nil {
t.Fatalf("test %d: expected no error but got: %v", i, err) t.Fatalf("expected no error but got: %v", err)
} }
actual := parsedFlags.MustDeprecatedStringSlice("foo", "bar") actual := parsedFlags.MustDeprecatedStringSlice("foo", "bar")
if !reflect.DeepEqual(actual, tc.expectValue) { if !reflect.DeepEqual(actual, tc.expectValue) {
t.Errorf("test %d: expected %+v but got %+v", i, tc.expectValue, actual) t.Errorf("expected %+v but got %+v", tc.expectValue, actual)
} }
})
} }
} }
@@ -182,23 +202,27 @@ func TestParsedFlags_MustBool(t *testing.T) {
parsedFlags := ParsedFlags{FlagSet: fs} parsedFlags := ParsedFlags{FlagSet: fs}
for i, tc := range []struct { for _, tc := range []struct {
scenario string
name string name string
expectPanic bool expectPanic bool
}{ }{
{ {
name: "foo", scenario: "success",
name: "foo",
expectPanic: false,
}, },
{ {
scenario: "non-existing flag",
name: "bar", name: "bar",
expectPanic: true, expectPanic: true,
}, },
} { } {
func() { t.Run(tc.scenario, func(t *testing.T) {
if tc.expectPanic { if tc.expectPanic {
defer func() { defer func() {
if r := recover(); r == nil { if r := recover(); r == nil {
t.Errorf("test %d: expected panic but got none", i) t.Fatal("expected panic but got none")
} }
}() }()
} }
@@ -206,49 +230,55 @@ func TestParsedFlags_MustBool(t *testing.T) {
if !tc.expectPanic { if !tc.expectPanic {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
t.Errorf("test %d: expected no panic but got: %v", i, r) t.Fatalf("expected no panic but got: %v", r)
} }
}() }()
} }
parsedFlags.MustBool(tc.name) parsedFlags.MustBool(tc.name)
}() })
} }
} }
func TestParsedFlags_MustDeprecatedBool(t *testing.T) { func TestParsedFlags_MustDeprecatedBool(t *testing.T) {
for i, tc := range []struct { for _, tc := range []struct {
scenario string
rawFlags []string rawFlags []string
expectValue bool expectValue bool
}{ }{
{ {
scenario: "deprecated flag value",
rawFlags: []string{"--foo=true"}, rawFlags: []string{"--foo=true"},
expectValue: true, expectValue: true,
}, },
{ {
scenario: "non-deprecated flag value",
rawFlags: []string{"--bar=false"}, rawFlags: []string{"--bar=false"},
expectValue: false, expectValue: false,
}, },
{ {
scenario: "deprecated flag value > non-deprecated flag value",
rawFlags: []string{"--foo=true", "--bar=false"}, rawFlags: []string{"--foo=true", "--bar=false"},
expectValue: true, expectValue: true,
}, },
} { } {
fs := flag.NewFlagSet("tests", flag.ContinueOnError) t.Run(tc.scenario, func(t *testing.T) {
fs.Bool("foo", false, "") fs := flag.NewFlagSet("tests", flag.ContinueOnError)
fs.Bool("bar", true, "") fs.Bool("foo", false, "")
fs.Bool("bar", true, "")
parsedFlags := ParsedFlags{FlagSet: fs} parsedFlags := ParsedFlags{FlagSet: fs}
err := parsedFlags.Parse(tc.rawFlags) err := parsedFlags.Parse(tc.rawFlags)
if err != nil { if err != nil {
t.Fatalf("test %d: expected no error but got: %v", i, err) t.Fatalf("expected no error but got: %v", err)
} }
actual := parsedFlags.MustDeprecatedBool("foo", "bar") actual := parsedFlags.MustDeprecatedBool("foo", "bar")
if actual != tc.expectValue { if actual != tc.expectValue {
t.Errorf("test %d: expected %v but got %v", i, tc.expectValue, actual) t.Errorf("expected %v but got %v", tc.expectValue, actual)
} }
})
} }
} }
@@ -263,23 +293,27 @@ func TestParsedFlags_MustInt64(t *testing.T) {
parsedFlags := ParsedFlags{FlagSet: fs} parsedFlags := ParsedFlags{FlagSet: fs}
for i, tc := range []struct { for _, tc := range []struct {
scenario string
name string name string
expectPanic bool expectPanic bool
}{ }{
{ {
name: "foo", scenario: "success",
name: "foo",
expectPanic: false,
}, },
{ {
scenario: "non-existing flag",
name: "bar", name: "bar",
expectPanic: true, expectPanic: true,
}, },
} { } {
func() { t.Run(tc.scenario, func(t *testing.T) {
if tc.expectPanic { if tc.expectPanic {
defer func() { defer func() {
if r := recover(); r == nil { if r := recover(); r == nil {
t.Errorf("test %d: expected panic but got none", i) t.Fatal("expected panic but got none")
} }
}() }()
} }
@@ -287,30 +321,34 @@ func TestParsedFlags_MustInt64(t *testing.T) {
if !tc.expectPanic { if !tc.expectPanic {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
t.Errorf("test %d: expected no panic but got: %v", i, r) t.Fatalf("expected no panic but got: %v", r)
} }
}() }()
} }
parsedFlags.MustInt64(tc.name) parsedFlags.MustInt64(tc.name)
}() })
} }
} }
func TestParsedFlags_MustDeprecatedInt64(t *testing.T) { func TestParsedFlags_MustDeprecatedInt64(t *testing.T) {
for i, tc := range []struct { for _, tc := range []struct {
scenario string
rawFlags []string rawFlags []string
expectValue int64 expectValue int64
}{ }{
{ {
scenario: "deprecated flag value",
rawFlags: []string{"--foo=1"}, rawFlags: []string{"--foo=1"},
expectValue: 1, expectValue: 1,
}, },
{ {
scenario: "non-deprecated flag value",
rawFlags: []string{"--bar=2"}, rawFlags: []string{"--bar=2"},
expectValue: 2, expectValue: 2,
}, },
{ {
scenario: "deprecated flag value > non-deprecated flag value",
rawFlags: []string{"--foo=1", "--bar=2"}, rawFlags: []string{"--foo=1", "--bar=2"},
expectValue: 1, expectValue: 1,
}, },
@@ -323,12 +361,12 @@ func TestParsedFlags_MustDeprecatedInt64(t *testing.T) {
err := parsedFlags.Parse(tc.rawFlags) err := parsedFlags.Parse(tc.rawFlags)
if err != nil { if err != nil {
t.Fatalf("test %d: expected no error but got: %v", i, err) t.Fatalf("expected no error but got: %v", err)
} }
actual := parsedFlags.MustDeprecatedInt64("foo", "bar") actual := parsedFlags.MustDeprecatedInt64("foo", "bar")
if actual != tc.expectValue { if actual != tc.expectValue {
t.Errorf("test %d: expected %d but got %d", i, tc.expectValue, actual) t.Errorf("expected %d but got %d", tc.expectValue, actual)
} }
} }
} }
@@ -344,23 +382,27 @@ func TestParsedFlags_MustInt(t *testing.T) {
parsedFlags := ParsedFlags{FlagSet: fs} parsedFlags := ParsedFlags{FlagSet: fs}
for i, tc := range []struct { for _, tc := range []struct {
scenario string
name string name string
expectPanic bool expectPanic bool
}{ }{
{ {
name: "foo", scenario: "success",
name: "foo",
expectPanic: false,
}, },
{ {
scenario: "non-existing flag",
name: "bar", name: "bar",
expectPanic: true, expectPanic: true,
}, },
} { } {
func() { t.Run(tc.scenario, func(t *testing.T) {
if tc.expectPanic { if tc.expectPanic {
defer func() { defer func() {
if r := recover(); r == nil { if r := recover(); r == nil {
t.Errorf("test %d: expected panic but got none", i) t.Fatal("expected panic but got none")
} }
}() }()
} }
@@ -368,49 +410,55 @@ func TestParsedFlags_MustInt(t *testing.T) {
if !tc.expectPanic { if !tc.expectPanic {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
t.Errorf("test %d: expected no panic but got: %v", i, r) t.Fatalf("expected no panic but got: %v", r)
} }
}() }()
} }
parsedFlags.MustInt(tc.name) parsedFlags.MustInt(tc.name)
}() })
} }
} }
func TestParsedFlags_MustDeprecatedInt(t *testing.T) { func TestParsedFlags_MustDeprecatedInt(t *testing.T) {
for i, tc := range []struct { for _, tc := range []struct {
scenario string
rawFlags []string rawFlags []string
expectValue int expectValue int
}{ }{
{ {
scenario: "deprecated flag value",
rawFlags: []string{"--foo=1"}, rawFlags: []string{"--foo=1"},
expectValue: 1, expectValue: 1,
}, },
{ {
scenario: "non-deprecated flag value",
rawFlags: []string{"--bar=2"}, rawFlags: []string{"--bar=2"},
expectValue: 2, expectValue: 2,
}, },
{ {
scenario: "deprecated flag value > non-deprecated flag value",
rawFlags: []string{"--foo=1", "--bar=2"}, rawFlags: []string{"--foo=1", "--bar=2"},
expectValue: 1, expectValue: 1,
}, },
} { } {
fs := flag.NewFlagSet("tests", flag.ContinueOnError) t.Run(tc.scenario, func(t *testing.T) {
fs.Int("foo", 0, "") fs := flag.NewFlagSet("tests", flag.ContinueOnError)
fs.Int("bar", 0, "") fs.Int("foo", 0, "")
fs.Int("bar", 0, "")
parsedFlags := ParsedFlags{FlagSet: fs} parsedFlags := ParsedFlags{FlagSet: fs}
err := parsedFlags.Parse(tc.rawFlags) err := parsedFlags.Parse(tc.rawFlags)
if err != nil { if err != nil {
t.Fatalf("test %d: expected no error but got: %v", i, err) t.Fatalf("expected no error but got: %v", err)
} }
actual := parsedFlags.MustDeprecatedInt("foo", "bar") actual := parsedFlags.MustDeprecatedInt("foo", "bar")
if actual != tc.expectValue { if actual != tc.expectValue {
t.Errorf("test %d: expected %d but got %d", i, tc.expectValue, actual) t.Errorf("expected %d but got %d", tc.expectValue, actual)
} }
})
} }
} }
@@ -425,23 +473,27 @@ func TestParsedFlags_MustFloat64(t *testing.T) {
parsedFlags := ParsedFlags{FlagSet: fs} parsedFlags := ParsedFlags{FlagSet: fs}
for i, tc := range []struct { for _, tc := range []struct {
scenario string
name string name string
expectPanic bool expectPanic bool
}{ }{
{ {
name: "foo", scenario: "success",
name: "foo",
expectPanic: false,
}, },
{ {
scenario: "non-existing flag",
name: "bar", name: "bar",
expectPanic: true, expectPanic: true,
}, },
} { } {
func() { t.Run(tc.scenario, func(t *testing.T) {
if tc.expectPanic { if tc.expectPanic {
defer func() { defer func() {
if r := recover(); r == nil { if r := recover(); r == nil {
t.Errorf("test %d: expected panic but got none", i) t.Fatal("expected panic but got none")
} }
}() }()
} }
@@ -449,49 +501,55 @@ func TestParsedFlags_MustFloat64(t *testing.T) {
if !tc.expectPanic { if !tc.expectPanic {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
t.Errorf("test %d: expected no panic but got: %v", i, r) t.Fatalf("expected no panic but got: %v", r)
} }
}() }()
} }
parsedFlags.MustFloat64(tc.name) parsedFlags.MustFloat64(tc.name)
}() })
} }
} }
func TestParsedFlags_MustDeprecatedFloat64(t *testing.T) { func TestParsedFlags_MustDeprecatedFloat64(t *testing.T) {
for i, tc := range []struct { for _, tc := range []struct {
scenario string
rawFlags []string rawFlags []string
expectValue float64 expectValue float64
}{ }{
{ {
scenario: "deprecated flag value",
rawFlags: []string{"--foo=1.0"}, rawFlags: []string{"--foo=1.0"},
expectValue: 1.0, expectValue: 1.0,
}, },
{ {
scenario: "non-deprecated flag value",
rawFlags: []string{"--bar=2.0"}, rawFlags: []string{"--bar=2.0"},
expectValue: 2.0, expectValue: 2.0,
}, },
{ {
scenario: "deprecated flag value > non-deprecated flag value",
rawFlags: []string{"--foo=1.0", "--bar=2.0"}, rawFlags: []string{"--foo=1.0", "--bar=2.0"},
expectValue: 1.0, expectValue: 1.0,
}, },
} { } {
fs := flag.NewFlagSet("tests", flag.ContinueOnError) t.Run(tc.scenario, func(t *testing.T) {
fs.Float64("foo", 0, "") fs := flag.NewFlagSet("tests", flag.ContinueOnError)
fs.Float64("bar", 0, "") fs.Float64("foo", 0, "")
fs.Float64("bar", 0, "")
parsedFlags := ParsedFlags{FlagSet: fs} parsedFlags := ParsedFlags{FlagSet: fs}
err := parsedFlags.Parse(tc.rawFlags) err := parsedFlags.Parse(tc.rawFlags)
if err != nil { if err != nil {
t.Fatalf("test %d: expected no error but got: %v", i, err) t.Fatalf("expected no error but got: %v", err)
} }
actual := parsedFlags.MustDeprecatedFloat64("foo", "bar") actual := parsedFlags.MustDeprecatedFloat64("foo", "bar")
if actual != tc.expectValue { if actual != tc.expectValue {
t.Errorf("test %d: expected %f but got %f", i, tc.expectValue, actual) t.Errorf("expected %f but got %f", tc.expectValue, actual)
} }
})
} }
} }
@@ -506,23 +564,27 @@ func TestParsedFlags_MustDuration(t *testing.T) {
parsedFlags := ParsedFlags{FlagSet: fs} parsedFlags := ParsedFlags{FlagSet: fs}
for i, tc := range []struct { for _, tc := range []struct {
scenario string
name string name string
expectPanic bool expectPanic bool
}{ }{
{ {
name: "foo", scenario: "success",
name: "foo",
expectPanic: false,
}, },
{ {
scenario: "non-existing flag",
name: "bar", name: "bar",
expectPanic: true, expectPanic: true,
}, },
} { } {
func() { t.Run(tc.scenario, func(t *testing.T) {
if tc.expectPanic { if tc.expectPanic {
defer func() { defer func() {
if r := recover(); r == nil { if r := recover(); r == nil {
t.Errorf("test %d: expected panic but got none", i) t.Fatal("expected panic but got none")
} }
}() }()
} }
@@ -530,49 +592,55 @@ func TestParsedFlags_MustDuration(t *testing.T) {
if !tc.expectPanic { if !tc.expectPanic {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
t.Errorf("test %d: expected no panic but got: %v", i, r) t.Fatalf("expected no panic but got: %v", r)
} }
}() }()
} }
parsedFlags.MustDuration(tc.name) parsedFlags.MustDuration(tc.name)
}() })
} }
} }
func TestParsedFlags_MustDeprecatedDuration(t *testing.T) { func TestParsedFlags_MustDeprecatedDuration(t *testing.T) {
for i, tc := range []struct { for _, tc := range []struct {
scenario string
rawFlags []string rawFlags []string
expectValue time.Duration expectValue time.Duration
}{ }{
{ {
scenario: "deprecated flag value",
rawFlags: []string{"--foo=1s"}, rawFlags: []string{"--foo=1s"},
expectValue: time.Duration(1) * time.Second, expectValue: time.Duration(1) * time.Second,
}, },
{ {
scenario: "non-deprecated flag value",
rawFlags: []string{"--bar=2s"}, rawFlags: []string{"--bar=2s"},
expectValue: time.Duration(2) * time.Second, expectValue: time.Duration(2) * time.Second,
}, },
{ {
scenario: "deprecated flag value > non-deprecated flag value",
rawFlags: []string{"--foo=1s", "--bar=2s"}, rawFlags: []string{"--foo=1s", "--bar=2s"},
expectValue: time.Duration(1) * time.Second, expectValue: time.Duration(1) * time.Second,
}, },
} { } {
fs := flag.NewFlagSet("tests", flag.ContinueOnError) t.Run(tc.scenario, func(t *testing.T) {
fs.Duration("foo", 0, "") fs := flag.NewFlagSet("tests", flag.ContinueOnError)
fs.Duration("bar", 0, "") fs.Duration("foo", 0, "")
fs.Duration("bar", 0, "")
parsedFlags := ParsedFlags{FlagSet: fs} parsedFlags := ParsedFlags{FlagSet: fs}
err := parsedFlags.Parse(tc.rawFlags) err := parsedFlags.Parse(tc.rawFlags)
if err != nil { if err != nil {
t.Fatalf("test %d: expected no error but got: %v", i, err) t.Fatalf("expected no error but got: %v", err)
} }
actual := parsedFlags.MustDeprecatedDuration("foo", "bar") actual := parsedFlags.MustDeprecatedDuration("foo", "bar")
if actual != tc.expectValue { if actual != tc.expectValue {
t.Errorf("test %d: expected '%s' but got '%s'", i, tc.expectValue, actual) t.Errorf("expected '%s' but got '%s'", tc.expectValue, actual)
} }
})
} }
} }
@@ -588,27 +656,27 @@ func TestParsedFlags_MustHumanReadableBytesString(t *testing.T) {
parsedFlags := ParsedFlags{FlagSet: fs} parsedFlags := ParsedFlags{FlagSet: fs}
for i, tc := range []struct { for _, tc := range []struct {
scenario string
name string name string
expectPanic bool expectPanic bool
}{ }{
{ {
name: "foo", scenario: "success",
name: "foo",
expectPanic: false,
}, },
{ {
scenario: "non-existing flag",
name: "bar", name: "bar",
expectPanic: true, expectPanic: true,
}, },
{
name: "baz",
expectPanic: true,
},
} { } {
func() { t.Run(tc.scenario, func(t *testing.T) {
if tc.expectPanic { if tc.expectPanic {
defer func() { defer func() {
if r := recover(); r == nil { if r := recover(); r == nil {
t.Errorf("test %d: expected panic but got none", i) t.Fatal("expected panic but got none")
} }
}() }()
} }
@@ -616,49 +684,55 @@ func TestParsedFlags_MustHumanReadableBytesString(t *testing.T) {
if !tc.expectPanic { if !tc.expectPanic {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
t.Errorf("test %d: expected no panic but got: %v", i, r) t.Fatalf("expected no panic but got: %v", r)
} }
}() }()
} }
parsedFlags.MustHumanReadableBytesString(tc.name) parsedFlags.MustHumanReadableBytesString(tc.name)
}() })
} }
} }
func TestParsedFlags_MustDeprecatedHumanReadableBytesString(t *testing.T) { func TestParsedFlags_MustDeprecatedHumanReadableBytesString(t *testing.T) {
for i, tc := range []struct { for _, tc := range []struct {
scenario string
rawFlags []string rawFlags []string
expectValue string expectValue string
}{ }{
{ {
scenario: "deprecated flag value",
rawFlags: []string{"--foo=1MB"}, rawFlags: []string{"--foo=1MB"},
expectValue: "1MB", expectValue: "1MB",
}, },
{ {
scenario: "non-deprecated flag value",
rawFlags: []string{"--bar=2MB"}, rawFlags: []string{"--bar=2MB"},
expectValue: "2MB", expectValue: "2MB",
}, },
{ {
scenario: "deprecated flag value > non-deprecated flag value",
rawFlags: []string{"--foo=1MB", "--bar=2MB"}, rawFlags: []string{"--foo=1MB", "--bar=2MB"},
expectValue: "1MB", expectValue: "1MB",
}, },
} { } {
fs := flag.NewFlagSet("tests", flag.ContinueOnError) t.Run(tc.scenario, func(t *testing.T) {
fs.String("foo", "", "") fs := flag.NewFlagSet("tests", flag.ContinueOnError)
fs.String("bar", "", "") fs.String("foo", "", "")
fs.String("bar", "", "")
parsedFlags := ParsedFlags{FlagSet: fs} parsedFlags := ParsedFlags{FlagSet: fs}
err := parsedFlags.Parse(tc.rawFlags) err := parsedFlags.Parse(tc.rawFlags)
if err != nil { if err != nil {
t.Fatalf("test %d: expected no error but got: %v", i, err) t.Fatalf("expected no error but got: %v", err)
} }
actual := parsedFlags.MustDeprecatedHumanReadableBytesString("foo", "bar") actual := parsedFlags.MustDeprecatedHumanReadableBytesString("foo", "bar")
if actual != tc.expectValue { if actual != tc.expectValue {
t.Errorf("test %d: expected '%s' but got '%s'", i, tc.expectValue, actual) t.Errorf("expected '%s' but got '%s'", tc.expectValue, actual)
} }
})
} }
} }
@@ -674,27 +748,27 @@ func TestParsedFlags_MustRegexp(t *testing.T) {
parsedFlags := ParsedFlags{FlagSet: fs} parsedFlags := ParsedFlags{FlagSet: fs}
for i, tc := range []struct { for _, tc := range []struct {
scenario string
name string name string
expectPanic bool expectPanic bool
}{ }{
{ {
name: "foo", scenario: "success",
name: "foo",
expectPanic: false,
}, },
{ {
scenario: "non-existing flag",
name: "bar", name: "bar",
expectPanic: true, expectPanic: true,
}, },
{
name: "baz",
expectPanic: true,
},
} { } {
func() { t.Run(tc.scenario, func(t *testing.T) {
if tc.expectPanic { if tc.expectPanic {
defer func() { defer func() {
if r := recover(); r == nil { if r := recover(); r == nil {
t.Errorf("test %d: expected panic but got none", i) t.Fatal("expected panic but got none")
} }
}() }()
} }
@@ -702,48 +776,54 @@ func TestParsedFlags_MustRegexp(t *testing.T) {
if !tc.expectPanic { if !tc.expectPanic {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
t.Errorf("test %d: expected no panic but got: %v", i, r) t.Fatalf("expected no panic but got: %v", r)
} }
}() }()
} }
parsedFlags.MustRegexp(tc.name) parsedFlags.MustRegexp(tc.name)
}() })
} }
} }
func TestParsedFlags_MustDeprecatedRegexp(t *testing.T) { func TestParsedFlags_MustDeprecatedRegexp(t *testing.T) {
for i, tc := range []struct { for _, tc := range []struct {
scenario string
rawFlags []string rawFlags []string
expectValue *regexp.Regexp expectValue *regexp.Regexp
}{ }{
{ {
scenario: "deprecated flag value",
rawFlags: []string{"--foo=foo"}, rawFlags: []string{"--foo=foo"},
expectValue: regexp.MustCompile("foo"), expectValue: regexp.MustCompile("foo"),
}, },
{ {
scenario: "non-deprecated flag value",
rawFlags: []string{"--bar=bar"}, rawFlags: []string{"--bar=bar"},
expectValue: regexp.MustCompile("bar"), expectValue: regexp.MustCompile("bar"),
}, },
{ {
scenario: "deprecated flag value > non-deprecated flag value",
rawFlags: []string{"--foo=foo", "--bar=bar"}, rawFlags: []string{"--foo=foo", "--bar=bar"},
expectValue: regexp.MustCompile("foo"), expectValue: regexp.MustCompile("foo"),
}, },
} { } {
fs := flag.NewFlagSet("tests", flag.ContinueOnError) t.Run(tc.scenario, func(t *testing.T) {
fs.String("foo", "", "") fs := flag.NewFlagSet("tests", flag.ContinueOnError)
fs.String("bar", "", "") fs.String("foo", "", "")
fs.String("bar", "", "")
parsedFlags := ParsedFlags{FlagSet: fs} parsedFlags := ParsedFlags{FlagSet: fs}
err := parsedFlags.Parse(tc.rawFlags) err := parsedFlags.Parse(tc.rawFlags)
if err != nil { if err != nil {
t.Fatalf("test %d: expected no error but got: %v", i, err) t.Fatalf("expected no error but got: %v", err)
} }
actual := parsedFlags.MustDeprecatedRegexp("foo", "bar") actual := parsedFlags.MustDeprecatedRegexp("foo", "bar")
if actual.String() != tc.expectValue.String() { if actual.String() != tc.expectValue.String() {
t.Errorf("test %d: expected '%s' but got '%s'", i, tc.expectValue.String(), actual.String()) t.Errorf("expected '%s' but got '%s'", tc.expectValue.String(), actual.String())
} }
})
} }
} }

View File

@@ -14,14 +14,14 @@ func TestGarbageCollect(t *testing.T) {
scenario string scenario string
rootPath string rootPath string
includeSubstr []string includeSubstr []string
expectErr bool expectError bool
expectNotExists []string expectNotExists []string
expectExists []string expectExists []string
}{ }{
{ {
scenario: "root path does not exist", scenario: "root path does not exist",
rootPath: uuid.NewString(), rootPath: uuid.NewString(),
expectErr: true, expectError: true,
}, },
{ {
scenario: "remove include substrings", scenario: "remove include substrings",
@@ -51,29 +51,30 @@ func TestGarbageCollect(t *testing.T) {
return path return path
}(), }(),
includeSubstr: []string{"foo", fmt.Sprintf("%s/a_directory/a_bar_file", os.TempDir())}, includeSubstr: []string{"foo", fmt.Sprintf("%s/a_directory/a_bar_file", os.TempDir())},
expectError: false,
expectExists: []string{"a_baz_file"}, expectExists: []string{"a_baz_file"},
expectNotExists: []string{"a_foo_file", "a_bar_file"}, expectNotExists: []string{"a_foo_file", "a_bar_file"},
}, },
} { } {
func() { t.Run(tc.scenario, func(t *testing.T) {
defer func() { defer func() {
err := os.RemoveAll(tc.rootPath) err := os.RemoveAll(tc.rootPath)
if err != nil { if err != nil {
t.Fatalf("%s: expected no error while cleaning up but got: %v", tc.scenario, err) t.Fatalf("expected no error while cleaning up but got: %v", err)
} }
}() }()
err := GarbageCollect(zap.NewNop(), tc.rootPath, tc.includeSubstr) err := GarbageCollect(zap.NewNop(), tc.rootPath, tc.includeSubstr)
if !tc.expectErr && err != nil { if !tc.expectError && err != nil {
t.Fatalf("%s: expected no error but got: %v", tc.scenario, err) t.Fatalf("expected no error but got: %v", err)
} }
if tc.expectErr && err == nil { if tc.expectError && err == nil {
t.Fatalf("%s: expected error but got: %v", tc.scenario, err) t.Fatal("expected error but got none")
} }
if tc.expectErr && err != nil { if tc.expectError && err != nil {
return return
} }
@@ -81,7 +82,7 @@ func TestGarbageCollect(t *testing.T) {
path := fmt.Sprintf("%s/%s", tc.rootPath, name) path := fmt.Sprintf("%s/%s", tc.rootPath, name)
_, err = os.Stat(path) _, err = os.Stat(path)
if !os.IsNotExist(err) { if !os.IsNotExist(err) {
t.Errorf("%s: expected '%s' not to exist but it does: %v", tc.scenario, path, err) t.Errorf("expected '%s' not to exist but it does: %v", path, err)
} }
} }
@@ -89,9 +90,9 @@ func TestGarbageCollect(t *testing.T) {
path := fmt.Sprintf("%s/%s", tc.rootPath, name) path := fmt.Sprintf("%s/%s", tc.rootPath, name)
_, err = os.Stat(path) _, err = os.Stat(path)
if os.IsNotExist(err) { if os.IsNotExist(err) {
t.Errorf("%s: expected '%s' to exist but it does not: %v", tc.scenario, path, err) t.Errorf("expected '%s' to exist but it does not: %v", path, err)
} }
} }
}() })
} }
} }

View File

@@ -15,6 +15,15 @@ func (mod *ModuleMock) Descriptor() ModuleDescriptor {
return mod.DescriptorMock() return mod.DescriptorMock()
} }
// ProvisionerMock is a mock for the [Provisioner] interface.
type ProvisionerMock struct {
ProvisionMock func(*Context) error
}
func (mod *ProvisionerMock) Provision(ctx *Context) error {
return mod.ProvisionMock(ctx)
}
// ValidatorMock is a mock for the [Validator] interface. // ValidatorMock is a mock for the [Validator] interface.
type ValidatorMock struct { type ValidatorMock struct {
ValidateMock func() error ValidateMock func() error

View File

@@ -21,6 +21,19 @@ func TestModuleMock(t *testing.T) {
} }
} }
func TestProvisionerMock(t *testing.T) {
mock := &ProvisionerMock{
ProvisionMock: func(*Context) error {
return nil
},
}
err := mock.Provision(&Context{})
if err != nil {
t.Errorf("expected no error from ProvisionerMock.Provision, but got: %v", err)
}
}
func TestValidatorMock(t *testing.T) { func TestValidatorMock(t *testing.T) {
mock := &ValidatorMock{ mock := &ValidatorMock{
ValidateMock: func() error { ValidateMock: func() error {

View File

@@ -5,32 +5,6 @@ import (
"testing" "testing"
) )
type ProtoModule struct {
descriptor func() ModuleDescriptor
}
func (mod ProtoModule) Descriptor() ModuleDescriptor {
return mod.descriptor()
}
type ProtoProvisioner struct {
ProtoModule
provision func(ctx *Context) error
}
func (mod ProtoProvisioner) Provision(ctx *Context) error {
return mod.provision(ctx)
}
type ProtoValidator struct {
ProtoModule
validate func() error
}
func (mod ProtoValidator) Validate() error {
return mod.validate()
}
func TestMustRegisterModule(t *testing.T) { func TestMustRegisterModule(t *testing.T) {
descriptorsMu.RLock() descriptorsMu.RLock()
descriptors = map[string]ModuleDescriptor{ descriptors = map[string]ModuleDescriptor{
@@ -38,44 +12,51 @@ func TestMustRegisterModule(t *testing.T) {
} }
descriptorsMu.RUnlock() descriptorsMu.RUnlock()
for i, tc := range []struct { for _, tc := range []struct {
scenario string
ID string ID string
New func() Module New func() Module
expectPanic bool expectPanic bool
}{ }{
{ {
scenario: "no ID",
ID: "", ID: "",
New: func() Module { return new(ProtoModule) }, New: func() Module { return new(ModuleMock) },
expectPanic: true, expectPanic: true,
}, },
{ {
scenario: "nil New method",
ID: "b", ID: "b",
New: nil, New: nil,
expectPanic: true, expectPanic: true,
}, },
{ {
scenario: "nil module",
ID: "b", ID: "b",
New: func() Module { return nil }, New: func() Module { return nil },
expectPanic: true, expectPanic: true,
}, },
{ {
scenario: "existing module",
ID: "a", ID: "a",
New: func() Module { return new(ProtoModule) }, New: func() Module { return new(ModuleMock) },
expectPanic: true, expectPanic: true,
}, },
{ {
ID: "b", scenario: "success",
New: func() Module { return new(ProtoModule) }, ID: "b",
New: func() Module { return new(ModuleMock) },
expectPanic: false,
}, },
} { } {
func() { t.Run(tc.scenario, func(t *testing.T) {
mod := struct{ ProtoModule }{} mod := &struct{ ModuleMock }{}
mod.descriptor = func() ModuleDescriptor { return ModuleDescriptor{ID: tc.ID, New: tc.New} } mod.DescriptorMock = func() ModuleDescriptor { return ModuleDescriptor{ID: tc.ID, New: tc.New} }
if tc.expectPanic { if tc.expectPanic {
defer func() { defer func() {
if r := recover(); r == nil { if r := recover(); r == nil {
t.Errorf("test %d: expected panic but got none", i) t.Error("expected panic but got none")
} }
}() }()
} }
@@ -83,13 +64,13 @@ func TestMustRegisterModule(t *testing.T) {
if !tc.expectPanic { if !tc.expectPanic {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
t.Errorf("test %d: expected no panic but got: %v", i, r) t.Errorf("expected no panic but got: %v", r)
} }
}() }()
} }
MustRegisterModule(mod) MustRegisterModule(mod)
}() })
} }
descriptorsMu.RLock() descriptorsMu.RLock()
@@ -124,12 +105,3 @@ func TestGetModuleDescriptors(t *testing.T) {
descriptors = make(map[string]ModuleDescriptor) descriptors = make(map[string]ModuleDescriptor)
descriptorsMu.RUnlock() descriptorsMu.RUnlock()
} }
// Interface guards.
var (
_ Module = (*ProtoModule)(nil)
_ Provisioner = (*ProtoProvisioner)(nil)
_ Module = (*ProtoProvisioner)(nil)
_ Validator = (*ProtoValidator)(nil)
_ Module = (*ProtoValidator)(nil)
)

View File

@@ -76,6 +76,6 @@ type PdfEngine interface {
// engine, _ := provider.(gotenberg.PdfEngineProvider).PdfEngine() // engine, _ := provider.(gotenberg.PdfEngineProvider).PdfEngine()
// } // }
type PdfEngineProvider interface { type PdfEngineProvider interface {
// PdfEngine returns an instance of the PdfEngine interface for PDF operations. // PdfEngine returns an instance of the [PdfEngine] interface for PDF operations.
PdfEngine() (PdfEngine, error) PdfEngine() (PdfEngine, error)
} }

View File

@@ -29,7 +29,7 @@ type libreOfficeArguments struct {
type libreOfficeProcess struct { type libreOfficeProcess struct {
socketPort int socketPort int
userProfileDirPath string userProfileDirPath string
cmd gotenberg.Cmd cmd *gotenberg.Cmd
cfgMu sync.RWMutex cfgMu sync.RWMutex
isStarted atomic.Bool isStarted atomic.Bool
@@ -213,7 +213,7 @@ func (p *libreOfficeProcess) Stop(logger *zap.Logger) error {
p.socketPort = 0 p.socketPort = 0
p.userProfileDirPath = "" p.userProfileDirPath = ""
p.cmd = gotenberg.Cmd{} // FIXME: pointer. p.cmd = nil
p.isStarted.Store(false) p.isStarted.Store(false)
return nil return nil