Skip to content

Commit

Permalink
readme
Browse files Browse the repository at this point in the history
  • Loading branch information
RangelReale committed Oct 11, 2023
1 parent 3fb6b89 commit d49b210
Showing 1 changed file with 78 additions and 17 deletions.
95 changes: 78 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,53 @@
# qdiimpl - Quick'n'Dirty Interface Implementation (for Golang)

`qdiimpl` is a Go generator cli that generates "Quick and **Dirty** Interface Implementations" meant for quick
debugging, absolutely not production-ready.

This is for that time when you want to test one feature that depends on lots of external service interfaces like
databases, cloud storage, message queues, and you don't want to use the real thing just to test something that has
nothing to do with these interfaces.

Another option would be using a mock, however outside of tests they are cumbersome to use because mocks need to set
expectations and usually are limited by execution amounts.

## Usage

```shell
$ cd app/pkg/client
$ go run github.com/RangelReale/qdiimpl/cmd/qdiimpl -type=StorageClient
Writing file storageclient_qdii.go...
```

There is an option for each interface method called `WithDebugTYPEMETHOD` to set a function that will be called when
the method is called. If a method is called when a function is not set, the implementation panics with a useful
message.

## Command line parameters

```
Usage of qdiimpl:
qdiimpl [flags] -type T [directory]
Flags:
-force-package string
force package name
-name-prefix string
interface name prefix; default is 'debug' (default "debug")
-name-suffix string
interface name suffix; default is blank
-output string
output file name; default srcdir/<type>_qdiimpl.go
-overwrite
overwrite file if exists
-package string
package name; if not set, use package from dir
-same-package
output package should be the same as the source (default true)
-tags string
comma-separated list of build tags to apply
-type string
type name; must be set
```

# Samples

### io.Reader
Expand Down Expand Up @@ -28,60 +76,60 @@ type DebugReaderContext struct {
Data any
}

type debugReader struct {
execCount map[string]int
data any
type DebugReader struct {
Data any

implRead func(debugCtx *DebugReaderContext, p []byte) (n int, err error)
execCount map[string]int
implRead func(debugCtx *DebugReaderContext, p []byte) (n int, err error)
}

var _ io.Reader = (*debugReader)(nil)
var _ io.Reader = (*DebugReader)(nil)

type DebugReaderOption func(*debugReader)
type DebugReaderOption func(*DebugReader)

func NewDebugReader(options ...DebugReaderOption) io.Reader {
ret := &debugReader{execCount: map[string]int{}}
func NewDebugReader(options ...DebugReaderOption) *DebugReader {
ret := &DebugReader{execCount: map[string]int{}}
for _, opt := range options {
opt(ret)
}
return ret
}

func (d *debugReader) Read(p []byte) (n int, err error) {
func (d *DebugReader) Read(p []byte) (n int, err error) {
return d.implRead(d.createContext("Read", d.implRead == nil), p)
}

func (d *debugReader) getCallerFuncName(skip int) (funcName string, file string, line int) {
func (d *DebugReader) getCallerFuncName(skip int) (funcName string, file string, line int) {
counter, file, line, success := runtime.Caller(skip)
if !success {
panic("runtime.Caller failed")
}
return runtime.FuncForPC(counter).Name(), file, line
}

func (d *debugReader) checkCallMethod(methodName string, implIsNil bool) (count int) {
func (d *DebugReader) checkCallMethod(methodName string, implIsNil bool) (count int) {
if implIsNil {
panic(fmt.Errorf("[debugReader] method '%s' not implemented", methodName))
panic(fmt.Errorf("[DebugReader] method '%s' not implemented", methodName))
}
d.execCount[methodName]++
return d.execCount[methodName]
}

func (d *debugReader) createContext(methodName string, implIsNil bool) *DebugReaderContext {
func (d *DebugReader) createContext(methodName string, implIsNil bool) *DebugReaderContext {
callerFunc, callerFile, callerLine := d.getCallerFuncName(3)
return &DebugReaderContext{ExecCount: d.checkCallMethod(methodName, implIsNil), CallerFunc: callerFunc, CallerFile: callerFile, CallerLine: callerLine, Data: d.data}
return &DebugReaderContext{ExecCount: d.checkCallMethod(methodName, implIsNil), CallerFunc: callerFunc, CallerFile: callerFile, CallerLine: callerLine, Data: d.Data}
}

// Options

func WithDebugReaderData(data any) DebugReaderOption {
return func(d *debugReader) {
d.data = data
return func(d *DebugReader) {
d.Data = data
}
}

func WithDebugReaderRead(implRead func(debugCtx *DebugReaderContext, p []byte) (n int, err error)) DebugReaderOption {
return func(d *debugReader) {
return func(d *DebugReader) {
d.implRead = implRead
}
}
Expand Down Expand Up @@ -113,6 +161,19 @@ func readInterface(r io.Reader) {
}
```

# Details

### Debug Context

Each method is passed a "DebugContext" which contains these fields:

- `ExecCount`: times this method was called since the execution start.
- `CallerFunc`: fully-qualified function name that called the interface method.
- `CallerFile`: source file name of the function that called the interface method.
- `CallerLine`: line number of the source file of the function that called the interface method.
- `Data`: a custom data field set by `WithDebugTYPEData` option.

Use these properties to help detect where from the method was called from and return different responses if needed.

# Author

Expand Down

0 comments on commit d49b210

Please sign in to comment.