Skip to content

Commit cf83e13

Browse files
dnegreiraclaude
andcommitted
test(maven/analyze): add tests for PropertySources and parent POM resolution
Cover the new property source tracking and parent chain resolution added in the previous commit: - PropertySources is attributed to the correct file for both single-pom and directory (analyzeAllPoms) analysis paths - Properties declared in a parent POM outside the project tree (../pom.xml) are found via resolveUnknownProperties and attributed to their source - mergeProperty first-definition-wins and conflict behaviour - resolveUnknownProperties returns correct value/source for a parent chain property and empty slice when the property is absent Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
1 parent cec450b commit cf83e13

1 file changed

Lines changed: 236 additions & 0 deletions

File tree

pkg/languages/java/maven/integration_test.go

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,3 +681,239 @@ func TestConvertDependenciesToPatches(t *testing.T) {
681681
})
682682
}
683683
}
684+
685+
// pomWithParentAndProp returns a parent POM that declares a property.
686+
func pomWithParentAndProp(propName, propValue string) string {
687+
return `<?xml version="1.0" encoding="UTF-8"?>
688+
<project xmlns="http://maven.apache.org/POM/4.0.0">
689+
<modelVersion>4.0.0</modelVersion>
690+
<groupId>com.example</groupId>
691+
<artifactId>parent</artifactId>
692+
<version>1.0.0</version>
693+
<packaging>pom</packaging>
694+
<properties>
695+
<` + propName + `>` + propValue + `</` + propName + `>
696+
</properties>
697+
</project>`
698+
}
699+
700+
// pomWithRelativeParent returns a POM that points at a parent via <relativePath>.
701+
func pomWithRelativeParent(relativePath string) string {
702+
return `<?xml version="1.0" encoding="UTF-8"?>
703+
<project xmlns="http://maven.apache.org/POM/4.0.0">
704+
<modelVersion>4.0.0</modelVersion>
705+
<parent>
706+
<groupId>com.example</groupId>
707+
<artifactId>parent</artifactId>
708+
<version>1.0.0</version>
709+
<relativePath>` + relativePath + `</relativePath>
710+
</parent>
711+
<groupId>com.example</groupId>
712+
<artifactId>child</artifactId>
713+
<version>1.0.0</version>
714+
</project>`
715+
}
716+
717+
// TestAnalyze_PropertySources_SinglePom checks that properties defined in the
718+
// analysed pom.xml itself are attributed to that file.
719+
func TestAnalyze_PropertySources_SinglePom(t *testing.T) {
720+
dir := t.TempDir()
721+
pomContent := `<?xml version="1.0" encoding="UTF-8"?>
722+
<project xmlns="http://maven.apache.org/POM/4.0.0">
723+
<modelVersion>4.0.0</modelVersion>
724+
<groupId>com.example</groupId>
725+
<artifactId>test</artifactId>
726+
<version>1.0.0</version>
727+
<properties>
728+
<netty.version>4.1.100.Final</netty.version>
729+
</properties>
730+
</project>`
731+
pomPath := filepath.Join(dir, "pom.xml")
732+
writeFile(t, pomPath, pomContent)
733+
734+
ma := &MavenAnalyzer{}
735+
result, err := ma.Analyze(t.Context(), pomPath)
736+
if err != nil {
737+
t.Fatalf("Analyze: %v", err)
738+
}
739+
740+
if src := result.PropertySources["netty.version"]; src != "pom.xml" {
741+
t.Errorf("PropertySources[netty.version] = %q, want %q", src, "pom.xml")
742+
}
743+
}
744+
745+
// TestAnalyze_PropertySources_DirectoryAnalysis checks that analyzeAllPoms
746+
// attributes each property to the POM file that declares it.
747+
func TestAnalyze_PropertySources_DirectoryAnalysis(t *testing.T) {
748+
dir := t.TempDir()
749+
writeFile(t, filepath.Join(dir, "pom.xml"), `<?xml version="1.0" encoding="UTF-8"?>
750+
<project xmlns="http://maven.apache.org/POM/4.0.0">
751+
<modelVersion>4.0.0</modelVersion>
752+
<groupId>com.example</groupId><artifactId>root</artifactId><version>1.0.0</version>
753+
<properties><root.prop>1.0</root.prop></properties>
754+
</project>`)
755+
writeFile(t, filepath.Join(dir, "module-a", "pom.xml"), `<?xml version="1.0" encoding="UTF-8"?>
756+
<project xmlns="http://maven.apache.org/POM/4.0.0">
757+
<modelVersion>4.0.0</modelVersion>
758+
<groupId>com.example</groupId><artifactId>module-a</artifactId><version>1.0.0</version>
759+
<properties><module.prop>2.0</module.prop></properties>
760+
</project>`)
761+
762+
ma := &MavenAnalyzer{}
763+
result, err := ma.Analyze(t.Context(), dir)
764+
if err != nil {
765+
t.Fatalf("Analyze: %v", err)
766+
}
767+
768+
if src := result.PropertySources["root.prop"]; src != "pom.xml" {
769+
t.Errorf("PropertySources[root.prop] = %q, want %q", src, "pom.xml")
770+
}
771+
if src := result.PropertySources["module.prop"]; src != filepath.Join("module-a", "pom.xml") {
772+
t.Errorf("PropertySources[module.prop] = %q, want %q", src, filepath.Join("module-a", "pom.xml"))
773+
}
774+
}
775+
776+
// TestAnalyze_PropertySources_ParentPomOutsideTree checks that a property
777+
// declared in a parent POM referenced via <parent><relativePath> (potentially
778+
// outside the project root) is found and attributed correctly.
779+
func TestAnalyze_PropertySources_ParentPomOutsideTree(t *testing.T) {
780+
root := t.TempDir()
781+
// Project layout: root/lib/pom.xml → parent is root/pom.xml
782+
parentPom := filepath.Join(root, "pom.xml")
783+
childPom := filepath.Join(root, "lib", "pom.xml")
784+
785+
writeFile(t, parentPom, pomWithParentAndProp("netty.version", "4.1.100.Final"))
786+
writeFile(t, childPom, pomWithRelativeParent("../pom.xml"))
787+
788+
ma := &MavenAnalyzer{}
789+
result, err := ma.Analyze(t.Context(), childPom)
790+
if err != nil {
791+
t.Fatalf("Analyze: %v", err)
792+
}
793+
794+
if v := result.Properties["netty.version"]; v != "4.1.100.Final" {
795+
t.Errorf("Properties[netty.version] = %q, want 4.1.100.Final", v)
796+
}
797+
if src := result.PropertySources["netty.version"]; src != "../pom.xml" {
798+
t.Errorf("PropertySources[netty.version] = %q, want %q", src, "../pom.xml")
799+
}
800+
}
801+
802+
// TestAnalyze_PropertySources_ParentPomOutsideTree_Directory checks the same
803+
// for directory-mode analysis (analyzeAllPoms path).
804+
func TestAnalyze_PropertySources_ParentPomOutsideTree_Directory(t *testing.T) {
805+
root := t.TempDir()
806+
parentPom := filepath.Join(root, "pom.xml")
807+
childDir := filepath.Join(root, "lib")
808+
childPom := filepath.Join(childDir, "pom.xml")
809+
810+
writeFile(t, parentPom, pomWithParentAndProp("netty.version", "4.1.100.Final"))
811+
// Child POM references a dep via the property so PropertyUsage is populated.
812+
writeFile(t, childPom, `<?xml version="1.0" encoding="UTF-8"?>
813+
<project xmlns="http://maven.apache.org/POM/4.0.0">
814+
<modelVersion>4.0.0</modelVersion>
815+
<parent>
816+
<groupId>com.example</groupId>
817+
<artifactId>parent</artifactId>
818+
<version>1.0.0</version>
819+
<relativePath>../pom.xml</relativePath>
820+
</parent>
821+
<groupId>com.example</groupId>
822+
<artifactId>child</artifactId>
823+
<version>1.0.0</version>
824+
<dependencyManagement>
825+
<dependencies>
826+
<dependency>
827+
<groupId>io.netty</groupId>
828+
<artifactId>netty-all</artifactId>
829+
<version>${netty.version}</version>
830+
</dependency>
831+
</dependencies>
832+
</dependencyManagement>
833+
</project>`)
834+
835+
ma := &MavenAnalyzer{}
836+
result, err := ma.Analyze(t.Context(), childDir)
837+
if err != nil {
838+
t.Fatalf("Analyze: %v", err)
839+
}
840+
841+
if v := result.Properties["netty.version"]; v != "4.1.100.Final" {
842+
t.Errorf("Properties[netty.version] = %q, want 4.1.100.Final", v)
843+
}
844+
if src := result.PropertySources["netty.version"]; src != "../pom.xml" {
845+
t.Errorf("PropertySources[netty.version] = %q, want %q", src, "../pom.xml")
846+
}
847+
}
848+
849+
// TestMergeProperty verifies first-definition-wins and no double-assignment.
850+
func TestMergeProperty(t *testing.T) {
851+
result := &analyzer.AnalysisResult{
852+
Properties: make(map[string]string),
853+
PropertySources: make(map[string]string),
854+
}
855+
856+
if !mergeProperty(t.Context(), result, "k", "v1", "a.xml") {
857+
t.Error("first merge should return true (newly added)")
858+
}
859+
if result.Properties["k"] != "v1" || result.PropertySources["k"] != "a.xml" {
860+
t.Errorf("property not set correctly after first merge")
861+
}
862+
863+
// Same value — no warning, returns false.
864+
if mergeProperty(t.Context(), result, "k", "v1", "b.xml") {
865+
t.Error("merge of same value should return false (already present)")
866+
}
867+
if result.PropertySources["k"] != "a.xml" {
868+
t.Error("source should not change when property already present")
869+
}
870+
871+
// Different value — conflict warning, still returns false, source unchanged.
872+
if mergeProperty(t.Context(), result, "k", "v2", "c.xml") {
873+
t.Error("conflicting merge should return false (already present)")
874+
}
875+
if result.Properties["k"] != "v1" {
876+
t.Error("conflicting value should not overwrite existing")
877+
}
878+
}
879+
880+
// TestResolveUnknownProperties_ParentChain verifies that properties missing
881+
// from the scanned files are found by following <parent><relativePath>.
882+
func TestResolveUnknownProperties_ParentChain(t *testing.T) {
883+
root := t.TempDir()
884+
parentPom := filepath.Join(root, "pom.xml")
885+
childPom := filepath.Join(root, "lib", "pom.xml")
886+
887+
writeFile(t, parentPom, pomWithParentAndProp("netty.version", "4.1.100.Final"))
888+
writeFile(t, childPom, pomWithRelativeParent("../pom.xml"))
889+
890+
usage := map[string]int{"netty.version": 1, "already.found": 1}
891+
known := map[string]string{"already.found": "1.0"} // pre-filled, should be skipped
892+
893+
results := resolveUnknownProperties(t.Context(), usage, known, childPom, filepath.Join(root, "lib"))
894+
895+
if len(results) != 1 {
896+
t.Fatalf("expected 1 result, got %d", len(results))
897+
}
898+
pf := results[0]
899+
if v := pf.Properties["netty.version"]; v != "4.1.100.Final" {
900+
t.Errorf("Properties[netty.version] = %q, want 4.1.100.Final", v)
901+
}
902+
if pf.PomFile != "../pom.xml" {
903+
t.Errorf("PomFile = %q, want %q", pf.PomFile, "../pom.xml")
904+
}
905+
}
906+
907+
// TestResolveUnknownProperties_NotFound verifies that a property absent from
908+
// the entire parent chain returns no results (not an error).
909+
func TestResolveUnknownProperties_NotFound(t *testing.T) {
910+
dir := t.TempDir()
911+
writeFile(t, filepath.Join(dir, "pom.xml"), minimalPOM)
912+
913+
usage := map[string]int{"does.not.exist": 1}
914+
results := resolveUnknownProperties(t.Context(), usage, nil, filepath.Join(dir, "pom.xml"), dir)
915+
916+
if len(results) != 0 {
917+
t.Errorf("expected 0 results for unknown property, got %d", len(results))
918+
}
919+
}

0 commit comments

Comments
 (0)