Skip to content

Commit

Permalink
Add plugin runner and move utils into subpackage
Browse files Browse the repository at this point in the history
  • Loading branch information
pseudomuto committed Feb 19, 2018
1 parent e62a38f commit d1aa8a1
Show file tree
Hide file tree
Showing 11 changed files with 357 additions and 78 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ fixtures/fileset.pb: fixtures/*.proto
@cd fixtures && go generate

test: fixtures/fileset.pb
@go test -race -cover ./
@go test -race -cover ./ ./utils

test-ci: fixtures/fileset.pb
@retool do goverage -race -coverprofile=coverage.txt -covermode=atomic ./
@retool do goverage -race -coverprofile=coverage.txt -covermode=atomic ./ ./utils
3 changes: 2 additions & 1 deletion comments_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"testing"

"github.com/pseudomuto/protokit"
"github.com/pseudomuto/protokit/utils"
)

type CommentsTest struct {
Expand All @@ -18,7 +19,7 @@ func TestComments(t *testing.T) {
}

func (assert *CommentsTest) SetupSuite() {
pf, err := protokit.LoadDescriptor("todo.proto", "fixtures", "fileset.pb")
pf, err := utils.LoadDescriptor("todo.proto", "fixtures", "fileset.pb")
assert.NoError(err)

assert.comments = protokit.ParseComments(pf)
Expand Down
5 changes: 3 additions & 2 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"testing"

"github.com/pseudomuto/protokit"
"github.com/pseudomuto/protokit/utils"
)

var (
Expand All @@ -25,10 +26,10 @@ func TestParser(t *testing.T) {
func (assert *ParserTest) SetupSuite() {
var err error

proto2, err = protokit.LoadDescriptor("booking.proto", "fixtures", "fileset.pb")
proto2, err = utils.LoadDescriptor("booking.proto", "fixtures", "fileset.pb")
assert.NoError(err)

proto3, err = protokit.LoadDescriptor("todo.proto", "fixtures", "fileset.pb")
proto3, err = utils.LoadDescriptor("todo.proto", "fixtures", "fileset.pb")
assert.NoError(err)
}

Expand Down
67 changes: 67 additions & 0 deletions plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package protokit

import (
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/protoc-gen-go/plugin"

"fmt"
"io"
"io/ioutil"
"os"
)

// Plugin describes an interface for running protoc code generator plugins
type Plugin interface {
Generate(req *plugin_go.CodeGeneratorRequest) (*plugin_go.CodeGeneratorResponse, error)
}

// RunPlugin runs the supplied plugin by reading input from stdin and generating output to stdout.
func RunPlugin(p Plugin) error {
return RunPluginWithIO(p, os.Stdin, os.Stdout)
}

// RunPluginWithIO runs the supplied plugin using the supplied reader and writer for IO.
func RunPluginWithIO(p Plugin, r io.Reader, w io.Writer) error {
req, err := readRequest(r)
if err != nil {
return err
}

resp, err := p.Generate(req)
if err != nil {
return err
}

return writeResponse(w, resp)
}

func readRequest(r io.Reader) (*plugin_go.CodeGeneratorRequest, error) {
data, err := ioutil.ReadAll(r)
if err != nil {
return nil, err
}

req := new(plugin_go.CodeGeneratorRequest)
if err = proto.Unmarshal(data, req); err != nil {
return nil, err
}

if len(req.GetFileToGenerate()) == 0 {
return nil, fmt.Errorf("no files were supplied to the generator")
}

return req, nil
}

func writeResponse(w io.Writer, resp *plugin_go.CodeGeneratorResponse) error {
data, err := proto.Marshal(resp)
if err != nil {
return err
}

if _, err := w.Write(data); err != nil {
return err
}

return nil
}
96 changes: 96 additions & 0 deletions plugin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package protokit_test

import (
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/protoc-gen-go/plugin"
"github.com/stretchr/testify/suite"

"bytes"
"errors"
"testing"

"github.com/pseudomuto/protokit"
"github.com/pseudomuto/protokit/utils"
)

type PluginTest struct {
suite.Suite
}

func TestPlugin(t *testing.T) {
suite.Run(t, new(PluginTest))
}

func (assert *PluginTest) TestRunPlugin() {
fds, err := utils.LoadDescriptorSet("fixtures", "fileset.pb")
assert.NoError(err)

req := utils.CreateGenRequest(fds, "booking.proto", "todo.proto")
data, err := proto.Marshal(req)
assert.NoError(err)

in := bytes.NewBuffer(data)
out := new(bytes.Buffer)

assert.NoError(protokit.RunPluginWithIO(new(OkPlugin), in, out))
assert.NotEmpty(out)
}

func (assert *PluginTest) TestRunPluginInputError() {
in := bytes.NewBufferString("Not a codegen request")
out := new(bytes.Buffer)

err := protokit.RunPluginWithIO(nil, in, out)
assert.EqualError(err, "proto: can't skip unknown wire type 6 for plugin_go.CodeGeneratorRequest")
assert.Empty(out)
}

func (assert *PluginTest) TestRunPluginNoFilesToGenerate() {
fds, err := utils.LoadDescriptorSet("fixtures", "fileset.pb")
assert.NoError(err)

req := utils.CreateGenRequest(fds)
data, err := proto.Marshal(req)
assert.NoError(err)

in := bytes.NewBuffer(data)
out := new(bytes.Buffer)

err = protokit.RunPluginWithIO(new(ErrorPlugin), in, out)
assert.EqualError(err, "no files were supplied to the generator")
assert.Empty(out)
}

func (assert *PluginTest) TestRunPluginGeneratorError() {
fds, err := utils.LoadDescriptorSet("fixtures", "fileset.pb")
assert.NoError(err)

req := utils.CreateGenRequest(fds, "booking.proto", "todo.proto")
data, err := proto.Marshal(req)
assert.NoError(err)

in := bytes.NewBuffer(data)
out := new(bytes.Buffer)

err = protokit.RunPluginWithIO(new(ErrorPlugin), in, out)
assert.EqualError(err, "generator error")
assert.Empty(out)
}

type ErrorPlugin struct{}

func (ep *ErrorPlugin) Generate(r *plugin_go.CodeGeneratorRequest) (*plugin_go.CodeGeneratorResponse, error) {
return nil, errors.New("generator error")
}

type OkPlugin struct{}

func (op *OkPlugin) Generate(r *plugin_go.CodeGeneratorRequest) (*plugin_go.CodeGeneratorResponse, error) {
resp := new(plugin_go.CodeGeneratorResponse)
resp.File = append(resp.File, &plugin_go.CodeGeneratorResponse_File{
Name: proto.String("myfile.out"),
Content: proto.String("someoutput"),
})

return resp, nil
}
32 changes: 0 additions & 32 deletions utils.go

This file was deleted.

71 changes: 71 additions & 0 deletions utils/protobuf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package utils

import (
"github.com/golang/protobuf/proto"
"github.com/golang/protobuf/protoc-gen-go/descriptor"
"github.com/golang/protobuf/protoc-gen-go/plugin"

"errors"
"io/ioutil"
"path/filepath"
)

// CreateGenRequest creates a codegen request from a `FileDescriptorSet`
func CreateGenRequest(fds *descriptor.FileDescriptorSet, filesToGen ...string) *plugin_go.CodeGeneratorRequest {
req := new(plugin_go.CodeGeneratorRequest)
req.ProtoFile = fds.GetFile()

for _, f := range req.GetProtoFile() {
if InStringSlice(filesToGen, filepath.Base(f.GetName())) {
req.FileToGenerate = append(req.FileToGenerate, f.GetName())
}
}

return req
}

// LoadDescriptorSet loads a `FileDescriptorSet` from a file on disk. Such a file can be generated using the
// `--descriptor_set_out` flag with `protoc`.
//
// Example:
// protoc --descriptor_set_out=fileset.pb --include_imports --include_source_info ./booking.proto ./todo.proto
func LoadDescriptorSet(pathSegments ...string) (*descriptor.FileDescriptorSet, error) {
f, err := ioutil.ReadFile(filepath.Join(pathSegments...))
if err != nil {
return nil, err
}

set := new(descriptor.FileDescriptorSet)
if err = proto.Unmarshal(f, set); err != nil {
return nil, err
}

return set, nil
}

// FindDescriptor finds the named descriptor in the given set. Only base names are searched. The first match is
// returned, on `nil` if not found
func FindDescriptor(set *descriptor.FileDescriptorSet, name string) *descriptor.FileDescriptorProto {
for _, pf := range set.GetFile() {
if filepath.Base(pf.GetName()) == name {
return pf
}
}

return nil
}

// LoadDescriptor loads file descriptor protos from a file on disk, and returns the named proto descriptor. This is
// useful mostly for testing purposes.
func LoadDescriptor(name string, pathSegments ...string) (*descriptor.FileDescriptorProto, error) {
set, err := LoadDescriptorSet(pathSegments...)
if err != nil {
return nil, err
}

if pf := FindDescriptor(set, name); pf != nil {
return pf, nil
}

return nil, errors.New("FileDescriptor not found")
}
81 changes: 81 additions & 0 deletions utils/protobuf_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package utils_test

import (
"github.com/stretchr/testify/suite"

"testing"

"github.com/pseudomuto/protokit/utils"
)

type UtilsTest struct {
suite.Suite
}

func TestUtils(t *testing.T) {
suite.Run(t, new(UtilsTest))
}

func (assert *UtilsTest) TestCreateGenRequest() {
fds, err := utils.LoadDescriptorSet("..", "fixtures", "fileset.pb")
assert.NoError(err)

req := utils.CreateGenRequest(fds, "booking.proto", "todo.proto")
assert.Equal([]string{"booking.proto", "todo.proto"}, req.GetFileToGenerate())

expectedProtos := []string{
"booking.proto",
"google/protobuf/any.proto",
"google/protobuf/timestamp.proto",
"todo.proto",
}

for _, pf := range req.GetProtoFile() {
assert.True(utils.InStringSlice(expectedProtos, pf.GetName()))
}
}

func (assert *UtilsTest) TestLoadDescriptorSet() {
set, err := utils.LoadDescriptorSet("..", "fixtures", "fileset.pb")
assert.NoError(err)
assert.Len(set.GetFile(), 4)

assert.NotNil(utils.FindDescriptor(set, "todo.proto"))
assert.Nil(utils.FindDescriptor(set, "whodis.proto"))
}

func (assert *UtilsTest) TestLoadDescriptorSetFileNotFound() {
set, err := utils.LoadDescriptorSet("..", "fixtures", "notgonnadoit.pb")
assert.Nil(set)
assert.EqualError(err, "open ../fixtures/notgonnadoit.pb: no such file or directory")
}

func (assert *UtilsTest) TestLoadDescriptorSetMarshalError() {
set, err := utils.LoadDescriptorSet("..", "fixtures", "todo.proto")
assert.Nil(set)
assert.EqualError(err, "proto: can't skip unknown wire type 7 for descriptor.FileDescriptorSet")
}

func (assert *UtilsTest) TestLoadDescriptor() {
proto, err := utils.LoadDescriptor("todo.proto", "..", "fixtures", "fileset.pb")
assert.NotNil(proto)
assert.NoError(err)
}

func (assert *UtilsTest) TestLoadDescriptorFileNotFound() {
proto, err := utils.LoadDescriptor("todo.proto", "..", "fixtures", "notgonnadoit.pb")
assert.Nil(proto)
assert.EqualError(err, "open ../fixtures/notgonnadoit.pb: no such file or directory")
}

func (assert *UtilsTest) TestLoadDescriptorMarshalError() {
proto, err := utils.LoadDescriptor("todo.proto", "..", "fixtures", "todo.proto")
assert.Nil(proto)
assert.EqualError(err, "proto: can't skip unknown wire type 7 for descriptor.FileDescriptorSet")
}

func (assert *UtilsTest) TestLoadDescriptorDescriptorNotFound() {
proto, err := utils.LoadDescriptor("nothere.proto", "..", "fixtures", "fileset.pb")
assert.Nil(proto)
assert.EqualError(err, "FileDescriptor not found")
}
Loading

0 comments on commit d1aa8a1

Please sign in to comment.