1+ /*******************************************************************************
2+ * Copyright (c) 2025 Red Hat, Inc.
3+ * Distributed under license by Red Hat, Inc. All rights reserved.
4+ * This program is made available under the terms of the
5+ * Eclipse Public License v2.0 which accompanies this distribution,
6+ * and is available at http://www.eclipse.org/legal/epl-v20.html
7+ *
8+ * Contributors:
9+ * Red Hat, Inc. - initial API and implementation
10+ ******************************************************************************/
11+
12+ package org .jboss .tools .intellij .componentanalysis .golang ;
13+
14+ import com .intellij .psi .PsiElement ;
15+ import com .intellij .psi .PsiFile ;
16+ import com .intellij .testFramework .fixtures .BasePlatformTestCase ;
17+ import org .jboss .tools .intellij .componentanalysis .Dependency ;
18+ import org .junit .Test ;
19+
20+ import java .util .List ;
21+ import java .util .Map ;
22+
23+ /**
24+ * Comprehensive test to verify the GoCAAnnotator fix for all dependency declaration formats:
25+ * 1. Single-line require and replace statements
26+ * 2. Require and replace blocks
27+ * 3. Mixed cases
28+ */
29+ public class GoCAAnnotatorTest extends BasePlatformTestCase {
30+
31+ /**
32+ * Testable subclass that exposes the protected getDependencies method
33+ */
34+ private static class TestableGoCAAnnotator extends GoCAAnnotator {
35+ @ Override
36+ public Map <Dependency , List <PsiElement >> getDependencies (PsiFile file ) {
37+ return super .getDependencies (file );
38+ }
39+ }
40+
41+ /**
42+ * Test Case 1: Single-line require and replace statements
43+ */
44+ @ Test
45+ public void testSingleLineRequireAndReplaceStatements () {
46+ String goModContent = """
47+ module test-single-line
48+
49+ go 1.20
50+
51+ require golang.org/x/net v0.10.0
52+ require golang.org/x/text v0.9.0 // indirect
53+ require google.golang.org/protobuf v1.30.0
54+ require github.com/google/go-cmp v0.5.9 // indirect
55+
56+ replace github.com/gin-gonic/gin v1.9.1 => github.com/myfork/gin v1.9.1-patch.2
57+ replace github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20250416174521-4eb003743b54
58+ replace golang.org/x/crypto => golang.org/x/crypto v0.1.0
59+ """ ;
60+
61+ TestableGoCAAnnotator annotator = new TestableGoCAAnnotator ();
62+ PsiFile file = myFixture .configureByText ("go.mod" , goModContent );
63+ Map <Dependency , List <PsiElement >> dependencies = annotator .getDependencies (file );
64+
65+ assertEquals ("Should find 7 dependencies (4 require + 3 replace targets)" , 7 , dependencies .size ());
66+
67+ // Verify require dependencies
68+ assertTrue ("Should contain golang.org/x/net" , containsDependency (dependencies , "golang.org/x" , "net" , "v0.10.0" ));
69+ assertTrue ("Should contain golang.org/x/text" , containsDependency (dependencies , "golang.org/x" , "text" , "v0.9.0" ));
70+ assertTrue ("Should contain google.golang.org/protobuf" , containsDependency (dependencies , "google.golang.org" , "protobuf" , "v1.30.0" ));
71+ assertTrue ("Should contain github.com/google/go-cmp" , containsDependency (dependencies , "github.com/google" , "go-cmp" , "v0.5.9" ));
72+
73+ // Verify replace target dependencies
74+ assertTrue ("Should contain github.com/myfork/gin" , containsDependency (dependencies , "github.com/myfork" , "gin" , "v1.9.1-patch.2" ));
75+ assertTrue ("Should contain github.com/openshift/onsi-ginkgo/v2" , containsDependency (dependencies , "github.com/openshift/onsi-ginkgo" , "v2" , "v2.6.1-0.20250416174521-4eb003743b54" ));
76+ assertTrue ("Should contain golang.org/x/crypto" , containsDependency (dependencies , "golang.org/x" , "crypto" , "v0.1.0" ));
77+ }
78+
79+ /**
80+ * Test Case 2: Require and replace blocks
81+ */
82+ @ Test
83+ public void testRequireAndReplaceBlocks () {
84+ String goModContent = """
85+ module test-blocks
86+
87+ go 1.20
88+
89+ require (
90+ github.com/gin-gonic/gin v1.4.0
91+ github.com/spf13/viper v1.3.2
92+ google.golang.org/protobuf v1.30.0
93+ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 // indirect
94+ github.com/dgrijalva/jwt-go v3.2.0+incompatible
95+ )
96+
97+ replace (
98+ github.com/gin-gonic/gin v1.9.1 => github.com/myfork/gin v1.9.1-patch.2
99+ github.com/onsi/ginkgo/v2 => github.com/openshift/onsi-ginkgo/v2 v2.6.1-0.20250416174521-4eb003743b54
100+ golang.org/x/crypto => golang.org/x/crypto v0.1.0
101+ )
102+ """ ;
103+
104+ TestableGoCAAnnotator annotator = new TestableGoCAAnnotator ();
105+ PsiFile file = myFixture .configureByText ("go.mod" , goModContent );
106+ Map <Dependency , List <PsiElement >> dependencies = annotator .getDependencies (file );
107+
108+ assertEquals ("Should find 8 dependencies (5 require + 3 replace targets)" , 8 , dependencies .size ());
109+
110+ // Verify require block dependencies
111+ assertTrue ("Should contain github.com/gin-gonic/gin" , containsDependency (dependencies , "github.com/gin-gonic" , "gin" , "v1.4.0" ));
112+ assertTrue ("Should contain github.com/spf13/viper" , containsDependency (dependencies , "github.com/spf13" , "viper" , "v1.3.2" ));
113+ assertTrue ("Should contain google.golang.org/protobuf" , containsDependency (dependencies , "google.golang.org" , "protobuf" , "v1.30.0" ));
114+ assertTrue ("Should contain golang.org/x/crypto" , containsDependency (dependencies , "golang.org/x" , "crypto" , "v0.0.0-20190308221718-c2843e01d9a2" ));
115+ assertTrue ("Should contain github.com/dgrijalva/jwt-go" , containsDependency (dependencies , "github.com/dgrijalva" , "jwt-go" , "v3.2.0+incompatible" ));
116+
117+ // Verify replace block target dependencies
118+ assertTrue ("Should contain github.com/myfork/gin" , containsDependency (dependencies , "github.com/myfork" , "gin" , "v1.9.1-patch.2" ));
119+ assertTrue ("Should contain github.com/openshift/onsi-ginkgo/v2" , containsDependency (dependencies , "github.com/openshift/onsi-ginkgo" , "v2" , "v2.6.1-0.20250416174521-4eb003743b54" ));
120+ assertTrue ("Should contain golang.org/x/crypto v0.1.0" , containsDependency (dependencies , "golang.org/x" , "crypto" , "v0.1.0" ));
121+ }
122+
123+ /**
124+ * Test Case 3: Mixed cases (single-line and blocks combined)
125+ */
126+ @ Test
127+ public void testMixedRequireAndReplaceStatements () {
128+ String goModContent = """
129+ module test-mixed
130+
131+ go 1.20
132+
133+ require golang.org/x/net v0.10.0
134+
135+ require (
136+ github.com/gin-gonic/gin v1.4.0
137+ github.com/spf13/viper v1.3.2
138+ )
139+
140+ require google.golang.org/protobuf v1.30.0
141+
142+ replace github.com/gin-gonic/gin v1.4.0 => github.com/myfork/gin v1.4.1
143+
144+ replace (
145+ golang.org/x/crypto => golang.org/x/crypto v0.1.0
146+ github.com/old/module => github.com/new/module v1.0.0
147+ )
148+
149+ require golang.org/x/text v0.9.0 // indirect
150+ """ ;
151+
152+ TestableGoCAAnnotator annotator = new TestableGoCAAnnotator ();
153+ PsiFile file = myFixture .configureByText ("go.mod" , goModContent );
154+ Map <Dependency , List <PsiElement >> dependencies = annotator .getDependencies (file );
155+
156+ assertEquals ("Should find 8 dependencies from mixed statements" , 8 , dependencies .size ());
157+
158+ // Verify single-line require dependencies
159+ assertTrue ("Should contain golang.org/x/net" , containsDependency (dependencies , "golang.org/x" , "net" , "v0.10.0" ));
160+ assertTrue ("Should contain google.golang.org/protobuf" , containsDependency (dependencies , "google.golang.org" , "protobuf" , "v1.30.0" ));
161+ assertTrue ("Should contain golang.org/x/text" , containsDependency (dependencies , "golang.org/x" , "text" , "v0.9.0" ));
162+
163+ // Verify require block dependencies
164+ assertTrue ("Should contain github.com/gin-gonic/gin" , containsDependency (dependencies , "github.com/gin-gonic" , "gin" , "v1.4.0" ));
165+ assertTrue ("Should contain github.com/spf13/viper" , containsDependency (dependencies , "github.com/spf13" , "viper" , "v1.3.2" ));
166+
167+ // Verify single-line replace target dependencies
168+ assertTrue ("Should contain github.com/myfork/gin" , containsDependency (dependencies , "github.com/myfork" , "gin" , "v1.4.1" ));
169+
170+ // Verify replace block target dependencies
171+ assertTrue ("Should contain golang.org/x/crypto" , containsDependency (dependencies , "golang.org/x" , "crypto" , "v0.1.0" ));
172+ assertTrue ("Should contain github.com/new/module" , containsDependency (dependencies , "github.com/new" , "module" , "v1.0.0" ));
173+ }
174+
175+ /**
176+ * Test Case 4: Robust parentheses detection - ensure comments with '(' don't break parsing
177+ */
178+ @ Test
179+ public void testRobustParenthesesDetection () {
180+ String goModContent = """
181+ module test-robust-parentheses
182+
183+ go 1.20
184+
185+ require golang.org/x/net v0.10.0 // This comment has parentheses (test)
186+ require golang.org/x/text v0.9.0 // Another comment (with parens)
187+
188+ require (
189+ github.com/gin-gonic/gin v1.4.0 // Comment with (parentheses) inside block
190+ github.com/spf13/viper v1.3.2
191+ )
192+
193+ replace golang.org/x/crypto v0.1.0 => golang.org/x/crypto v0.2.0 // Comment (test)
194+
195+ replace (
196+ github.com/old/lib => github.com/new/lib v1.0.0
197+ )
198+ """ ;
199+
200+ TestableGoCAAnnotator annotator = new TestableGoCAAnnotator ();
201+ PsiFile file = myFixture .configureByText ("go.mod" , goModContent );
202+ Map <Dependency , List <PsiElement >> dependencies = annotator .getDependencies (file );
203+
204+ assertEquals ("Should find 6 dependencies despite comments with parentheses" , 6 , dependencies .size ());
205+
206+ // Verify single-line requires with comments containing '(' are parsed correctly
207+ assertTrue ("Should contain golang.org/x/net despite comment with ()" ,
208+ containsDependency (dependencies , "golang.org/x" , "net" , "v0.10.0" ));
209+ assertTrue ("Should contain golang.org/x/text despite comment with ()" ,
210+ containsDependency (dependencies , "golang.org/x" , "text" , "v0.9.0" ));
211+
212+ // Verify block requires work correctly
213+ assertTrue ("Should contain github.com/gin-gonic/gin from block" ,
214+ containsDependency (dependencies , "github.com/gin-gonic" , "gin" , "v1.4.0" ));
215+ assertTrue ("Should contain github.com/spf13/viper from block" ,
216+ containsDependency (dependencies , "github.com/spf13" , "viper" , "v1.3.2" ));
217+
218+ // Verify replace statements work correctly
219+ assertTrue ("Should contain golang.org/x/crypto from single-line replace" ,
220+ containsDependency (dependencies , "golang.org/x" , "crypto" , "v0.2.0" ));
221+ assertTrue ("Should contain github.com/new/lib from replace block" ,
222+ containsDependency (dependencies , "github.com/new" , "lib" , "v1.0.0" ));
223+ }
224+
225+ /**
226+ * Helper method to check if dependencies contain a specific dependency with namespace, name, and version.
227+ */
228+ private boolean containsDependency (Map <Dependency , List <PsiElement >> dependencies , String expectedNamespace , String expectedName , String expectedVersion ) {
229+ return dependencies .keySet ().stream ()
230+ .anyMatch (dep -> "golang" .equals (dep .getType ()) &&
231+ expectedNamespace .equals (dep .getNamespace ()) &&
232+ expectedName .equals (dep .getName ()) &&
233+ expectedVersion .equals (dep .getVersion ()));
234+ }
235+ }
0 commit comments