github.com/splunk/qbec@v0.15.2/vm/internal/natives/nativefuncs_test.go (about)

     1  // Copyright 2017 The kubecfg authors
     2  //
     3  //
     4  //    Licensed under the Apache License, Version 2.0 (the "License");
     5  //    you may not use this file except in compliance with the License.
     6  //    You may obtain a copy of the License at
     7  //
     8  //      http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  //    Unless required by applicable law or agreed to in writing, software
    11  //    distributed under the License is distributed on an "AS IS" BASIS,
    12  //    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  //    See the License for the specific language governing permissions and
    14  //    limitations under the License.
    15  
    16  package natives
    17  
    18  import (
    19  	"encoding/json"
    20  	"fmt"
    21  	"regexp"
    22  	"testing"
    23  
    24  	"github.com/google/go-jsonnet"
    25  )
    26  
    27  // copied from original code at https://github.com/ksonnet/kubecfg/blob/master/utils/nativefuncs_test.go
    28  // and modified for use.
    29  
    30  // check there is no err, and a == b.
    31  func check(t *testing.T, err error, actual, expected string) {
    32  	if err != nil {
    33  		t.Errorf("Expected %q, got error: %q", expected, err.Error())
    34  	} else if actual != expected {
    35  		t.Errorf("Expected %q, got %q", expected, actual)
    36  	}
    37  }
    38  
    39  func TestParseJson(t *testing.T) {
    40  	vm := jsonnet.MakeVM()
    41  	Register(vm)
    42  
    43  	_, err := vm.EvaluateAnonymousSnippet("failtest", `std.native("parseJson")("barf{")`)
    44  	if err == nil {
    45  		t.Errorf("parseJson succeeded on invalid json")
    46  	}
    47  
    48  	x, err := vm.EvaluateAnonymousSnippet("test", `std.native("parseJson")("null")`)
    49  	check(t, err, x, "null\n")
    50  
    51  	x, err = vm.EvaluateAnonymousSnippet("test", `
    52      local a = std.native("parseJson")('{"foo": 3, "bar": 4}');
    53      a.foo + a.bar`)
    54  	check(t, err, x, "7\n")
    55  }
    56  
    57  func TestParseYaml(t *testing.T) {
    58  	vm := jsonnet.MakeVM()
    59  	Register(vm)
    60  
    61  	_, err := vm.EvaluateAnonymousSnippet("failtest", `std.native("parseYaml")("[barf")`)
    62  	if err == nil {
    63  		t.Errorf("parseYaml succeeded on invalid yaml")
    64  	}
    65  
    66  	x, err := vm.EvaluateAnonymousSnippet("test", `std.native("parseYaml")("")`)
    67  	check(t, err, x, "[ ]\n")
    68  
    69  	x, err = vm.EvaluateAnonymousSnippet("test", `
    70      local a = std.native("parseYaml")("foo:\n- 3\n- 4\n")[0];
    71      a.foo[0] + a.foo[1]`)
    72  	check(t, err, x, "7\n")
    73  
    74  	x, err = vm.EvaluateAnonymousSnippet("test", `
    75      local a = std.native("parseYaml")("---\nhello\n---\nworld");
    76      a[0] + a[1]`)
    77  	check(t, err, x, "\"helloworld\"\n")
    78  }
    79  
    80  func TestRenderYaml(t *testing.T) {
    81  	data := `{
    82  		"a": 1,
    83  		"b": true,
    84  		"c": "foo"
    85  	}`
    86  	vm := jsonnet.MakeVM()
    87  	Register(vm)
    88  	vm.ExtCode("data", data)
    89  	x, err := vm.EvaluateAnonymousSnippet("test", `
    90  		local renderYaml = std.native('renderYaml');
    91  		local parseYaml = std.native('parseYaml');
    92  		parseYaml(renderYaml(std.extVar('data')))[0]
    93  	`)
    94  	if err != nil {
    95  		t.Fatalf("unexpected error: %q", err)
    96  	}
    97  	var expected, actual map[string]interface{}
    98  	err = json.Unmarshal([]byte(data), &expected)
    99  	if err != nil {
   100  		t.Fatalf("unexpected error: %q", err)
   101  	}
   102  	err = json.Unmarshal([]byte(x), &actual)
   103  	if err != nil {
   104  		t.Fatalf("unexpected error: %q", err)
   105  	}
   106  
   107  	for k, v := range expected {
   108  		if actual[k] != v {
   109  			t.Errorf("mismatch, want %v got %v", v, actual[k])
   110  		}
   111  	}
   112  }
   113  
   114  func TestRenderYamlArray(t *testing.T) {
   115  	data := `[
   116  		{ "foo": "bar" },
   117  		{ "foo": "baz" }
   118  	]`
   119  	vm := jsonnet.MakeVM()
   120  	Register(vm)
   121  	vm.ExtCode("data", data)
   122  	x, err := vm.EvaluateAnonymousSnippet("test", `
   123  		local renderYaml = std.native('renderYaml');
   124  		local parseYaml = std.native('parseYaml');
   125  		parseYaml(renderYaml(std.extVar('data')))
   126  	`)
   127  	if err != nil {
   128  		t.Fatalf("unexpected error: %q", err)
   129  	}
   130  	var expected, actual []interface{}
   131  	err = json.Unmarshal([]byte(data), &expected)
   132  	if err != nil {
   133  		t.Fatalf("unexpected error: %q", err)
   134  	}
   135  	err = json.Unmarshal([]byte(x), &actual)
   136  	if err != nil {
   137  		t.Fatalf("unexpected error: %q", err)
   138  	}
   139  
   140  	for k, v := range expected {
   141  		e := v.(map[string]interface{})
   142  		a := actual[k].(map[string]interface{})
   143  		for k2, v2 := range e {
   144  			if a[k2] != v2 {
   145  				t.Errorf("mismatch, want %v got %v", v2, a[k2])
   146  			}
   147  		}
   148  	}
   149  }
   150  
   151  func TestRegexMatch(t *testing.T) {
   152  	vm := jsonnet.MakeVM()
   153  	Register(vm)
   154  
   155  	_, err := vm.EvaluateAnonymousSnippet("failtest", `std.native("regexMatch")("[f", "foo")`)
   156  	if err == nil {
   157  		t.Errorf("regexMatch succeeded with invalid regex")
   158  	}
   159  
   160  	x, err := vm.EvaluateAnonymousSnippet("test", `std.native("regexMatch")("foo.*", "seafood")`)
   161  	check(t, err, x, "true\n")
   162  
   163  	x, err = vm.EvaluateAnonymousSnippet("test", `std.native("regexMatch")("bar.*", "seafood")`)
   164  	check(t, err, x, "false\n")
   165  }
   166  
   167  func TestRegexSubst(t *testing.T) {
   168  	vm := jsonnet.MakeVM()
   169  	Register(vm)
   170  
   171  	_, err := vm.EvaluateAnonymousSnippet("failtest", `std.native("regexSubst")("[f", "foo", "bar")`)
   172  	if err == nil {
   173  		t.Errorf("regexSubst succeeded with invalid regex")
   174  	}
   175  
   176  	x, err := vm.EvaluateAnonymousSnippet("test", `std.native("regexSubst")("a(x*)b", "-ab-axxb-", "T")`)
   177  	check(t, err, x, "\"-T-T-\"\n")
   178  
   179  	x, err = vm.EvaluateAnonymousSnippet("test", `std.native("regexSubst")("a(x*)b", "-ab-axxb-", "${1}W")`)
   180  	check(t, err, x, "\"-W-xxW-\"\n")
   181  }
   182  
   183  func TestRegexQuoteMeta(t *testing.T) {
   184  	vm := jsonnet.MakeVM()
   185  	Register(vm)
   186  	x, err := vm.EvaluateAnonymousSnippet("test", `std.native("escapeStringRegex")("[f]")`)
   187  	check(t, err, x, `"\\[f\\]"`+"\n")
   188  }
   189  
   190  func TestLabelSelectorMatch(t *testing.T) {
   191  	vm := jsonnet.MakeVM()
   192  	Register(vm)
   193  	tests := []struct {
   194  		name     string
   195  		selector string
   196  		expected string
   197  	}{
   198  		{
   199  			name:     "presence",
   200  			selector: "env",
   201  			expected: "yes",
   202  		},
   203  		{
   204  			name:     "absence",
   205  			selector: "!env",
   206  			expected: "no",
   207  		},
   208  		{
   209  			name:     "and-presence",
   210  			selector: "env,region",
   211  			expected: "yes",
   212  		},
   213  		{
   214  			name:     "no-presence",
   215  			selector: "foo",
   216  			expected: "no",
   217  		},
   218  		{
   219  			name:     "equality",
   220  			selector: "region=us-west",
   221  			expected: "yes",
   222  		},
   223  		{
   224  			name:     "equality-no-match",
   225  			selector: "env=prod",
   226  			expected: "no",
   227  		},
   228  		{
   229  			name:     "and",
   230  			selector: "region=us-west,env=dev",
   231  			expected: "yes",
   232  		},
   233  		{
   234  			name:     "and-no-match",
   235  			selector: "region=us-west,!env",
   236  			expected: "no",
   237  		},
   238  		{
   239  			name:     "in",
   240  			selector: "env in (prod, dev)",
   241  			expected: "yes",
   242  		},
   243  		{
   244  			name:     "not-in",
   245  			selector: "env notin (prod, dev)",
   246  			expected: "no",
   247  		},
   248  	}
   249  
   250  	for _, test := range tests {
   251  		t.Run(test.name, func(t *testing.T) {
   252  			code := fmt.Sprintf(`
   253  			local labels = { env: 'dev', region: 'us-west' };
   254  			if std.native('labelsMatchSelector')(labels, '%s') then 'yes' else 'no'
   255  `, test.selector)
   256  			ret, err := vm.EvaluateAnonymousSnippet("test.jsonnet", code)
   257  			check(t, err, ret, fmt.Sprintf(`"%s"`+"\n", test.expected))
   258  		})
   259  	}
   260  }
   261  
   262  func TestLabelSelectorNegative(t *testing.T) {
   263  	vm := jsonnet.MakeVM()
   264  	Register(vm)
   265  	tests := []struct {
   266  		name     string
   267  		code     string
   268  		errMatch *regexp.Regexp
   269  	}{
   270  		{
   271  			name:     "bad map",
   272  			code:     `std.native('labelsMatchSelector')([],'foo')`,
   273  			errMatch: regexp.MustCompile(`invalid labels type, \[\]interface {}, want a map`),
   274  		},
   275  		{
   276  			name:     "non-string map",
   277  			code:     `std.native('labelsMatchSelector')({ foo: {} },'foo')`,
   278  			errMatch: regexp.MustCompile(`invalid label map value, map\[string\]interface {}, want a string`),
   279  		},
   280  		{
   281  			name:     "bad selector type",
   282  			code:     `std.native('labelsMatchSelector')({},{})`,
   283  			errMatch: regexp.MustCompile(`invalid selector of type map\[string\]interface {}, want a string`),
   284  		},
   285  		{
   286  			name:     "bad selector",
   287  			code:     `std.native('labelsMatchSelector')({},'!!env')`,
   288  			errMatch: regexp.MustCompile(`invalid label selector: '!!env'`),
   289  		},
   290  	}
   291  	for _, test := range tests {
   292  		t.Run(test.name, func(t *testing.T) {
   293  			_, err := vm.EvaluateAnonymousSnippet("test.jsonnet", test.code)
   294  			if err == nil {
   295  				t.Errorf("labelsMatchSelector succeeded on invalid input")
   296  			}
   297  			if !test.errMatch.MatchString(err.Error()) {
   298  				t.Errorf("message %q does not match %v", err.Error(), test.errMatch)
   299  			}
   300  		})
   301  	}
   302  
   303  }