cuelang.org/go@v0.13.0/internal/core/adt/typocheck_test.go (about) 1 // Copyright 2025 CUE Authors 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 // https://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 adt 16 17 import ( 18 "slices" 19 "testing" 20 ) 21 22 func TestReplaceIDs(t *testing.T) { 23 tests := []struct { 24 name string 25 reqSets reqSets 26 replace []replaceID 27 expected reqSets 28 }{{ 29 name: "replace single set", 30 reqSets: reqSets{ 31 {id: 1, size: 1}, 32 }, 33 replace: []replaceID{ 34 {from: 1, to: 2}, 35 }, 36 // The group was already added as a requirement, so the original group 37 // should be deleted. 38 expected: reqSets{}, 39 }, { 40 name: "empty result", 41 reqSets: reqSets{ 42 {id: 1, size: 1}, 43 }, 44 replace: []replaceID{ 45 {from: 1, to: deleteID, add: true}, 46 }, 47 }, { 48 name: "replace first set", 49 reqSets: reqSets{ 50 {id: 1, size: 2}, 51 {id: 2}, 52 {id: 3, size: 2}, 53 {id: 4}, 54 }, 55 replace: []replaceID{ 56 {from: 1, to: 5}, 57 }, 58 expected: reqSets{ 59 {id: 3, size: 2}, 60 {id: 4}, 61 }, 62 }, { 63 name: "replace last set", 64 reqSets: reqSets{ 65 {id: 1, size: 2}, 66 {id: 2}, 67 {id: 3, size: 2}, 68 {id: 4}, 69 }, 70 replace: []replaceID{ 71 {from: 3, to: 5}, 72 }, 73 expected: reqSets{ 74 {id: 1, size: 2}, 75 {id: 2}, 76 }, 77 }, { 78 name: "replace multiple ids", 79 reqSets: reqSets{ 80 {id: 1, size: 1}, 81 {id: 2, size: 1}, 82 }, 83 replace: []replaceID{ 84 {from: 1, to: 3}, 85 {from: 2, to: 4}, 86 }, 87 expected: reqSets{}, 88 }, { 89 name: "replace with zero id", 90 reqSets: reqSets{ 91 {id: 1, size: 1}, 92 }, 93 replace: []replaceID{ 94 {from: 1, to: deleteID}, 95 }, 96 expected: reqSets{}, 97 }, { 98 name: "replace equivalent", 99 reqSets: reqSets{ 100 {id: 1, size: 2}, 101 {id: 2}, // e.g. from embedding 102 }, 103 replace: []replaceID{ 104 {from: 2, to: 3}, // replacing an embedding is additive. 105 }, 106 expected: reqSets{ 107 {id: 1, size: 3}, 108 {id: 2}, 109 {id: 3}, 110 }, 111 }, { 112 name: "no replacement", 113 reqSets: reqSets{ 114 {id: 1, size: 1}, 115 }, 116 replace: []replaceID{}, 117 expected: reqSets{ 118 {id: 1, size: 1}, 119 }, 120 }, { 121 name: "remove multiple from equivalence set", 122 reqSets: reqSets{ 123 {id: 1, size: 4}, 124 {id: 2}, 125 {id: 3}, 126 {id: 4}, 127 {id: 5, size: 1}, 128 }, 129 replace: []replaceID{ 130 {from: 4, to: deleteID}, 131 {from: 2, to: deleteID}, 132 }, 133 expected: reqSets{ 134 {id: 1, size: 2}, 135 {id: 3}, 136 {id: 5, size: 1}, 137 }, 138 }, { 139 name: "add new id to existing set", 140 reqSets: reqSets{ 141 {id: 1, size: 1}, 142 }, 143 replace: []replaceID{ 144 {from: 1, to: 2, add: true}, 145 }, 146 expected: reqSets{ 147 {id: 1, size: 2}, 148 {id: 2}, 149 }, 150 }, { 151 name: "add new id to multiple sets", 152 reqSets: reqSets{ 153 {id: 1, size: 1}, 154 {id: 3, size: 1}, 155 }, 156 replace: []replaceID{ 157 {from: 3, to: 4, add: true}, 158 {from: 1, to: 2, add: true}, 159 }, 160 expected: reqSets{ 161 {id: 1, size: 2}, 162 {id: 2}, 163 {id: 3, size: 2}, 164 {id: 4}, 165 }, 166 }, { 167 name: "add new id to empty set", 168 reqSets: reqSets{}, 169 replace: []replaceID{ 170 {from: 0, to: 1, add: true}, 171 }, 172 expected: reqSets{}, 173 }, { 174 name: "add new id to non-existent set", 175 reqSets: reqSets{ 176 {id: 1, size: 1}, 177 }, 178 replace: []replaceID{ 179 {from: 2, to: 3, add: true}, 180 }, 181 expected: reqSets{ 182 {id: 1, size: 1}, 183 }, 184 }, { 185 name: "add then delete", 186 reqSets: reqSets{ 187 {id: 1, size: 1}, // delete this 188 {id: 3, size: 1}, 189 }, 190 replace: []replaceID{ 191 {from: 1, to: 2, add: true}, 192 {from: 1, to: deleteID}, 193 }, 194 expected: reqSets{ 195 {id: 3, size: 1}, 196 }, 197 }, { 198 name: "delete then add", 199 reqSets: reqSets{ 200 {id: 1, size: 1}, // delete this 201 {id: 3, size: 1}, 202 }, 203 replace: []replaceID{ 204 {from: 1, to: deleteID}, 205 {from: 1, to: 2, add: true}, 206 }, 207 expected: reqSets{ 208 {id: 3, size: 1}, 209 }, 210 }, { 211 name: "fixed point", 212 reqSets: reqSets{ 213 {id: 1, size: 1}, 214 {id: 4, size: 2}, 215 {id: 1}, 216 }, 217 replace: []replaceID{ 218 {from: 1, to: 2, add: true}, 219 {from: 2, to: 3, add: true}, 220 {from: 3, to: 4, add: true}, 221 }, 222 expected: reqSets{ 223 {id: 1, size: 4}, 224 {id: 2}, 225 {id: 3}, 226 {id: 4}, 227 {id: 4, size: 4}, 228 {id: 1}, 229 {id: 2}, 230 {id: 3}, 231 }, 232 }, { 233 name: "fixed point with jumps", 234 reqSets: reqSets{ 235 {id: 4, size: 1}, 236 {id: 1, size: 1}, 237 }, 238 replace: []replaceID{ 239 {from: 1, to: 3, add: true}, 240 {from: 2, to: 1, add: true}, 241 {from: 3, to: 2, add: true}, 242 }, 243 expected: reqSets{ 244 {id: 4, size: 1}, 245 {id: 1, size: 3}, 246 {id: 3}, // TODO: maybe order? 247 {id: 2}, 248 }, 249 }, { 250 name: "fixed idempotent", 251 reqSets: reqSets{ 252 {id: 1, size: 3}, 253 {id: 3}, 254 {id: 2}, 255 {id: 4, size: 2}, 256 {id: 1}, 257 }, 258 replace: []replaceID{ 259 {from: 1, to: 3, add: true}, 260 {from: 2, to: 1, add: true}, 261 {from: 3, to: 2, add: true}, 262 }, 263 expected: reqSets{ 264 {id: 1, size: 3}, 265 {id: 3}, 266 {id: 2}, 267 {id: 4, size: 4}, 268 {id: 1}, 269 {id: 3}, 270 {id: 2}, 271 }, 272 }, { 273 name: "add and drop", 274 reqSets: reqSets{ 275 // A main group needs to be fully deleted in case of a replacement. 276 // This corresponds to that #B can be dropped as a requirement 277 // for `c` in `#B: c: #A` when replacing it with #A. 278 {id: 1, size: 1}, // add to this set. 279 {id: 2, size: 2}, // drop this set. 280 // A replacement of an equivalent id should just add the new id. 281 // This corresponds to embeddings being additive. 282 {id: 1}, 283 }, 284 replace: []replaceID{ 285 // A main group needs to be fully deleted in case of a replacement. 286 // This corresponds to that #B can be dropped as a requirement 287 // for `c` in `#B: c: #A` when replacing it with #A. 288 {from: 1, to: 3, add: true}, 289 // A replacement of an equivalent id should just add the new id. 290 // This corresponds to embeddings being additive. 291 {from: 2, to: 3}, 292 }, 293 expected: reqSets{ 294 {id: 1, size: 2}, 295 {id: 3}, 296 }, 297 }, { 298 name: "drop and add", 299 reqSets: reqSets{ 300 {id: 1, size: 1}, 301 {id: 2, size: 2}, 302 {id: 1}, 303 }, 304 replace: []replaceID{ 305 {from: 1, to: 3}, 306 {from: 1, to: 3, add: true}, 307 }, 308 expected: reqSets{ 309 {id: 2, size: 3}, 310 {id: 1}, 311 {id: 3}, 312 }, 313 }, { 314 name: "cycle", 315 reqSets: []reqSet{ 316 {id: 1, size: 1}, 317 {id: 2, size: 1}, 318 {id: 3, size: 1, del: 2}, 319 {id: 4, size: 1, del: 2}, 320 }, 321 replace: []replaceID{ 322 {from: 1, to: 2, add: true}, // , headOnly: true}, 323 {from: 2, to: 3, add: true}, 324 {from: 2, to: 4, add: true}, 325 {from: 3, to: 1, add: true}, 326 {from: 4, to: 1, add: true}, 327 }, 328 expected: reqSets{ 329 {id: 1, size: 4}, 330 {id: 2}, 331 {id: 3}, 332 {id: 4}, 333 {id: 2, size: 4}, 334 {id: 3}, 335 {id: 1}, 336 {id: 4}, 337 {id: 3, size: 2, del: 2}, 338 {id: 1}, 339 {id: 4, size: 2, del: 2}, 340 {id: 1}, 341 }, 342 }, { 343 name: "exclude 1", 344 reqSets: []reqSet{ 345 {id: 3, size: 1, del: 2}, 346 }, 347 replace: []replaceID{ 348 {from: 1, to: 2, add: true}, 349 {from: 2, to: 3, add: true}, 350 {from: 3, to: 1, add: true}, 351 }, 352 expected: reqSets{ 353 {id: 3, size: 2, del: 2}, 354 {id: 1}, 355 }, 356 }, { 357 name: "exclude 2", 358 reqSets: []reqSet{ 359 {id: 5, size: 1}, 360 {id: 6, size: 1}, 361 {id: 7, size: 1, del: 6}, 362 {id: 8, size: 1, del: 6}, 363 }, 364 replace: []replaceID{ 365 {from: 5, to: 6, add: true}, 366 {from: 6, to: 7, add: true}, 367 {from: 6, to: 8, add: true}, 368 {from: 7, to: 0, add: true}, 369 {from: 8, to: 0, add: true}, 370 }, 371 expected: reqSets{ 372 {id: 5, size: 4}, 373 {id: 6}, 374 {id: 7}, 375 {id: 8}, 376 {id: 6, size: 3}, 377 {id: 7}, 378 {id: 8}, 379 {id: 7, size: 2, del: 6}, 380 {id: 0}, 381 {id: 8, size: 2, del: 6}, 382 {id: 0}, 383 }, 384 }, { 385 name: "exclude 3", 386 reqSets: []reqSet{ 387 {id: 5, size: 1}, 388 {id: 8, size: 1, del: 7}, 389 {id: 9, size: 1, del: 7}, 390 }, 391 replace: []replaceID{ 392 {from: 5, to: 6, add: true}, 393 {from: 6, to: 7, add: true}, 394 {from: 7, to: 8, add: true}, 395 {from: 7, to: 9, add: true}, 396 {from: 8, to: 6, add: true}, 397 {from: 9, to: 6, add: true}, 398 }, 399 expected: reqSets{ 400 {id: 5, size: 5}, 401 {id: 6}, 402 {id: 7}, 403 {id: 8}, 404 {id: 9}, 405 {id: 8, size: 2, del: 7}, 406 {id: 6}, 407 {id: 9, size: 2, del: 7}, 408 {id: 6}, 409 }, 410 }, { 411 name: "exclude 4", 412 // represents 413 // #a: [>="k"]: int // 11 414 // #b: [<="m"]: int // 12 415 // #c: [>="w"]: int // 13 416 // #d: [<="y"]: int // 14 417 // X: { // 8 418 // #a & #b // 9 419 // #c & #d // 10 420 // } 421 // ignored groups (9, 10), are omitted. 422 reqSets: []reqSet{ 423 {id: 8, size: 1}, 424 {id: 11, size: 1, del: 9}, 425 {id: 12, size: 1, del: 9}, 426 {id: 13, size: 1, del: 10}, 427 {id: 14, size: 1, del: 10}, 428 }, 429 replace: []replaceID{ 430 {from: 8, to: 9, add: true}, 431 {from: 8, to: 10, add: true}, 432 {from: 9, to: 11, add: true}, 433 {from: 11, to: 8, add: true}, 434 {from: 9, to: 12, add: true}, 435 {from: 12, to: 8, add: true}, 436 {from: 10, to: 13, add: true}, 437 {from: 13, to: 8, add: true}, 438 {from: 10, to: 14, add: true}, 439 {from: 14, to: 8, add: true}, 440 }, 441 expected: reqSets{ 442 {id: 8, size: 7}, 443 {id: 9}, 444 {id: 11}, 445 {id: 12}, 446 {id: 10}, 447 {id: 13}, 448 {id: 14}, 449 {id: 11, size: 5, del: 9}, 450 {id: 8}, 451 {id: 10}, 452 {id: 13}, 453 {id: 14}, 454 {id: 12, size: 5, del: 9}, 455 {id: 8}, 456 {id: 10}, 457 {id: 13}, 458 {id: 14}, 459 {id: 13, size: 5, del: 10}, 460 {id: 8}, 461 {id: 9}, 462 {id: 11}, 463 {id: 12}, 464 {id: 14, size: 5, del: 10}, 465 {id: 8}, 466 {id: 9}, 467 {id: 11}, 468 {id: 12}, 469 }, 470 }} 471 472 for _, tt := range tests { 473 t.Run(tt.name, func(t *testing.T) { 474 if tt.name != "exclude1" { 475 // return 476 } 477 tt.reqSets.assert() 478 tt.expected.assert() 479 480 tt.reqSets.replaceIDs(&OpContext{}, tt.replace...) 481 if !slices.Equal(tt.reqSets, tt.expected) { 482 t.Errorf("got: \n%v, want:\n%v", tt.reqSets, tt.expected) 483 } 484 }) 485 } 486 } 487 488 func TestHasEvidence(t *testing.T) { 489 tests := []struct { 490 name string 491 reqSets reqSets 492 conjuncts []conjunctInfo 493 want bool 494 }{{ 495 name: "single match", 496 reqSets: reqSets{ 497 {id: 1, size: 1}, 498 }, 499 conjuncts: []conjunctInfo{ 500 {id: 1}, 501 }, 502 want: true, 503 }, { 504 name: "no match", 505 reqSets: reqSets{ 506 {id: 1, size: 1}, 507 }, 508 conjuncts: []conjunctInfo{ 509 {id: 2}, 510 }, 511 want: false, 512 }, { 513 name: "no conjuncts", 514 reqSets: reqSets{ 515 {id: 1, size: 1}, 516 }, 517 conjuncts: []conjunctInfo{}, 518 want: false, 519 }, { 520 name: "no requirements", 521 reqSets: reqSets{}, 522 conjuncts: []conjunctInfo{ 523 {id: 2}, 524 }, 525 want: true, 526 }, { 527 name: "no requirements, no conjuncts", 528 reqSets: reqSets{}, 529 conjuncts: []conjunctInfo{}, 530 want: true, 531 }, { 532 name: "multiple, all match", 533 reqSets: reqSets{ 534 {id: 1, size: 1}, 535 {id: 2, size: 1}, 536 }, 537 conjuncts: []conjunctInfo{ 538 {id: 1}, 539 {id: 2}, 540 }, 541 want: true, 542 }, { 543 name: "multiple, one does not match", 544 reqSets: reqSets{ 545 {id: 1, size: 1}, 546 {id: 2, size: 1}, 547 }, 548 conjuncts: []conjunctInfo{ 549 {id: 2}, 550 }, 551 want: false, 552 }, { 553 name: "multiset match", 554 reqSets: reqSets{ 555 {id: 1, size: 2}, 556 {id: 2}, 557 }, 558 conjuncts: []conjunctInfo{ 559 {id: 2}, 560 }, 561 want: true, 562 }, { 563 name: "multiset no match", 564 reqSets: reqSets{ 565 {id: 1, size: 2}, 566 {id: 2}, 567 }, 568 conjuncts: []conjunctInfo{ 569 {id: 3}, 570 }, 571 want: false, 572 }} 573 574 n := &nodeContext{} 575 n.ctx = &OpContext{} 576 for _, tt := range tests { 577 t.Run(tt.name, func(t *testing.T) { 578 tt.reqSets.assert() 579 580 if got := n.hasEvidenceForAll(tt.reqSets, tt.conjuncts); got != tt.want { 581 t.Errorf("got %v, want %v", got, tt.want) 582 } 583 }) 584 } 585 } 586 func TestMergeCloseInfo(t *testing.T) { 587 tests := []struct { 588 name string 589 nv *nodeContext 590 nw *nodeContext 591 expected *nodeContext 592 }{{ 593 name: "merge with no conflicts", 594 nv: &nodeContext{ 595 node: &Vertex{ 596 Arcs: []*Vertex{ 597 {Label: 1, state: &nodeContext{}}, 598 }, 599 }, 600 conjunctInfo: []conjunctInfo{ 601 {id: 1}, 602 }, 603 replaceIDs: []replaceID{ 604 {from: 1, to: 2}, 605 }, 606 }, 607 nw: &nodeContext{ 608 node: &Vertex{ 609 Arcs: []*Vertex{ 610 {Label: 1, state: &nodeContext{}}, 611 }, 612 }, 613 conjunctInfo: []conjunctInfo{ 614 {id: 2}, 615 }, 616 replaceIDs: []replaceID{ 617 {from: 2, to: 3}, 618 }, 619 }, 620 expected: &nodeContext{ 621 conjunctInfo: []conjunctInfo{ 622 {id: 1}, 623 {id: 2}, 624 }, 625 replaceIDs: []replaceID{ 626 {from: 1, to: 2}, 627 {from: 2, to: 3}, 628 }, 629 }, 630 }, { 631 name: "merge with conflicts", 632 nv: &nodeContext{ 633 node: &Vertex{ 634 Arcs: []*Vertex{ 635 {Label: 1, state: &nodeContext{}}, 636 }, 637 }, 638 conjunctInfo: []conjunctInfo{ 639 {id: 1}, 640 }, 641 replaceIDs: []replaceID{ 642 {from: 1, to: 2}, 643 }, 644 }, 645 nw: &nodeContext{ 646 node: &Vertex{ 647 Arcs: []*Vertex{ 648 {Label: 2, state: &nodeContext{}}, 649 }, 650 }, 651 conjunctInfo: []conjunctInfo{ 652 {id: 1}, 653 }, 654 replaceIDs: []replaceID{ 655 {from: 1, to: 3}, 656 }, 657 }, 658 expected: &nodeContext{ 659 conjunctInfo: []conjunctInfo{ 660 {id: 1}, 661 }, 662 replaceIDs: []replaceID{ 663 {from: 1, to: 2}, 664 {from: 1, to: 3}, 665 }, 666 }, 667 }, 668 } 669 670 for _, tt := range tests { 671 t.Run(tt.name, func(t *testing.T) { 672 mergeCloseInfo(tt.nv, tt.nw) 673 if !slices.Equal(tt.nv.conjunctInfo, tt.expected.conjunctInfo) { 674 t.Errorf("conjunctInfo got %v, want %v", tt.nv.conjunctInfo, tt.expected.conjunctInfo) 675 } 676 if !slices.Equal(tt.nv.replaceIDs, tt.expected.replaceIDs) { 677 t.Errorf("replaceIDs got %v, want %v", tt.nv.replaceIDs, tt.expected.replaceIDs) 678 } 679 }) 680 } 681 }