Files
llgo/internal/targets/targets_test.go
Li Jie b80a54eb0f feat: implement target configuration system for issue #1176
Add comprehensive target configuration parsing and inheritance system:

- Create internal/targets package with config structures
- Support JSON configuration loading with inheritance resolution
- Implement multi-level inheritance (e.g., rp2040 → cortex-m0plus → cortex-m)
- Add 206 target configurations from TinyGo for embedded platforms
- Support core fields: name, llvm-target, cpu, features, build-tags, goos, goarch, cflags, ldflags
- Provide high-level resolver interface for target lookup
- Include comprehensive unit tests with 100% target parsing coverage

This foundation enables future -target parameter support for cross-compilation
to diverse embedded platforms beyond current GOOS/GOARCH limitations.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-29 15:12:58 +08:00

323 lines
8.5 KiB
Go

//go:build !llgo
package targets
import (
"os"
"path/filepath"
"testing"
)
func TestConfigBasics(t *testing.T) {
config := &Config{
Name: "test",
LLVMTarget: "arm-none-eabi",
GOOS: "linux",
GOARCH: "arm",
}
if config.IsEmpty() {
t.Error("Config should not be empty when fields are set")
}
empty := &Config{}
if !empty.IsEmpty() {
t.Error("Empty config should report as empty")
}
}
func TestRawConfigInheritance(t *testing.T) {
raw := &RawConfig{
Inherits: []string{"parent1", "parent2"},
Config: Config{
Name: "child",
},
}
if !raw.HasInheritance() {
t.Error("RawConfig should report having inheritance")
}
inherits := raw.GetInherits()
if len(inherits) != 2 || inherits[0] != "parent1" || inherits[1] != "parent2" {
t.Errorf("Expected inheritance list [parent1, parent2], got %v", inherits)
}
noInherit := &RawConfig{}
if noInherit.HasInheritance() {
t.Error("RawConfig with no inherits should not report having inheritance")
}
}
func TestLoaderLoadRaw(t *testing.T) {
// Create a temporary directory for test configs
tempDir := t.TempDir()
// Create a test config file
testConfig := `{
"llvm-target": "thumbv6m-unknown-unknown-eabi",
"cpu": "cortex-m0plus",
"goos": "linux",
"goarch": "arm",
"build-tags": ["test", "embedded"],
"cflags": ["-Os", "-g"],
"ldflags": ["--gc-sections"]
}`
configPath := filepath.Join(tempDir, "test-target.json")
if err := os.WriteFile(configPath, []byte(testConfig), 0644); err != nil {
t.Fatalf("Failed to write test config: %v", err)
}
loader := NewLoader(tempDir)
config, err := loader.LoadRaw("test-target")
if err != nil {
t.Fatalf("Failed to load raw config: %v", err)
}
if config.Name != "test-target" {
t.Errorf("Expected name 'test-target', got '%s'", config.Name)
}
if config.LLVMTarget != "thumbv6m-unknown-unknown-eabi" {
t.Errorf("Expected llvm-target 'thumbv6m-unknown-unknown-eabi', got '%s'", config.LLVMTarget)
}
if config.CPU != "cortex-m0plus" {
t.Errorf("Expected cpu 'cortex-m0plus', got '%s'", config.CPU)
}
if len(config.BuildTags) != 2 || config.BuildTags[0] != "test" || config.BuildTags[1] != "embedded" {
t.Errorf("Expected build-tags [test, embedded], got %v", config.BuildTags)
}
}
func TestLoaderInheritance(t *testing.T) {
tempDir := t.TempDir()
// Create parent config
parentConfig := `{
"llvm-target": "thumbv6m-unknown-unknown-eabi",
"cpu": "cortex-m0plus",
"goos": "linux",
"goarch": "arm",
"cflags": ["-Os"],
"ldflags": ["--gc-sections"]
}`
// Create child config that inherits from parent
childConfig := `{
"inherits": ["parent"],
"cpu": "cortex-m4",
"build-tags": ["child"],
"cflags": ["-O2"],
"ldflags": ["-g"]
}`
parentPath := filepath.Join(tempDir, "parent.json")
childPath := filepath.Join(tempDir, "child.json")
if err := os.WriteFile(parentPath, []byte(parentConfig), 0644); err != nil {
t.Fatalf("Failed to write parent config: %v", err)
}
if err := os.WriteFile(childPath, []byte(childConfig), 0644); err != nil {
t.Fatalf("Failed to write child config: %v", err)
}
loader := NewLoader(tempDir)
config, err := loader.Load("child")
if err != nil {
t.Fatalf("Failed to load child config: %v", err)
}
// Check inherited values
if config.LLVMTarget != "thumbv6m-unknown-unknown-eabi" {
t.Errorf("Expected inherited llvm-target 'thumbv6m-unknown-unknown-eabi', got '%s'", config.LLVMTarget)
}
if config.GOOS != "linux" {
t.Errorf("Expected inherited goos 'linux', got '%s'", config.GOOS)
}
if config.GOARCH != "arm" {
t.Errorf("Expected inherited goarch 'arm', got '%s'", config.GOARCH)
}
// Check overridden values
if config.CPU != "cortex-m4" {
t.Errorf("Expected overridden cpu 'cortex-m4', got '%s'", config.CPU)
}
// Check merged arrays
expectedCFlags := []string{"-Os", "-O2"}
if len(config.CFlags) != 2 || config.CFlags[0] != "-Os" || config.CFlags[1] != "-O2" {
t.Errorf("Expected merged cflags %v, got %v", expectedCFlags, config.CFlags)
}
expectedLDFlags := []string{"--gc-sections", "-g"}
if len(config.LDFlags) != 2 || config.LDFlags[0] != "--gc-sections" || config.LDFlags[1] != "-g" {
t.Errorf("Expected merged ldflags %v, got %v", expectedLDFlags, config.LDFlags)
}
// Check child-specific values
if len(config.BuildTags) != 1 || config.BuildTags[0] != "child" {
t.Errorf("Expected build-tags [child], got %v", config.BuildTags)
}
}
func TestLoaderListTargets(t *testing.T) {
tempDir := t.TempDir()
// Create some test config files
configs := []string{"target1.json", "target2.json", "not-a-target.txt"}
for _, config := range configs {
configPath := filepath.Join(tempDir, config)
if err := os.WriteFile(configPath, []byte("{}"), 0644); err != nil {
t.Fatalf("Failed to write config %s: %v", config, err)
}
}
loader := NewLoader(tempDir)
targets, err := loader.ListTargets()
if err != nil {
t.Fatalf("Failed to list targets: %v", err)
}
expectedTargets := []string{"target1", "target2"}
if len(targets) != len(expectedTargets) {
t.Errorf("Expected %d targets, got %d", len(expectedTargets), len(targets))
}
for _, expected := range expectedTargets {
found := false
for _, target := range targets {
if target == expected {
found = true
break
}
}
if !found {
t.Errorf("Expected target %s not found in list %v", expected, targets)
}
}
}
func TestResolver(t *testing.T) {
tempDir := t.TempDir()
// Create a test config
testConfig := `{
"llvm-target": "wasm32-unknown-wasi",
"cpu": "generic",
"goos": "wasip1",
"goarch": "wasm"
}`
configPath := filepath.Join(tempDir, "wasi.json")
if err := os.WriteFile(configPath, []byte(testConfig), 0644); err != nil {
t.Fatalf("Failed to write test config: %v", err)
}
resolver := NewResolver(tempDir)
// Test resolve
config, err := resolver.Resolve("wasi")
if err != nil {
t.Fatalf("Failed to resolve target: %v", err)
}
if config.Name != "wasi" {
t.Errorf("Expected name 'wasi', got '%s'", config.Name)
}
// Test has target
if !resolver.HasTarget("wasi") {
t.Error("Resolver should report having 'wasi' target")
}
if resolver.HasTarget("nonexistent") {
t.Error("Resolver should not report having 'nonexistent' target")
}
// Test list available targets
targets, err := resolver.ListAvailableTargets()
if err != nil {
t.Fatalf("Failed to list available targets: %v", err)
}
if len(targets) != 1 || targets[0] != "wasi" {
t.Errorf("Expected targets [wasi], got %v", targets)
}
}
func TestResolverWithRealTargets(t *testing.T) {
// Test with actual targets directory if it exists
resolver := NewDefaultResolver()
targetsDir := resolver.GetTargetsDirectory()
// Check if targets directory exists
if _, err := os.Stat(targetsDir); os.IsNotExist(err) {
t.Skipf("Targets directory %s does not exist, skipping real targets test", targetsDir)
}
// Test listing real targets
targets, err := resolver.ListAvailableTargets()
if err != nil {
t.Fatalf("Failed to list real targets: %v", err)
}
t.Logf("Found %d targets in %s", len(targets), targetsDir)
// Test resolving some known targets
knownTargets := []string{"wasi", "cortex-m", "rp2040"}
for _, targetName := range knownTargets {
if resolver.HasTarget(targetName) {
config, err := resolver.Resolve(targetName)
if err != nil {
t.Errorf("Failed to resolve known target %s: %v", targetName, err)
continue
}
t.Logf("Resolved target %s: LLVM=%s, CPU=%s, GOOS=%s, GOARCH=%s",
targetName, config.LLVMTarget, config.CPU, config.GOOS, config.GOARCH)
}
}
}
func TestResolveAllRealTargets(t *testing.T) {
resolver := NewDefaultResolver()
targetsDir := resolver.GetTargetsDirectory()
// Check if targets directory exists
if _, err := os.Stat(targetsDir); os.IsNotExist(err) {
t.Skipf("Targets directory %s does not exist, skipping resolve all test", targetsDir)
}
// Test resolving all targets
configs, err := resolver.ResolveAll()
if err != nil {
t.Fatalf("Failed to resolve all targets: %v", err)
}
t.Logf("Successfully resolved %d targets", len(configs))
// Check that all configs have names
for name, config := range configs {
if config.Name != name {
t.Errorf("Config name mismatch: key=%s, config.Name=%s", name, config.Name)
}
if config.IsEmpty() {
t.Errorf("Config %s appears to be empty", name)
}
}
// Log some statistics
goosCounts := make(map[string]int)
goarchCounts := make(map[string]int)
for _, config := range configs {
if config.GOOS != "" {
goosCounts[config.GOOS]++
}
if config.GOARCH != "" {
goarchCounts[config.GOARCH]++
}
}
t.Logf("GOOS distribution: %v", goosCounts)
t.Logf("GOARCH distribution: %v", goarchCounts)
}