Merge pull request #8 from TuneLab/truss-integration-test-refactor
Refactor truss and integration_tests
This commit is contained in:
Коммит
9245d19ab2
|
@ -17,7 +17,9 @@ clean:
|
|||
|
||||
# Run truss on all proto files in all dirs in ./tests/
|
||||
test:
|
||||
go test -tags=integration
|
||||
|
||||
go test -tags=integration -v ./integration_tests
|
||||
|
||||
testclean:
|
||||
go test -tags=integration ./integration_tests -clean
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
package generator
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
|
||||
templates "github.com/TuneLab/gob/truss/template"
|
||||
)
|
||||
|
||||
func init() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
log.SetFormatter(&log.TextFormatter{
|
||||
ForceColors: true,
|
||||
})
|
||||
}
|
||||
|
||||
// GenerateMicroservice takes a golang importPath, a path to .proto definition files workingDirectory, and a slice of
|
||||
// definition files DefinitionFiles and outputs a ./service direcotry in workingDirectory with a generated and built golang microservice
|
||||
func GenerateMicroservice(importPath string, workingDirectory string, definitionFiles []string) {
|
||||
|
||||
done := make(chan bool)
|
||||
|
||||
// Stage 1
|
||||
buildDirectories(workingDirectory)
|
||||
outputGoogleImport(workingDirectory)
|
||||
|
||||
// Stage 2, 3, 4
|
||||
generatePbGoCmd := "--go_out=Mgoogle/api/annotations.proto=" + importPath + "/service/DONOTEDIT/third_party/googleapis/google/api,plugins=grpc:./service/DONOTEDIT/pb"
|
||||
const generateDocsCmd = "--truss-doc_out=."
|
||||
const generateGoKitCmd = "--truss-gokit_out=."
|
||||
|
||||
go protoc(workingDirectory, definitionFiles, generatePbGoCmd, done)
|
||||
go protoc(workingDirectory, definitionFiles, generateDocsCmd, done)
|
||||
go protoc(workingDirectory, definitionFiles, generateGoKitCmd, done)
|
||||
|
||||
<-done
|
||||
<-done
|
||||
<-done
|
||||
|
||||
// Stage 5
|
||||
go goBuild("server", importPath+"/service/DONOTEDIT/cmd/svc/...", done)
|
||||
go goBuild("cliclient", importPath+"/service/DONOTEDIT/cmd/cliclient/...", done)
|
||||
|
||||
<-done
|
||||
<-done
|
||||
}
|
||||
|
||||
// buildDirectories puts the following directories in place
|
||||
// .
|
||||
// └── service
|
||||
// ├── bin
|
||||
// └── DONOTEDIT
|
||||
// ├── pb
|
||||
// └── third_party
|
||||
// └── googleapis
|
||||
// └── google
|
||||
// └── api
|
||||
func buildDirectories(workingDirectory string) {
|
||||
// third_party created by going through assets in template
|
||||
// and creating directoires that are not there
|
||||
for _, filePath := range templates.AssetNames() {
|
||||
fullPath := workingDirectory + "/" + filePath
|
||||
|
||||
dirPath := filepath.Dir(fullPath)
|
||||
|
||||
err := os.MkdirAll(dirPath, 0777)
|
||||
if err != nil {
|
||||
log.WithField("DirPath", dirPath).WithError(err).Fatal("Cannot create directories")
|
||||
}
|
||||
}
|
||||
|
||||
// Create the directory where protoc will store the compiled .pb.go files
|
||||
err := os.MkdirAll(workingDirectory+"/service/DONOTEDIT/pb", 0777)
|
||||
if err != nil {
|
||||
log.WithField("DirPath", "service/DONOTEDIT/pb").WithError(err).Fatal("Cannot create directories")
|
||||
}
|
||||
|
||||
// Create the directory where go build will put the compiled binaries
|
||||
err = os.MkdirAll(workingDirectory+"/service/bin", 0777)
|
||||
if err != nil {
|
||||
log.WithField("DirPath", "service/bin").WithError(err).Fatal("Cannot create directories")
|
||||
}
|
||||
}
|
||||
|
||||
// outputGoogleImport places imported and required google.api.http protobuf option files
|
||||
// into their required directories as part of stage one generation
|
||||
func outputGoogleImport(workingDirectory string) {
|
||||
// Output files that are stored in template package
|
||||
for _, filePath := range templates.AssetNames() {
|
||||
fileBytes, _ := templates.Asset(filePath)
|
||||
fullPath := workingDirectory + "/" + filePath
|
||||
|
||||
err := ioutil.WriteFile(fullPath, fileBytes, 0666)
|
||||
if err != nil {
|
||||
log.WithField("FilePath", fullPath).WithError(err).Fatal("Cannot create ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// goBuild calls the `$ go get ` to install dependenices
|
||||
// and then calls `$ go build service/bin/$name $path`
|
||||
// to put the iterating binaries in the correct place
|
||||
func goBuild(name string, path string, done chan bool) {
|
||||
|
||||
// $ go get
|
||||
|
||||
goGetExec := exec.Command(
|
||||
"go",
|
||||
"get",
|
||||
"-d",
|
||||
"-v",
|
||||
path,
|
||||
)
|
||||
|
||||
goGetExec.Stderr = os.Stderr
|
||||
|
||||
log.WithField("cmd", strings.Join(goGetExec.Args, " ")).Info("go get")
|
||||
val, err := goGetExec.Output()
|
||||
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"output": string(val),
|
||||
"input": goGetExec.Args,
|
||||
}).WithError(err).Warn("go get failed")
|
||||
}
|
||||
|
||||
// $ go build
|
||||
|
||||
goBuildExec := exec.Command(
|
||||
"go",
|
||||
"build",
|
||||
"-o",
|
||||
"service/bin/"+name,
|
||||
path,
|
||||
)
|
||||
|
||||
goBuildExec.Stderr = os.Stderr
|
||||
|
||||
log.WithField("cmd", strings.Join(goBuildExec.Args, " ")).Info("go build")
|
||||
val, err = goBuildExec.Output()
|
||||
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"output": string(val),
|
||||
"input": goBuildExec.Args,
|
||||
}).WithError(err).Fatal("go build failed")
|
||||
}
|
||||
|
||||
done <- true
|
||||
}
|
||||
|
||||
func protoc(workingDirectory string, definitionPaths []string, command string, done chan bool) {
|
||||
const googleApiHttpImportPath = "/service/DONOTEDIT/third_party/googleapis"
|
||||
|
||||
cmdArgs := []string{
|
||||
"-I.",
|
||||
"-I" + workingDirectory + googleApiHttpImportPath,
|
||||
command,
|
||||
}
|
||||
// Append each definition file path to the end of that command args
|
||||
cmdArgs = append(cmdArgs, definitionPaths...)
|
||||
|
||||
protocExec := exec.Command(
|
||||
"protoc",
|
||||
cmdArgs...,
|
||||
)
|
||||
|
||||
protocExec.Stderr = os.Stderr
|
||||
|
||||
log.WithField("cmd", strings.Join(protocExec.Args, " ")).Info("protoc")
|
||||
val, err := protocExec.Output()
|
||||
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"output": string(val),
|
||||
"input": protocExec.Args,
|
||||
}).WithError(err).Fatal("Protoc call failed")
|
||||
}
|
||||
|
||||
done <- true
|
||||
}
|
|
@ -1,312 +1,2 @@
|
|||
// This file is here so that go's tooling does not complain there are no builable gofiles in this directory
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
// From within a folder with a truss `service`
|
||||
// These are the paths to the compiled binaries
|
||||
const RELATIVESERVERPATH = "/service/bin/server"
|
||||
const RELATIVECLIENTPATH = "/service/bin/cliclient"
|
||||
|
||||
// runReference stores data about a client-server interaction
|
||||
// This data is then used to display output
|
||||
type runReference struct {
|
||||
path string
|
||||
clientErr bool
|
||||
serverErr bool
|
||||
clientOutput string
|
||||
serverOutput string
|
||||
}
|
||||
|
||||
func init() {
|
||||
log.SetLevel(log.InfoLevel)
|
||||
log.SetFormatter(&log.TextFormatter{
|
||||
ForceColors: true,
|
||||
})
|
||||
}
|
||||
|
||||
func runIntegrationTests() bool {
|
||||
allPassed := true
|
||||
|
||||
workingDirectory, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.WithError(err).Fatal("Cannot get working directory")
|
||||
}
|
||||
workingDirectory = workingDirectory + "/test_service_definitions"
|
||||
|
||||
// runRefs will be passed to all gorutines running communication tests
|
||||
// and will be read to display output
|
||||
runRefs := make(chan runReference)
|
||||
// tasksCount is increased for every server/client call
|
||||
// and decreased every time one is display, for exiting
|
||||
tasksCount := 0
|
||||
|
||||
// Loop through all directories in the running path
|
||||
dirs, err := ioutil.ReadDir(workingDirectory)
|
||||
for _, d := range dirs {
|
||||
// If this item is not a directory skip it
|
||||
if !d.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Clean up the service directories in each test
|
||||
if fileExists(workingDirectory + "/" + d.Name() + "/service") {
|
||||
os.RemoveAll(workingDirectory + "/" + d.Name() + "/service")
|
||||
}
|
||||
|
||||
// tests will be run on the fullpath to directory
|
||||
testDir := workingDirectory + "/" + d.Name()
|
||||
// On port relative to 8082, increasing by tasksCount
|
||||
port := 8082 + tasksCount
|
||||
|
||||
log.WithField("Service", d.Name()).Info("Starting integration test")
|
||||
|
||||
// Running the integration tests one at a time because truss running on all files at once
|
||||
// seems to slow the system more than distribute the work
|
||||
communicationTestRan := runTest(testDir, port, runRefs)
|
||||
|
||||
// If communication test ran, increase the running tasksCount
|
||||
if communicationTestRan {
|
||||
tasksCount = tasksCount + 1
|
||||
} else {
|
||||
allPassed = false
|
||||
}
|
||||
}
|
||||
|
||||
// range through the runRefs channel, display info if pass
|
||||
// display warn with debug info if fail
|
||||
for i := 0; i < tasksCount; i++ {
|
||||
ref := <-runRefs
|
||||
if ref.clientErr || ref.serverErr {
|
||||
log.WithField("Service", filepath.Base(ref.path)).Warn("Communication test FAILED")
|
||||
log.Warnf("Client Output\n%v", ref.clientOutput)
|
||||
log.Warnf("Server Output\n%v", ref.serverOutput)
|
||||
allPassed = false
|
||||
} else {
|
||||
log.WithField("Service", filepath.Base(ref.path)).Info("Communication test passed")
|
||||
}
|
||||
}
|
||||
|
||||
if allPassed {
|
||||
// Clean up the service directories in each test
|
||||
dirs, err = ioutil.ReadDir(workingDirectory)
|
||||
for _, d := range dirs {
|
||||
// If this item is not a directory skip it
|
||||
if !d.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
if fileExists(workingDirectory + "/" + d.Name() + "/service") {
|
||||
os.RemoveAll(workingDirectory + "/" + d.Name() + "/service")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allPassed
|
||||
}
|
||||
|
||||
// runTest generates, builds, and runs truss services
|
||||
// testPath is the full path to the definition files
|
||||
// portRef is a reference port to launch services on
|
||||
// runRefs is a channel where references to client/server communication will be passed back
|
||||
// runTest returns a bool representing whether or not the client/server communication was tested
|
||||
func runTest(testPath string, portRef int, runRefs chan runReference) (communicationTestRan bool) {
|
||||
// Build the full path to this directory and the path to the client and server
|
||||
// binaries within it
|
||||
log.WithField("Test path", testPath).Debug()
|
||||
|
||||
// Generate and build service
|
||||
truss(testPath)
|
||||
|
||||
serverPath := testPath + RELATIVESERVERPATH
|
||||
clientPath := testPath + RELATIVECLIENTPATH
|
||||
|
||||
// If the server and client binary exist then run them against each other
|
||||
if fileExists(serverPath) && fileExists(clientPath) {
|
||||
port := portRef
|
||||
debugPort := portRef + 1000
|
||||
checkPort(port)
|
||||
log.WithFields(log.Fields{
|
||||
"testPath": testPath,
|
||||
"port": port,
|
||||
"debugPort": debugPort,
|
||||
}).
|
||||
Debug("LAUNCH")
|
||||
go runServerAndClient(testPath, port, debugPort, runRefs)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// truss calls truss on *.proto in path
|
||||
// Truss logs to Stdout when generation passes or fails
|
||||
func truss(path string) {
|
||||
var protofiles []string
|
||||
files, err := ioutil.ReadDir(path)
|
||||
for _, f := range files {
|
||||
if f.IsDir() {
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(f.Name(), ".proto") {
|
||||
protofiles = append(protofiles, f.Name())
|
||||
}
|
||||
}
|
||||
|
||||
trussExec := exec.Command(
|
||||
"truss",
|
||||
protofiles...,
|
||||
)
|
||||
trussExec.Dir = path
|
||||
|
||||
log.WithField("Path", path).Debug("Exec Truss")
|
||||
val, err := trussExec.CombinedOutput()
|
||||
|
||||
if err != nil {
|
||||
log.Warn(err)
|
||||
log.Warn(trussExec.Args)
|
||||
log.Warn(path)
|
||||
log.WithField("Service", filepath.Base(path)).Warn("Truss generation FAILED")
|
||||
log.Warnf("Truss Output:\n%v", string(val))
|
||||
} else {
|
||||
log.WithField("Service", filepath.Base(path)).Info("Truss generation passed")
|
||||
}
|
||||
}
|
||||
|
||||
// checkPort checks if port is being used
|
||||
// TODO: Make work
|
||||
func checkPort(port int) {
|
||||
log.Debug("Checking Port")
|
||||
ips, _ := net.LookupIP("localhost")
|
||||
listener, err := net.ListenTCP("tcp",
|
||||
&net.TCPAddr{
|
||||
IP: ips[0],
|
||||
Port: port,
|
||||
})
|
||||
_ = listener
|
||||
|
||||
log.Debug("Checking Error")
|
||||
if err != nil {
|
||||
log.WithField("port", port).Warn("PORT MAY BE TAKEN")
|
||||
}
|
||||
listener.Close()
|
||||
|
||||
//net.Dial("tcp", "localhost:"+strconv.Itoa(port))
|
||||
|
||||
}
|
||||
|
||||
func runServerAndClient(path string, port int, debugPort int, refChan chan runReference) {
|
||||
|
||||
// Output buffer for the server Stdout and Stderr
|
||||
serverOut := bytes.NewBuffer(nil)
|
||||
// Get the server command ready with the port
|
||||
server := exec.Command(
|
||||
path+RELATIVESERVERPATH,
|
||||
"-grpc.addr",
|
||||
":"+strconv.Itoa(port),
|
||||
"-debug.addr",
|
||||
":"+strconv.Itoa(debugPort),
|
||||
)
|
||||
|
||||
// Put serverOut to be the writer of data from Stdout and Stderr
|
||||
server.Stdout = serverOut
|
||||
server.Stderr = serverOut
|
||||
|
||||
log.Debug("Starting the server!")
|
||||
// Start the server
|
||||
serverErrChan := make(chan error)
|
||||
go func() {
|
||||
err := server.Run()
|
||||
serverErrChan <- err
|
||||
defer server.Process.Kill()
|
||||
}()
|
||||
|
||||
// We may need to wait a few miliseconds for the server to startup
|
||||
retryTime := time.Millisecond * 100
|
||||
t := time.NewTimer(retryTime)
|
||||
for server.Process == nil {
|
||||
<-t.C
|
||||
t.Reset(retryTime)
|
||||
log.WithField("path", path).Debug("Timer Reset")
|
||||
}
|
||||
<-t.C
|
||||
log.WithField("path", path).Debug("Timer Reset last")
|
||||
|
||||
cOut, cErr := runClient(path, port)
|
||||
|
||||
log.WithField("Client Output", string(cOut)).Debug("Client returned")
|
||||
|
||||
var sErr bool
|
||||
|
||||
// If the server ever stopped then it errored
|
||||
// If it did not stop, kill it and see if that errors
|
||||
select {
|
||||
case <-serverErrChan:
|
||||
sErr = true
|
||||
default:
|
||||
if server.Process == nil {
|
||||
// This likely means the server never started
|
||||
sErr = true
|
||||
} else {
|
||||
// If the Process is not nil, kill it, clean up our mess
|
||||
err := server.Process.Kill()
|
||||
if err != nil {
|
||||
sErr = true
|
||||
} else {
|
||||
sErr = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Construct a reference to what happened here
|
||||
ref := runReference{
|
||||
path: path,
|
||||
clientErr: cErr,
|
||||
serverErr: sErr,
|
||||
clientOutput: string(cOut),
|
||||
serverOutput: serverOut.String(),
|
||||
}
|
||||
refChan <- ref
|
||||
}
|
||||
|
||||
func runClient(path string, port int) ([]byte, bool) {
|
||||
client := exec.Command(
|
||||
path+RELATIVECLIENTPATH,
|
||||
"-grpc.addr",
|
||||
":"+strconv.Itoa(port),
|
||||
)
|
||||
|
||||
log.Debug("Starting the client!")
|
||||
|
||||
cOut, err := client.CombinedOutput()
|
||||
|
||||
var cErr bool
|
||||
if err != nil {
|
||||
log.WithError(err).Warn()
|
||||
cErr = true
|
||||
} else {
|
||||
cErr = false
|
||||
}
|
||||
|
||||
return cOut, cErr
|
||||
}
|
||||
|
||||
// fileExists checks if a file at the given path exists. Returns true if the
|
||||
// file exists, and false if the file does not exist.
|
||||
func fileExists(path string) bool {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -3,12 +3,234 @@
|
|||
package integration
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTruss(t *testing.T) {
|
||||
allPassed := runIntegrationTests()
|
||||
if !allPassed {
|
||||
t.Fail()
|
||||
func init() {
|
||||
clean := flag.Bool("clean", false, "Remove all generated test files and do nothing else")
|
||||
flag.Parse()
|
||||
if *clean {
|
||||
wd, _ := os.Getwd()
|
||||
servicesDir := wd + "/test_service_definitions"
|
||||
cleanTests(servicesDir)
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
// runReference stores data about a client-server interaction
|
||||
// This data is then used to display output
|
||||
type runReference struct {
|
||||
name string
|
||||
clientErr bool
|
||||
serverErr bool
|
||||
clientOutput string
|
||||
serverOutput string
|
||||
}
|
||||
|
||||
func TestTruss(t *testing.T) {
|
||||
wd, _ := os.Getwd()
|
||||
servicesDir := wd + "/test_service_definitions"
|
||||
|
||||
runRefs := make(chan runReference)
|
||||
runCount := 0
|
||||
|
||||
dirs, _ := ioutil.ReadDir(servicesDir)
|
||||
for i, d := range dirs {
|
||||
// If this item is not a directory skip it
|
||||
if !d.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
// tests will be run on the fullpath to service directoru
|
||||
sDir := servicesDir + "/" + d.Name()
|
||||
|
||||
// Clean up the service directories in each test
|
||||
if fileExists(sDir + "/service") {
|
||||
os.RemoveAll(sDir + "/service")
|
||||
}
|
||||
|
||||
// On port relative to 8082
|
||||
port := 8082 + i
|
||||
|
||||
// Running the integration tests one at a time because truss running on all files at once
|
||||
// seems to slow the system more than distribute the work
|
||||
t.Logf("Running integration test for %v...", d.Name())
|
||||
out, err := truss(sDir)
|
||||
|
||||
// If truss fails, test error and skip communication
|
||||
if err != nil {
|
||||
t.Errorf("Truss generation FAILED - %v\nTruss Output:\n%v", d.Name(), out)
|
||||
continue
|
||||
}
|
||||
|
||||
go runServerAndClient(sDir, port, port+1000, runRefs)
|
||||
runCount++
|
||||
}
|
||||
|
||||
for i := 0; i < runCount; i++ {
|
||||
ref := <-runRefs
|
||||
if ref.clientErr || ref.serverErr {
|
||||
t.Errorf("Communication test FAILED - %v", ref.name)
|
||||
t.Logf("Client Output\n%v", ref.clientOutput)
|
||||
t.Logf("Server Output\n%v", ref.serverOutput)
|
||||
}
|
||||
}
|
||||
|
||||
// If nothing failed, delete the generated files
|
||||
if !t.Failed() {
|
||||
cleanTests(servicesDir)
|
||||
}
|
||||
}
|
||||
|
||||
// truss calls truss on *.proto in path
|
||||
// Truss logs to Stdout when generation passes or fails
|
||||
func truss(path string) (string, error) {
|
||||
var protofiles []string
|
||||
files, err := ioutil.ReadDir(path)
|
||||
for _, f := range files {
|
||||
if f.IsDir() {
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(f.Name(), ".proto") {
|
||||
protofiles = append(protofiles, f.Name())
|
||||
}
|
||||
}
|
||||
|
||||
trussExec := exec.Command(
|
||||
"truss",
|
||||
protofiles...,
|
||||
)
|
||||
trussExec.Dir = path
|
||||
|
||||
out, err := trussExec.CombinedOutput()
|
||||
|
||||
return string(out), err
|
||||
}
|
||||
|
||||
func runServerAndClient(path string, port int, debugPort int, runRefs chan runReference) {
|
||||
// From within a folder with a truss `service`
|
||||
// These are the paths to the compiled binaries
|
||||
const relativeServerPath = "/service/bin/server"
|
||||
|
||||
// Output buffer for the server Stdout and Stderr
|
||||
serverOut := bytes.NewBuffer(nil)
|
||||
// Get the server command ready with the port
|
||||
server := exec.Command(
|
||||
path+relativeServerPath,
|
||||
"-grpc.addr",
|
||||
":"+strconv.Itoa(port),
|
||||
"-debug.addr",
|
||||
":"+strconv.Itoa(debugPort),
|
||||
)
|
||||
|
||||
// Put serverOut to be the writer of data from Stdout and Stderr
|
||||
server.Stdout = serverOut
|
||||
server.Stderr = serverOut
|
||||
|
||||
// Start the server
|
||||
serverErrChan := make(chan error)
|
||||
go func() {
|
||||
err := server.Run()
|
||||
serverErrChan <- err
|
||||
defer server.Process.Kill()
|
||||
}()
|
||||
|
||||
// We may need to wait a few miliseconds for the server to startup
|
||||
retryTime := time.Millisecond * 100
|
||||
t := time.NewTimer(retryTime)
|
||||
for server.Process == nil {
|
||||
<-t.C
|
||||
t.Reset(retryTime)
|
||||
}
|
||||
<-t.C
|
||||
|
||||
cOut, cErr := runClient(path, port)
|
||||
|
||||
var sErr bool
|
||||
|
||||
// If the server ever stopped then it errored
|
||||
// If it did not stop, kill it and see if that errors
|
||||
select {
|
||||
case <-serverErrChan:
|
||||
sErr = true
|
||||
default:
|
||||
if server.Process == nil {
|
||||
// This likely means the server never started
|
||||
sErr = true
|
||||
} else {
|
||||
// If the Process is not nil, kill it, clean up our mess
|
||||
err := server.Process.Kill()
|
||||
if err != nil {
|
||||
sErr = true
|
||||
} else {
|
||||
sErr = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Construct a reference to what happened here
|
||||
ref := runReference{
|
||||
name: filepath.Base(path),
|
||||
clientErr: cErr,
|
||||
serverErr: sErr,
|
||||
clientOutput: string(cOut),
|
||||
serverOutput: serverOut.String(),
|
||||
}
|
||||
|
||||
runRefs <- ref
|
||||
}
|
||||
|
||||
func runClient(path string, port int) ([]byte, bool) {
|
||||
const relativeClientPath = "/service/bin/cliclient"
|
||||
|
||||
client := exec.Command(
|
||||
path+relativeClientPath,
|
||||
"-grpc.addr",
|
||||
":"+strconv.Itoa(port),
|
||||
)
|
||||
|
||||
cOut, err := client.CombinedOutput()
|
||||
|
||||
var cErr bool
|
||||
if err != nil {
|
||||
cErr = true
|
||||
} else {
|
||||
cErr = false
|
||||
}
|
||||
|
||||
return cOut, cErr
|
||||
}
|
||||
|
||||
// fileExists checks if a file at the given path exists. Returns true if the
|
||||
// file exists, and false if the file does not exist.
|
||||
func fileExists(path string) bool {
|
||||
if _, err := os.Stat(path); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func cleanTests(servicesDir string) {
|
||||
// Clean up the service directories in each test
|
||||
dirs, _ := ioutil.ReadDir(servicesDir)
|
||||
for _, d := range dirs {
|
||||
// If this item is not a directory skip it
|
||||
if !d.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
if fileExists(servicesDir + "/" + d.Name() + "/service") {
|
||||
os.RemoveAll(servicesDir + "/" + d.Name() + "/service")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
241
truss/main.go
241
truss/main.go
|
@ -2,61 +2,21 @@ package main
|
|||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
//"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"github.com/TuneLab/gob/truss/generator"
|
||||
|
||||
templates "github.com/TuneLab/gob/truss/template"
|
||||
log "github.com/Sirupsen/logrus"
|
||||
)
|
||||
|
||||
const GENERATED_PATH = "service"
|
||||
const GOOGLE_API_HTTP_IMPORT_PATH = "/service/DONOTEDIT/third_party/googleapis"
|
||||
|
||||
type globalStruct struct {
|
||||
workingDirectory string
|
||||
genImportPath string
|
||||
GOPATH string
|
||||
generatePbGoCmd string
|
||||
generateDocsCmd string
|
||||
generateGoKitCmd string
|
||||
}
|
||||
|
||||
var global globalStruct
|
||||
|
||||
// We build up environment knowledge here
|
||||
// 1. Get working directory
|
||||
// 2. Get $GOPATH
|
||||
// 3. Use 1,2 to build path for golang imports for this package
|
||||
// 4. Build 3 proto commands to invoke
|
||||
func init() {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
log.SetFormatter(&log.TextFormatter{
|
||||
ForceColors: true,
|
||||
})
|
||||
|
||||
var err error
|
||||
global.workingDirectory, err = os.Getwd()
|
||||
if err != nil {
|
||||
log.WithError(err).Fatal("Cannot get working directory")
|
||||
}
|
||||
|
||||
global.GOPATH = os.Getenv("GOPATH")
|
||||
|
||||
// From `$GOPATH/src/org/user/thing` get `org/user/thing` from importing in golang
|
||||
global.genImportPath = strings.TrimPrefix(global.workingDirectory, global.GOPATH+"/src/")
|
||||
|
||||
// Generate grpc golang code
|
||||
global.generatePbGoCmd = "--go_out=Mgoogle/api/annotations.proto=" + global.genImportPath + GOOGLE_API_HTTP_IMPORT_PATH + "/google/api,plugins=grpc:./service/DONOTEDIT/pb"
|
||||
// Generate documentation
|
||||
global.generateDocsCmd = "--truss-doc_out=."
|
||||
// Generate gokit-base service
|
||||
global.generateGoKitCmd = "--truss-gokit_out=."
|
||||
|
||||
}
|
||||
|
||||
// Stages are documented in README.md
|
||||
|
@ -64,175 +24,52 @@ func main() {
|
|||
flag.Parse()
|
||||
|
||||
if len(flag.Args()) == 0 {
|
||||
fmt.Fprintf(os.Stderr, "usage: truss microservice.proto\n")
|
||||
log.Fatal("No proto files passed")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
definitionPaths := flag.Args()
|
||||
rawDefinitionPaths := flag.Args()
|
||||
|
||||
Stage1()
|
||||
execWd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.WithError(err).Fatal("Cannot get working directory")
|
||||
}
|
||||
|
||||
// Stage 2, 3, 4
|
||||
Stage234(definitionPaths)
|
||||
var workingDirectory string
|
||||
var definitionFiles []string
|
||||
|
||||
// Stage 5
|
||||
Stage5()
|
||||
// Parsed passed file paths
|
||||
for _, def := range rawDefinitionPaths {
|
||||
// If the definition file path is not absolute, then make it absolute using trusses working directory
|
||||
if !path.IsAbs(def) {
|
||||
def = path.Clean(def)
|
||||
def = path.Join(execWd, def)
|
||||
}
|
||||
|
||||
}
|
||||
// The working direcotry for this definition file
|
||||
wd := path.Dir(def)
|
||||
// Add the base name of definition file to the slice
|
||||
definitionFiles = append(definitionFiles, path.Base(def))
|
||||
|
||||
func Stage1() {
|
||||
// Stage 1
|
||||
global.buildDirectories()
|
||||
global.outputGoogleImport()
|
||||
}
|
||||
|
||||
func Stage234(definitionPaths []string) {
|
||||
genPbGoDone := make(chan bool)
|
||||
genDocsDone := make(chan bool)
|
||||
genGoKitDone := make(chan bool)
|
||||
go global.protoc(definitionPaths, global.generatePbGoCmd, genPbGoDone)
|
||||
go global.protoc(definitionPaths, global.generateDocsCmd, genDocsDone)
|
||||
go global.protoc(definitionPaths, global.generateGoKitCmd, genGoKitDone)
|
||||
<-genPbGoDone
|
||||
<-genDocsDone
|
||||
<-genGoKitDone
|
||||
}
|
||||
|
||||
func Stage5() {
|
||||
serverDone := make(chan bool)
|
||||
clientDone := make(chan bool)
|
||||
go goBuild("server", "./service/DONOTEDIT/cmd/svc/...", serverDone)
|
||||
go goBuild("cliclient", "./service/DONOTEDIT/cmd/cliclient/...", clientDone)
|
||||
<-serverDone
|
||||
<-clientDone
|
||||
}
|
||||
|
||||
// buildDirectories puts the following directories in place
|
||||
// .
|
||||
// └── service
|
||||
// ├── bin
|
||||
// └── DONOTEDIT
|
||||
// ├── pb
|
||||
// └── third_party
|
||||
// └── googleapis
|
||||
// └── google
|
||||
// └── api
|
||||
func (g globalStruct) buildDirectories() {
|
||||
// third_party created by going through assets in template
|
||||
// and creating directoires that are not there
|
||||
for _, filePath := range templates.AssetNames() {
|
||||
fullPath := g.workingDirectory + "/" + filePath
|
||||
|
||||
dirPath := filepath.Dir(fullPath)
|
||||
|
||||
err := os.MkdirAll(dirPath, 0777)
|
||||
if err != nil {
|
||||
log.WithField("DirPath", dirPath).WithError(err).Fatal("Cannot create directories")
|
||||
// If the working directory has not beenset before set it
|
||||
if workingDirectory == "" {
|
||||
workingDirectory = wd
|
||||
} else {
|
||||
// If the working directory for this definition file is different than the previous
|
||||
if wd != workingDirectory {
|
||||
log.Fatal("Passed protofiles reside in different directories")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create the directory where protoc will store the compiled .pb.go files
|
||||
err := os.MkdirAll("service/DONOTEDIT/pb", 0777)
|
||||
if err != nil {
|
||||
log.WithField("DirPath", "service/DONOTEDIT/pb").WithError(err).Fatal("Cannot create directories")
|
||||
goPath := os.Getenv("GOPATH")
|
||||
|
||||
if !strings.HasPrefix(workingDirectory, goPath) {
|
||||
log.Fatal("truss envoked from outside of $GOPATH")
|
||||
}
|
||||
|
||||
// Create the directory where go build will put the compiled binaries
|
||||
err = os.MkdirAll("service/bin", 0777)
|
||||
if err != nil {
|
||||
log.WithField("DirPath", "service/bin").WithError(err).Fatal("Cannot create directories")
|
||||
}
|
||||
}
|
||||
|
||||
// outputGoogleImport places imported and required google.api.http protobuf option files
|
||||
// into their required directories as part of stage one generation
|
||||
func (g globalStruct) outputGoogleImport() {
|
||||
// Output files that are stored in template package
|
||||
for _, filePath := range templates.AssetNames() {
|
||||
fileBytes, _ := templates.Asset(filePath)
|
||||
fullPath := g.workingDirectory + "/" + filePath
|
||||
|
||||
err := ioutil.WriteFile(fullPath, fileBytes, 0666)
|
||||
if err != nil {
|
||||
log.WithField("FilePath", fullPath).WithError(err).Fatal("Cannot create ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// goBuild calls the `$ go get ` to install dependenices
|
||||
// and then calls `$ go build service/bin/$name $path`
|
||||
// to put the iterating binaries in the correct place
|
||||
func goBuild(name string, path string, done chan bool) {
|
||||
|
||||
goGetExec := exec.Command(
|
||||
"go",
|
||||
"get",
|
||||
"-d",
|
||||
"-v",
|
||||
path,
|
||||
)
|
||||
|
||||
goGetExec.Stderr = os.Stderr
|
||||
|
||||
log.WithField("cmd", strings.Join(goGetExec.Args, " ")).Info("go get")
|
||||
val, err := goGetExec.Output()
|
||||
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"output": string(val),
|
||||
"input": goGetExec.Args,
|
||||
}).WithError(err).Warn("go get failed")
|
||||
}
|
||||
|
||||
goBuildExec := exec.Command(
|
||||
"go",
|
||||
"build",
|
||||
"-o",
|
||||
"service/bin/"+name,
|
||||
path,
|
||||
)
|
||||
//env := os.Environ()
|
||||
//env = append(env, "CGO_ENABLED=0")
|
||||
//goBuildExec.Env = env
|
||||
|
||||
goBuildExec.Stderr = os.Stderr
|
||||
|
||||
log.WithField("cmd", strings.Join(goBuildExec.Args, " ")).Info("go build")
|
||||
val, err = goBuildExec.Output()
|
||||
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"output": string(val),
|
||||
"input": goBuildExec.Args,
|
||||
}).WithError(err).Fatal("go build failed")
|
||||
}
|
||||
done <- true
|
||||
}
|
||||
|
||||
func (g globalStruct) protoc(definitionPaths []string, command string, done chan bool) {
|
||||
cmdArgs := []string{
|
||||
"-I.",
|
||||
"-I" + g.workingDirectory + GOOGLE_API_HTTP_IMPORT_PATH,
|
||||
command,
|
||||
}
|
||||
// Append each definition file path to the end of that command args
|
||||
cmdArgs = append(cmdArgs, definitionPaths...)
|
||||
|
||||
protocExec := exec.Command(
|
||||
"protoc",
|
||||
cmdArgs...,
|
||||
)
|
||||
|
||||
protocExec.Stderr = os.Stderr
|
||||
|
||||
log.WithField("cmd", strings.Join(protocExec.Args, " ")).Info("protoc")
|
||||
val, err := protocExec.Output()
|
||||
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"output": string(val),
|
||||
"input": protocExec.Args,
|
||||
}).WithError(err).Fatal("Protoc call failed")
|
||||
}
|
||||
done <- true
|
||||
// From `$GOPATH/src/org/user/thing` get `org/user/thing` for importing in golang
|
||||
genImportPath := strings.TrimPrefix(workingDirectory, goPath+"/src/")
|
||||
|
||||
generator.GenerateMicroservice(genImportPath, workingDirectory, definitionFiles)
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче