github.com/aristanetworks/goarista@v0.0.0-20240514173732-cca2755bbd44/gnmi/path_test.go (about)

     1  // Copyright (c) 2017 Arista Networks, Inc.
     2  // Use of this source code is governed by the Apache License 2.0
     3  // that can be found in the COPYING file.
     4  
     5  package gnmi
     6  
     7  import (
     8  	"fmt"
     9  	"testing"
    10  
    11  	"github.com/aristanetworks/goarista/test"
    12  
    13  	pb "github.com/openconfig/gnmi/proto/gnmi"
    14  )
    15  
    16  func p(s ...string) []string {
    17  	return s
    18  }
    19  
    20  func TestSplitPath(t *testing.T) {
    21  	for i, tc := range []struct {
    22  		in  string
    23  		exp []string
    24  	}{{
    25  		in:  "/foo/bar",
    26  		exp: p("foo", "bar"),
    27  	}, {
    28  		in:  "/foo/bar/",
    29  		exp: p("foo", "bar"),
    30  	}, {
    31  		in:  "//foo//bar//",
    32  		exp: p("", "foo", "", "bar", ""),
    33  	}, {
    34  		in:  "/foo[name=///]/bar",
    35  		exp: p("foo[name=///]", "bar"),
    36  	}, {
    37  		in:  `/foo[name=[\\\]/]/bar`,
    38  		exp: p(`foo[name=[\\\]/]`, "bar"),
    39  	}, {
    40  		in:  `/foo[name=[\\]/bar`,
    41  		exp: p(`foo[name=[\\]`, "bar"),
    42  	}, {
    43  		in:  "/foo[a=1][b=2]/bar",
    44  		exp: p("foo[a=1][b=2]", "bar"),
    45  	}, {
    46  		in:  "/foo[a=1\\]2][b=2]/bar",
    47  		exp: p("foo[a=1\\]2][b=2]", "bar"),
    48  	}, {
    49  		in:  "/foo[a=1][b=2]/bar\\baz",
    50  		exp: p("foo[a=1][b=2]", "bar\\baz"),
    51  	}} {
    52  		got := SplitPath(tc.in)
    53  		if !test.DeepEqual(tc.exp, got) {
    54  			t.Errorf("[%d] unexpect split for %q. Expected: %v, Got: %v",
    55  				i, tc.in, tc.exp, got)
    56  		}
    57  	}
    58  }
    59  
    60  func TestStrPath(t *testing.T) {
    61  	for i, tc := range []struct {
    62  		path string
    63  	}{{
    64  		path: "/",
    65  	}, {
    66  		path: "/foo/bar",
    67  	}, {
    68  		path: "/foo[name=a]/bar",
    69  	}, {
    70  		path: "/foo[a=1][b=2]/bar",
    71  	}, {
    72  		path: "/foo[a=1\\]2][b=2]/bar",
    73  	}, {
    74  		path: "/foo[a=1][b=2]/bar\\/baz",
    75  	}, {
    76  		path: `/foo[a\==1]`,
    77  	}, {
    78  		path: `/f\/o\[o[a\==1]`,
    79  	}} {
    80  		sElms := SplitPath(tc.path)
    81  		pbPath, err := ParseGNMIElements(sElms)
    82  		if err != nil {
    83  			t.Errorf("failed to parse %s: %s", sElms, err)
    84  		}
    85  		s := StrPath(pbPath)
    86  		if tc.path != s {
    87  			t.Errorf("[%d] want %s, got %s", i, tc.path, s)
    88  		}
    89  	}
    90  }
    91  
    92  func TestStrPathBackwardsCompat(t *testing.T) {
    93  	for i, tc := range []struct {
    94  		path *pb.Path
    95  		str  string
    96  	}{{
    97  		path: &pb.Path{
    98  			Element: p("foo[a=1][b=2]", "bar"),
    99  		},
   100  		str: "/foo[a=1][b=2]/bar",
   101  	}} {
   102  		got := StrPath(tc.path)
   103  		if got != tc.str {
   104  			t.Errorf("[%d] want %q, got %q", i, tc.str, got)
   105  		}
   106  	}
   107  }
   108  
   109  func TestParseElement(t *testing.T) {
   110  	// test cases
   111  	cases := []struct {
   112  		// name is the name of the test useful if you want to run a single test
   113  		// from the command line -run TestParseElement/<name>
   114  		name string
   115  		// in is the path element to be parsed
   116  		in string
   117  		// fieldName is field name (YANG node name) expected to be parsed from the path element.
   118  		// Normally this is simply the path element, or if the path element contains keys this is
   119  		// the text before the first [
   120  		fieldName string
   121  		// keys is a map of the expected key value pairs from within the []s in the
   122  		// `path element.
   123  		//
   124  		// For example prefix[ip-prefix=10.0.0.0/24][masklength-range=26..28]
   125  		// fieldName would be "prefix"
   126  		// keys would be {"ip-prefix": "10.0.0.0/24", "masklength-range": "26..28"}
   127  		keys map[string]string
   128  		// expectedError is the exact error we expect.
   129  		expectedError error
   130  	}{{
   131  		name:      "no_elms",
   132  		in:        "hello",
   133  		fieldName: "hello",
   134  	}, {
   135  		name:          "single_open",
   136  		in:            "[",
   137  		expectedError: fmt.Errorf("failed to find element name in %q", "["),
   138  	}, {
   139  		name:          "no_equal_no_close",
   140  		in:            "hello[there",
   141  		expectedError: fmt.Errorf("failed to find '=' in %q", "[there"),
   142  	}, {
   143  		name:          "no_equals",
   144  		in:            "hello[there]",
   145  		expectedError: fmt.Errorf("failed to find '=' in %q", "[there]"),
   146  	}, {
   147  		name:      "no_left_side",
   148  		in:        "hello[=there]",
   149  		fieldName: "hello",
   150  		keys:      map[string]string{"": "there"},
   151  	}, {
   152  		name:      "no_right_side",
   153  		in:        "hello[there=]",
   154  		fieldName: "hello",
   155  		keys:      map[string]string{"there": ""},
   156  	}, {
   157  		name:          "hanging_escape",
   158  		in:            "hello[there\\",
   159  		expectedError: fmt.Errorf("failed to find '=' in %q", "[there\\"),
   160  	}, {
   161  		name:      "single_name_value",
   162  		in:        "hello[there=where]",
   163  		fieldName: "hello",
   164  		keys:      map[string]string{"there": "where"},
   165  	}, {
   166  		name:      "single_value_with=",
   167  		in:        "hello[there=whe=r=e]",
   168  		fieldName: "hello",
   169  		keys:      map[string]string{"there": "whe=r=e"},
   170  	}, {
   171  		name:      "single_value_with=_and_escaped_]",
   172  		in:        `hello[there=whe=\]r=e]`,
   173  		fieldName: "hello",
   174  		keys:      map[string]string{"there": `whe=]r=e`},
   175  	}, {
   176  		name:      "single_value_with[",
   177  		in:        "hello[there=w[[here]",
   178  		fieldName: "hello",
   179  		keys:      map[string]string{"there": "w[[here"},
   180  	}, {
   181  		name:          "value_single_open",
   182  		in:            "hello[first=value][",
   183  		expectedError: fmt.Errorf("failed to find '=' in %q", "["),
   184  	}, {
   185  		name:          "value_no_close",
   186  		in:            "hello[there=where][somename",
   187  		expectedError: fmt.Errorf("failed to find '=' in %q", "[somename"),
   188  	}, {
   189  		name:          "value_no_equals",
   190  		in:            "hello[there=where][somename]",
   191  		expectedError: fmt.Errorf("failed to find '=' in %q", "[somename]"),
   192  	}, {
   193  		name:      "no_left_side",
   194  		in:        "hello[there=where][=somevalue]",
   195  		fieldName: "hello",
   196  		keys:      map[string]string{"there": "where", "": "somevalue"},
   197  	}, {
   198  		name:      "no_right_side",
   199  		in:        "hello[there=where][somename=]",
   200  		fieldName: "hello",
   201  		keys:      map[string]string{"there": "where", "somename": ""},
   202  	}, {
   203  		name:      "two_name_values",
   204  		in:        "hello[there=where][somename=somevalue]",
   205  		fieldName: "hello",
   206  		keys:      map[string]string{"there": "where", "somename": "somevalue"},
   207  	}, {
   208  		name:      "three_name_values",
   209  		in:        "hello[there=where][somename=somevalue][anothername=value]",
   210  		fieldName: "hello",
   211  		keys: map[string]string{"there": "where", "somename": "somevalue",
   212  			"anothername": "value"},
   213  	}, {
   214  		name:      "aserisk_value",
   215  		in:        "hello[there=*][somename=somevalue][anothername=value]",
   216  		fieldName: "hello",
   217  		keys: map[string]string{"there": "*", "somename": "somevalue",
   218  			"anothername": "value"},
   219  	}, {
   220  		name:      "escaped =",
   221  		in:        `hello[foo\==bar]`,
   222  		fieldName: "hello",
   223  		keys:      map[string]string{"foo=": "bar"},
   224  	}, {
   225  		name:      "escaped [",
   226  		in:        `hell\[o[foo=bar]`,
   227  		fieldName: "hell[o",
   228  		keys:      map[string]string{"foo": "bar"},
   229  	}}
   230  
   231  	for _, tc := range cases {
   232  		t.Run(tc.name, func(t *testing.T) {
   233  			fieldName, keys, err := parseElement(tc.in)
   234  			if !test.DeepEqual(tc.expectedError, err) {
   235  				t.Fatalf("[%s] expected err %#v, got %#v", tc.name, tc.expectedError, err)
   236  			}
   237  			if !test.DeepEqual(tc.keys, keys) {
   238  				t.Fatalf("[%s] expected output %#v, got %#v", tc.name, tc.keys, keys)
   239  			}
   240  			if tc.fieldName != fieldName {
   241  				t.Fatalf("[%s] expected field name %s, got %s", tc.name, tc.fieldName, fieldName)
   242  			}
   243  		})
   244  	}
   245  }
   246  
   247  func TestParseKeys(t *testing.T) {
   248  	// test cases
   249  	cases := []struct {
   250  		// name is the name of the test useful if you want to run a single test
   251  		// from the command line -run TestParseElement/<name>
   252  		name string
   253  		// in is the path element to be parsed
   254  		in string
   255  		// keys is a map of the expected key value pairs from within the []s in the
   256  		// `path element.
   257  		//
   258  		// For example prefix[ip-prefix=10.0.0.0/24][masklength-range=26..28]
   259  		// fieldName would be "prefix"
   260  		// keys would be {"ip-prefix": "10.0.0.0/24", "masklength-range": "26..28"}
   261  		keys map[string]string
   262  		// expectedError is the exact error we expect.
   263  		expectedError error
   264  	}{{
   265  		name:          "no_equal_no_close",
   266  		in:            "[there",
   267  		expectedError: fmt.Errorf("failed to find '=' in %q", "[there"),
   268  	}, {
   269  		name:          "no_equals",
   270  		in:            "[there]",
   271  		expectedError: fmt.Errorf("failed to find '=' in %q", "[there]"),
   272  	}, {
   273  		name: "no_left_side",
   274  		in:   "[=there]",
   275  		keys: map[string]string{"": "there"},
   276  	}, {
   277  		name: "no_right_side",
   278  		in:   "[there=]",
   279  		keys: map[string]string{"there": ""},
   280  	}, {
   281  		name:          "hanging_escape",
   282  		in:            "[there\\",
   283  		expectedError: fmt.Errorf("failed to find '=' in %q", "[there\\"),
   284  	}, {
   285  		name: "single_name_value",
   286  		in:   "[there=where]",
   287  		keys: map[string]string{"there": "where"},
   288  	}, {
   289  		name: "single_value_with=",
   290  		in:   "[there=whe=r=e]",
   291  		keys: map[string]string{"there": "whe=r=e"},
   292  	}, {
   293  		name: "single_value_with=_and_escaped_]",
   294  		in:   `[there=whe=\]r=e]`,
   295  		keys: map[string]string{"there": `whe=]r=e`},
   296  	}, {
   297  		name: "single_value_with[",
   298  		in:   "[there=w[[here]",
   299  		keys: map[string]string{"there": "w[[here"},
   300  	}, {
   301  		name:          "value_single_open",
   302  		in:            "[first=value][",
   303  		expectedError: fmt.Errorf("failed to find '=' in %q", "["),
   304  	}, {
   305  		name:          "value_no_close",
   306  		in:            "[there=where][somename",
   307  		expectedError: fmt.Errorf("failed to find '=' in %q", "[somename"),
   308  	}, {
   309  		name:          "value_no_equals",
   310  		in:            "[there=where][somename]",
   311  		expectedError: fmt.Errorf("failed to find '=' in %q", "[somename]"),
   312  	}, {
   313  		name: "no_left_side",
   314  		in:   "[there=where][=somevalue]",
   315  		keys: map[string]string{"there": "where", "": "somevalue"},
   316  	}, {
   317  		name: "no_right_side",
   318  		in:   "[there=where][somename=]",
   319  		keys: map[string]string{"there": "where", "somename": ""},
   320  	}, {
   321  		name: "two_name_values",
   322  		in:   "[there=where][somename=somevalue]",
   323  		keys: map[string]string{"there": "where", "somename": "somevalue"},
   324  	}, {
   325  		name: "three_name_values",
   326  		in:   "[there=where][somename=somevalue][anothername=value]",
   327  		keys: map[string]string{"there": "where", "somename": "somevalue",
   328  			"anothername": "value"},
   329  	}, {
   330  		name: "aserisk_value",
   331  		in:   "[there=*][somename=somevalue][anothername=value]",
   332  		keys: map[string]string{"there": "*", "somename": "somevalue",
   333  			"anothername": "value"},
   334  	}, {
   335  		name: "escaped =",
   336  		in:   `[foo\==bar]`,
   337  		keys: map[string]string{"foo=": "bar"},
   338  	}}
   339  
   340  	for _, tc := range cases {
   341  		t.Run(tc.name, func(t *testing.T) {
   342  			keys, err := ParseKeys(tc.in)
   343  			if !test.DeepEqual(tc.expectedError, err) {
   344  				t.Fatalf("[%s] expected err %#v, got %#v", tc.name, tc.expectedError, err)
   345  			}
   346  			if !test.DeepEqual(tc.keys, keys) {
   347  				t.Fatalf("[%s] expected output %#v, got %#v", tc.name, tc.keys, keys)
   348  			}
   349  		})
   350  	}
   351  }
   352  
   353  func strToPath(pathStr string) *pb.Path {
   354  	splitPath := SplitPath(pathStr)
   355  	path, _ := ParseGNMIElements(splitPath)
   356  	path.Element = nil
   357  	return path
   358  }
   359  
   360  func strsToPaths(pathStrs []string) []*pb.Path {
   361  	var paths []*pb.Path
   362  	for _, splitPath := range SplitPaths(pathStrs) {
   363  		path, _ := ParseGNMIElements(splitPath)
   364  		path.Element = nil
   365  		paths = append(paths, path)
   366  	}
   367  	return paths
   368  }
   369  
   370  func TestJoinPath(t *testing.T) {
   371  	cases := []struct {
   372  		paths []*pb.Path
   373  		exp   string
   374  	}{{
   375  		paths: strsToPaths([]string{"/foo/bar", "/baz/qux"}),
   376  		exp:   "/foo/bar/baz/qux",
   377  	},
   378  		{
   379  			paths: strsToPaths([]string{
   380  				"/foo/bar[somekey=someval][otherkey=otherval]", "/baz/qux"}),
   381  			exp: "/foo/bar[otherkey=otherval][somekey=someval]/baz/qux",
   382  		},
   383  		{
   384  			paths: strsToPaths([]string{
   385  				"/foo/bar[somekey=someval][otherkey=otherval]",
   386  				"/baz/qux[somekey=someval][otherkey=otherval]"}),
   387  			exp: "/foo/bar[otherkey=otherval][somekey=someval]/" +
   388  				"baz/qux[otherkey=otherval][somekey=someval]",
   389  		},
   390  		{
   391  			paths: []*pb.Path{
   392  				{Element: []string{"foo", "bar[somekey=someval][otherkey=otherval]"}},
   393  				{Element: []string{"baz", "qux[somekey=someval][otherkey=otherval]"}}},
   394  			exp: "/foo/bar[somekey=someval][otherkey=otherval]/" +
   395  				"baz/qux[somekey=someval][otherkey=otherval]",
   396  		},
   397  		{
   398  			paths: []*pb.Path{
   399  				nil,
   400  				{Element: []string{"baz", "qux[somekey=someval][otherkey=otherval]"}},
   401  				nil,
   402  				{Element: []string{"foo", "bar[somekey=someval][otherkey=otherval]"}},
   403  				nil},
   404  			exp: "baz/qux[otherkey=otherval][somekey=someval]/" +
   405  				"foo/bar[somekey=someval][otherkey=otherval]",
   406  		},
   407  	}
   408  
   409  	for _, tc := range cases {
   410  		got := JoinPaths(tc.paths...)
   411  		exp := strToPath(tc.exp)
   412  		exp.Element = nil
   413  		if !test.DeepEqual(got, exp) {
   414  			t.Fatalf("ERROR!\n Got: %s,\n Want %s\n", got, exp)
   415  		}
   416  	}
   417  }
   418  
   419  func BenchmarkPathElementToSigleElementName(b *testing.B) {
   420  	for i := 0; i < b.N; i++ {
   421  		_, _, _ = parseElement("hello")
   422  	}
   423  }
   424  
   425  func BenchmarkPathElementTwoKeys(b *testing.B) {
   426  	for i := 0; i < b.N; i++ {
   427  		_, _, _ = parseElement("hello[hello=world][bye=moon]")
   428  	}
   429  }
   430  
   431  func BenchmarkPathElementBadKeys(b *testing.B) {
   432  	for i := 0; i < b.N; i++ {
   433  		_, _, _ = parseElement("hello[hello=world][byemoon]")
   434  	}
   435  }
   436  
   437  func BenchmarkPathElementMaxKeys(b *testing.B) {
   438  	for i := 0; i < b.N; i++ {
   439  		_, _, _ = parseElement("hello[name=firstName][name=secondName][name=thirdName]" +
   440  			"[name=fourthName][name=fifthName][name=sixthName]")
   441  	}
   442  }
   443  
   444  func BenchmarkKeyToString(b *testing.B) {
   445  	for _, bench := range []struct {
   446  		name string
   447  		key  map[string]string
   448  	}{{
   449  		name: "singlekey",
   450  		key:  map[string]string{"abcdefghijkm": "nopqrstuvwxyz"},
   451  	}, {
   452  		name: "fivekeys",
   453  		key: map[string]string{
   454  			"one":   "one",
   455  			"two":   "two",
   456  			"three": "three",
   457  			"four":  "four",
   458  			"five":  "five",
   459  		},
   460  	}, {
   461  		name: "escaped",
   462  		key: map[string]string{
   463  			"foo=": "bar",
   464  			"foo":  "ba]r]",
   465  		},
   466  	}} {
   467  		b.Run(bench.name, func(b *testing.B) {
   468  			b.ReportAllocs()
   469  			for i := 0; i < b.N; i++ {
   470  				KeyToString(bench.key)
   471  			}
   472  		})
   473  	}
   474  }