github.com/6543-forks/go-swagger@v0.26.0/generator/template_repo_test.go (about)

     1  package generator
     2  
     3  import (
     4  	"bytes"
     5  	"io/ioutil"
     6  	"log"
     7  	"os"
     8  	"testing"
     9  
    10  	"github.com/go-openapi/loads"
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  const (
    16  	// Test template environment
    17  	singleTemplate        = `test`
    18  	multipleDefinitions   = `{{ define "T1" }}T1{{end}}{{ define "T2" }}T2{{end}}`
    19  	dependantTemplate     = `{{ template "T1" }}D1`
    20  	cirularDeps1          = `{{ define "T1" }}{{ .Name }}: {{ range .Children }}{{ template "T2" . }}{{end}}{{end}}{{template "T1" . }}`
    21  	cirularDeps2          = `{{ define "T2" }}{{if .Recurse }}{{ template "T1" . }}{{ else }}Children{{end}}{{end}}`
    22  	customHeader          = `custom header`
    23  	customMultiple        = `{{define "bindprimitiveparam" }}custom primitive{{end}}`
    24  	customNewTemplate     = `new template`
    25  	customExistingUsesNew = `{{define "bindprimitiveparam" }}{{ template "newtemplate" }}{{end}}`
    26  )
    27  
    28  func testFuncTpl() string {
    29  	return `
    30  Pascalize={{ pascalize "WeArePonies_Of_the_round table" }}
    31  Snakize={{ snakize "WeArePonies_Of_the_round table" }}
    32  Humanize={{ humanize "WeArePonies_Of_the_round table" }}
    33  PluralizeFirstWord={{ pluralizeFirstWord "pony of the round table" }}
    34  PluralizeFirstOfOneWord={{ pluralizeFirstWord "dwarf" }}
    35  PluralizeFirstOfNoWord={{ pluralizeFirstWord "" }}
    36  DropPackage={{ dropPackage "prefix.suffix" }}
    37  DropNoPackage={{ dropPackage "suffix" }}
    38  DropEmptyPackage={{ dropPackage "" }}
    39  ContainsString={{ contains .DependsOn "x"}}
    40  DoesNotContainString={{ contains .DependsOn "y"}}
    41  PadSurround1={{ padSurround "padme" "-" 3 12}}
    42  PadSurround2={{ padSurround "padme" "-" 0 12}}
    43  Json={{ json .DefaultImports }}
    44  PrettyJson={{ prettyjson . }}
    45  Snakize1={{ snakize "endingInOsNameLinux" }}
    46  Snakize2={{ snakize "endingInArchNameLinuxAmd64" }}
    47  Snakize3={{ snakize "endingInTest" }}
    48  toPackage1={{ toPackage "a/b-c/d-e" }}
    49  toPackage2={{ toPackage "a.a/b_c/d_e" }}
    50  toPackage3={{ toPackage "d_e" }}
    51  toPackage4={{ toPackage "d-e" }}
    52  toPackageName={{ toPackageName "d-e/f-g" }}
    53  PascalizeSpecialChar1={{ pascalize "+1" }}
    54  PascalizeSpecialChar2={{ pascalize "-1" }}
    55  PascalizeSpecialChar3={{ pascalize "1" }}
    56  PascalizeSpecialChar4={{ pascalize "-" }}
    57  PascalizeSpecialChar5={{ pascalize "+" }}
    58  Dict={{ template "dictTemplate" dict "Animal" "Pony" "Shape" "round" "Furniture" "table" }}
    59  {{ define "dictTemplate" }}{{ .Animal }} of the {{ .Shape }} {{ .Furniture }}{{ end }}
    60  `
    61  }
    62  
    63  func TestTemplates_CustomTemplates(t *testing.T) {
    64  
    65  	var buf bytes.Buffer
    66  	headerTempl, err := templates.Get("bindprimitiveparam")
    67  	assert.NoError(t, err)
    68  
    69  	err = headerTempl.Execute(&buf, nil)
    70  	require.NoError(t, err)
    71  	require.NotNil(t, buf)
    72  	assert.Equal(t, "\n", buf.String())
    73  
    74  	buf.Reset()
    75  	err = templates.AddFile("bindprimitiveparam", customHeader)
    76  	assert.NoError(t, err)
    77  
    78  	headerTempl, err = templates.Get("bindprimitiveparam")
    79  	assert.NoError(t, err)
    80  	assert.NotNil(t, headerTempl)
    81  
    82  	err = headerTempl.Execute(&buf, nil)
    83  	assert.NoError(t, err)
    84  	assert.Equal(t, "custom header", buf.String())
    85  
    86  }
    87  
    88  func TestTemplates_CustomTemplatesMultiple(t *testing.T) {
    89  	var buf bytes.Buffer
    90  
    91  	err := templates.AddFile("differentFileName", customMultiple)
    92  	assert.NoError(t, err)
    93  
    94  	headerTempl, err := templates.Get("bindprimitiveparam")
    95  	assert.NoError(t, err)
    96  
    97  	err = headerTempl.Execute(&buf, nil)
    98  	require.NoError(t, err)
    99  
   100  	assert.Equal(t, "custom primitive", buf.String())
   101  }
   102  
   103  func TestTemplates_CustomNewTemplates(t *testing.T) {
   104  	var buf bytes.Buffer
   105  
   106  	err := templates.AddFile("newtemplate", customNewTemplate)
   107  	assert.NoError(t, err)
   108  
   109  	err = templates.AddFile("existingUsesNew", customExistingUsesNew)
   110  	assert.NoError(t, err)
   111  
   112  	headerTempl, err := templates.Get("bindprimitiveparam")
   113  	assert.NoError(t, err)
   114  
   115  	err = headerTempl.Execute(&buf, nil)
   116  	require.NoError(t, err)
   117  
   118  	assert.Equal(t, "new template", buf.String())
   119  }
   120  
   121  func TestTemplates_RepoLoadingTemplates(t *testing.T) {
   122  
   123  	repo := NewRepository(nil)
   124  
   125  	err := repo.AddFile("simple", singleTemplate)
   126  	assert.NoError(t, err)
   127  
   128  	templ, err := repo.Get("simple")
   129  	require.NoError(t, err)
   130  
   131  	var b bytes.Buffer
   132  	err = templ.Execute(&b, nil)
   133  	require.NoError(t, err)
   134  
   135  	assert.Equal(t, "test", b.String())
   136  }
   137  
   138  func TestTemplates_RepoLoadsAllTemplatesDefined(t *testing.T) {
   139  
   140  	var b bytes.Buffer
   141  	repo := NewRepository(nil)
   142  
   143  	err := repo.AddFile("multiple", multipleDefinitions)
   144  	assert.NoError(t, err)
   145  
   146  	templ, err := repo.Get("multiple")
   147  	assert.NoError(t, err)
   148  
   149  	err = templ.Execute(&b, nil)
   150  	require.NoError(t, err)
   151  
   152  	assert.Equal(t, "", b.String())
   153  
   154  	templ, err = repo.Get("T1")
   155  	require.NoError(t, err)
   156  	require.NotNil(t, templ)
   157  
   158  	err = templ.Execute(&b, nil)
   159  	require.NoError(t, err)
   160  
   161  	assert.Equal(t, "T1", b.String())
   162  }
   163  
   164  type testData struct {
   165  	Children []testData
   166  	Name     string
   167  	Recurse  bool
   168  }
   169  
   170  func TestTemplates_RepoLoadsAllDependantTemplates(t *testing.T) {
   171  
   172  	var b bytes.Buffer
   173  	repo := NewRepository(nil)
   174  
   175  	err := repo.AddFile("multiple", multipleDefinitions)
   176  	assert.NoError(t, err)
   177  
   178  	err = repo.AddFile("dependant", dependantTemplate)
   179  	assert.NoError(t, err)
   180  
   181  	templ, err := repo.Get("dependant")
   182  	require.NoError(t, err)
   183  	require.NotNil(t, templ)
   184  
   185  	err = templ.Execute(&b, nil)
   186  	require.NoError(t, err)
   187  
   188  	assert.Equal(t, "T1D1", b.String())
   189  }
   190  
   191  func TestTemplates_RepoRecursiveTemplates(t *testing.T) {
   192  
   193  	var b bytes.Buffer
   194  	repo := NewRepository(nil)
   195  
   196  	err := repo.AddFile("c1", cirularDeps1)
   197  	assert.NoError(t, err)
   198  
   199  	err = repo.AddFile("c2", cirularDeps2)
   200  	assert.NoError(t, err)
   201  
   202  	templ, err := repo.Get("c1")
   203  	require.NoError(t, err)
   204  	require.NotNil(t, templ)
   205  
   206  	data := testData{
   207  		Name: "Root",
   208  		Children: []testData{
   209  			{Recurse: false},
   210  		},
   211  	}
   212  	expected := `Root: Children`
   213  	err = templ.Execute(&b, data)
   214  	require.NoError(t, err)
   215  	assert.Equal(t, expected, b.String())
   216  
   217  	data = testData{
   218  		Name: "Root",
   219  		Children: []testData{
   220  			{Name: "Child1", Recurse: true, Children: []testData{{Name: "Child2"}}},
   221  		},
   222  	}
   223  
   224  	b.Reset()
   225  
   226  	expected = `Root: Child1: Children`
   227  
   228  	err = templ.Execute(&b, data)
   229  	require.NoError(t, err)
   230  
   231  	assert.Equal(t, expected, b.String())
   232  
   233  	data = testData{
   234  		Name: "Root",
   235  		Children: []testData{
   236  			{Name: "Child1", Recurse: false, Children: []testData{{Name: "Child2"}}},
   237  		},
   238  	}
   239  
   240  	b.Reset()
   241  
   242  	expected = `Root: Children`
   243  
   244  	err = templ.Execute(&b, data)
   245  	require.NoError(t, err)
   246  
   247  	assert.Equal(t, expected, b.String())
   248  }
   249  
   250  // Test that definitions are available to templates
   251  // TODO: should test also with the codeGenApp context
   252  
   253  // Test copyright definition
   254  func TestTemplates_DefinitionCopyright(t *testing.T) {
   255  	const copyright = `{{ .Copyright }}`
   256  	log.SetOutput(os.Stdout)
   257  
   258  	repo := NewRepository(nil)
   259  
   260  	err := repo.AddFile("copyright", copyright)
   261  	assert.NoError(t, err)
   262  
   263  	templ, err := repo.Get("copyright")
   264  	require.NoError(t, err)
   265  	require.NotNil(t, templ)
   266  
   267  	opts := opts()
   268  	opts.Copyright = "My copyright clause"
   269  	expected := opts.Copyright
   270  
   271  	// executes template against model definitions
   272  	genModel, err := getModelEnvironment("../fixtures/codegen/todolist.models.yml", opts)
   273  	require.NoError(t, err)
   274  	require.NotNil(t, genModel)
   275  
   276  	rendered := bytes.NewBuffer(nil)
   277  	err = templ.Execute(rendered, genModel)
   278  	assert.NoError(t, err)
   279  	assert.Equal(t, expected, rendered.String())
   280  
   281  	// executes template against operations definitions
   282  	genOperation, err := getOperationEnvironment("get", "/media/search", "../fixtures/codegen/instagram.yml", opts)
   283  	require.NoError(t, err)
   284  	require.NotNil(t, genOperation)
   285  
   286  	rendered.Reset()
   287  
   288  	err = templ.Execute(rendered, genOperation)
   289  	require.NoError(t, err)
   290  
   291  	assert.Equal(t, expected, rendered.String())
   292  
   293  }
   294  
   295  // Test TargetImportPath definition
   296  func TestTemplates_DefinitionTargetImportPath(t *testing.T) {
   297  	const targetImportPath = `{{ .TargetImportPath }}`
   298  	log.SetOutput(os.Stdout)
   299  
   300  	repo := NewRepository(nil)
   301  
   302  	err := repo.AddFile("targetimportpath", targetImportPath)
   303  	assert.NoError(t, err)
   304  
   305  	templ, err := repo.Get("targetimportpath")
   306  	require.NoError(t, err)
   307  	require.NotNil(t, templ)
   308  
   309  	opts := opts()
   310  	// Non existing target would panic: to be tested too, but in another module
   311  	opts.Target = "../fixtures"
   312  	var expected = "github.com/go-swagger/go-swagger/fixtures"
   313  
   314  	// executes template against model definitions
   315  	genModel, err := getModelEnvironment("../fixtures/codegen/todolist.models.yml", opts)
   316  	require.NoError(t, err)
   317  	require.NotNil(t, genModel)
   318  
   319  	rendered := bytes.NewBuffer(nil)
   320  	err = templ.Execute(rendered, genModel)
   321  	assert.NoError(t, err)
   322  
   323  	assert.Equal(t, expected, rendered.String())
   324  
   325  	// executes template against operations definitions
   326  	genOperation, err := getOperationEnvironment("get", "/media/search", "../fixtures/codegen/instagram.yml", opts)
   327  	require.NoError(t, err)
   328  	require.NotNil(t, genOperation)
   329  
   330  	rendered.Reset()
   331  
   332  	err = templ.Execute(rendered, genOperation)
   333  	require.NoError(t, err)
   334  
   335  	assert.Equal(t, expected, rendered.String())
   336  
   337  }
   338  
   339  // Simulates a definition environment for model templates
   340  func getModelEnvironment(spec string, opts *GenOpts) (*GenDefinition, error) {
   341  	// Don't want stderr output to pollute CI
   342  	log.SetOutput(ioutil.Discard)
   343  	defer log.SetOutput(os.Stdout)
   344  
   345  	specDoc, err := loads.Spec("../fixtures/codegen/todolist.models.yml")
   346  	if err != nil {
   347  		return nil, err
   348  	}
   349  	definitions := specDoc.Spec().Definitions
   350  
   351  	for k, schema := range definitions {
   352  		genModel, err := makeGenDefinition(k, "models", schema, specDoc, opts)
   353  		if err != nil {
   354  			return nil, err
   355  		}
   356  		// One is enough
   357  		return genModel, nil
   358  	}
   359  	return nil, nil
   360  }
   361  
   362  // Simulates a definition environment for operation templates
   363  func getOperationEnvironment(operation string, path string, spec string, opts *GenOpts) (*GenOperation, error) {
   364  	// Don't want stderr output to pollute CI
   365  	log.SetOutput(ioutil.Discard)
   366  	defer log.SetOutput(os.Stdout)
   367  
   368  	b, err := methodPathOpBuilder(operation, path, spec)
   369  	if err != nil {
   370  		return nil, err
   371  	}
   372  	b.GenOpts = opts
   373  	g, err := b.MakeOperation()
   374  	if err != nil {
   375  		return nil, err
   376  	}
   377  	return &g, nil
   378  }
   379  
   380  // Exercises FuncMap
   381  // Just running basic tests to make sure the function map works and all functions are available as expected.
   382  // More complete unit tests are provided by go-openapi/swag.
   383  func TestTemplates_FuncMap(t *testing.T) {
   384  	log.SetOutput(os.Stdout)
   385  	funcTpl := testFuncTpl()
   386  
   387  	err := templates.AddFile("functpl", funcTpl)
   388  	require.NoError(t, err)
   389  
   390  	templ, err := templates.Get("functpl")
   391  	require.NoError(t, err)
   392  
   393  	opts := opts()
   394  	// executes template against model definitions
   395  	genModel, err := getModelEnvironment("../fixtures/codegen/todolist.models.yml", opts)
   396  	require.NoError(t, err)
   397  
   398  	genModel.DependsOn = []string{"x", "z"}
   399  	rendered := bytes.NewBuffer(nil)
   400  	err = templ.Execute(rendered, genModel)
   401  	require.NoError(t, err)
   402  
   403  	assert.Contains(t, rendered.String(), "Pascalize=WeArePoniesOfTheRoundTable\n")
   404  	assert.Contains(t, rendered.String(), "Snakize=we_are_ponies_of_the_round_table\n")
   405  	assert.Contains(t, rendered.String(), "Humanize=we are ponies of the round table\n")
   406  	assert.Contains(t, rendered.String(), "PluralizeFirstWord=ponies of the round table\n")
   407  	assert.Contains(t, rendered.String(), "PluralizeFirstOfOneWord=dwarves\n")
   408  	assert.Contains(t, rendered.String(), "PluralizeFirstOfNoWord=\n")
   409  	assert.Contains(t, rendered.String(), "DropPackage=suffix\n")
   410  	assert.Contains(t, rendered.String(), "DropNoPackage=suffix\n")
   411  	assert.Contains(t, rendered.String(), "DropEmptyPackage=\n")
   412  	assert.Contains(t, rendered.String(), "DropEmptyPackage=\n")
   413  	assert.Contains(t, rendered.String(), "ContainsString=true\n")
   414  	assert.Contains(t, rendered.String(), "DoesNotContainString=false\n")
   415  	assert.Contains(t, rendered.String(), "PadSurround1=-,-,-,padme,-,-,-,-,-,-,-,-\n")
   416  	assert.Contains(t, rendered.String(), "PadSurround2=padme,-,-,-,-,-,-,-,-,-,-,-\n")
   417  	assert.Contains(t, rendered.String(), `Json={"errors":"github.com/go-openapi/errors","runtime":"github.com/go-openapi/runtime","swag":"github.com/go-openapi/swag","validate":"github.com/go-openapi/validate"}`)
   418  	assert.Contains(t, rendered.String(), "\"TargetImportPath\": \"github.com/go-swagger/go-swagger/generator\"")
   419  	assert.Contains(t, rendered.String(), "Snakize1=ending_in_os_name_linux_swagger\n")
   420  	assert.Contains(t, rendered.String(), "Snakize2=ending_in_arch_name_linux_amd64_swagger\n")
   421  	assert.Contains(t, rendered.String(), "Snakize3=ending_in_test_swagger\n")
   422  	assert.Contains(t, rendered.String(), "toPackage1=a/b-c/d_e\n")
   423  	assert.Contains(t, rendered.String(), "toPackage2=a.a/b_c/d_e\n")
   424  	assert.Contains(t, rendered.String(), "toPackage3=d_e\n")
   425  	assert.Contains(t, rendered.String(), "toPackage4=d_e\n")
   426  	assert.Contains(t, rendered.String(), "toPackageName=f_g\n")
   427  	assert.Contains(t, rendered.String(), "PascalizeSpecialChar1=Plus1\n")
   428  	assert.Contains(t, rendered.String(), "PascalizeSpecialChar2=Minus1\n")
   429  	assert.Contains(t, rendered.String(), "PascalizeSpecialChar3=Nr1\n")
   430  	assert.Contains(t, rendered.String(), "PascalizeSpecialChar4=Minus\n")
   431  	assert.Contains(t, rendered.String(), "PascalizeSpecialChar5=Plus\n")
   432  	assert.Contains(t, rendered.String(), "Dict=Pony of the round table\n")
   433  }
   434  
   435  // AddFile() global package function (protected vs unprotected)
   436  // Mostly unused in tests, since the Repository.AddFile()
   437  // is generally preferred.
   438  func TestTemplates_AddFile(t *testing.T) {
   439  	log.SetOutput(os.Stdout)
   440  	funcTpl := testFuncTpl()
   441  
   442  	// unprotected
   443  	err := AddFile("functpl", funcTpl)
   444  	require.NoError(t, err)
   445  
   446  	_, err = templates.Get("functpl")
   447  	require.NoError(t, err)
   448  
   449  	// protected
   450  	err = AddFile("schemabody", funcTpl)
   451  	require.Error(t, err)
   452  	assert.Contains(t, err.Error(), "cannot overwrite protected template")
   453  }
   454  
   455  // Test LoadDir
   456  func TestTemplates_LoadDir(t *testing.T) {
   457  	log.SetOutput(os.Stdout)
   458  
   459  	// Fails
   460  	err := templates.LoadDir("")
   461  	require.Error(t, err)
   462  	assert.Contains(t, err.Error(), "could not complete")
   463  
   464  	// Fails again (from any dir?)
   465  	err = templates.LoadDir("templates")
   466  	require.Error(t, err)
   467  	assert.Contains(t, err.Error(), "cannot overwrite protected template")
   468  
   469  	// TODO: success case
   470  	// To force a success, we need to empty the global list of protected
   471  	// templates...
   472  	origProtectedTemplates := protectedTemplates
   473  
   474  	defer func() {
   475  		// Restore variable initialized with package
   476  		protectedTemplates = origProtectedTemplates
   477  	}()
   478  
   479  	protectedTemplates = make(map[string]bool)
   480  	repo := NewRepository(FuncMapFunc(DefaultLanguageFunc()))
   481  	err = repo.LoadDir("templates")
   482  	assert.NoError(t, err)
   483  }
   484  
   485  // Test LoadDir
   486  func TestTemplates_SetAllowOverride(t *testing.T) {
   487  	log.SetOutput(os.Stdout)
   488  
   489  	// adding protected file with allowOverride set to false fails
   490  	templates.SetAllowOverride(false)
   491  	err := templates.AddFile("schemabody", "some data")
   492  	require.Error(t, err)
   493  	assert.Contains(t, err.Error(), "cannot overwrite protected template schemabody")
   494  
   495  	// adding protected file with allowOverride set to true should not fail
   496  	templates.SetAllowOverride(true)
   497  	err = templates.AddFile("schemabody", "some data")
   498  	assert.NoError(t, err)
   499  }
   500  
   501  // Test LoadContrib
   502  func TestTemplates_LoadContrib(t *testing.T) {
   503  	tests := []struct {
   504  		name      string
   505  		template  string
   506  		wantError bool
   507  	}{
   508  		{
   509  			name:      "None_existing_contributor_template",
   510  			template:  "NonExistingContributorTemplate",
   511  			wantError: true,
   512  		},
   513  		{
   514  			name:      "Existing_contributor",
   515  			template:  "stratoscale",
   516  			wantError: false,
   517  		},
   518  	}
   519  
   520  	for _, tt := range tests {
   521  		t.Run(tt.name, func(t *testing.T) {
   522  			err := templates.LoadContrib(tt.template)
   523  			if tt.wantError {
   524  				assert.Error(t, err)
   525  			} else {
   526  				assert.NoError(t, err)
   527  			}
   528  		})
   529  	}
   530  }
   531  
   532  // TODO: test error case in LoadDefaults()
   533  // test DumpTemplates()
   534  func TestTemplates_DumpTemplates(t *testing.T) {
   535  	buf := bytes.NewBuffer(nil)
   536  	log.SetOutput(buf)
   537  	defer func() {
   538  		log.SetOutput(os.Stdout)
   539  	}()
   540  
   541  	templates.DumpTemplates()
   542  	assert.NotEmpty(t, buf)
   543  	// Sample output
   544  	assert.Contains(t, buf.String(), "## tupleSerializer")
   545  	assert.Contains(t, buf.String(), "Defined in `tupleserializer.gotmpl`")
   546  	assert.Contains(t, buf.String(), "####requires \n - schemaType")
   547  }
   548  
   549  func TestFuncMap_Pascalize(t *testing.T) {
   550  	assert.Equal(t, "Plus1", pascalize("+1"))
   551  	assert.Equal(t, "Plus", pascalize("+"))
   552  	assert.Equal(t, "Minus1", pascalize("-1"))
   553  	assert.Equal(t, "Minus", pascalize("-"))
   554  	assert.Equal(t, "Nr8", pascalize("8"))
   555  
   556  	assert.Equal(t, "Hello", pascalize("+hello"))
   557  
   558  	// other values from swag rules
   559  	assert.Equal(t, "At8", pascalize("@8"))
   560  	assert.Equal(t, "AtHello", pascalize("@hello"))
   561  	assert.Equal(t, "Bang8", pascalize("!8"))
   562  	assert.Equal(t, "At", pascalize("@"))
   563  
   564  	// # values
   565  	assert.Equal(t, "Hello", pascalize("#hello"))
   566  	assert.Equal(t, "BangHello", pascalize("#!hello"))
   567  	assert.Equal(t, "HashTag8", pascalize("#8"))
   568  	assert.Equal(t, "HashTag", pascalize("#"))
   569  
   570  	// single '_'
   571  	assert.Equal(t, "Nr", pascalize("_"))
   572  	assert.Equal(t, "Hello", pascalize("_hello"))
   573  
   574  	// remove spaces
   575  	assert.Equal(t, "HelloWorld", pascalize("# hello world"))
   576  	assert.Equal(t, "HashTag8HelloWorld", pascalize("# 8 hello world"))
   577  
   578  	assert.Equal(t, "Empty", pascalize(""))
   579  }
   580  
   581  func TestFuncMap_DropPackage(t *testing.T) {
   582  	assert.Equal(t, "trail", dropPackage("base.trail"))
   583  	assert.Equal(t, "trail", dropPackage("base.another.trail"))
   584  	assert.Equal(t, "trail", dropPackage("trail"))
   585  }
   586  
   587  func TestFuncMap_AsJSON(t *testing.T) {
   588  	for _, jsonFunc := range []func(interface{}) (string, error){
   589  		asJSON,
   590  		asPrettyJSON,
   591  	} {
   592  		res, err := jsonFunc(struct {
   593  			A string `json:"a"`
   594  			B int
   595  		}{A: "good", B: 3})
   596  		require.NoError(t, err)
   597  		assert.JSONEq(t, `{"a":"good","B":3}`, res)
   598  
   599  		_, err = jsonFunc(struct {
   600  			A string `json:"a"`
   601  			B func() string
   602  		}{A: "good", B: func() string { return "" }})
   603  		require.Error(t, err)
   604  	}
   605  }
   606  
   607  func TestFuncMap_Dict(t *testing.T) {
   608  	d, err := dict("a", "b", "c", "d")
   609  	require.NoError(t, err)
   610  	assert.Equal(t, map[string]interface{}{"a": "b", "c": "d"}, d)
   611  
   612  	// odd number of arguments
   613  	_, err = dict("a", "b", "c")
   614  	require.Error(t, err)
   615  
   616  	// none-string key
   617  	_, err = dict("a", "b", 3, "d")
   618  	require.Error(t, err)
   619  }