Merge pull request #8 from OpenCHAMI/unit-tests
[WIP] Added initial general unit tests
This commit is contained in:
commit
7bed71fc3e
24 changed files with 468 additions and 64 deletions
7
Makefile
7
Makefile
|
|
@ -1,6 +1,6 @@
|
||||||
|
|
||||||
# build everything at once
|
# build everything at once
|
||||||
all: plugins exe
|
all: plugins exe test
|
||||||
|
|
||||||
# build the main executable to make configs
|
# build the main executable to make configs
|
||||||
main: exe
|
main: exe
|
||||||
|
|
@ -21,8 +21,11 @@ plugins:
|
||||||
go build -buildmode=plugin -o lib/syslog.so internal/generator/plugins/syslog/syslog.go
|
go build -buildmode=plugin -o lib/syslog.so internal/generator/plugins/syslog/syslog.go
|
||||||
go build -buildmode=plugin -o lib/warewulf.so internal/generator/plugins/warewulf/warewulf.go
|
go build -buildmode=plugin -o lib/warewulf.so internal/generator/plugins/warewulf/warewulf.go
|
||||||
|
|
||||||
# remove executable and all plugins
|
# remove executable and all built plugins
|
||||||
clean:
|
clean:
|
||||||
rm configurator
|
rm configurator
|
||||||
rm lib/*
|
rm lib/*
|
||||||
|
|
||||||
|
# run all of the unit tests
|
||||||
|
test:
|
||||||
|
go test ./tests/generate_test.go --tags=all
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,8 @@ import (
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
configurator "github.com/OpenCHAMI/configurator/internal"
|
configurator "github.com/OpenCHAMI/configurator/pkg"
|
||||||
"github.com/OpenCHAMI/configurator/internal/util"
|
"github.com/OpenCHAMI/configurator/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
var configCmd = &cobra.Command{
|
var configCmd = &cobra.Command{
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"maps"
|
"maps"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/OpenCHAMI/configurator/internal/generator"
|
"github.com/OpenCHAMI/configurator/pkg/generator"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
configurator "github.com/OpenCHAMI/configurator/internal"
|
configurator "github.com/OpenCHAMI/configurator/pkg"
|
||||||
"github.com/OpenCHAMI/configurator/internal/util"
|
"github.com/OpenCHAMI/configurator/pkg/util"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
package main
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
// TODO: implement a way to fetch schemas from node orchestrator
|
|
||||||
package configurator
|
|
||||||
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/OpenCHAMI/configurator/internal/util"
|
"github.com/OpenCHAMI/configurator/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClientOption func(*SmdClient)
|
type ClientOption func(*SmdClient)
|
||||||
|
|
@ -11,9 +11,9 @@ import (
|
||||||
type Options struct{}
|
type Options struct{}
|
||||||
|
|
||||||
type Target struct {
|
type Target struct {
|
||||||
Templates []string `yaml:"templates,omitempty"`
|
TemplatePaths []string `yaml:"templates,omitempty"`
|
||||||
FilePaths []string `yaml:"files,omitempty"`
|
FilePaths []string `yaml:"files,omitempty"`
|
||||||
RunTargets []string `yaml:"targets,omitempty"`
|
RunTargets []string `yaml:"targets,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Jwks struct {
|
type Jwks struct {
|
||||||
|
|
@ -48,13 +48,13 @@ func NewConfig() Config {
|
||||||
},
|
},
|
||||||
Targets: map[string]Target{
|
Targets: map[string]Target{
|
||||||
"dnsmasq": Target{
|
"dnsmasq": Target{
|
||||||
Templates: []string{},
|
TemplatePaths: []string{},
|
||||||
},
|
},
|
||||||
"conman": Target{
|
"conman": Target{
|
||||||
Templates: []string{},
|
TemplatePaths: []string{},
|
||||||
},
|
},
|
||||||
"warewulf": Target{
|
"warewulf": Target{
|
||||||
Templates: []string{
|
TemplatePaths: []string{
|
||||||
"templates/warewulf/defaults/node.jinja",
|
"templates/warewulf/defaults/node.jinja",
|
||||||
"templates/warewulf/defaults/provision.jinja",
|
"templates/warewulf/defaults/provision.jinja",
|
||||||
},
|
},
|
||||||
|
|
@ -8,11 +8,10 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"plugin"
|
"plugin"
|
||||||
|
|
||||||
configurator "github.com/OpenCHAMI/configurator/internal"
|
configurator "github.com/OpenCHAMI/configurator/pkg"
|
||||||
"github.com/OpenCHAMI/configurator/internal/util"
|
"github.com/OpenCHAMI/configurator/pkg/util"
|
||||||
"github.com/nikolalohinski/gonja/v2"
|
"github.com/nikolalohinski/gonja/v2"
|
||||||
"github.com/nikolalohinski/gonja/v2/exec"
|
"github.com/nikolalohinski/gonja/v2/exec"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Mappings map[string]any
|
type Mappings map[string]any
|
||||||
|
|
@ -32,6 +31,7 @@ type Generator interface {
|
||||||
type Params struct {
|
type Params struct {
|
||||||
Args []string
|
Args []string
|
||||||
PluginPaths []string
|
PluginPaths []string
|
||||||
|
Generators map[string]Generator
|
||||||
Target string
|
Target string
|
||||||
Verbose bool
|
Verbose bool
|
||||||
}
|
}
|
||||||
|
|
@ -259,14 +259,14 @@ func ApplyTemplateFromFiles(mappings Mappings, paths ...string) (FileMap, error)
|
||||||
|
|
||||||
// Main function to generate a collection of files as a map with the path as the key and
|
// Main function to generate a collection of files as a map with the path as the key and
|
||||||
// the contents of the file as the value. This function currently expects a list of plugin
|
// the contents of the file as the value. This function currently expects a list of plugin
|
||||||
// paths to load all plugins within a directory. Then, each plugin's generator.Generate()
|
// paths to load all plugins within a directory. Then, each plugin's generator.GenerateWithTarget()
|
||||||
// function is called for each target specified.
|
// function is called for each target specified.
|
||||||
//
|
//
|
||||||
// This function is the corresponding implementation for the "generate" CLI subcommand.
|
// This function is the corresponding implementation for the "generate" CLI subcommand.
|
||||||
// It is also call when running the configurator as a service with the "/generate" route.
|
// It is also call when running the configurator as a service with the "/generate" route.
|
||||||
//
|
//
|
||||||
// TODO: Separate loading plugins so we can load them once when running as a service.
|
// TODO: Separate loading plugins so we can load them once when running as a service.
|
||||||
func Generate(config *configurator.Config, params Params) (FileMap, error) {
|
func GenerateWithTarget(config *configurator.Config, params Params) (FileMap, error) {
|
||||||
// load generator plugins to generate configs or to print
|
// load generator plugins to generate configs or to print
|
||||||
var (
|
var (
|
||||||
generators = make(map[string]Generator)
|
generators = make(map[string]Generator)
|
||||||
|
|
@ -278,12 +278,12 @@ func Generate(config *configurator.Config, params Params) (FileMap, error) {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// load all plugins from params
|
// load all plugins from supplied arguments
|
||||||
for _, path := range params.PluginPaths {
|
for _, path := range params.PluginPaths {
|
||||||
if params.Verbose {
|
if params.Verbose {
|
||||||
fmt.Printf("loading plugins from '%s'\n", path)
|
fmt.Printf("loading plugins from '%s'\n", path)
|
||||||
}
|
}
|
||||||
gens, err := LoadPlugins(path)
|
plugins, err := LoadPlugins(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("failed to load plugins: %v\n", err)
|
fmt.Printf("failed to load plugins: %v\n", err)
|
||||||
err = nil
|
err = nil
|
||||||
|
|
@ -291,9 +291,12 @@ func Generate(config *configurator.Config, params Params) (FileMap, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// add loaded generator plugins to set
|
// add loaded generator plugins to set
|
||||||
maps.Copy(generators, gens)
|
maps.Copy(generators, plugins)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copy all generators supplied from arguments
|
||||||
|
maps.Copy(generators, params.Generators)
|
||||||
|
|
||||||
// show available targets then exit
|
// show available targets then exit
|
||||||
if len(params.Args) == 0 && params.Target == "" {
|
if len(params.Args) == 0 && params.Target == "" {
|
||||||
for g := range generators {
|
for g := range generators {
|
||||||
|
|
@ -302,19 +305,14 @@ func Generate(config *configurator.Config, params Params) (FileMap, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if params.Target == "" {
|
// run the generator plugin from target passed
|
||||||
logrus.Errorf("no target supplied (--target name)")
|
gen := generators[params.Target]
|
||||||
} else {
|
if gen == nil {
|
||||||
// run the generator plugin from target passed
|
return nil, fmt.Errorf("invalid generator target (%s)", params.Target)
|
||||||
gen := generators[params.Target]
|
|
||||||
if gen == nil {
|
|
||||||
return nil, fmt.Errorf("invalid generator target (%s)", params.Target)
|
|
||||||
}
|
|
||||||
return gen.Generate(
|
|
||||||
config,
|
|
||||||
WithTarget(gen.GetName()),
|
|
||||||
WithClient(client),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("an unknown error has occurred")
|
return gen.Generate(
|
||||||
|
config,
|
||||||
|
WithTarget(gen.GetName()),
|
||||||
|
WithClient(client),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -3,9 +3,9 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
configurator "github.com/OpenCHAMI/configurator/internal"
|
configurator "github.com/OpenCHAMI/configurator/pkg"
|
||||||
"github.com/OpenCHAMI/configurator/internal/generator"
|
"github.com/OpenCHAMI/configurator/pkg/generator"
|
||||||
"github.com/OpenCHAMI/configurator/internal/util"
|
"github.com/OpenCHAMI/configurator/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Conman struct{}
|
type Conman struct{}
|
||||||
|
|
@ -62,7 +62,7 @@ func (g *Conman) Generate(config *configurator.Config, opts ...util.Option) (gen
|
||||||
"plugin_description": g.GetDescription(),
|
"plugin_description": g.GetDescription(),
|
||||||
"server_opts": "",
|
"server_opts": "",
|
||||||
"global_opts": "",
|
"global_opts": "",
|
||||||
}, target.Templates...)
|
}, target.TemplatePaths...)
|
||||||
}
|
}
|
||||||
|
|
||||||
var Generator Conman
|
var Generator Conman
|
||||||
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
configurator "github.com/OpenCHAMI/configurator/internal"
|
configurator "github.com/OpenCHAMI/configurator/pkg"
|
||||||
"github.com/OpenCHAMI/configurator/internal/generator"
|
"github.com/OpenCHAMI/configurator/pkg/generator"
|
||||||
"github.com/OpenCHAMI/configurator/internal/util"
|
"github.com/OpenCHAMI/configurator/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DnsMasq struct{}
|
type DnsMasq struct{}
|
||||||
|
|
@ -58,7 +58,7 @@ func (g *DnsMasq) Generate(config *configurator.Config, opts ...util.Option) (ge
|
||||||
// print message if verbose param found
|
// print message if verbose param found
|
||||||
if verbose, ok := params["verbose"].(bool); ok {
|
if verbose, ok := params["verbose"].(bool); ok {
|
||||||
if verbose {
|
if verbose {
|
||||||
fmt.Printf("template: \n%s\nethernet interfaces found: %v\n", strings.Join(target.Templates, "\n\t"), len(eths))
|
fmt.Printf("template: \n%s\nethernet interfaces found: %v\n", strings.Join(target.TemplatePaths, "\n\t"), len(eths))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,7 +79,7 @@ func (g *DnsMasq) Generate(config *configurator.Config, opts ...util.Option) (ge
|
||||||
"plugin_version": g.GetVersion(),
|
"plugin_version": g.GetVersion(),
|
||||||
"plugin_description": g.GetDescription(),
|
"plugin_description": g.GetDescription(),
|
||||||
"dhcp-hosts": output,
|
"dhcp-hosts": output,
|
||||||
}, target.Templates...)
|
}, target.TemplatePaths...)
|
||||||
}
|
}
|
||||||
|
|
||||||
var Generator DnsMasq
|
var Generator DnsMasq
|
||||||
|
|
@ -5,9 +5,9 @@ import (
|
||||||
"maps"
|
"maps"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
configurator "github.com/OpenCHAMI/configurator/internal"
|
configurator "github.com/OpenCHAMI/configurator/pkg"
|
||||||
"github.com/OpenCHAMI/configurator/internal/generator"
|
"github.com/OpenCHAMI/configurator/pkg/generator"
|
||||||
"github.com/OpenCHAMI/configurator/internal/util"
|
"github.com/OpenCHAMI/configurator/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Warewulf struct{}
|
type Warewulf struct{}
|
||||||
|
|
@ -30,7 +30,7 @@ func (g *Warewulf) Generate(config *configurator.Config, opts ...util.Option) (g
|
||||||
client = generator.GetClient(params)
|
client = generator.GetClient(params)
|
||||||
targetKey = params["target"].(string)
|
targetKey = params["target"].(string)
|
||||||
target = config.Targets[targetKey]
|
target = config.Targets[targetKey]
|
||||||
outputs = make(generator.FileMap, len(target.FilePaths)+len(target.Templates))
|
outputs = make(generator.FileMap, len(target.FilePaths)+len(target.TemplatePaths))
|
||||||
)
|
)
|
||||||
|
|
||||||
// check if our client is included and is valid
|
// check if our client is included and is valid
|
||||||
|
|
@ -55,7 +55,7 @@ func (g *Warewulf) Generate(config *configurator.Config, opts ...util.Option) (g
|
||||||
// print message if verbose param found
|
// print message if verbose param found
|
||||||
if verbose, ok := params["verbose"].(bool); ok {
|
if verbose, ok := params["verbose"].(bool); ok {
|
||||||
if verbose {
|
if verbose {
|
||||||
fmt.Printf("template: \n%s\n ethernet interfaces found: %v\n", strings.Join(target.Templates, "\n\t"), len(eths))
|
fmt.Printf("template: \n%s\n ethernet interfaces found: %v\n", strings.Join(target.TemplatePaths, "\n\t"), len(eths))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,7 +78,7 @@ func (g *Warewulf) Generate(config *configurator.Config, opts ...util.Option) (g
|
||||||
}
|
}
|
||||||
templates, err := generator.ApplyTemplateFromFiles(generator.Mappings{
|
templates, err := generator.ApplyTemplateFromFiles(generator.Mappings{
|
||||||
"node_entries": nodeEntries,
|
"node_entries": nodeEntries,
|
||||||
}, target.Templates...)
|
}, target.TemplatePaths...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load templates: %v", err)
|
return nil, fmt.Errorf("failed to load templates: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -9,8 +9,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
configurator "github.com/OpenCHAMI/configurator/internal"
|
configurator "github.com/OpenCHAMI/configurator/pkg"
|
||||||
"github.com/OpenCHAMI/configurator/internal/generator"
|
"github.com/OpenCHAMI/configurator/pkg/generator"
|
||||||
"github.com/OpenCHAMI/jwtauth/v5"
|
"github.com/OpenCHAMI/jwtauth/v5"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
|
@ -108,6 +108,10 @@ func (s *Server) Serve() error {
|
||||||
return s.ListenAndServe()
|
return s.ListenAndServe()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) Close() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// This is the corresponding service function to generate templated files, that
|
// This is the corresponding service function to generate templated files, that
|
||||||
// works similarly to the CLI variant. This function takes similiar arguments as
|
// works similarly to the CLI variant. This function takes similiar arguments as
|
||||||
// query parameters that are included in the HTTP request URL.
|
// query parameters that are included in the HTTP request URL.
|
||||||
|
|
@ -115,14 +119,14 @@ func (s *Server) Generate(w http.ResponseWriter, r *http.Request) {
|
||||||
// get all of the expect query URL params and validate
|
// get all of the expect query URL params and validate
|
||||||
s.GeneratorParams.Target = r.URL.Query().Get("target")
|
s.GeneratorParams.Target = r.URL.Query().Get("target")
|
||||||
if s.GeneratorParams.Target == "" {
|
if s.GeneratorParams.Target == "" {
|
||||||
writeError(w, "no targets supplied")
|
writeErrorResponse(w, "no targets supplied")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate a new config file from supplied params
|
// generate a new config file from supplied params
|
||||||
outputs, err := generator.Generate(s.Config, s.GeneratorParams)
|
outputs, err := generator.GenerateWithTarget(s.Config, s.GeneratorParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(w, "failed to generate config: %v", err)
|
writeErrorResponse(w, "failed to generate config: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,12 +134,12 @@ func (s *Server) Generate(w http.ResponseWriter, r *http.Request) {
|
||||||
tmp := generator.ConvertContentsToString(outputs)
|
tmp := generator.ConvertContentsToString(outputs)
|
||||||
b, err := json.Marshal(tmp)
|
b, err := json.Marshal(tmp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(w, "failed to marshal output: %v", err)
|
writeErrorResponse(w, "failed to marshal output: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, err = w.Write(b)
|
_, err = w.Write(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(w, "failed to write response: %v", err)
|
writeErrorResponse(w, "failed to write response: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -147,15 +151,15 @@ func (s *Server) Generate(w http.ResponseWriter, r *http.Request) {
|
||||||
func (s *Server) ManageTemplates(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) ManageTemplates(w http.ResponseWriter, r *http.Request) {
|
||||||
_, err := w.Write([]byte("this is not implemented yet"))
|
_, err := w.Write([]byte("this is not implemented yet"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeError(w, "failed to write response: %v", err)
|
writeErrorResponse(w, "failed to write response: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wrapper function to simplify writting error message responses. This function
|
// Wrapper function to simplify writting error message responses. This function
|
||||||
// is only intended to be used with the service and nothing else.
|
// is only intended to be used with the service and nothing else.
|
||||||
func writeError(w http.ResponseWriter, format string, a ...any) {
|
func writeErrorResponse(w http.ResponseWriter, format string, a ...any) error {
|
||||||
errmsg := fmt.Sprintf(format, a...)
|
errmsg := fmt.Sprintf(format, a...)
|
||||||
fmt.Printf(errmsg)
|
|
||||||
w.Write([]byte(errmsg))
|
w.Write([]byte(errmsg))
|
||||||
|
return fmt.Errorf(errmsg)
|
||||||
}
|
}
|
||||||
402
tests/generate_test.go
Normal file
402
tests/generate_test.go
Normal file
|
|
@ -0,0 +1,402 @@
|
||||||
|
package tests
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
configurator "github.com/OpenCHAMI/configurator/pkg"
|
||||||
|
"github.com/OpenCHAMI/configurator/pkg/generator"
|
||||||
|
"github.com/OpenCHAMI/configurator/pkg/server"
|
||||||
|
"github.com/OpenCHAMI/configurator/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// A valid test generator that implements the `Generator` interface.
|
||||||
|
type TestGenerator struct{}
|
||||||
|
|
||||||
|
func (g *TestGenerator) GetName() string { return "test" }
|
||||||
|
func (g *TestGenerator) GetVersion() string { return "v1.0.0" }
|
||||||
|
func (g *TestGenerator) GetDescription() string {
|
||||||
|
return "This is a plugin created for running tests."
|
||||||
|
}
|
||||||
|
func (g *TestGenerator) Generate(config *configurator.Config, opts ...util.Option) (generator.FileMap, error) {
|
||||||
|
// Jinja 2 template file
|
||||||
|
files := [][]byte{
|
||||||
|
[]byte(`
|
||||||
|
Name: {{plugin_name}}
|
||||||
|
Version: {{plugin_version}}
|
||||||
|
Description: {{plugin_description}}
|
||||||
|
|
||||||
|
This is the first test template file.
|
||||||
|
`),
|
||||||
|
[]byte(`
|
||||||
|
This is another testing Jinja 2 template file using {{plugin_name}}.
|
||||||
|
`),
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply Jinja templates to file
|
||||||
|
fileList, err := generator.ApplyTemplates(generator.Mappings{
|
||||||
|
"plugin_name": g.GetName(),
|
||||||
|
"plugin_version": g.GetVersion(),
|
||||||
|
"plugin_description": g.GetDescription(),
|
||||||
|
}, files...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to apply templates: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we're able to receive certain arguments when passed
|
||||||
|
params := generator.GetParams(opts...)
|
||||||
|
if len(params) <= 0 {
|
||||||
|
return nil, fmt.Errorf("expect at least one params, but found none")
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we have a valid config we can access
|
||||||
|
if config == nil {
|
||||||
|
return nil, fmt.Errorf("invalid config (config is nil)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure we're able to get a valid client as well
|
||||||
|
client := generator.GetClient(params)
|
||||||
|
if client == nil {
|
||||||
|
return nil, fmt.Errorf("invalid client (client is nil)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: make sure we can get a target
|
||||||
|
|
||||||
|
// make sure we have the same number of files in file list
|
||||||
|
if len(files) != len(fileList) {
|
||||||
|
return nil, fmt.Errorf("file list output count is not the same as the input")
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert file list to file map
|
||||||
|
fileMap := make(generator.FileMap, len(fileList))
|
||||||
|
for i, contents := range fileList {
|
||||||
|
fileMap[fmt.Sprintf("t-%d.txt", i)] = contents
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test building and loading plugins
|
||||||
|
func TestPlugin(t *testing.T) {
|
||||||
|
var (
|
||||||
|
testPluginDir = t.TempDir()
|
||||||
|
testPluginPath = fmt.Sprintf("%s/test-plugin.so", testPluginDir)
|
||||||
|
testPluginSourcePath = fmt.Sprintf("%s/test-plugin.go", testPluginDir)
|
||||||
|
testPluginSource = []byte(`
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
configurator "github.com/OpenCHAMI/configurator/pkg"
|
||||||
|
"github.com/OpenCHAMI/configurator/pkg/generator"
|
||||||
|
"github.com/OpenCHAMI/configurator/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TestGenerator struct{}
|
||||||
|
|
||||||
|
func (g *TestGenerator) GetName() string { return "test" }
|
||||||
|
func (g *TestGenerator) GetVersion() string { return "v1.0.0" }
|
||||||
|
func (g *TestGenerator) GetDescription() string { return "This is a plugin creating for running tests." }
|
||||||
|
func (g *TestGenerator) Generate(config *configurator.Config, opts ...util.Option) (generator.FileMap, error) {
|
||||||
|
return generator.FileMap{"test": []byte("test")}, nil
|
||||||
|
}
|
||||||
|
var Generator TestGenerator
|
||||||
|
`)
|
||||||
|
)
|
||||||
|
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to get working directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// show all paths to make sure we're using the correct ones
|
||||||
|
fmt.Printf("(TestPlugin) working directory: %v\n", wd)
|
||||||
|
fmt.Printf("(TestPlugin) plugin directory: %v\n", testPluginDir)
|
||||||
|
fmt.Printf("(TestPlugin) plugin path: %v\n", testPluginPath)
|
||||||
|
fmt.Printf("(TestPlugin) plugin source path: %v\n", testPluginSourcePath)
|
||||||
|
|
||||||
|
// make temporary directory to test plugin
|
||||||
|
err = os.MkdirAll(testPluginDir, os.ModeDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to make temporary directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dump the plugin source code to a file
|
||||||
|
err = os.WriteFile(testPluginSourcePath, testPluginSource, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to write test plugin file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the source file was actually written
|
||||||
|
fileInfo, err := os.Stat(testPluginSourcePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to stat path: %v", err)
|
||||||
|
}
|
||||||
|
if fileInfo.IsDir() {
|
||||||
|
t.Fatalf("expected file but found directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
// change to testing directory to run command
|
||||||
|
err = os.Chdir(testPluginDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to 'cd' to temporary directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute command to build the plugin
|
||||||
|
cmd := exec.Command("go", "build", "-buildmode=plugin", fmt.Sprintf("-o=%s", testPluginPath), testPluginSourcePath)
|
||||||
|
if output, err := cmd.Output(); err != nil {
|
||||||
|
t.Fatalf("failed to execute command: %v\n%s", err, string(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
// stat the file to confirm that the plugin was built
|
||||||
|
fileInfo, err = os.Stat(testPluginPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to stat plugin file: %v", err)
|
||||||
|
}
|
||||||
|
if fileInfo.IsDir() {
|
||||||
|
t.Fatalf("directory file but a file was expected")
|
||||||
|
}
|
||||||
|
if fileInfo.Size() <= 0 {
|
||||||
|
t.Fatal("found an empty file or file with size of 0 bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test loading plugins both individually and in a dir
|
||||||
|
gen, err := generator.LoadPlugin(testPluginSourcePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load the test plugin: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test that we have all expected methods with type assertions
|
||||||
|
if _, ok := gen.(interface {
|
||||||
|
GetName() string
|
||||||
|
GetVersion() string
|
||||||
|
GetDescription() string
|
||||||
|
Generate(*configurator.Config, ...util.Option) (generator.FileMap, error)
|
||||||
|
}); !ok {
|
||||||
|
t.Error("plugin does not implement all of the generator interface")
|
||||||
|
}
|
||||||
|
|
||||||
|
// test loading plugins from a directory (should just load a single one)
|
||||||
|
gens, err := generator.LoadPlugins(testPluginDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to load plugins in '%s': %v", testPluginDir, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test all of the plugins loaded from a directory (should expect same result as above)
|
||||||
|
for _, gen := range gens {
|
||||||
|
if _, ok := gen.(interface {
|
||||||
|
GetName() string
|
||||||
|
GetVersion() string
|
||||||
|
GetDescription() string
|
||||||
|
Generate(*configurator.Config, ...util.Option) (generator.FileMap, error)
|
||||||
|
}); !ok {
|
||||||
|
t.Error("plugin does not implement all of the generator interface")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that expects to fail with a specific error using a partially
|
||||||
|
// implemented generator. The purpose of this test is to make sure we're
|
||||||
|
// seeing the correct error that we would expect in these situations.
|
||||||
|
// The errors should be something like:
|
||||||
|
// - no symbol: "failed to look up symbol at path"
|
||||||
|
// - invalid symbol: "failed to load the correct symbol type at path"
|
||||||
|
func TestPluginWithInvalidOrNoSymbol(t *testing.T) {
|
||||||
|
var (
|
||||||
|
testPluginDir = t.TempDir()
|
||||||
|
testPluginPath = fmt.Sprintf("%s/invalid-plugin.so", testPluginDir)
|
||||||
|
testPluginSourcePath = fmt.Sprintf("%s/invalid-plugin.go", testPluginDir)
|
||||||
|
testPluginSource = []byte(`
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
configurator "github.com/OpenCHAMI/configurator/pkg"
|
||||||
|
"github.com/OpenCHAMI/configurator/pkg/generator"
|
||||||
|
"github.com/OpenCHAMI/configurator/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An invalid generator that does not or partially implements
|
||||||
|
// the "Generator" interface.
|
||||||
|
type InvalidGenerator struct{}
|
||||||
|
var Generator TestGenerator
|
||||||
|
`)
|
||||||
|
)
|
||||||
|
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to get working directory: %v", err)
|
||||||
|
}
|
||||||
|
// show all paths to make sure we're using the correct ones
|
||||||
|
fmt.Printf("(TestPluginWithInvalidOrNoSymbol) working directory: %v\n", wd)
|
||||||
|
fmt.Printf("(TestPluginWithInvalidOrNoSymbol) plugin directory: %v\n", testPluginDir)
|
||||||
|
fmt.Printf("(TestPluginWithInvalidOrNoSymbol) plugin path: %v\n", testPluginPath)
|
||||||
|
fmt.Printf("(TestPluginWithInvalidOrNoSymbol) plugin source path: %v\n", testPluginSourcePath)
|
||||||
|
|
||||||
|
// make temporary directory to test plugin
|
||||||
|
err = os.MkdirAll(testPluginDir, os.ModeDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to make temporary directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dump the plugin source code to a file
|
||||||
|
err = os.WriteFile(testPluginSourcePath, testPluginSource, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to write test plugin file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the source file was actually written
|
||||||
|
fileInfo, err := os.Stat(testPluginSourcePath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to stat path: %v", err)
|
||||||
|
}
|
||||||
|
if fileInfo.IsDir() {
|
||||||
|
t.Fatalf("expected file but found directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
// change to testing directory to run command
|
||||||
|
err = os.Chdir(testPluginDir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to 'cd' to temporary directory: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute command to build the plugin
|
||||||
|
cmd := exec.Command("go", "build", "-buildmode=plugin", fmt.Sprintf("-o=%s", testPluginPath), testPluginSourcePath)
|
||||||
|
if output, err := cmd.Output(); err != nil {
|
||||||
|
t.Fatalf("failed to execute command: %v\n%s", err, string(output))
|
||||||
|
}
|
||||||
|
|
||||||
|
// stat the file to confirm that the plugin was built
|
||||||
|
fileInfo, err = os.Stat(testPluginPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to stat plugin file: %v", err)
|
||||||
|
}
|
||||||
|
if fileInfo.IsDir() {
|
||||||
|
t.Fatalf("directory file but a file was expected")
|
||||||
|
}
|
||||||
|
if fileInfo.Size() <= 0 {
|
||||||
|
t.Fatal("found an empty file or file with size of 0 bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
// try and load plugin, but expect specific error
|
||||||
|
_, err = generator.LoadPlugin(testPluginSourcePath)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected an error, but returned nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that expects to successfully "generate" a file using the built-in
|
||||||
|
// example plugin with no fetching.
|
||||||
|
//
|
||||||
|
// NOTE: Normally we would dynamically load a generator from a plugin, but
|
||||||
|
// we're not doing it here since that's not what is being tested.
|
||||||
|
func TestGenerateExample(t *testing.T) {
|
||||||
|
var (
|
||||||
|
config = configurator.NewConfig()
|
||||||
|
client = configurator.NewSmdClient()
|
||||||
|
gen = TestGenerator{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// make sure our generator returns expected strings
|
||||||
|
t.Run("properties", func(t *testing.T) {
|
||||||
|
if gen.GetName() != "test" {
|
||||||
|
t.Error("test generator return unexpected name")
|
||||||
|
}
|
||||||
|
if gen.GetVersion() != "v1.0.0" {
|
||||||
|
t.Error("test generator return unexpected version")
|
||||||
|
}
|
||||||
|
if gen.GetDescription() != "This is a plugin creating for running tests." {
|
||||||
|
t.Error("test generator return unexpected description")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// try to generate a file with templating applied
|
||||||
|
fileMap, err := gen.Generate(
|
||||||
|
&config,
|
||||||
|
generator.WithTarget("test"),
|
||||||
|
generator.WithClient(client),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to generate file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test for 2 expected files to be generated in the output (hint: check the
|
||||||
|
// TestGenerator.Generate implementation)
|
||||||
|
if len(fileMap) != 2 {
|
||||||
|
t.Error("expected 2 files in generated output")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test that expects to successfully "generate" a file using the built-in
|
||||||
|
// example plugin but by making a HTTP request to a service instance instead.
|
||||||
|
//
|
||||||
|
// NOTE: This test uses the default server settings to run. Also, no need to
|
||||||
|
// try and load the plugin from a lib here either.
|
||||||
|
func TestGenerateExampleWithServer(t *testing.T) {
|
||||||
|
var (
|
||||||
|
config = configurator.NewConfig()
|
||||||
|
client = configurator.NewSmdClient()
|
||||||
|
gen = TestGenerator{}
|
||||||
|
headers = make(map[string]string, 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
// NOTE: Currently, the server needs a config to know where to get load plugins,
|
||||||
|
// and how to handle targets/templates. This will be simplified in the future to
|
||||||
|
// decoupled the server from required a config altogether.
|
||||||
|
config.Targets["test"] = configurator.Target{
|
||||||
|
TemplatePaths: []string{},
|
||||||
|
FilePaths: []string{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// show which targets are availabe in the config
|
||||||
|
fmt.Printf("targets:\n")
|
||||||
|
for target, _ := range config.Targets {
|
||||||
|
fmt.Printf("\t- %s\n", target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// create new server, add test generator, and start in background
|
||||||
|
server := server.New(&config)
|
||||||
|
server.GeneratorParams.Generators = map[string]generator.Generator{
|
||||||
|
"test": &gen,
|
||||||
|
}
|
||||||
|
go server.Serve()
|
||||||
|
|
||||||
|
// make request to server to generate a file
|
||||||
|
res, b, err := util.MakeRequest("http://127.0.0.1:3334/generate?target=test", http.MethodGet, nil, headers)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to make request: %v", err)
|
||||||
|
}
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
t.Fatalf("expect status code 200 from response but received %d instead", res.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// test for specific output from request
|
||||||
|
//
|
||||||
|
// NOTE: we don't actually use the config in this plugin implementation,
|
||||||
|
// but we do check that a valid config was passed.
|
||||||
|
fileMap, err := gen.Generate(
|
||||||
|
&config,
|
||||||
|
generator.WithClient(client),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to generate file: %v", err)
|
||||||
|
}
|
||||||
|
for path, contents := range fileMap {
|
||||||
|
tmp := make(map[string]string, 1)
|
||||||
|
err := json.Unmarshal(b, &tmp)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed to unmarshal response: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if string(contents) != string(tmp[path]) {
|
||||||
|
t.Fatalf("response does not match expected output...\nexpected:%s\noutput:%s", string(contents), string(tmp[path]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue