grokkit/internal/linter/linter_test.go
Greg Gauthier 918ccc01c8
All checks were successful
CI / Test (push) Successful in 31s
CI / Lint (push) Successful in 25s
CI / Build (push) Successful in 22s
Release / Create Release (push) Successful in 36s
refactor(tests): improve error handling and defer usage
- Add error checking for os.Setenv and io operations in test files
- Use anonymous functions in defer to ignore errors from os.Remove, os.Setenv, etc.
- Minor formatting and consistency fixes in tests and client code
2026-03-02 21:33:11 +00:00

335 lines
8.6 KiB
Go

package linter
import (
"os"
"path/filepath"
"testing"
)
func TestDetectLanguage(t *testing.T) {
tests := []struct {
name string
filePath string
wantLang string
wantErr bool
}{
{"Go file", "main.go", "Go", false},
{"Python file", "script.py", "Python", false},
{"JavaScript file", "app.js", "JavaScript", false},
{"JSX file", "component.jsx", "JavaScript", false},
{"TypeScript file", "app.ts", "TypeScript", false},
{"TSX file", "component.tsx", "TypeScript", false},
{"Rust file", "main.rs", "Rust", false},
{"Ruby file", "script.rb", "Ruby", false},
{"Java file", "Main.java", "Java", false},
{"C file", "program.c", "C/C++", false},
{"C++ file", "program.cpp", "C/C++", false},
{"Header file", "header.h", "C/C++", false},
{"Shell script", "script.sh", "Shell", false},
{"Bash script", "script.bash", "Shell", false},
{"Unsupported file", "file.txt", "", true},
{"No extension", "Makefile", "", true},
{"Case insensitive", "MAIN.GO", "Go", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
lang, err := DetectLanguage(tt.filePath)
if tt.wantErr {
if err == nil {
t.Errorf("DetectLanguage(%q) expected error, got nil", tt.filePath)
}
return
}
if err != nil {
t.Errorf("DetectLanguage(%q) unexpected error: %v", tt.filePath, err)
return
}
if lang.Name != tt.wantLang {
t.Errorf("DetectLanguage(%q) = %q, want %q", tt.filePath, lang.Name, tt.wantLang)
}
})
}
}
func TestCheckLinterAvailable(t *testing.T) {
tests := []struct {
name string
linter Linter
wantMsg string
}{
{
name: "go command should be available",
linter: Linter{
Name: "go",
Command: "go",
},
wantMsg: "go should be available on system with Go installed",
},
{
name: "nonexistent command",
linter: Linter{
Name: "nonexistent",
Command: "this-command-does-not-exist-12345",
},
wantMsg: "nonexistent command should not be available",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
available := CheckLinterAvailable(tt.linter)
// We can't assert exact true/false since it depends on the system
// Just verify the function runs without panic
t.Logf("%s: available=%v", tt.wantMsg, available)
})
}
}
func TestFindAvailableLinter(t *testing.T) {
// Test with Go language since 'go' command should be available
// (we're running tests with Go installed)
t.Run("Go language should find a linter", func(t *testing.T) {
goLang := &Language{
Name: "Go",
Extensions: []string{".go"},
Linters: []Linter{
{
Name: "go vet",
Command: "go",
Args: []string{"vet"},
InstallInfo: "Built-in with Go",
},
},
}
linter, err := FindAvailableLinter(goLang)
if err != nil {
t.Errorf("FindAvailableLinter(Go) expected to find 'go', got error: %v", err)
}
if linter == nil {
t.Errorf("FindAvailableLinter(Go) returned nil linter")
} else if linter.Command != "go" {
t.Errorf("FindAvailableLinter(Go) = %q, want 'go'", linter.Command)
}
})
t.Run("Language with no available linters", func(t *testing.T) {
fakeLang := &Language{
Name: "FakeLang",
Extensions: []string{".fake"},
Linters: []Linter{
{
Name: "fakelint",
Command: "this-does-not-exist-12345",
InstallInfo: "not installable",
},
},
}
linter, err := FindAvailableLinter(fakeLang)
if err == nil {
t.Errorf("FindAvailableLinter(FakeLang) expected error, got linter: %v", linter)
}
if linter != nil {
t.Errorf("FindAvailableLinter(FakeLang) expected nil linter, got: %v", linter)
}
})
}
func TestRunLinter(t *testing.T) {
// Create a temporary Go file with a simple error
tmpDir := t.TempDir()
testFile := filepath.Join(tmpDir, "test.go")
// Valid Go code
validCode := `package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
`
if err := os.WriteFile(testFile, []byte(validCode), 0644); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
t.Run("Run go vet on valid file", func(t *testing.T) {
linter := Linter{
Name: "go vet",
Command: "go",
Args: []string{"vet"},
}
result, err := RunLinter(testFile, linter)
if err != nil {
t.Errorf("RunLinter() unexpected error: %v", err)
}
if result == nil {
t.Fatal("RunLinter() returned nil result")
}
if !result.LinterExists {
t.Errorf("RunLinter() LinterExists = false, want true")
}
if result.LinterUsed != "go vet" {
t.Errorf("RunLinter() LinterUsed = %q, want 'go vet'", result.LinterUsed)
}
// Note: go vet might find issues or not, so we don't assert HasIssues
t.Logf("go vet result: ExitCode=%d, HasIssues=%v, Output=%q",
result.ExitCode, result.HasIssues, result.Output)
})
t.Run("Run nonexistent linter", func(t *testing.T) {
linter := Linter{
Name: "fake",
Command: "this-does-not-exist-12345",
Args: []string{},
}
result, err := RunLinter(testFile, linter)
if err == nil {
t.Errorf("RunLinter() expected error for nonexistent command, got result: %v", result)
}
})
}
func TestLintFile(t *testing.T) {
tmpDir := t.TempDir()
t.Run("Lint valid Go file", func(t *testing.T) {
testFile := filepath.Join(tmpDir, "valid.go")
validCode := `package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
`
if err := os.WriteFile(testFile, []byte(validCode), 0644); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
result, err := LintFile(testFile)
if err != nil {
t.Errorf("LintFile() unexpected error: %v", err)
}
if result == nil {
t.Fatal("LintFile() returned nil result")
}
if result.Language != "Go" {
t.Errorf("LintFile() Language = %q, want 'Go'", result.Language)
}
if !result.LinterExists {
t.Errorf("LintFile() LinterExists = false, want true")
}
})
t.Run("Lint nonexistent file", func(t *testing.T) {
testFile := filepath.Join(tmpDir, "nonexistent.go")
result, err := LintFile(testFile)
if err == nil {
t.Errorf("LintFile() expected error for nonexistent file, got result: %v", result)
}
})
t.Run("Lint unsupported file type", func(t *testing.T) {
testFile := filepath.Join(tmpDir, "test.txt")
if err := os.WriteFile(testFile, []byte("hello"), 0644); err != nil {
t.Fatalf("Failed to create test file: %v", err)
}
result, err := LintFile(testFile)
if err == nil {
t.Errorf("LintFile() expected error for unsupported file type, got result: %v", result)
}
})
}
func TestGetSupportedLanguages(t *testing.T) {
langs := GetSupportedLanguages()
if len(langs) == 0 {
t.Errorf("GetSupportedLanguages() returned empty list")
}
// Check that we have some expected languages
expectedLangs := []string{"Go", "Python", "JavaScript", "TypeScript"}
for _, expected := range expectedLangs {
found := false
for _, lang := range langs {
if contains(lang, expected) {
found = true
break
}
}
if !found {
t.Errorf("GetSupportedLanguages() missing expected language: %s", expected)
}
}
// Verify format includes extensions
for _, lang := range langs {
if !contains(lang, "(") || !contains(lang, ")") {
t.Errorf("GetSupportedLanguages() entry missing format 'Name (extensions)': %s", lang)
}
}
}
func TestLanguageStructure(t *testing.T) {
// Verify the languages slice is properly structured
if len(languages) == 0 {
t.Fatal("languages slice is empty")
}
for _, lang := range languages {
if lang.Name == "" {
t.Errorf("Language with empty Name found")
}
if len(lang.Extensions) == 0 {
t.Errorf("Language %q has no extensions", lang.Name)
}
if len(lang.Linters) == 0 {
t.Errorf("Language %q has no linters", lang.Name)
}
// Check extensions start with dot
for _, ext := range lang.Extensions {
if ext == "" || ext[0] != '.' {
t.Errorf("Language %q has invalid extension: %q", lang.Name, ext)
}
}
// Check linters have required fields
for _, linter := range lang.Linters {
if linter.Name == "" {
t.Errorf("Language %q has linter with empty Name", lang.Name)
}
if linter.Command == "" {
t.Errorf("Language %q has linter %q with empty Command", lang.Name, linter.Name)
}
if linter.InstallInfo == "" {
t.Errorf("Language %q has linter %q with empty InstallInfo", lang.Name, linter.Name)
}
}
}
}
// Helper function
func contains(s, substr string) bool {
return len(s) >= len(substr) && (s == substr || len(s) > len(substr) &&
(s[:len(substr)] == substr || s[len(s)-len(substr):] == substr ||
containsMiddle(s, substr)))
}
func containsMiddle(s, substr string) bool {
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}