Skip to content

Commit

Permalink
Add a few tests
Browse files Browse the repository at this point in the history
  • Loading branch information
svrana committed Jul 11, 2023
1 parent 958b490 commit 556f6f7
Show file tree
Hide file tree
Showing 8 changed files with 594 additions and 22 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ package {{.service_name}}.v1;
import "validate/validate.proto";
// note that Title is a geniveev builtin that we use to capalize the Name of the service, i.e.,
// note that Title is a geniveev builtin that we use to capitalize the Name of the service, i.e.,
// UserService
service {{ .service_name | Title }}Service {
}
Expand Down
10 changes: 8 additions & 2 deletions cmd/geniveev/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,15 @@ THE SOFTWARE.
*/
package main

import "github.com/svrana/geniveev/cmd"
import (
"os"

"github.com/svrana/geniveev/cmd"
)

func main() {
cmd.Initialize()
if err := cmd.Initialize(); err != nil {
os.Exit(1)
}
cmd.Execute()
}
35 changes: 23 additions & 12 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import (
"strings"

"github.com/pelletier/go-toml/v2"
"github.com/spf13/afero"
"github.com/spf13/cobra"

"github.com/svrana/geniveev"
"github.com/svrana/geniveev/template"
)

var AppFs = afero.NewOsFs()
var cfgFile string = ".geniveev.toml"
var config = geniveev.NewConfig()

Expand All @@ -39,7 +41,7 @@ func constructFilename(templatedFilename geniveev.Filename, templateValues geniv
}

func createPath(filename string) error {
if _, err := os.Stat(filename); err != nil {
if _, err := AppFs.Stat(filename); err != nil {
if !errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("failed to check %s for existence: %s", filename, err)

Expand All @@ -48,7 +50,7 @@ func createPath(filename string) error {
return fmt.Errorf("%s already exists, will not overwrite", filename)
}

if err := os.MkdirAll(path.Dir(filename), 0755); err != nil {
if err := AppFs.MkdirAll(path.Dir(filename), 0755); err != nil {
fmt.Fprintf(os.Stderr, "failed to create directory %s: %s", path.Dir(filename), err)
os.Exit(1)
}
Expand All @@ -60,7 +62,7 @@ func generate(generatorConfig geniveev.GeneratorConfig, filename string, templat
if err != nil {
return err
}
if err := os.WriteFile(filename, []byte(code), 0644); err != nil {
if err := afero.WriteFile(AppFs, filename, []byte(code), 0644); err != nil {
return err
}
return nil
Expand All @@ -82,25 +84,24 @@ func start(templateConfig *geniveev.TemplateConfig) error {
return nil
}

// Initialize reads in config file and ENV variables if set.
func Initialize() {
func readConfig() (map[string]interface{}, error) {
// key splitting in cobra is borked with toml and keys containing periods, so just handle
// the file reading ourselves here.
b, err := os.ReadFile(cfgFile)
b, err := afero.ReadFile(AppFs, cfgFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Error reading .geniveev.toml: %s", err)
os.Exit(1)
return nil, fmt.Errorf("error reading %s", cfgFile)
}

cfg := make(map[string]interface{})

if err := toml.Unmarshal(b, &cfg); err != nil {
fmt.Fprintf(os.Stderr, "failed to unmarshal toml: %s\n", err)
os.Exit(1)
return nil, fmt.Errorf("failed to unmarshal toml: %sn", err)
}

re := regexp.MustCompile(`{(.\w+)}`)
return cfg, nil
}

func parseConfig(cfg map[string]interface{}) {
re := regexp.MustCompile(`{(.\w+)}`)
for name, v := range cfg {
config.Generator[name] = geniveev.NewTemplateConfigEmpty()

Expand Down Expand Up @@ -164,3 +165,13 @@ func Initialize() {
rootCmd.AddCommand(newCmd)
}
}

func Initialize() error {
cfg, err := readConfig()
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
return err
}
parseConfig(cfg)
return nil
}
131 changes: 128 additions & 3 deletions cmd/root_test.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,31 @@
package cmd

import (
"path/filepath"
"testing"

"github.com/spf13/afero"

"github.com/svrana/geniveev"
)

func String(s string) *string {
func ptr(s string) *string {
return &s
}

func teardown() {
AppFs = afero.NewOsFs()
}

func TestConstructFilename(t *testing.T) {
tests := []struct {
inputFilename geniveev.Filename
inputValues map[string]*string
want string
}{
{inputFilename: "foo.go", inputValues: map[string]*string{}, want: "foo.go"},
{inputFilename: "protos/{{.service_name}}.proto", inputValues: map[string]*string{"service_name": String("user")}, want: "protos/user.proto"},
{inputFilename: "services/v1/{{.service_name}}/{{.service_name}}.go", inputValues: map[string]*string{"service_name": String("user")}, want: "services/v1/user/user.go"},
{inputFilename: "protos/{{.service_name}}.proto", inputValues: map[string]*string{"service_name": ptr("user")}, want: "protos/user.proto"},
{inputFilename: "services/v1/{{.service_name}}/{{.service_name}}.go", inputValues: map[string]*string{"service_name": ptr("user")}, want: "services/v1/user/user.go"},
{inputFilename: "{{Title \"protos\"}}", inputValues: map[string]*string{}, want: "Protos"},
}

Expand All @@ -32,3 +39,121 @@ func TestConstructFilename(t *testing.T) {
}
}
}

func TestCreatePath(t *testing.T) {
AppFs = afero.NewMemMapFs()
defer teardown()

directory := "/foo/bar"
filename := filepath.Join(directory, "baz.go")
// non-existent path should succeed
if err := createPath(filename); err != nil {
t.Fatalf("expected success got %s", err)
}
_, err := AppFs.Stat(directory)
if err != nil {
t.Fatalf("file does not exist")
}
if err = afero.WriteFile(AppFs, filename, []byte("file b"), 0644); err != nil {
t.Fatalf("unexpected error setting up test: %s", err)
}
// now try again with an existing file and make sure it fails as we do not want
// to overwrite any existing files.
err = createPath(filename)
if err == nil {
t.Fatalf("unexpected success for existing file")
}
}

func TestIntegration(t *testing.T) {
// first read in example geniveev configuration file from disk
absFilepath, err := filepath.Abs(filepath.Join("..", "example", cfgFile))
if err != nil {
t.Fatalf("failed to get absolute filename of example config file: %s", err)

}
b, err := afero.ReadFile(AppFs, absFilepath)
if err != nil {
t.Fatalf("failed to read config file: %s", err)
}
AppFs = afero.NewMemMapFs()
defer teardown()
if err = afero.WriteFile(AppFs, cfgFile, b, 0644); err != nil {
t.Fatalf("failed to write test config to in memory filesystem for test setup: %s", err)
}
if err = Initialize(); err != nil {
t.Fatalf("initialize returned an error: %s", err)
}
if v := config.Generator["service-stubs"]; v == nil {
t.Fatalf("service-stubs not parsed")
}

config.Generator["service-stubs"].TemplateValues["service_name"] = ptr("user") // match the readme

if err = start(config.Generator["service-stubs"]); err != nil {
t.Fatalf("failed to generate code: %s", err)
}
userProto := "protos/user/v1/user/user.proto"
_, err = AppFs.Stat(userProto)
if err != nil {
t.Fatalf("could not locate generated file")
}
userProtoMem, err := afero.ReadFile(AppFs, userProto)
if err != nil {
t.Fatalf("failed to read generated proto: %s", err)
}
expected := `syntax = "proto3";
package user.v1;
import "validate/validate.proto";
// note that Title is a geniveev builtin that we use to capitalize the Name of the service, i.e.,
// UserService
service UserService {
}
`
if string(userProtoMem) != expected {
t.Fatalf("expected\n%s\ngot\n%s\n", expected, userProtoMem)
}

implProto := "services/v1/user/user.go"
_, err = AppFs.Stat(implProto)
if err != nil {
t.Fatalf("could not locate generated file")
}
userImplMem, err := afero.ReadFile(AppFs, implProto)
if err != nil {
t.Fatalf("failed to read generated user implementation: %s", err)
}
expected = `package userv1
import (
"context"
"fmt"
"net/http"
"github.com/bufbuild/connect-go"
"github.com/bommie/b6/config"
"github.com/bommie/b6/db"
)
type UserServer struct {
db *db.DB
cfg *config.AuthConfig
}
var _ userv1connect.UserServiceClient = (*UserServer)(nil)
func NewServer(_ context.Context, db *db.DB, cfg *config.AuthConfig) *UserServer {
return &UserServer{
db: db,
cfg: cfg,
}
}
`
if string(userImplMem) != expected {
t.Fatalf("expected \n%s\ngot \n%s\n", expected, userImplMem)
}
}
2 changes: 0 additions & 2 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package geniveev

import "fmt"

// This is probably a poor name if we've only got this structure
// to allow other rendering engines.
type GeneratorConfig struct {
Code string
}
Expand Down
2 changes: 1 addition & 1 deletion example/.geniveev.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package {{.service_name}}.v1;
import "validate/validate.proto";
// note that Title is a geniveev builtin that we use to capalize the Name of the service, i.e.,
// note that Title is a geniveev builtin that we use to capitalize the Name of the service, i.e.,
// UserService
service {{ .service_name | Title }}Service {
}
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ go 1.19

require (
github.com/pelletier/go-toml/v2 v2.0.8
github.com/spf13/afero v1.9.5
github.com/spf13/cobra v1.7.0
golang.org/x/text v0.10.0
)

require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/text v0.10.0 // indirect
)
Loading

0 comments on commit 556f6f7

Please sign in to comment.