Skip to content

Commit

Permalink
feat: refactor and improve tests (#0)
Browse files Browse the repository at this point in the history
Signed-off-by: tkrop <[email protected]>
  • Loading branch information
tkrop committed Dec 13, 2023
1 parent 9421068 commit aa6c5cd
Show file tree
Hide file tree
Showing 23 changed files with 2,297 additions and 1,420 deletions.
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.0.4
0.0.5
8 changes: 3 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,18 @@ go 1.21

toolchain go1.21.0

require (
github.com/stretchr/testify v1.8.4
golang.org/x/exp v0.0.0-20231127185646-65229373498e
)
require github.com/stretchr/testify v1.8.4

require (
github.com/kr/text v0.2.0 // indirect
github.com/rogpeppe/go-internal v1.11.0 // indirect
golang.org/x/exp v0.0.0-20231127185646-65229373498e // indirect
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/mock v1.6.0
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tkrop/go-testing v0.0.0-20231116145516-bd6d45b76891
gopkg.in/yaml.v3 v3.0.1 // indirect
gopkg.in/yaml.v3 v3.0.1
)
38 changes: 38 additions & 0 deletions internal/cmd/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Package cmd provides a common interface for executing commands.
package cmd

import (
"io"
"os"
"os/exec"
)

// Executor provides a common interface for executing commands.
type Executor interface {
// Exec executes the command with given name and arguments in given
// directory redirecting stdout and stderr to given writers.
Exec(stdout, stderr io.Writer, dir string, args ...string) error
}

// defaultExecutor provides a default command executor using `os/exec`
// supporting optional tracing.
type defaultExecutor struct{}

// NewExecutor creates a new default command executor.
func NewExecutor() Executor {
return &defaultExecutor{}
}

// Exec executes the command with given name and arguments in given directory
// redirecting stdout and stderr to given writers.
func (*defaultExecutor) Exec(
stdout, stderr io.Writer, dir string, args ...string,
) error {
//#nosec G204 -- caller ensures safe commands
cmd := exec.Command(args[0], args[1:]...)
cmd.Dir, cmd.Env = dir, os.Environ()
cmd.Env = append(cmd.Env, "MAKE=go-make")
cmd.Stdout, cmd.Stderr = stdout, stderr

return cmd.Run() //nolint:wrapcheck // checked on next layer
}
42 changes: 42 additions & 0 deletions internal/cmd/cmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package cmd_test

import (
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/tkrop/go-make/internal/cmd"
"github.com/tkrop/go-testing/test"
)

type ExecutorParams struct {
args []string
expectStdout string
expectStderr string
expectError error
}

var testExecutorParams = map[string]ExecutorParams{
"ls": {
args: []string{"ls"},
expectStdout: "cmd.go\ncmd_test.go\n",
},
}

func TestExecutor(t *testing.T) {
test.Map(t, testExecutorParams).
Run(func(t test.Test, param ExecutorParams) {
// Given
exec := cmd.NewExecutor()
stdout := &strings.Builder{}
stderr := &strings.Builder{}

// When
err := exec.Exec(stdout, stderr, ".", param.args...)

// Then
assert.Equal(t, param.expectError, err)
assert.Equal(t, param.expectStdout, stdout.String())
assert.Equal(t, param.expectStderr, stderr.String())
})
}
118 changes: 118 additions & 0 deletions internal/coding/coding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Package coding provides functions for encoding and decoding of objects to
// and from strings and byte slices. The encoding and decoding is done using
// JSON and YAML encoders and decoders. Instead of throwing errors, the
// functions return textually encoded errors for further analysis.
package coding

import (
"encoding/json"
"errors"
"fmt"
"regexp"

"gopkg.in/yaml.v3"
)

// Type encoding type.
type Type string

// Check whether yaml input should return a nil object.
var yamlNilObjectMatch = regexp.MustCompile("null\n?")

const (
// TypeUnkown constant for unknown encoding type.
TypeUnkown Type = "UNKNOWN"
// TypeJSON constant for JSON encoding type.
TypeJSON Type = "JSON"
// TypeYAML constant for YAML encoding type.
TypeYAML Type = "YAML"
)

// ToString encodes any object to a string using the requested encoder. If the
// encoding fails or the encoder is unknown, no error is returned but a failure
// report encoded in a string is returned for analysis.
func ToString(ctype Type, obj any) string {
return string(ToBytes(ctype, obj))
}

// FromString converts a string into any object using the requested decoder.
// If the decoding fails or the decoder is unknown, no error is thrown but an
// error is returned for further analysis.
func FromString(ctype Type, s string, obj any) any {
return FromBytes(ctype, []byte(s), obj)
}

// ToBytes encodes any object to a byte slice using the requested encoder. If
// the encoding fails or the encoder is unknown, no error is returned, but a
// failure report encoded in a byte slice is returned for analysis.
func ToBytes(ctype Type, obj any) []byte {
switch ctype {
case TypeJSON:
if b, err := json.Marshal(obj); err != nil {
return []byte(NewErrEncoding(obj, err).Error())
} else {
return b
}
case TypeYAML:
if b, err := yaml.Marshal(obj); err != nil {
return []byte(NewErrEncoding(obj, err).Error())
} else {
return b
}
case TypeUnkown:
fallthrough
default:
return []byte(NewErrEncoding(obj, NewErrCoding(ctype)).Error())
}
}

// FromBytes converts a byte slice into any object using the requested decoder.
// If the decoding fails or the decoder is unknown, no error is thrown but an
// error is returned for further analysis.
func FromBytes(ctype Type, b []byte, obj any) any {
switch ctype {
case TypeJSON:
if err := json.Unmarshal(b, &obj); err != nil {
return NewErrDecoding(string(b), err)
}
return obj
case TypeYAML:
if yamlNilObjectMatch.Match(b) {
obj = nil
return obj
} else if err := yaml.Unmarshal(b, obj); err != nil {
return NewErrDecoding(string(b), err)
}
return obj
case TypeUnkown:
fallthrough
default:
return NewErrDecoding(string(b), NewErrCoding(ctype))
}
}

// ErrCoding is the error type for unknown encoding types.
var ErrCoding = errors.New("unknown coding")

// NewErrCoding creates a new unknown coding error with given encoding type.
func NewErrCoding(ctype Type) error {
return fmt.Errorf("%w: %v", ErrCoding, ctype)
}

// ErrDecoding is the error type for decoding errors.
var ErrDecoding = errors.New("error decoding")

// NewErrDecoding creates a new decoding error with given string and given
// root cause decoding error.
func NewErrDecoding(buf string, err error) error {
return fmt.Errorf("%w [%s]: %w", ErrDecoding, buf, err)
}

// ErrEncoding is the error type for encoding errors.
var ErrEncoding = errors.New("error encoding")

// NewErrEncoding creates a new encoding error with the given object to be
// encoded and given root cause encoding error.
func NewErrEncoding(obj any, err error) error {
return fmt.Errorf("%w [%T]: %w", ErrEncoding, obj, err)
}
Loading

0 comments on commit aa6c5cd

Please sign in to comment.