github.com/openconfig/goyang@v1.4.5/pkg/yang/node_test.go (about) 1 // Copyright 2019 Google Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package yang 16 17 import ( 18 "errors" 19 "fmt" 20 "testing" 21 22 "github.com/google/go-cmp/cmp" 23 "github.com/openconfig/gnmi/errdiff" 24 ) 25 26 func TestNodePath(t *testing.T) { 27 tests := []struct { 28 desc string 29 in Node 30 want string 31 }{{ 32 desc: "basic", 33 in: &Leaf{ 34 Name: "bar", 35 Parent: &Container{ 36 Name: "c", 37 Parent: &List{ 38 Name: "b", 39 Parent: &Module{ 40 Name: "foo", 41 }, 42 }, 43 }, 44 }, 45 want: "/foo/b/c/bar", 46 }, { 47 desc: "nil input node", 48 in: nil, 49 want: "", 50 }, { 51 desc: "single node", 52 in: &Module{ 53 Name: "foo", 54 }, 55 want: "/foo", 56 }} 57 58 for _, tt := range tests { 59 t.Run(tt.desc, func(t *testing.T) { 60 if diff := cmp.Diff(NodePath(tt.in), tt.want); diff != "" { 61 t.Errorf("(-got, +want):\n%s", diff) 62 } 63 }) 64 } 65 } 66 67 // TestNode provides a framework for processing tests that can check particular 68 // nodes being added to the grammar. It can be used to ensure that particular 69 // statement combinations are supported, especially where they are opaque to 70 // the YANG library. 71 func TestNode(t *testing.T) { 72 tests := []struct { 73 desc string 74 inFn func(*Modules) (Node, error) 75 inModules map[string]string 76 wantNode func(Node) error 77 wantErrSubstr string 78 }{{ 79 desc: "import reference statement", 80 inFn: func(ms *Modules) (Node, error) { 81 82 const module = "test" 83 m, ok := ms.Modules[module] 84 if !ok { 85 return nil, fmt.Errorf("can't find module %q", module) 86 } 87 88 if len(m.Import) == 0 { 89 return nil, fmt.Errorf("node %v is missing imports", m) 90 } 91 92 return m.Import[0], nil 93 }, 94 inModules: map[string]string{ 95 "test": ` 96 module test { 97 prefix "t"; 98 namespace "urn:t"; 99 100 import foo { 101 prefix "f"; 102 reference "bar"; 103 } 104 } 105 `, 106 "foo": ` 107 module foo { 108 prefix "f"; 109 namespace "urn:f"; 110 } 111 `, 112 }, 113 wantNode: func(n Node) error { 114 is, ok := n.(*Import) 115 if !ok { 116 return fmt.Errorf("got node: %v, want type: import", n) 117 } 118 119 switch { 120 case is.Reference == nil: 121 return errors.New("did not get expected reference, got: nil, want: *yang.Statement") 122 case is.Reference.Statement().Argument != "bar": 123 return fmt.Errorf("did not get expected reference, got: %v, want: 'bar'", is.Reference.Statement()) 124 } 125 126 return nil 127 }, 128 }, { 129 desc: "get submodule from prefix in submodule", 130 inFn: func(ms *Modules) (Node, error) { 131 132 m, ok := ms.SubModules["foo"] 133 if !ok { 134 return nil, fmt.Errorf("can't find submodule in %v", ms) 135 } 136 137 if m.BelongsTo == nil { 138 return nil, fmt.Errorf("node %v is missing belongs-to", m) 139 } 140 141 return m.BelongsTo, nil 142 }, 143 inModules: map[string]string{ 144 "test": ` 145 module test { 146 prefix "t"; 147 namespace "urn:t"; 148 149 include foo { 150 revision-date 2008-01-01; 151 } 152 } 153 `, 154 "foo": ` 155 submodule foo { 156 belongs-to test { 157 prefix "t"; 158 } 159 } 160 `, 161 }, 162 wantNode: func(n Node) error { 163 is, ok := n.(*BelongsTo) 164 if !ok { 165 return fmt.Errorf("got node: %v, want type: belongs-to", n) 166 } 167 168 switch { 169 case is.Prefix == nil: 170 return errors.New("did not get expected reference, got: nil, want: *yang.Statement") 171 case is.Prefix.Statement().Argument != "t": 172 return fmt.Errorf("did not get expected reference, got: %v, want: 't'", is.Prefix.Statement()) 173 } 174 175 m := FindModuleByPrefix(is, is.Prefix.Statement().Argument) 176 if m == nil { 177 return fmt.Errorf("can't find module from submodule's belongs-to prefix value") 178 } 179 if want := "foo"; m.Name != want { 180 return fmt.Errorf("module from submodule's belongs-to prefix value doesn't match, got %q, want %q", m.Name, want) 181 } 182 183 return nil 184 }, 185 }, { 186 desc: "import statement from submodule", 187 inFn: func(ms *Modules) (Node, error) { 188 189 m, ok := ms.SubModules["foo"] 190 if !ok { 191 return nil, fmt.Errorf("can't find submodule in %v", ms) 192 } 193 194 if len(m.Import) == 0 { 195 return nil, fmt.Errorf("node %v is missing import statement", m) 196 } 197 198 return m.Import[0], nil 199 }, 200 inModules: map[string]string{ 201 "test": ` 202 module test { 203 prefix "t"; 204 namespace "urn:t"; 205 206 include foo { 207 revision-date 2008-01-01; 208 } 209 210 typedef t { 211 type string; 212 } 213 } 214 `, 215 "foo": ` 216 submodule foo { 217 belongs-to test { 218 prefix "t"; 219 } 220 221 import test2 { 222 prefix "t2"; 223 description "test2 module"; 224 } 225 } 226 `, 227 "test2": ` 228 module test2 { 229 prefix "t2"; 230 namespace "urn:t2"; 231 } 232 `, 233 }, 234 wantNode: func(n Node) error { 235 is, ok := n.(*Import) 236 if !ok { 237 return fmt.Errorf("got node: %v, want type: belongs-to", n) 238 } 239 240 switch { 241 case is.Prefix == nil: 242 return errors.New("did not get expected reference, got: nil, want: *yang.Statement") 243 case is.Prefix.Statement().Argument != "t2": 244 return fmt.Errorf("did not get expected reference, got: %v, want: 't'", is.Prefix.Statement()) 245 } 246 247 m := FindModuleByPrefix(is, is.Prefix.Statement().Argument) 248 if m == nil { 249 return fmt.Errorf("can't find module from submodule's import prefix value") 250 } 251 if want := "test2"; m.Name != want { 252 return fmt.Errorf("module from submodule's import prefix value doesn't match, got %q, want %q", m.Name, want) 253 } 254 255 return nil 256 }, 257 }, { 258 desc: "import description statement", 259 inFn: func(ms *Modules) (Node, error) { 260 261 const module = "test" 262 m, ok := ms.Modules[module] 263 if !ok { 264 return nil, fmt.Errorf("can't find module %q", module) 265 } 266 267 if len(m.Import) == 0 { 268 return nil, fmt.Errorf("node %v is missing imports", m) 269 } 270 271 return m.Import[0], nil 272 }, 273 inModules: map[string]string{ 274 "test": ` 275 module test { 276 prefix "t"; 277 namespace "urn:t"; 278 279 import foo { 280 prefix "f"; 281 description "foo module"; 282 } 283 } 284 `, 285 "foo": ` 286 module foo { 287 prefix "f"; 288 namespace "urn:f"; 289 } 290 `, 291 }, 292 wantNode: func(n Node) error { 293 is, ok := n.(*Import) 294 if !ok { 295 return fmt.Errorf("got node: %v, want type: import", n) 296 } 297 298 switch { 299 case is.Description == nil: 300 return errors.New("did not get expected reference, got: nil, want: *yang.Statement") 301 case is.Description.Statement().Argument != "foo module": 302 return fmt.Errorf("did not get expected reference, got: '%v', want: 'foo module'", is.Description.Statement().Argument) 303 } 304 305 return nil 306 }, 307 }, { 308 desc: "Test matchingExtensions", 309 inFn: func(ms *Modules) (Node, error) { 310 311 module := "test" 312 m, ok := ms.Modules[module] 313 if !ok { 314 return nil, fmt.Errorf("can't find module %q", module) 315 } 316 317 if len(m.Leaf) == 0 { 318 return nil, fmt.Errorf("node %v is missing imports", m) 319 } 320 321 module = "foo" 322 if _, ok := ms.Modules[module]; !ok { 323 return nil, fmt.Errorf("can't find module %q", module) 324 } 325 326 return m.Leaf[0].Type, nil 327 }, 328 inModules: map[string]string{ 329 "test": ` 330 module test { 331 prefix "t"; 332 namespace "urn:t"; 333 334 import foo { 335 prefix "f"; 336 description "foo module"; 337 } 338 339 import foo2 { 340 prefix "f2"; 341 description "foo2 module"; 342 } 343 344 leaf test-leaf { 345 type string { 346 pattern 'alpha'; 347 // Test different modules and different ext names. 348 f:bar 'boo'; 349 f2:bar 'boo2'; 350 351 f:bar 'coo'; 352 f2:bar 'coo2'; 353 354 f:far 'doo'; 355 f2:far 'doo2'; 356 357 f:bar 'foo'; 358 f2:bar 'foo2'; 359 360 f:far 'goo'; 361 f2:far 'goo2'; 362 } 363 } 364 } 365 `, 366 "foo": ` 367 module foo { 368 prefix "f"; 369 namespace "urn:f"; 370 371 extension bar { 372 argument "baz"; 373 } 374 375 extension far { 376 argument "baz"; 377 } 378 } 379 `, 380 "foo2": ` 381 module foo2 { 382 prefix "f2"; 383 namespace "urn:f2"; 384 385 extension bar { 386 argument "baz"; 387 } 388 389 extension far { 390 argument "baz"; 391 } 392 } 393 `, 394 }, 395 wantNode: func(n Node) error { 396 n, ok := n.(*Type) 397 if !ok { 398 return fmt.Errorf("got node: %v, want type: Leaf", n) 399 } 400 401 var bars []string 402 matches, err := matchingExtensions(n, n.Exts(), "foo", "bar") 403 if err != nil { 404 return err 405 } 406 for _, ext := range matches { 407 bars = append(bars, ext.Argument) 408 } 409 410 if diff := cmp.Diff(bars, []string{"boo", "coo", "foo"}); diff != "" { 411 return fmt.Errorf("matchingExtensions (-got, +want):\n%s", diff) 412 } 413 414 return nil 415 }, 416 }, { 417 desc: "Test matchingExtensions when module is not found", 418 inFn: func(ms *Modules) (Node, error) { 419 420 module := "test" 421 m, ok := ms.Modules[module] 422 if !ok { 423 return nil, fmt.Errorf("can't find module %q", module) 424 } 425 426 if len(m.Leaf) == 0 { 427 return nil, fmt.Errorf("node %v is missing imports", m) 428 } 429 430 module = "foo" 431 if _, ok := ms.Modules[module]; !ok { 432 return nil, fmt.Errorf("can't find module %q", module) 433 } 434 435 return m.Leaf[0].Type, nil 436 }, 437 inModules: map[string]string{ 438 "test": ` 439 module test { 440 prefix "t"; 441 namespace "urn:t"; 442 443 import foo { 444 prefix "f"; 445 description "foo module"; 446 } 447 448 leaf test-leaf { 449 type string { 450 pattern 'alpha'; 451 not-found:bar 'foo'; 452 } 453 } 454 } 455 `, 456 "foo": ` 457 module foo { 458 prefix "f"; 459 namespace "urn:f"; 460 461 extension bar { 462 argument "baz"; 463 } 464 465 extension far { 466 argument "baz"; 467 } 468 } 469 `, 470 }, 471 wantNode: func(n Node) error { 472 n, ok := n.(*Type) 473 if !ok { 474 return fmt.Errorf("got node: %v, want type: Leaf", n) 475 } 476 477 var bars []string 478 matches, err := matchingExtensions(n, n.Exts(), "foo", "bar") 479 if err != nil { 480 return err 481 } 482 for _, ext := range matches { 483 bars = append(bars, ext.Argument) 484 } 485 486 if diff := cmp.Diff(bars, []string{"boo", "coo", "foo"}); diff != "" { 487 return fmt.Errorf("matchingExtensions (-got, +want):\n%s", diff) 488 } 489 490 return nil 491 }, 492 wantErrSubstr: `module prefix "not-found" not found`, 493 }} 494 495 for _, tt := range tests { 496 t.Run(tt.desc, func(t *testing.T) { 497 ms := NewModules() 498 499 for n, m := range tt.inModules { 500 if err := ms.Parse(m, n); err != nil { 501 t.Errorf("error parsing module %s, got: %v, want: nil", n, err) 502 } 503 } 504 505 errs := ms.Process() 506 var err error 507 if len(errs) > 1 { 508 t.Fatalf("Got more than 1 error: %v", errs) 509 } else if len(errs) == 1 { 510 err = errs[0] 511 } 512 if diff := errdiff.Substring(err, tt.wantErrSubstr); diff != "" { 513 t.Errorf("Did not get expected error: %s", diff) 514 } 515 if err != nil { 516 return 517 } 518 519 node, err := tt.inFn(ms) 520 if err != nil { 521 t.Fatalf("cannot run in function, %v", err) 522 } 523 524 if err := tt.wantNode(node); err != nil { 525 t.Fatalf("failed check function, %v", err) 526 } 527 }) 528 } 529 } 530 531 func TestModulesFindByPrefix(t *testing.T) { 532 // Some examples of where prefixes might be used are in the following 533 // YANG statements: extension, uses, augment, deviation, type, leafref. 534 // Not all are put into the test here, since the logic is the same for 535 // each. 536 modules := map[string]string{ 537 "foo": `module foo { prefix "foo"; namespace "urn:foo"; include bar; leaf leafref { type leafref { path "../foo:leaf"; } } uses foo:lg; }`, 538 "bar": `submodule bar { belongs-to foo { prefix "bar"; } container c { uses bar:lg; } grouping lg { leaf leaf { type string; } } }`, 539 "baz": `module baz { prefix "foo"; namespace "urn:foo"; import foo { prefix f; } extension e; uses f:lg; foo:e; }`, 540 } 541 542 ms := NewModules() 543 for name, modtext := range modules { 544 if err := ms.Parse(modtext, name+".yang"); err != nil { 545 t.Fatalf("error parsing module %q: %v", name, err) 546 } 547 } 548 if errs := ms.Process(); errs != nil { 549 for _, err := range errs { 550 t.Errorf("error: %v", err) 551 } 552 t.Fatalf("fatal error(s) calling Process()") 553 } 554 555 for _, tt := range []struct { 556 desc string 557 node Node 558 prefix string 559 want *Module 560 }{ 561 { 562 desc: "nil node", 563 node: nil, 564 prefix: "does-not-exist", 565 want: nil, 566 }, 567 { 568 desc: "module foo", 569 node: ms.Modules["foo"], 570 prefix: "foo", 571 want: ms.Modules["foo"], 572 }, 573 { 574 desc: "submodule bar", 575 node: ms.SubModules["bar"], 576 prefix: "bar", 577 want: ms.SubModules["bar"], 578 }, 579 { 580 desc: "module baz", 581 node: ms.Modules["baz"], 582 prefix: "foo", 583 want: ms.Modules["baz"], 584 }, 585 { 586 desc: "foo leafref", 587 node: ms.Modules["foo"].Leaf[0].Type, 588 prefix: "foo", 589 want: ms.Modules["foo"], 590 }, 591 { 592 desc: "foo uses", 593 node: ms.Modules["foo"].Uses[0], 594 prefix: "foo", 595 want: ms.Modules["foo"], 596 }, 597 { 598 desc: "bar uses", 599 node: ms.SubModules["bar"].Container[0].Uses[0], 600 prefix: "bar", 601 want: ms.SubModules["bar"], 602 }, 603 { 604 desc: "baz uses", 605 node: ms.Modules["baz"].Uses[0], 606 prefix: "f", 607 want: ms.Modules["foo"], 608 }, 609 { 610 desc: "baz extension", 611 node: ms.Modules["baz"], 612 prefix: "foo", 613 want: ms.Modules["baz"], 614 }, 615 } { 616 t.Run(tt.desc, func(t *testing.T) { 617 if got := FindModuleByPrefix(tt.node, tt.prefix); got != tt.want { 618 t.Errorf("got: %v, want: %v", got, tt.want) 619 } 620 }) 621 } 622 }