golang_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. package golang
  2. import (
  3. "fmt"
  4. "os"
  5. "path/filepath"
  6. "strings"
  7. "testing"
  8. )
  9. func TestGolang(t *testing.T) {
  10. // Create a new Golang stack instance
  11. golangStack := &Golang{}
  12. // Test the Name method
  13. expectedName := "Golang"
  14. if golangStack.Name() != expectedName {
  15. t.Errorf("Expected name %s, got %s", expectedName, golangStack.Name())
  16. }
  17. }
  18. func TestAnalyze(t *testing.T) {
  19. golangStack := &Golang{}
  20. tests := []struct {
  21. name string
  22. setup func() string
  23. cleanup func(string)
  24. expected bool
  25. wantErr bool
  26. }{
  27. {
  28. name: "valid go project with go.mod",
  29. setup: func() string {
  30. tempDir, _ := os.MkdirTemp("", "test-go-project-")
  31. os.WriteFile(filepath.Join(tempDir, "go.mod"), []byte("module test\ngo 1.21\n"), 0644)
  32. os.WriteFile(filepath.Join(tempDir, "main.go"), []byte("package main\nfunc main() {}\n"), 0644)
  33. return tempDir
  34. },
  35. cleanup: func(dir string) { os.RemoveAll(dir) },
  36. expected: true,
  37. wantErr: false,
  38. },
  39. {
  40. name: "valid go project with main.go only",
  41. setup: func() string {
  42. tempDir, _ := os.MkdirTemp("", "test-go-project-")
  43. os.WriteFile(filepath.Join(tempDir, "main.go"), []byte("package main\nfunc main() {}\n"), 0644)
  44. return tempDir
  45. },
  46. cleanup: func(dir string) { os.RemoveAll(dir) },
  47. expected: true,
  48. wantErr: false,
  49. },
  50. {
  51. name: "non-go project",
  52. setup: func() string {
  53. tempDir, _ := os.MkdirTemp("", "test-non-go-project-")
  54. os.WriteFile(filepath.Join(tempDir, "package.json"), []byte("{}"), 0644)
  55. return tempDir
  56. },
  57. cleanup: func(dir string) { os.RemoveAll(dir) },
  58. expected: false,
  59. wantErr: false,
  60. },
  61. {
  62. name: "empty directory",
  63. setup: func() string {
  64. tempDir, _ := os.MkdirTemp("", "test-empty-")
  65. return tempDir
  66. },
  67. cleanup: func(dir string) { os.RemoveAll(dir) },
  68. expected: false,
  69. wantErr: false,
  70. },
  71. {
  72. name: "non-existent directory",
  73. setup: func() string {
  74. return "/non/existent/path"
  75. },
  76. cleanup: func(dir string) {},
  77. expected: false,
  78. wantErr: true,
  79. },
  80. }
  81. for _, tt := range tests {
  82. t.Run(tt.name, func(t *testing.T) {
  83. codebasePath := tt.setup()
  84. defer tt.cleanup(codebasePath)
  85. isGo, err := golangStack.Analyze(codebasePath)
  86. if tt.wantErr && err == nil {
  87. t.Errorf("Expected error but got none")
  88. }
  89. if !tt.wantErr && err != nil {
  90. t.Errorf("Unexpected error: %v", err)
  91. }
  92. if isGo != tt.expected {
  93. t.Errorf("Expected %v, got %v", tt.expected, isGo)
  94. }
  95. })
  96. }
  97. }
  98. func TestFindMainPackage(t *testing.T) {
  99. golangStack := &Golang{}
  100. tests := []struct {
  101. name string
  102. setup func() string
  103. cleanup func(string)
  104. expected string
  105. }{
  106. {
  107. name: "main.go in root",
  108. setup: func() string {
  109. tempDir, _ := os.MkdirTemp("", "test-main-root-")
  110. os.WriteFile(filepath.Join(tempDir, "main.go"), []byte("package main\nfunc main() {}\n"), 0644)
  111. return tempDir
  112. },
  113. cleanup: func(dir string) { os.RemoveAll(dir) },
  114. expected: ".",
  115. },
  116. {
  117. name: "main.go in cmd/server",
  118. setup: func() string {
  119. tempDir, _ := os.MkdirTemp("", "test-cmd-server-")
  120. cmdDir := filepath.Join(tempDir, "cmd", "server")
  121. os.MkdirAll(cmdDir, 0755)
  122. os.WriteFile(filepath.Join(cmdDir, "main.go"), []byte("package main\nfunc main() {}\n"), 0644)
  123. return tempDir
  124. },
  125. cleanup: func(dir string) { os.RemoveAll(dir) },
  126. expected: "./cmd/server",
  127. },
  128. {
  129. name: "main function in cmd/web-server/app.go",
  130. setup: func() string {
  131. tempDir, _ := os.MkdirTemp("", "test-cmd-web-server-")
  132. cmdDir := filepath.Join(tempDir, "cmd", "web-server")
  133. os.MkdirAll(cmdDir, 0755)
  134. os.WriteFile(filepath.Join(cmdDir, "app.go"), []byte("package main\nfunc main() {}\n"), 0644)
  135. return tempDir
  136. },
  137. cleanup: func(dir string) { os.RemoveAll(dir) },
  138. expected: "./cmd/web-server",
  139. },
  140. {
  141. name: "no main package found",
  142. setup: func() string {
  143. tempDir, _ := os.MkdirTemp("", "test-no-main-")
  144. libDir := filepath.Join(tempDir, "lib")
  145. os.MkdirAll(libDir, 0755)
  146. os.WriteFile(filepath.Join(libDir, "helper.go"), []byte("package lib\nfunc Helper() {}\n"), 0644)
  147. return tempDir
  148. },
  149. cleanup: func(dir string) { os.RemoveAll(dir) },
  150. expected: ".",
  151. },
  152. }
  153. for _, tt := range tests {
  154. t.Run(tt.name, func(t *testing.T) {
  155. codebasePath := tt.setup()
  156. defer tt.cleanup(codebasePath)
  157. result := golangStack.findMainPackage(codebasePath)
  158. if result != tt.expected {
  159. t.Errorf("Expected %q, got %q", tt.expected, result)
  160. }
  161. })
  162. }
  163. }
  164. func TestAnalyzeGoProject(t *testing.T) {
  165. golangStack := &Golang{}
  166. // Create a test project structure
  167. tempDir, err := os.MkdirTemp("", "test-analyze-project-")
  168. if err != nil {
  169. t.Fatal(err)
  170. }
  171. defer os.RemoveAll(tempDir)
  172. // Create go.mod
  173. goMod := `module github.com/example/web-server
  174. go 1.21
  175. require (
  176. github.com/gin-gonic/gin v1.9.1
  177. )
  178. `
  179. os.WriteFile(filepath.Join(tempDir, "go.mod"), []byte(goMod), 0644)
  180. // Create cmd/web-server/main.go
  181. cmdDir := filepath.Join(tempDir, "cmd", "web-server")
  182. os.MkdirAll(cmdDir, 0755)
  183. mainGo := `package main
  184. import (
  185. "net/http"
  186. "github.com/gin-gonic/gin"
  187. )
  188. func main() {
  189. r := gin.Default()
  190. r.GET("/", func(c *gin.Context) {
  191. c.JSON(http.StatusOK, gin.H{"message": "Hello World"})
  192. })
  193. r.Run(":8080")
  194. }
  195. `
  196. os.WriteFile(filepath.Join(cmdDir, "main.go"), []byte(mainGo), 0644)
  197. // Run analysis
  198. analysis, err := golangStack.analyzeGoProject(tempDir)
  199. if err != nil {
  200. t.Fatalf("Analysis failed: %v", err)
  201. }
  202. // Verify results
  203. if analysis.GoVersion != "1.21" {
  204. t.Errorf("Expected Go version 1.21, got %s", analysis.GoVersion)
  205. }
  206. if analysis.AppName != "web-server" {
  207. t.Errorf("Expected app name 'web-server', got %s", analysis.AppName)
  208. }
  209. if analysis.MainPackage != "./cmd/web-server" {
  210. t.Errorf("Expected main package './cmd/web-server', got %s", analysis.MainPackage)
  211. }
  212. if analysis.Port != 8080 {
  213. t.Errorf("Expected port 8080, got %d", analysis.Port)
  214. }
  215. if !analysis.RequiresCACerts {
  216. t.Error("Expected RequiresCACerts to be true (due to net/http import)")
  217. }
  218. if !contains(analysis.Modules, "gin") {
  219. t.Error("Expected gin module to be detected")
  220. }
  221. }
  222. func TestWebServerProjectStructure(t *testing.T) {
  223. // This test specifically replicates the structure from the failed build
  224. golangStack := &Golang{}
  225. tempDir, err := os.MkdirTemp("", "test-web-server-structure-")
  226. if err != nil {
  227. t.Fatal(err)
  228. }
  229. defer os.RemoveAll(tempDir)
  230. // Create the directory structure that was failing
  231. os.MkdirAll(filepath.Join(tempDir, ".vscode"), 0755)
  232. os.MkdirAll(filepath.Join(tempDir, "cmd", "web-server"), 0755)
  233. os.MkdirAll(filepath.Join(tempDir, "configs"), 0755)
  234. os.MkdirAll(filepath.Join(tempDir, "pkg", "mhttp"), 0755)
  235. os.MkdirAll(filepath.Join(tempDir, "scripts"), 0755)
  236. os.MkdirAll(filepath.Join(tempDir, "tests"), 0755)
  237. os.MkdirAll(filepath.Join(tempDir, "web"), 0755)
  238. // Create files
  239. os.WriteFile(filepath.Join(tempDir, "LICENSE"), []byte("MIT License"), 0644)
  240. os.WriteFile(filepath.Join(tempDir, "go.mod"), []byte("module golang-web-server\ngo 1.18\n"), 0644)
  241. // Create the main file in cmd/web-server (this should be detected)
  242. os.WriteFile(filepath.Join(tempDir, "cmd", "web-server", "golang-web-server.go"),
  243. []byte("package main\nfunc main() {}\n"), 0644)
  244. // Create other Go files that should NOT be chosen as main
  245. os.WriteFile(filepath.Join(tempDir, "pkg", "mhttp", "server.go"),
  246. []byte("package mhttp\nfunc StartServer() {}\n"), 0644)
  247. os.WriteFile(filepath.Join(tempDir, "pkg", "mhttp", "functions.go"),
  248. []byte("package mhttp\nfunc Handler() {}\n"), 0644)
  249. os.WriteFile(filepath.Join(tempDir, "configs", "server-config.go"),
  250. []byte("package configs\nvar Config = map[string]string{}\n"), 0644)
  251. // Test main package detection
  252. mainPackage := golangStack.findMainPackage(tempDir)
  253. if mainPackage != "./cmd/web-server" {
  254. t.Errorf("Expected main package './cmd/web-server', got '%s'", mainPackage)
  255. }
  256. // Test full analysis
  257. analysis, err := golangStack.analyzeGoProject(tempDir)
  258. if err != nil {
  259. t.Fatalf("Analysis failed: %v", err)
  260. }
  261. if analysis.MainPackage != "./cmd/web-server" {
  262. t.Errorf("Expected main package './cmd/web-server', got '%s'", analysis.MainPackage)
  263. }
  264. if analysis.AppName != "golang-web-server" {
  265. t.Errorf("Expected app name 'golang-web-server', got '%s'", analysis.AppName)
  266. }
  267. t.Logf("✅ Correctly detected main package: %s", analysis.MainPackage)
  268. t.Logf("✅ Correctly detected app name: %s", analysis.AppName)
  269. }
  270. func TestBuildCommandGeneration(t *testing.T) {
  271. tests := []struct {
  272. name string
  273. mainPackage string
  274. cgoEnabled bool
  275. expectedCommand string
  276. }{
  277. {
  278. name: "root package",
  279. mainPackage: ".",
  280. cgoEnabled: false,
  281. expectedCommand: "go build -ldflags='-w -s -extldflags \"-static\"' -a -installsuffix cgo -o testapp .",
  282. },
  283. {
  284. name: "cmd subdirectory",
  285. mainPackage: "cmd/server",
  286. cgoEnabled: false,
  287. expectedCommand: "go build -ldflags='-w -s -extldflags \"-static\"' -a -installsuffix cgo -o testapp ./cmd/server",
  288. },
  289. {
  290. name: "cmd subdirectory with CGO",
  291. mainPackage: "cmd/web-server",
  292. cgoEnabled: true,
  293. expectedCommand: "go build -ldflags='-w -s' -a -installsuffix cgo -o testapp ./cmd/web-server",
  294. },
  295. {
  296. name: "already has ./ prefix",
  297. mainPackage: "./cmd/api",
  298. cgoEnabled: false,
  299. expectedCommand: "go build -ldflags='-w -s -extldflags \"-static\"' -a -installsuffix cgo -o testapp ./cmd/api",
  300. },
  301. }
  302. for _, tt := range tests {
  303. t.Run(tt.name, func(t *testing.T) {
  304. // Create test project
  305. tempDir, err := os.MkdirTemp("", "build-cmd-test-")
  306. if err != nil {
  307. t.Fatal(err)
  308. }
  309. defer os.RemoveAll(tempDir)
  310. // Create minimal structure
  311. os.WriteFile(filepath.Join(tempDir, "go.mod"), []byte("module testapp\ngo 1.21\n"), 0644)
  312. // Mock the analysis result
  313. analysis := &GoProjectAnalysis{
  314. AppName: "testapp",
  315. MainPackage: tt.mainPackage,
  316. CGOEnabled: tt.cgoEnabled,
  317. }
  318. // Generate build command (simulate the logic from GenerateLLBAdvanced)
  319. appName := analysis.AppName
  320. if appName == "" {
  321. appName = "app"
  322. }
  323. var buildCmd string
  324. mainPackagePath := analysis.MainPackage
  325. if mainPackagePath != "." && !strings.HasPrefix(mainPackagePath, "./") {
  326. mainPackagePath = "./" + mainPackagePath
  327. }
  328. if analysis.CGOEnabled {
  329. buildCmd = fmt.Sprintf("go build -ldflags='-w -s' -a -installsuffix cgo -o %s %s",
  330. appName, mainPackagePath)
  331. } else {
  332. buildCmd = fmt.Sprintf("go build -ldflags='-w -s -extldflags \"-static\"' -a -installsuffix cgo -o %s %s",
  333. appName, mainPackagePath)
  334. }
  335. if buildCmd != tt.expectedCommand {
  336. t.Errorf("Expected command:\n%s\nGot:\n%s", tt.expectedCommand, buildCmd)
  337. }
  338. t.Logf("✅ Build command: %s", buildCmd)
  339. })
  340. }
  341. }
  342. // Helper function
  343. func contains(slice []string, item string) bool {
  344. for _, s := range slice {
  345. if s == item {
  346. return true
  347. }
  348. }
  349. return false
  350. }