Merge pull request #441 from sparkprime/expandy_tests

Write some new unit tests for expandybird
pull/445/head
Dave Cunningham 9 years ago
commit e65b6beed3

@ -58,6 +58,11 @@ func (e *expander) ExpandChart(request *common.ExpansionRequest) (*common.Expans
chartInv := request.ChartInvocation
chartFile := request.Chart.Chartfile
chartMembers := request.Chart.Members
if chartInv.Type != chartFile.Name {
return nil, fmt.Errorf("Request chart invocation does not match provided chart")
}
schemaName := chartInv.Type + ".schema"
if chartFile.Expander == nil {
@ -89,7 +94,7 @@ func (e *expander) ExpandChart(request *common.ExpansionRequest) (*common.Expans
message := fmt.Sprintf("The entrypoint in the chart.yaml cannot be found: %s", chartFile.Expander.Entrypoint)
return nil, fmt.Errorf("%s: %s", chartInv.Name, message)
}
if schemaIndex == -1 {
if chartFile.Schema != "" && schemaIndex == -1 {
message := fmt.Sprintf("The schema in the chart.yaml cannot be found: %s", chartFile.Schema)
return nil, fmt.Errorf("%s: %s", chartInv.Name, message)
}

@ -16,168 +16,691 @@ limitations under the License.
package expander
/*
import (
"fmt"
"io"
"io/ioutil"
"os"
"path"
"reflect"
"runtime"
"strings"
"testing"
"github.com/kubernetes/helm/pkg/chart"
"github.com/kubernetes/helm/pkg/common"
"github.com/kubernetes/helm/pkg/util"
)
var importFileNames = []string{
"../test/replicatedservice.py",
}
var validFileName = "../test/ValidContent.yaml"
var outputFileName = "../test/ExpectedOutput.yaml"
var archiveFileName = "../test/TestArchive.tar"
var expanderName = "../../../expansion/expansion.py"
type ExpanderTestCase struct {
type testCase struct {
Description string
TemplateFileName string
ImportFileNames []string
Request *common.ExpansionRequest
ExpectedResponse *common.ExpansionResponse
ExpectedError string
}
func (etc *ExpanderTestCase) GetTemplate(t *testing.T) *common.Template {
template, err := util.NewTemplateFromFileNames(etc.TemplateFileName, etc.ImportFileNames)
if err != nil {
t.Fatalf("cannot create template for test case '%s': %s", etc.Description, err)
// content provides an easy way to provide file content verbatim in tests.
func content(lines []string) []byte {
return []byte(strings.Join(lines, "\n") + "\n")
}
return template
// funcName returns the name of the calling function.
func funcName() string {
pc, _, _, _ := runtime.Caller(1)
return runtime.FuncForPC(pc).Name()
}
func GetOutputString(t *testing.T, description string) string {
output, err := ioutil.ReadFile(outputFileName)
func testExpansion(t *testing.T, req *common.ExpansionRequest,
expResponse *common.ExpansionResponse, expError string) {
backend := NewExpander(expanderName)
response, err := backend.ExpandChart(req)
if err != nil {
t.Fatalf("cannot read output file for test case '%s': %s", description, err)
message := err.Error()
if expResponse != nil || !strings.Contains(message, expError) {
t.Fatalf("unexpected error: %s\n", message)
}
} else {
if expResponse == nil {
t.Fatalf("expected error did not occur: %s\n", expError)
}
if !reflect.DeepEqual(response, expResponse) {
message := fmt.Sprintf(
"want:\n%s\nhave:\n%s\n", expResponse, response)
t.Fatalf("output mismatch:\n%s\n", message)
}
}
return string(output)
}
func expandAndVerifyOutput(t *testing.T, actualOutput, description string) {
actualResult, err := NewExpansionResult(actualOutput)
if err != nil {
t.Fatalf("error in test case '%s': %s\n", description, err)
var pyExpander = &chart.Expander{
Name: "ExpandyBird",
Entrypoint: "templates/main.py",
}
expectedOutput := GetOutputString(t, description)
expectedResult, err := NewExpansionResult(expectedOutput)
if err != nil {
t.Fatalf("error in test case '%s': %s\n", description, err)
var jinjaExpander = &chart.Expander{
Name: "ExpandyBird",
Entrypoint: "templates/main.jinja",
}
if !reflect.DeepEqual(actualResult, expectedResult) {
message := fmt.Sprintf("want:\n%s\nhave:\n%s\n", expectedOutput, actualOutput)
t.Fatalf("error in test case '%s':\n%s\n", description, message)
func TestEmptyJinja(t *testing.T) {
testExpansion(
t,
&common.ExpansionRequest{
ChartInvocation: &common.Resource{
Name: "test_invocation",
Type: funcName(),
},
Chart: &chart.Content{
Chartfile: &chart.Chartfile{
Name: funcName(),
Expander: jinjaExpander,
},
Members: []*chart.Member{
{
Path: "templates/main.jinja",
Content: content([]string{"resources:"}),
},
},
},
},
&common.ExpansionResponse{
Resources: []interface{}{},
},
"", // Error
)
}
func TestEmptyPython(t *testing.T) {
testExpansion(
t,
&common.ExpansionRequest{
ChartInvocation: &common.Resource{
Name: "test_invocation",
Type: funcName(),
},
Chart: &chart.Content{
Chartfile: &chart.Chartfile{
Name: funcName(),
Expander: pyExpander,
},
Members: []*chart.Member{
{
Path: "templates/main.py",
Content: content([]string{
"def GenerateConfig(ctx):",
" return 'resources:'",
}),
},
},
},
},
&common.ExpansionResponse{
Resources: []interface{}{},
},
"", // Error
)
}
func testExpandTemplateFromFile(t *testing.T, fileName, baseName string, importFileNames []string,
constructor func(string, io.Reader, []string) (*common.Template, error)) {
file, err := os.Open(fileName)
if err != nil {
t.Fatalf("cannot open file %s: %s", fileName, err)
func TestSimpleJinja(t *testing.T) {
testExpansion(
t,
&common.ExpansionRequest{
ChartInvocation: &common.Resource{
Name: "test_invocation",
Type: funcName(),
},
Chart: &chart.Content{
Chartfile: &chart.Chartfile{
Name: funcName(),
Expander: jinjaExpander,
},
Members: []*chart.Member{
{
Path: "templates/main.jinja",
Content: content([]string{
"resources:",
"- name: foo",
" type: bar",
}),
},
},
},
},
&common.ExpansionResponse{
Resources: []interface{}{
map[string]interface{}{
"name": "foo",
"type": "bar",
},
},
},
"", // Error
)
}
template, err := constructor(baseName, file, importFileNames)
if err != nil {
t.Fatalf("cannot create template from file %s: %s", fileName, err)
func TestSimplePython(t *testing.T) {
testExpansion(
t,
&common.ExpansionRequest{
ChartInvocation: &common.Resource{
Name: "test_invocation",
Type: funcName(),
},
Chart: &chart.Content{
Chartfile: &chart.Chartfile{
Name: funcName(),
Expander: pyExpander,
},
Members: []*chart.Member{
{
Path: "templates/main.py",
Content: content([]string{
"def GenerateConfig(ctx):",
" return '''resources:",
"- name: foo",
" type: bar",
"'''",
}),
},
},
},
},
&common.ExpansionResponse{
Resources: []interface{}{
map[string]interface{}{
"name": "foo",
"type": "bar",
},
},
},
"", // Error
)
}
backend := NewExpander(expanderName)
actualOutput, err := backend.ExpandTemplate(template)
if err != nil {
t.Fatalf("cannot expand template from file %s: %s", fileName, err)
func TestPropertiesJinja(t *testing.T) {
testExpansion(
t,
&common.ExpansionRequest{
ChartInvocation: &common.Resource{
Name: "test_invocation",
Type: funcName(),
Properties: map[string]interface{}{
"prop1": 3.0,
"prop2": "foo",
},
},
Chart: &chart.Content{
Chartfile: &chart.Chartfile{
Name: funcName(),
Expander: jinjaExpander,
},
Members: []*chart.Member{
{
Path: "templates/main.jinja",
Content: content([]string{
"resources:",
"- name: foo",
" type: {{ properties.prop2 }}",
" properties:",
" something: {{ properties.prop1 }}",
}),
},
},
},
},
&common.ExpansionResponse{
Resources: []interface{}{
map[string]interface{}{
"name": "foo",
"properties": map[string]interface{}{
"something": 3.0,
},
"type": "foo",
},
},
},
"", // Error
)
}
description := fmt.Sprintf("test expand template from file: %s", fileName)
expandAndVerifyOutput(t, actualOutput, description)
func TestPropertiesPython(t *testing.T) {
testExpansion(
t,
&common.ExpansionRequest{
ChartInvocation: &common.Resource{
Name: "test_invocation",
Type: funcName(),
Properties: map[string]interface{}{
"prop1": 3.0,
"prop2": "foo",
},
},
Chart: &chart.Content{
Chartfile: &chart.Chartfile{
Name: funcName(),
Expander: pyExpander,
},
Members: []*chart.Member{
{
Path: "templates/main.py",
Content: content([]string{
"def GenerateConfig(ctx):",
" return '''resources:",
"- name: foo",
" type: %(prop2)s",
" properties:",
" something: %(prop1)s",
"''' % ctx.properties",
}),
},
},
},
},
&common.ExpansionResponse{
Resources: []interface{}{
map[string]interface{}{
"name": "foo",
"properties": map[string]interface{}{
"something": 3.0,
},
"type": "foo",
},
},
},
"", // Error
)
}
func TestExpandTemplateFromReader(t *testing.T) {
baseName := path.Base(validFileName)
testExpandTemplateFromFile(t, validFileName, baseName, importFileNames, util.NewTemplateFromReader)
func TestMultiFileJinja(t *testing.T) {
testExpansion(
t,
&common.ExpansionRequest{
ChartInvocation: &common.Resource{
Name: "test_invocation",
Type: funcName(),
},
Chart: &chart.Content{
Chartfile: &chart.Chartfile{
Name: funcName(),
Expander: jinjaExpander,
},
Members: []*chart.Member{
{
Path: "templates/main.jinja",
Content: content([]string{"{% include 'templates/secondary.jinja' %}"}),
},
{
Path: "templates/secondary.jinja",
Content: content([]string{
"resources:",
"- name: foo",
" type: bar",
}),
},
},
},
},
&common.ExpansionResponse{
Resources: []interface{}{
map[string]interface{}{
"name": "foo",
"type": "bar",
},
},
},
"", // Error
)
}
func TestExpandTemplateFromArchive(t *testing.T) {
baseName := path.Base(validFileName)
testExpandTemplateFromFile(t, archiveFileName, baseName, nil, util.NewTemplateFromArchive)
var schemaContent = content([]string{
`{`,
` "required": ["prop1", "prop2"],`,
` "additionalProperties": false,`,
` "properties": {`,
` "prop1": {`,
` "description": "Nice description.",`,
` "type": "integer"`,
` },`,
` "prop2": {`,
` "description": "Nice description.",`,
` "type": "string"`,
` }`,
` }`,
`}`,
})
func TestSchema(t *testing.T) {
testExpansion(
t,
&common.ExpansionRequest{
ChartInvocation: &common.Resource{
Name: "test_invocation",
Type: funcName(),
Properties: map[string]interface{}{
"prop1": 3.0,
"prop2": "foo",
},
},
Chart: &chart.Content{
Chartfile: &chart.Chartfile{
Name: funcName(),
Expander: jinjaExpander,
Schema: "Schema.yaml",
},
Members: []*chart.Member{
{
Path: "Schema.yaml",
Content: schemaContent,
},
{
Path: "templates/main.jinja",
Content: content([]string{
"resources:",
"- name: foo",
" type: {{ properties.prop2 }}",
" properties:",
" something: {{ properties.prop1 }}",
}),
},
},
},
},
&common.ExpansionResponse{
Resources: []interface{}{
map[string]interface{}{
"name": "foo",
"properties": map[string]interface{}{
"something": 3.0,
},
"type": "foo",
},
},
},
"", // Error
)
}
var ExpanderTestCases = []ExpanderTestCase{
func TestSchemaFail(t *testing.T) {
testExpansion(
t,
&common.ExpansionRequest{
ChartInvocation: &common.Resource{
Name: "test_invocation",
Type: funcName(),
Properties: map[string]interface{}{
"prop1": 3.0,
"prop3": "foo",
},
},
Chart: &chart.Content{
Chartfile: &chart.Chartfile{
Name: funcName(),
Expander: jinjaExpander,
Schema: "Schema.yaml",
},
Members: []*chart.Member{
{
"expect error for invalid file name",
"../test/InvalidFileName.yaml",
importFileNames,
"ExpansionError: Exception",
Path: "Schema.yaml",
Content: schemaContent,
},
{
"expect error for invalid property",
"../test/InvalidProperty.yaml",
importFileNames,
"ExpansionError: Exception",
Path: "templates/main.jinja",
Content: content([]string{
"resources:",
"- name: foo",
" type: {{ properties.prop2 }}",
" properties:",
" something: {{ properties.prop1 }}",
}),
},
},
},
},
nil, // Response.
"Invalid properties for",
)
}
func TestMultiFileJinjaMissing(t *testing.T) {
testExpansion(
t,
&common.ExpansionRequest{
ChartInvocation: &common.Resource{
Name: "test_invocation",
Type: funcName(),
},
Chart: &chart.Content{
Chartfile: &chart.Chartfile{
Name: funcName(),
Expander: jinjaExpander,
},
Members: []*chart.Member{
{
"expect error for malformed content",
"../test/MalformedContent.yaml",
importFileNames,
"ExpansionError: Error parsing YAML: mapping values are not allowed here",
Path: "templates/main.jinja",
Content: content([]string{"{% include 'templates/secondary.jinja' %}"}),
},
},
},
},
nil, // Response
"TemplateNotFound: templates/secondary.jinja",
)
}
func TestMultiFilePython(t *testing.T) {
testExpansion(
t,
&common.ExpansionRequest{
ChartInvocation: &common.Resource{
Name: "test_invocation",
Type: funcName(),
},
Chart: &chart.Content{
Chartfile: &chart.Chartfile{
Name: funcName(),
Expander: pyExpander,
},
Members: []*chart.Member{
{
"expect error for missing imports",
"../test/MissingImports.yaml",
importFileNames,
"ExpansionError: Exception",
Path: "templates/main.py",
Content: content([]string{
"from templates import second",
"import templates.third",
"def GenerateConfig(ctx):",
" t2 = second.Gen()",
" t3 = templates.third.Gen()",
" return t2",
}),
},
{
"expect error for missing resource name",
"../test/MissingResourceName.yaml",
importFileNames,
"ExpansionError: Resource does not have a name",
Path: "templates/second.py",
Content: content([]string{
"def Gen():",
" return '''resources:",
"- name: foo",
" type: bar",
"'''",
}),
},
{
"expect error for missing type name",
"../test/MissingTypeName.yaml",
importFileNames,
"ExpansionError: Resource does not have type defined",
Path: "templates/third.py",
Content: content([]string{
"def Gen():",
" return '''resources:",
"- name: foo",
" type: bar",
"'''",
}),
},
},
},
},
&common.ExpansionResponse{
Resources: []interface{}{
map[string]interface{}{
"name": "foo",
"type": "bar",
},
},
},
"", // Error
)
}
func TestMultiFilePythonMissing(t *testing.T) {
testExpansion(
t,
&common.ExpansionRequest{
ChartInvocation: &common.Resource{
Name: "test_invocation",
Type: funcName(),
},
Chart: &chart.Content{
Chartfile: &chart.Chartfile{
Name: funcName(),
Expander: pyExpander,
},
Members: []*chart.Member{
{
"expect success",
validFileName,
importFileNames,
"",
Path: "templates/main.py",
Content: content([]string{
"from templates import second",
}),
},
},
},
},
nil, // Response
"cannot import name second", // Error
)
}
func TestExpandTemplate(t *testing.T) {
backend := NewExpander(expanderName)
for _, etc := range ExpanderTestCases {
template := etc.GetTemplate(t)
actualOutput, err := backend.ExpandTemplate(template)
if err != nil {
message := err.Error()
if !strings.Contains(message, etc.ExpectedError) {
t.Fatalf("error in test case '%s': %s\n", etc.Description, message)
func TestWrongChartName(t *testing.T) {
testExpansion(
t,
&common.ExpansionRequest{
ChartInvocation: &common.Resource{
Name: "test_invocation",
Type: funcName(),
},
Chart: &chart.Content{
Chartfile: &chart.Chartfile{
Name: "WrongName",
Expander: jinjaExpander,
},
Members: []*chart.Member{
{
Path: "templates/main.jinja",
Content: content([]string{"resources:"}),
},
},
},
},
nil, // Response
"Request chart invocation does not match provided chart",
)
}
} else {
if etc.ExpectedError != "" {
t.Fatalf("expected error did not occur in test case '%s': %s\n",
etc.Description, etc.ExpectedError)
func TestEntrypointNotFound(t *testing.T) {
testExpansion(
t,
&common.ExpansionRequest{
ChartInvocation: &common.Resource{
Name: "test_invocation",
Type: funcName(),
},
Chart: &chart.Content{
Chartfile: &chart.Chartfile{
Name: funcName(),
Expander: jinjaExpander,
},
Members: []*chart.Member{},
},
},
nil, // Response
"The entrypoint in the chart.yaml cannot be found",
)
}
expandAndVerifyOutput(t, actualOutput, etc.Description)
func TestMalformedResource(t *testing.T) {
testExpansion(
t,
&common.ExpansionRequest{
ChartInvocation: &common.Resource{
Name: "test_invocation",
Type: funcName(),
},
Chart: &chart.Content{
Chartfile: &chart.Chartfile{
Name: funcName(),
Expander: jinjaExpander,
},
Members: []*chart.Member{
{
Path: "templates/main.jinja",
Content: content([]string{
"resources:",
"fail",
}),
},
},
},
},
nil, // Response
"could not found expected ':'", // [sic]
)
}
func TestResourceNoName(t *testing.T) {
testExpansion(
t,
&common.ExpansionRequest{
ChartInvocation: &common.Resource{
Name: "test_invocation",
Type: funcName(),
},
Chart: &chart.Content{
Chartfile: &chart.Chartfile{
Name: funcName(),
Expander: jinjaExpander,
},
Members: []*chart.Member{
{
Path: "templates/main.jinja",
Content: content([]string{
"resources:",
"- type: bar",
}),
},
},
},
},
nil, // Response.
"Resource does not have a name",
)
}
func TestResourceNoType(t *testing.T) {
testExpansion(
t,
&common.ExpansionRequest{
ChartInvocation: &common.Resource{
Name: "test_invocation",
Type: funcName(),
},
Chart: &chart.Content{
Chartfile: &chart.Chartfile{
Name: funcName(),
Expander: jinjaExpander,
},
Members: []*chart.Member{
{
Path: "templates/main.jinja",
Content: content([]string{
"resources:",
"- name: foo",
}),
},
},
},
},
nil, // Response.
"Resource does not have type defined",
)
}
*/

Loading…
Cancel
Save