github.com/google/osv-scalibr@v0.4.1/guidedremediation/internal/manifest/maven/pomxml_test.go (about) 1 // Copyright 2025 Google LLC 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 maven 16 17 import ( 18 "bytes" 19 "io" 20 "os" 21 "path/filepath" 22 "reflect" 23 "runtime" 24 "testing" 25 26 "deps.dev/util/maven" 27 "deps.dev/util/resolve" 28 "deps.dev/util/resolve/dep" 29 "github.com/google/go-cmp/cmp" 30 "github.com/google/osv-scalibr/clients/clienttest" 31 "github.com/google/osv-scalibr/clients/datasource" 32 scalibrfs "github.com/google/osv-scalibr/fs" 33 "github.com/google/osv-scalibr/guidedremediation/internal/manifest" 34 "github.com/google/osv-scalibr/guidedremediation/result" 35 ) 36 37 var ( 38 depMgmt = depTypeWithOrigin("management") 39 depParent = depTypeWithOrigin("parent") 40 depPlugin = depTypeWithOrigin("plugin@org.plugin:plugin") 41 depProfileOne = depTypeWithOrigin("profile@profile-one") 42 depProfileTwoMgmt = depTypeWithOrigin("profile@profile-two@management") 43 ) 44 45 func depTypeWithOrigin(origin string) dep.Type { 46 var result dep.Type 47 result.AddAttr(dep.MavenDependencyOrigin, origin) 48 49 return result 50 } 51 52 func mavenReqKey(t *testing.T, name, artifactType, classifier string) manifest.RequirementKey { 53 t.Helper() 54 var typ dep.Type 55 if artifactType != "" { 56 typ.AddAttr(dep.MavenArtifactType, artifactType) 57 } 58 if classifier != "" { 59 typ.AddAttr(dep.MavenClassifier, classifier) 60 } 61 62 return MakeRequirementKey(resolve.RequirementVersion{ 63 VersionKey: resolve.VersionKey{ 64 PackageKey: resolve.PackageKey{ 65 Name: name, 66 System: resolve.Maven, 67 }, 68 }, 69 Type: typ, 70 }) 71 } 72 73 type testManifest struct { 74 FilePath string 75 Root resolve.Version 76 System resolve.System 77 Requirements []resolve.RequirementVersion 78 Groups map[manifest.RequirementKey][]string 79 EcosystemSpecific ManifestSpecific 80 } 81 82 func checkManifest(t *testing.T, name string, got manifest.Manifest, want testManifest) { 83 t.Helper() 84 if want.FilePath != got.FilePath() { 85 t.Errorf("%s.FilePath() = %q, want %q", name, got.FilePath(), want.FilePath) 86 } 87 if diff := cmp.Diff(want.Root, got.Root()); diff != "" { 88 t.Errorf("%s.Root() (-want +got):\n%s", name, diff) 89 } 90 if want.System != got.System() { 91 t.Errorf("%s.System() = %v, want %v", name, got.System(), want.System) 92 } 93 if diff := cmp.Diff(want.Requirements, got.Requirements()); diff != "" { 94 t.Errorf("%s.Requirements() (-want +got):\n%s", name, diff) 95 } 96 if diff := cmp.Diff(want.Groups, got.Groups()); diff != "" { 97 t.Errorf("%s.Groups() (-want +got):\n%s", name, diff) 98 } 99 if diff := cmp.Diff(want.EcosystemSpecific, got.EcosystemSpecific()); diff != "" { 100 t.Errorf("%s.EcosystemSpecific() (-want +got):\n%s", name, diff) 101 } 102 } 103 104 func compareToFile(t *testing.T, got io.Reader, wantFile string) { 105 t.Helper() 106 wantBytes, err := os.ReadFile(wantFile) 107 if err != nil { 108 t.Fatalf("error reading %s: %v", wantFile, err) 109 } 110 gotBytes, err := io.ReadAll(got) 111 if err != nil { 112 t.Fatalf("error reading manifest: %v", err) 113 } 114 115 if runtime.GOOS == "windows" { 116 // Go doesn't write CRLF in xml on Windows, trying to fix this is difficult. 117 // Just ignore it in the tests. 118 wantBytes = bytes.ReplaceAll(wantBytes, []byte("\r\n"), []byte("\n")) 119 gotBytes = bytes.ReplaceAll(gotBytes, []byte("\r\n"), []byte("\n")) 120 } 121 122 if diff := cmp.Diff(wantBytes, gotBytes); diff != "" { 123 t.Errorf("%s (-want +got):\n%s", wantFile, diff) 124 } 125 } 126 127 func TestReadWrite(t *testing.T) { 128 srv := clienttest.NewMockHTTPServer(t) 129 srv.SetResponse(t, "org/upstream/parent-pom/1.2.3/parent-pom-1.2.3.pom", []byte(` 130 <project> 131 <groupId>org.upstream</groupId> 132 <artifactId>parent-pom</artifactId> 133 <version>1.2.3</version> 134 <packaging>pom</packaging> 135 <properties> 136 <bbb.artifact>bbb</bbb.artifact> 137 <bbb.version>2.2.2</bbb.version> 138 </properties> 139 <dependencyManagement> 140 <dependencies> 141 <dependency> 142 <groupId>org.example</groupId> 143 <artifactId>${bbb.artifact}</artifactId> 144 <version>${bbb.version}</version> 145 </dependency> 146 </dependencies> 147 </dependencyManagement> 148 </project> 149 `)) 150 srv.SetResponse(t, "org/import/import/1.0.0/import-1.0.0.pom", []byte(` 151 <project> 152 <groupId>org.import</groupId> 153 <artifactId>import</artifactId> 154 <version>1.0.0</version> 155 <packaging>pom</packaging> 156 <properties> 157 <ccc.version>3.3.3</ccc.version> 158 </properties> 159 <dependencyManagement> 160 <dependencies> 161 <dependency> 162 <groupId>org.example</groupId> 163 <artifactId>ccc</artifactId> 164 <version>${ccc.version}</version> 165 </dependency> 166 </dependencies> 167 </dependencyManagement> 168 </project> 169 `)) 170 171 client, _ := datasource.NewDefaultMavenRegistryAPIClient(t.Context(), srv.URL) 172 mavenRW, err := GetReadWriter(client) 173 if err != nil { 174 t.Fatalf("error creating ReadWriter: %v", err) 175 } 176 177 fsys := scalibrfs.DirFS("./testdata") 178 got, err := mavenRW.Read("my-app/pom.xml", fsys) 179 if err != nil { 180 t.Fatalf("error reading manifest: %v", err) 181 } 182 183 depType := depMgmt.Clone() 184 depType.AddAttr(dep.MavenArtifactType, "pom") 185 depType.AddAttr(dep.Scope, "import") 186 187 depParent.AddAttr(dep.MavenArtifactType, "pom") 188 189 var depExclusions dep.Type 190 depExclusions.AddAttr(dep.MavenExclusions, "org.exclude:exclude") 191 192 want := testManifest{ 193 FilePath: "my-app/pom.xml", 194 Root: resolve.Version{ 195 VersionKey: resolve.VersionKey{ 196 PackageKey: resolve.PackageKey{ 197 System: resolve.Maven, 198 Name: "com.mycompany.app:my-app", 199 }, 200 VersionType: resolve.Concrete, 201 Version: "1.0", 202 }, 203 }, 204 System: resolve.Maven, 205 Requirements: []resolve.RequirementVersion{ 206 { 207 VersionKey: resolve.VersionKey{ 208 PackageKey: resolve.PackageKey{ 209 System: resolve.Maven, 210 Name: "junit:junit", 211 }, 212 VersionType: resolve.Requirement, 213 Version: "4.12", 214 }, 215 // Type: dep.NewType(dep.Test), test scope is ignored to make resolution work. 216 }, 217 { 218 VersionKey: resolve.VersionKey{ 219 PackageKey: resolve.PackageKey{ 220 System: resolve.Maven, 221 Name: "org.example:abc", 222 }, 223 VersionType: resolve.Requirement, 224 Version: "1.0.1", 225 }, 226 }, 227 { 228 VersionKey: resolve.VersionKey{ 229 PackageKey: resolve.PackageKey{ 230 System: resolve.Maven, 231 Name: "org.example:no-version", 232 }, 233 VersionType: resolve.Requirement, 234 Version: "2.0.0", 235 }, 236 }, 237 { 238 VersionKey: resolve.VersionKey{ 239 PackageKey: resolve.PackageKey{ 240 System: resolve.Maven, 241 Name: "org.example:exclusions", 242 }, 243 VersionType: resolve.Requirement, 244 Version: "1.0.0", 245 }, 246 Type: depExclusions, 247 }, 248 { 249 VersionKey: resolve.VersionKey{ 250 PackageKey: resolve.PackageKey{ 251 System: resolve.Maven, 252 Name: "org.profile:abc", 253 }, 254 VersionType: resolve.Requirement, 255 Version: "1.2.3", 256 }, 257 }, 258 { 259 VersionKey: resolve.VersionKey{ 260 PackageKey: resolve.PackageKey{ 261 System: resolve.Maven, 262 Name: "org.profile:def", 263 }, 264 VersionType: resolve.Requirement, 265 Version: "2.3.4", 266 }, 267 }, 268 { 269 VersionKey: resolve.VersionKey{ 270 PackageKey: resolve.PackageKey{ 271 System: resolve.Maven, 272 Name: "org.example:ddd", 273 }, 274 VersionType: resolve.Requirement, 275 Version: "1.2.3", 276 }, 277 }, 278 { 279 VersionKey: resolve.VersionKey{ 280 PackageKey: resolve.PackageKey{ 281 System: resolve.Maven, 282 Name: "org.example:xyz", 283 }, 284 VersionType: resolve.Requirement, 285 Version: "2.0.0", 286 }, 287 Type: depMgmt, 288 }, 289 { 290 VersionKey: resolve.VersionKey{ 291 PackageKey: resolve.PackageKey{ 292 System: resolve.Maven, 293 Name: "org.example:no-version", 294 }, 295 VersionType: resolve.Requirement, 296 Version: "2.0.0", 297 }, 298 Type: depMgmt, 299 }, 300 { 301 VersionKey: resolve.VersionKey{ 302 PackageKey: resolve.PackageKey{ 303 System: resolve.Maven, 304 Name: "org.example:aaa", 305 }, 306 VersionType: resolve.Requirement, 307 Version: "1.1.1", 308 }, 309 Type: depMgmt, 310 }, 311 { 312 VersionKey: resolve.VersionKey{ 313 PackageKey: resolve.PackageKey{ 314 System: resolve.Maven, 315 Name: "org.example:bbb", 316 }, 317 VersionType: resolve.Requirement, 318 Version: "2.2.2", 319 }, 320 Type: depMgmt, 321 }, 322 { 323 VersionKey: resolve.VersionKey{ 324 PackageKey: resolve.PackageKey{ 325 System: resolve.Maven, 326 Name: "org.example:ccc", 327 }, 328 VersionType: resolve.Requirement, 329 Version: "3.3.3", 330 }, 331 Type: depMgmt, 332 }, 333 }, 334 Groups: map[manifest.RequirementKey][]string{ 335 mavenReqKey(t, "junit:junit", "", ""): {"test"}, 336 mavenReqKey(t, "org.import:xyz", "pom", ""): {"import"}, 337 }, 338 EcosystemSpecific: ManifestSpecific{ 339 Parent: maven.Parent{ 340 ProjectKey: maven.ProjectKey{ 341 GroupID: "org.parent", 342 ArtifactID: "parent-pom", 343 Version: "1.1.1", 344 }, 345 RelativePath: "../parent/pom.xml", 346 }, 347 ParentPaths: []string{"my-app/pom.xml", "parent/pom.xml", "parent/grandparent/pom.xml"}, 348 Properties: []PropertyWithOrigin{ 349 {Property: maven.Property{Name: "project.build.sourceEncoding", Value: "UTF-8"}}, 350 {Property: maven.Property{Name: "maven.compiler.source", Value: "1.7"}}, 351 {Property: maven.Property{Name: "maven.compiler.target", Value: "1.7"}}, 352 {Property: maven.Property{Name: "junit.version", Value: "4.12"}}, 353 {Property: maven.Property{Name: "zeppelin.daemon.package.base", Value: "../bin"}}, 354 {Property: maven.Property{Name: "def.version", Value: "2.3.4"}, Origin: "profile@profile-one"}, 355 {Property: maven.Property{Name: "aaa.version", Value: "1.1.1"}}, 356 }, 357 OriginalRequirements: []DependencyWithOrigin{ 358 { 359 Dependency: maven.Dependency{GroupID: "org.parent", ArtifactID: "parent-pom", Version: "1.1.1", Type: "pom"}, 360 Origin: "parent", 361 }, 362 { 363 Dependency: maven.Dependency{GroupID: "junit", ArtifactID: "junit", Version: "${junit.version}", Scope: "test"}, 364 }, 365 { 366 Dependency: maven.Dependency{GroupID: "org.example", ArtifactID: "abc", Version: "1.0.1"}, 367 }, 368 { 369 Dependency: maven.Dependency{GroupID: "org.example", ArtifactID: "no-version"}, 370 }, 371 { 372 Dependency: maven.Dependency{GroupID: "org.example", ArtifactID: "exclusions", Version: "1.0.0", 373 Exclusions: []maven.Exclusion{ 374 {GroupID: "org.exclude", ArtifactID: "exclude"}, 375 }}, 376 }, 377 { 378 Dependency: maven.Dependency{GroupID: "org.example", ArtifactID: "xyz", Version: "2.0.0"}, 379 Origin: "management", 380 }, 381 { 382 Dependency: maven.Dependency{GroupID: "org.example", ArtifactID: "no-version", Version: "2.0.0"}, 383 Origin: "management", 384 }, 385 { 386 Dependency: maven.Dependency{GroupID: "org.import", ArtifactID: "import", Version: "1.0.0", Scope: "import", Type: "pom"}, 387 Origin: "management", 388 }, 389 { 390 Dependency: maven.Dependency{GroupID: "org.profile", ArtifactID: "abc", Version: "1.2.3"}, 391 Origin: "profile@profile-one", 392 }, 393 { 394 Dependency: maven.Dependency{GroupID: "org.profile", ArtifactID: "def", Version: "${def.version}"}, 395 Origin: "profile@profile-one", 396 }, 397 { 398 Dependency: maven.Dependency{GroupID: "org.import", ArtifactID: "xyz", Version: "6.6.6", Scope: "import", Type: "pom"}, 399 Origin: "profile@profile-two@management", 400 }, 401 { 402 Dependency: maven.Dependency{GroupID: "org.dep", ArtifactID: "plugin-dep", Version: "2.3.3"}, 403 Origin: "plugin@org.plugin:plugin", 404 }, 405 }, 406 LocalRequirements: []DependencyWithOrigin{ 407 { 408 Dependency: maven.Dependency{GroupID: "org.parent", ArtifactID: "parent-pom", Version: "1.1.1", Type: "pom"}, 409 Origin: "parent", 410 }, 411 { 412 Dependency: maven.Dependency{GroupID: "junit", ArtifactID: "junit", Version: "${junit.version}", Scope: "test"}, 413 }, 414 { 415 Dependency: maven.Dependency{GroupID: "org.example", ArtifactID: "abc", Version: "1.0.1"}, 416 }, 417 { 418 Dependency: maven.Dependency{GroupID: "org.example", ArtifactID: "no-version"}, 419 }, 420 { 421 Dependency: maven.Dependency{GroupID: "org.example", ArtifactID: "exclusions", Version: "1.0.0", 422 Exclusions: []maven.Exclusion{ 423 {GroupID: "org.exclude", ArtifactID: "exclude"}, 424 }}, 425 }, 426 { 427 Dependency: maven.Dependency{GroupID: "org.example", ArtifactID: "xyz", Version: "2.0.0"}, 428 Origin: "management", 429 }, 430 { 431 Dependency: maven.Dependency{GroupID: "org.example", ArtifactID: "no-version", Version: "2.0.0"}, 432 Origin: "management", 433 }, 434 { 435 Dependency: maven.Dependency{GroupID: "org.import", ArtifactID: "import", Version: "1.0.0", Scope: "import", Type: "pom"}, 436 Origin: "management", 437 }, 438 { 439 Dependency: maven.Dependency{GroupID: "org.profile", ArtifactID: "abc", Version: "1.2.3"}, 440 Origin: "profile@profile-one", 441 }, 442 { 443 Dependency: maven.Dependency{GroupID: "org.profile", ArtifactID: "def", Version: "${def.version}"}, 444 Origin: "profile@profile-one", 445 }, 446 { 447 Dependency: maven.Dependency{GroupID: "org.import", ArtifactID: "xyz", Version: "6.6.6", Scope: "import", Type: "pom"}, 448 Origin: "profile@profile-two@management", 449 }, 450 { 451 Dependency: maven.Dependency{GroupID: "org.dep", ArtifactID: "plugin-dep", Version: "2.3.3"}, 452 Origin: "plugin@org.plugin:plugin", 453 }, 454 { 455 Dependency: maven.Dependency{GroupID: "org.grandparent", ArtifactID: "grandparent-pom", Version: "1.1.1", Type: "pom"}, 456 Origin: "parent@parent/pom.xml@parent", 457 }, 458 { 459 Dependency: maven.Dependency{GroupID: "org.example", ArtifactID: "ddd", Version: "1.2.3"}, 460 Origin: "parent@parent/pom.xml", 461 }, 462 { 463 Dependency: maven.Dependency{GroupID: "org.example", ArtifactID: "aaa", Version: "${aaa.version}"}, 464 Origin: "parent@parent/pom.xml@management", 465 }, 466 { 467 Dependency: maven.Dependency{GroupID: "org.upstream", ArtifactID: "parent-pom", Version: "1.2.3", Type: "pom"}, 468 Origin: "parent@parent/grandparent/pom.xml@parent", 469 }, 470 }, 471 RequirementsForUpdates: []resolve.RequirementVersion{ 472 { 473 VersionKey: resolve.VersionKey{ 474 PackageKey: resolve.PackageKey{ 475 System: resolve.Maven, 476 Name: "org.parent:parent-pom", 477 }, 478 VersionType: resolve.Requirement, 479 Version: "1.1.1", 480 }, 481 Type: depParent, 482 }, 483 { 484 VersionKey: resolve.VersionKey{ 485 PackageKey: resolve.PackageKey{ 486 System: resolve.Maven, 487 Name: "org.import:import", 488 }, 489 VersionType: resolve.Requirement, 490 Version: "1.0.0", 491 }, 492 Type: depType, 493 }, 494 { 495 VersionKey: resolve.VersionKey{ 496 PackageKey: resolve.PackageKey{ 497 System: resolve.Maven, 498 Name: "org.profile:abc", 499 }, 500 VersionType: resolve.Requirement, 501 Version: "1.2.3", 502 }, 503 }, 504 { 505 VersionKey: resolve.VersionKey{ 506 PackageKey: resolve.PackageKey{ 507 System: resolve.Maven, 508 Name: "org.profile:def", 509 }, 510 VersionType: resolve.Requirement, 511 Version: "${def.version}", 512 }, 513 }, 514 { 515 VersionKey: resolve.VersionKey{ 516 PackageKey: resolve.PackageKey{ 517 System: resolve.Maven, 518 Name: "org.import:xyz", 519 }, 520 VersionType: resolve.Requirement, 521 Version: "6.6.6", 522 }, 523 Type: depType, 524 }, 525 { 526 VersionKey: resolve.VersionKey{ 527 PackageKey: resolve.PackageKey{ 528 System: resolve.Maven, 529 Name: "org.dep:plugin-dep", 530 }, 531 VersionType: resolve.Requirement, 532 Version: "2.3.3", 533 }, 534 }, 535 }, 536 }, 537 } 538 539 checkManifest(t, "Manifest", got, want) 540 541 // Test writing the files produces the same pom.xml files. 542 dir := t.TempDir() 543 if err := mavenRW.Write(got, fsys, nil, filepath.Join(dir, "my-app", "pom.xml")); err != nil { 544 t.Fatalf("error writing manifest: %v", err) 545 } 546 547 gotFile, err := os.Open(filepath.Join(dir, "my-app", "pom.xml")) 548 if err != nil { 549 t.Fatalf("error opening pom.xml: %v", err) 550 } 551 defer gotFile.Close() 552 compareToFile(t, gotFile, "testdata/my-app/pom.xml") 553 554 gotFile, err = os.Open(filepath.Join(dir, "parent", "pom.xml")) 555 if err != nil { 556 t.Fatalf("error opening pom.xml: %v", err) 557 } 558 defer gotFile.Close() 559 compareToFile(t, gotFile, "testdata/parent/pom.xml") 560 561 gotFile, err = os.Open(filepath.Join(dir, "parent", "grandparent", "pom.xml")) 562 if err != nil { 563 t.Fatalf("error opening pom.xml: %v", err) 564 } 565 defer gotFile.Close() 566 compareToFile(t, gotFile, "testdata/parent/grandparent/pom.xml") 567 } 568 569 func TestMavenWrite(t *testing.T) { 570 dir, err := os.Getwd() 571 if err != nil { 572 t.Fatalf("failed to get current directory: %v", err) 573 } 574 in, err := os.ReadFile(filepath.Join(dir, "testdata", "my-app", "pom.xml")) 575 if err != nil { 576 t.Fatalf("fail to open file: %v", err) 577 } 578 579 patches := Patches{ 580 DependencyPatches: DependencyPatches{ 581 "": map[Patch]bool{ 582 { 583 DependencyKey: maven.DependencyKey{ 584 GroupID: "org.example", 585 ArtifactID: "abc", 586 Type: "jar", 587 }, 588 NewRequire: "1.0.2", 589 }: true, 590 { 591 DependencyKey: maven.DependencyKey{ 592 GroupID: "org.example", 593 ArtifactID: "no-version", 594 Type: "jar", 595 }, 596 NewRequire: "2.0.1", 597 }: true, 598 }, 599 "management": map[Patch]bool{ 600 { 601 DependencyKey: maven.DependencyKey{ 602 GroupID: "org.example", 603 ArtifactID: "xyz", 604 Type: "jar", 605 }, 606 NewRequire: "2.0.1", 607 }: true, 608 { 609 DependencyKey: maven.DependencyKey{ 610 GroupID: "org.example", 611 ArtifactID: "extra-one", 612 Type: "jar", 613 }, 614 NewRequire: "6.6.6", 615 }: false, 616 { 617 DependencyKey: maven.DependencyKey{ 618 GroupID: "org.example", 619 ArtifactID: "extra-two", 620 Type: "jar", 621 }, 622 NewRequire: "9.9.9", 623 }: false, 624 }, 625 "profile@profile-one": map[Patch]bool{ 626 { 627 DependencyKey: maven.DependencyKey{ 628 GroupID: "org.profile", 629 ArtifactID: "abc", 630 Type: "jar", 631 }, 632 NewRequire: "1.2.4", 633 }: true, 634 }, 635 "profile@profile-two@management": map[Patch]bool{ 636 { 637 DependencyKey: maven.DependencyKey{ 638 GroupID: "org.import", 639 ArtifactID: "xyz", 640 Type: "pom", 641 }, 642 NewRequire: "7.0.0", 643 }: true, 644 }, 645 "plugin@org.plugin:plugin": map[Patch]bool{ 646 { 647 DependencyKey: maven.DependencyKey{ 648 GroupID: "org.dep", 649 ArtifactID: "plugin-dep", 650 Type: "jar", 651 }, 652 NewRequire: "2.3.4", 653 }: true, 654 }, 655 }, 656 PropertyPatches: PropertyPatches{ 657 "": { 658 "junit.version": "4.13.2", 659 }, 660 "profile@profile-one": { 661 "def.version": "2.3.5", 662 }, 663 }, 664 } 665 666 out := new(bytes.Buffer) 667 if err := write(string(in), out, patches); err != nil { 668 t.Fatalf("unable to update Maven pom.xml: %v", err) 669 } 670 compareToFile(t, out, filepath.Join(dir, "testdata", "my-app", "write_want.pom.xml")) 671 } 672 673 func TestMavenWriteDM(t *testing.T) { 674 dir, err := os.Getwd() 675 if err != nil { 676 t.Fatalf("failed to get current directory: %v", err) 677 } 678 in, err := os.ReadFile(filepath.Join(dir, "testdata", "no-dependency-management", "pom.xml")) 679 if err != nil { 680 t.Fatalf("fail to open file: %v", err) 681 } 682 683 patches := Patches{ 684 DependencyPatches: DependencyPatches{ 685 "": map[Patch]bool{ 686 { 687 DependencyKey: maven.DependencyKey{ 688 GroupID: "junit", 689 ArtifactID: "junit", 690 Type: "jar", 691 }, 692 NewRequire: "4.13.2", 693 }: true, 694 }, 695 "parent": map[Patch]bool{ 696 { 697 DependencyKey: maven.DependencyKey{ 698 GroupID: "org.parent", 699 ArtifactID: "parent-pom", 700 Type: "jar", 701 }, 702 NewRequire: "1.2.0", 703 }: true, 704 }, 705 "management": map[Patch]bool{ 706 { 707 DependencyKey: maven.DependencyKey{ 708 GroupID: "org.management", 709 ArtifactID: "abc", 710 Type: "jar", 711 }, 712 NewRequire: "1.2.3", 713 }: false, 714 { 715 DependencyKey: maven.DependencyKey{ 716 GroupID: "org.management", 717 ArtifactID: "xyz", 718 Type: "jar", 719 }, 720 NewRequire: "2.3.4", 721 }: false, 722 }, 723 }, 724 } 725 726 out := new(bytes.Buffer) 727 if err := write(string(in), out, patches); err != nil { 728 t.Fatalf("unable to update Maven pom.xml: %v", err) 729 } 730 compareToFile(t, out, filepath.Join(dir, "testdata", "no-dependency-management", "want.pom.xml")) 731 } 732 733 func Test_buildPatches(t *testing.T) { 734 const parentPath = "testdata/parent/pom.xml" 735 736 depProfileTwoMgmt.AddAttr(dep.MavenArtifactType, "pom") 737 depProfileTwoMgmt.AddAttr(dep.Scope, "import") 738 739 depParent.AddAttr(dep.MavenArtifactType, "pom") 740 741 patches := []result.Patch{ 742 { 743 PackageUpdates: []result.PackageUpdate{ 744 { 745 Name: "org.dep:plugin-dep", 746 VersionTo: "2.3.4", 747 Type: depPlugin, 748 }, 749 { 750 Name: "org.example:abc", 751 VersionTo: "1.0.2", 752 }, 753 { 754 Name: "org.example:aaa", 755 VersionTo: "1.2.0", 756 }, 757 { 758 Name: "org.example:ddd", 759 VersionTo: "1.3.0", 760 }, 761 { 762 Name: "org.example:property", 763 VersionTo: "1.0.1", 764 }, 765 { 766 Name: "org.example:same-property", 767 VersionTo: "1.0.1", 768 }, 769 { 770 Name: "org.example:another-property", 771 VersionTo: "1.1.0", 772 }, 773 { 774 Name: "org.example:property-no-update", 775 VersionTo: "2.0.0", 776 }, 777 { 778 Name: "org.example:xyz", 779 VersionTo: "2.0.1", 780 Type: depMgmt, 781 }, 782 { 783 Name: "org.import:xyz", 784 VersionTo: "6.7.0", 785 Type: depProfileTwoMgmt, 786 }, 787 { 788 Name: "org.profile:abc", 789 VersionTo: "1.2.4", 790 Type: depProfileOne, 791 }, 792 { 793 Name: "org.profile:def", 794 VersionTo: "2.3.5", 795 Type: depProfileOne, 796 }, 797 { 798 Name: "org.parent:parent-pom", 799 VersionTo: "1.2.0", 800 Type: depParent, 801 }, 802 { 803 Name: "org.example:suggest", 804 VersionFrom: "1.0.0", 805 VersionTo: "2.0.0", 806 Type: depMgmt, 807 }, 808 { 809 Name: "org.example:override", 810 VersionTo: "2.0.0", 811 Type: depMgmt, 812 }, 813 { 814 Name: "org.example:no-version", 815 VersionTo: "2.0.1", 816 Type: depMgmt, 817 }, 818 }, 819 }, 820 } 821 specific := ManifestSpecific{ 822 Parent: maven.Parent{ 823 ProjectKey: maven.ProjectKey{ 824 GroupID: "org.parent", 825 ArtifactID: "parent-pom", 826 Version: "1.1.1", 827 }, 828 RelativePath: "../parent/pom.xml", 829 }, 830 Properties: []PropertyWithOrigin{ 831 {Property: maven.Property{Name: "property.version", Value: "1.0.0"}}, 832 {Property: maven.Property{Name: "no.update.minor", Value: "9"}}, 833 {Property: maven.Property{Name: "def.version", Value: "2.3.4"}, Origin: "profile@profile-one"}, 834 {Property: maven.Property{Name: "aaa.version", Value: "1.1.1"}, Origin: "parent@" + parentPath}, 835 }, 836 LocalRequirements: []DependencyWithOrigin{ 837 { 838 Dependency: maven.Dependency{GroupID: "org.parent", ArtifactID: "parent-pom", Version: "1.2.0", Type: "pom"}, 839 Origin: "parent", 840 }, 841 { 842 Dependency: maven.Dependency{GroupID: "junit", ArtifactID: "junit", Version: "${junit.version}", Scope: "test"}, 843 }, 844 { 845 Dependency: maven.Dependency{GroupID: "org.example", ArtifactID: "abc", Version: "1.0.1"}, 846 }, 847 { 848 Dependency: maven.Dependency{GroupID: "org.example", ArtifactID: "no-updates", Version: "9.9.9"}, 849 }, 850 { 851 Dependency: maven.Dependency{GroupID: "org.example", ArtifactID: "no-version"}, 852 }, 853 { 854 Dependency: maven.Dependency{GroupID: "org.example", ArtifactID: "property", Version: "${property.version}"}, 855 }, 856 { 857 Dependency: maven.Dependency{GroupID: "org.example", ArtifactID: "property-no-update", Version: "1.${no.update.minor}"}, 858 }, 859 { 860 Dependency: maven.Dependency{GroupID: "org.example", ArtifactID: "same-property", Version: "${property.version}"}, 861 }, 862 { 863 Dependency: maven.Dependency{GroupID: "org.example", ArtifactID: "another-property", Version: "${property.version}"}, 864 }, 865 { 866 Dependency: maven.Dependency{GroupID: "org.example", ArtifactID: "no-version", Version: "2.0.0"}, 867 Origin: "management", 868 }, 869 { 870 Dependency: maven.Dependency{GroupID: "org.example", ArtifactID: "xyz", Version: "2.0.0"}, 871 Origin: "management", 872 }, 873 { 874 Dependency: maven.Dependency{GroupID: "org.profile", ArtifactID: "abc", Version: "1.2.3"}, 875 Origin: "profile@profile-one", 876 }, 877 { 878 Dependency: maven.Dependency{GroupID: "org.profile", ArtifactID: "def", Version: "${def.version}"}, 879 Origin: "profile@profile-one", 880 }, 881 { 882 Dependency: maven.Dependency{GroupID: "org.import", ArtifactID: "xyz", Version: "6.6.6", Scope: "import", Type: "pom"}, 883 Origin: "profile@profile-two@management", 884 }, 885 { 886 Dependency: maven.Dependency{GroupID: "org.dep", ArtifactID: "plugin-dep", Version: "2.3.3"}, 887 Origin: "plugin@org.plugin:plugin", 888 }, 889 { 890 Dependency: maven.Dependency{GroupID: "org.example", ArtifactID: "ddd", Version: "1.2.3"}, 891 Origin: "parent@" + parentPath, 892 }, 893 { 894 Dependency: maven.Dependency{GroupID: "org.example", ArtifactID: "aaa", Version: "${aaa.version}"}, 895 Origin: "parent@" + parentPath + "@management", 896 }, 897 }, 898 } 899 want := map[string]Patches{ 900 "": { 901 DependencyPatches: DependencyPatches{ 902 "": map[Patch]bool{ 903 { 904 DependencyKey: maven.DependencyKey{ 905 GroupID: "org.example", 906 ArtifactID: "abc", 907 Type: "jar", 908 }, 909 NewRequire: "1.0.2", 910 }: true, 911 { 912 DependencyKey: maven.DependencyKey{ 913 GroupID: "org.example", 914 ArtifactID: "another-property", 915 Type: "jar", 916 }, 917 NewRequire: "1.1.0", 918 }: true, 919 { 920 DependencyKey: maven.DependencyKey{ 921 GroupID: "org.example", 922 ArtifactID: "property-no-update", 923 Type: "jar", 924 }, 925 NewRequire: "2.0.0", 926 }: true, 927 }, 928 "management": map[Patch]bool{ 929 { 930 DependencyKey: maven.DependencyKey{ 931 GroupID: "org.example", 932 ArtifactID: "xyz", 933 Type: "jar", 934 }, 935 NewRequire: "2.0.1", 936 }: true, 937 { 938 DependencyKey: maven.DependencyKey{ 939 GroupID: "org.example", 940 ArtifactID: "no-version", 941 Type: "jar", 942 }, 943 NewRequire: "2.0.1", 944 }: true, 945 { 946 DependencyKey: maven.DependencyKey{ 947 GroupID: "org.example", 948 ArtifactID: "override", 949 Type: "jar", 950 }, 951 NewRequire: "2.0.0", 952 }: false, 953 { 954 DependencyKey: maven.DependencyKey{ 955 GroupID: "org.example", 956 ArtifactID: "suggest", 957 Type: "jar", 958 }, 959 NewRequire: "2.0.0", 960 }: false, 961 }, 962 "profile@profile-one": map[Patch]bool{ 963 { 964 DependencyKey: maven.DependencyKey{ 965 GroupID: "org.profile", 966 ArtifactID: "abc", 967 Type: "jar", 968 }, 969 NewRequire: "1.2.4", 970 }: true, 971 }, 972 "profile@profile-two@management": map[Patch]bool{ 973 { 974 DependencyKey: maven.DependencyKey{ 975 GroupID: "org.import", 976 ArtifactID: "xyz", 977 Type: "pom", 978 }, 979 NewRequire: "6.7.0", 980 }: true, 981 }, 982 "plugin@org.plugin:plugin": map[Patch]bool{ 983 { 984 DependencyKey: maven.DependencyKey{ 985 GroupID: "org.dep", 986 ArtifactID: "plugin-dep", 987 Type: "jar", 988 }, 989 NewRequire: "2.3.4", 990 }: true, 991 }, 992 "parent": map[Patch]bool{ 993 { 994 DependencyKey: maven.DependencyKey{ 995 GroupID: "org.parent", 996 ArtifactID: "parent-pom", 997 Type: "pom", 998 }, 999 NewRequire: "1.2.0", 1000 }: true, 1001 }, 1002 }, 1003 PropertyPatches: PropertyPatches{ 1004 "": { 1005 "property.version": "1.0.1", 1006 }, 1007 "profile@profile-one": { 1008 "def.version": "2.3.5", 1009 }, 1010 }, 1011 }, 1012 parentPath: { 1013 DependencyPatches: DependencyPatches{ 1014 "": map[Patch]bool{ 1015 { 1016 DependencyKey: maven.DependencyKey{ 1017 GroupID: "org.example", 1018 ArtifactID: "ddd", 1019 Type: "jar", 1020 }, 1021 NewRequire: "1.3.0", 1022 }: true, 1023 }, 1024 }, 1025 PropertyPatches: PropertyPatches{ 1026 "": { 1027 "aaa.version": "1.2.0", 1028 }, 1029 }, 1030 }, 1031 } 1032 1033 allPatches, err := buildPatches(patches, specific) 1034 if err != nil { 1035 t.Fatalf("failed to build patches: %v", err) 1036 } 1037 if diff := cmp.Diff(want, allPatches); diff != "" { 1038 t.Errorf("result patches mismatch (-want +got):\n%s", diff) 1039 } 1040 } 1041 1042 func Test_generatePropertyPatches(t *testing.T) { 1043 tests := []struct { 1044 s1 string 1045 s2 string 1046 possible bool 1047 patches map[string]string 1048 }{ 1049 {"${version}", "1.2.3", true, map[string]string{"version": "1.2.3"}}, 1050 {"${major}.2.3", "1.2.3", true, map[string]string{"major": "1"}}, 1051 {"1.${minor}.3", "1.2.3", true, map[string]string{"minor": "2"}}, 1052 {"1.2.${patch}", "1.2.3", true, map[string]string{"patch": "3"}}, 1053 {"${major}.${minor}.${patch}", "1.2.3", true, map[string]string{"major": "1", "minor": "2", "patch": "3"}}, 1054 {"${major}.2.3", "2.0.0", false, map[string]string{}}, 1055 {"1.${minor}.3", "2.0.0", false, map[string]string{}}, 1056 } 1057 for _, tt := range tests { 1058 patches, ok := generatePropertyPatches(tt.s1, tt.s2) 1059 if ok != tt.possible || !reflect.DeepEqual(patches, tt.patches) { 1060 t.Errorf("generatePropertyPatches(%s, %s): got %v %v, want %v %v", tt.s1, tt.s2, patches, ok, tt.patches, tt.possible) 1061 } 1062 } 1063 }