Ctrl K
ComunidadGuías…

bldr/util/gocompiler/goscript_test.go

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package gocompiler

import (
	"context"
	"os"
	"path/filepath"
	"slices"
	"strings"
	"testing"

	"github.com/sirupsen/logrus"
)

func TestExecGoScriptCompileIgnoresExternalCommandEnv(t *testing.T) {
	// Write a module and point BLDR_GOSCRIPT at a missing compiler binary.
	dir := t.TempDir()
	writeGoScriptModule(t, dir, "example.com/goscriptcmd", map[string]string{
		"main.go": "package goscriptcmd\nconst Value = 1\n",
	})
	t.Setenv("BLDR_GOSCRIPT", filepath.Join(dir, "missing-goscript"))
	err := ExecGoScriptCompile(context.Background(), logrus.NewEntry(logrus.New()), GoScriptCompileOptions{
		WorkDir:    dir,
		OutputPath: filepath.Join(dir, "out"),
		Packages:   []string{"."},
	})
	if err != nil {
		t.Fatal(err)
	}
	assertGoScriptOutputContains(t, filepath.Join(dir, "out"), "example.com/goscriptcmd", "Value")
}

func TestExecGoScriptCompilePreservesBuildFlags(t *testing.T) {
	// Write a module with a tagged file compiled only under customtag.
	dir := t.TempDir()
	writeGoScriptModule(t, dir, "example.com/goscripttags", map[string]string{
		"default.go": "package goscripttags\nconst DefaultValue = 1\n",
		"tagged.go":  "//go:build customtag\n\npackage goscripttags\nconst TaggedValue = 2\n",
	})
	err := ExecGoScriptCompile(context.Background(), logrus.NewEntry(logrus.New()), GoScriptCompileOptions{
		WorkDir:    dir,
		OutputPath: filepath.Join(dir, "out"),
		Packages:   []string{"."},
		BuildFlags: []string{"-tags=customtag"},
	})
	if err != nil {
		t.Fatal(err)
	}
	assertGoScriptOutputContains(t, filepath.Join(dir, "out"), "example.com/goscripttags", "TaggedValue")
}

func TestExecGoScriptCompileUsesJsWasmTarget(t *testing.T) {
	// Write a module with generic, js/wasm, and linux-only files.
	dir := t.TempDir()
	writeGoScriptModule(t, dir, "example.com/goscripttarget", map[string]string{
		"generic.go": "package goscripttarget\nconst GenericValue = 1\n",
		"js.go":      "//go:build js && wasm\n\npackage goscripttarget\nconst JsWasmValue = 2\n",
		"linux.go":   "//go:build linux\n\npackage goscripttarget\nconst LinuxValue = 3\n",
	})
	err := ExecGoScriptCompile(context.Background(), logrus.NewEntry(logrus.New()), GoScriptCompileOptions{
		WorkDir:    dir,
		OutputPath: filepath.Join(dir, "out"),
		Packages:   []string{"."},
	})
	if err != nil {
		t.Fatal(err)
	}
	output := readGoScriptOutput(t, filepath.Join(dir, "out"), "example.com/goscripttarget")
	for _, want := range []string{"GenericValue", "JsWasmValue"} {
		if !strings.Contains(output, want) {
			t.Fatalf("output missing %q:\n%s", want, output)
		}
	}
	if strings.Contains(output, "LinuxValue") {
		t.Fatalf("output should not include linux-only file:\n%s", output)
	}
}

func TestExecGoScriptCompileUsesCompilerCacheRoot(t *testing.T) {
	// Compile with an explicit cache root under the .bldr state root.
	dir := t.TempDir()
	cacheRoot := filepath.Join(dir, ".bldr", "cache", "gs")
	writeGoScriptModule(t, dir, "example.com/goscriptcache", map[string]string{
		"main.go": "package goscriptcache\nconst Value = 1\n",
	})
	err := ExecGoScriptCompile(context.Background(), logrus.NewEntry(logrus.New()), GoScriptCompileOptions{
		WorkDir:    dir,
		OutputPath: filepath.Join(dir, "out"),
		CacheRoot:  cacheRoot,
		Packages:   []string{"."},
	})
	if err != nil {
		t.Fatal(err)
	}
	if got := countGoScriptCacheManifests(t, cacheRoot); got == 0 {
		t.Fatal("compiler cache root did not receive cache manifests")
	}
}

func TestGoScriptCompilerCacheRootFromEnvDefaultsToUserCacheDir(t *testing.T) {
	// Configure an unset primary root and a relative fallback root.
	stateRoot := filepath.Join(t.TempDir(), ".bldr")
	buildPath := filepath.Join(stateRoot, "build", "web", "spacewave-core")
	userCacheDir, err := os.UserCacheDir()
	if err != nil {
		t.Fatal(err)
	}
	t.Setenv(GoScriptCompilerCacheRootEnv, "")
	t.Setenv("GOSCRIPT_COMPILER_CACHE_ROOT", filepath.Join("cache", "gs"))

	// Verify the fallback root resolves to the user cache dir.
	got, err := GoScriptCompilerCacheRootFromEnv(buildPath)
	if err != nil {
		t.Fatal(err)
	}
	if want := filepath.Join(userCacheDir, "goscript"); got != want {
		t.Fatalf("cache root = %q, want %q", got, want)
	}
}

func TestGoScriptCompilerCacheRootFromEnvOffDisablesCache(t *testing.T) {
	// Verify the off value disables the compiler cache.
	buildPath := filepath.Join(t.TempDir(), ".bldr", "build", "web", "spacewave-core")
	t.Setenv(GoScriptCompilerCacheRootEnv, "off")
	got, err := GoScriptCompilerCacheRootFromEnv(buildPath)
	if err != nil {
		t.Fatal(err)
	}
	if got != "" {
		t.Fatalf("cache root = %q, want empty", got)
	}
}

func TestGoScriptCompilerCacheRootFromEnvResolvesRelativeUnderBldrStateRoot(t *testing.T) {
	// Verify a relative root resolves under the .bldr state root.
	stateRoot := filepath.Join(t.TempDir(), ".bldr")
	buildPath := filepath.Join(stateRoot, "build", "web", "spacewave-core")
	t.Setenv(GoScriptCompilerCacheRootEnv, filepath.Join("cache", "gs"))
	got, err := GoScriptCompilerCacheRootFromEnv(buildPath)
	if err != nil {
		t.Fatal(err)
	}
	want := filepath.Join(stateRoot, "cache", "gs")
	if got != want {
		t.Fatalf("cache root = %q, want %q", got, want)
	}
}

func TestGoScriptCompilerCacheRootFromEnvResolvesRelativeUnderReleaseStateRoot(t *testing.T) {
	// Verify a relative root resolves under the .bldr-dist release root.
	stateRoot := filepath.Join(t.TempDir(), ".bldr-dist")
	buildPath := filepath.Join(stateRoot, "build", "web", "js", "wasm", "spacewave-core")
	t.Setenv(GoScriptCompilerCacheRootEnv, filepath.Join("cache", "gs"))
	got, err := GoScriptCompilerCacheRootFromEnv(buildPath)
	if err != nil {
		t.Fatal(err)
	}
	want := filepath.Join(stateRoot, "cache", "gs")
	if got != want {
		t.Fatalf("cache root = %q, want %q", got, want)
	}
}

func TestResolveGoScriptCompilerCacheRootAcceptsAbsoluteRoot(t *testing.T) {
	root := filepath.Join(t.TempDir(), "cache", "gs")
	got, err := ResolveGoScriptCompilerCacheRoot(filepath.Join(t.TempDir(), "work"), root)
	if err != nil {
		t.Fatal(err)
	}
	if got != root {
		t.Fatalf("cache root = %q, want %q", got, root)
	}
}

func TestResolveGoScriptCompilerCacheRootRejectsEscapingRelativeRoot(t *testing.T) {
	stateRoot := filepath.Join(t.TempDir(), ".bldr")
	buildPath := filepath.Join(stateRoot, "build", "web", "spacewave-core")
	_, err := ResolveGoScriptCompilerCacheRoot(buildPath, filepath.Join("..", "cache"))
	if err == nil || !strings.Contains(err.Error(), "escapes .bldr state root") {
		t.Fatalf("resolve escaping cache root error = %v, want escape error", err)
	}
}

func TestExecGoScriptCompileMapsBindingRoots(t *testing.T) {
	// Write a root module that depends on a module with protobuf siblings.
	dir := t.TempDir()
	depDir := filepath.Join(dir, "dep")
	if err := os.MkdirAll(depDir, 0o755); err != nil {
		t.Fatal(err)
	}
	writeGoScriptModule(t, dir, "example.com/root", map[string]string{
		"main.go": "package root\nimport \"example.com/dep\"\nvar Value dep.Message\n",
	})
	writeGoScriptModule(t, depDir, "example.com/dep", map[string]string{
		"msg.pb.go": "package dep\ntype Message struct { Value string `protobuf:\"bytes,1,opt,name=value,proto3\"` }\n",
		"msg.pb.ts": "export const Message = {}\n",
	})

	// Write the replace directive that points the root module at the dependency.
	goMod := `module example.com/root

go 1.25.3

require example.com/dep v0.0.0

replace example.com/dep => ./dep
`
	if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte(goMod), 0o644); err != nil {
		t.Fatal(err)
	}
	roots, err := GoScriptBindingRoots(context.Background(), dir)
	if err != nil {
		t.Fatal(err)
	}

	// Compile with the binding roots and verify the protobuf binding is emitted.
	out := filepath.Join(dir, "out")
	err = ExecGoScriptCompile(context.Background(), logrus.NewEntry(logrus.New()), GoScriptCompileOptions{
		WorkDir:                   dir,
		OutputPath:                out,
		Packages:                  []string{"."},
		BindingRoots:              roots,
		ProtobufTypeScriptBinding: true,
	})
	if err != nil {
		t.Fatal(err)
	}
	if _, err := os.Stat(filepath.Join(out, "@goscript", "example.com", "dep", "msg.pb.ts")); err != nil {
		t.Fatalf("protobuf TypeScript binding was not emitted: %v", err)
	}
}

func TestGoScriptBindingRootsFiltersModuleSources(t *testing.T) {
	// Write modules with and without qualifying protobuf TypeScript siblings.
	dir := t.TempDir()
	withPB := filepath.Join(dir, "with-pb")
	withoutPB := filepath.Join(dir, "without-pb")
	if err := os.MkdirAll(withPB, 0o755); err != nil {
		t.Fatal(err)
	}
	if err := os.MkdirAll(withoutPB, 0o755); err != nil {
		t.Fatal(err)
	}
	writeGoScriptModule(t, dir, "example.com/root", map[string]string{
		"main.pb.ts": "export {}\n",
	})
	writeGoScriptModule(t, withPB, "example.com/with-pb", map[string]string{
		"types.pb.ts": "export {}\n",
	})
	writeGoScriptModule(t, withoutPB, "example.com/without-pb", map[string]string{
		"node_modules/generated.pb.ts": "export {}\n",
		"a/b/c/d/e/f/g/types.pb.ts":    "export {}\n",
	})

	// Write the go.mod requiring both modules with vendor mode.
	goMod := `module example.com/root

go 1.25.3

require (
	example.com/with-pb v0.0.0
	example.com/without-pb v0.0.0
)

replace example.com/with-pb => ./with-pb
replace example.com/without-pb => ./without-pb
`
	if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte(goMod), 0o644); err != nil {
		t.Fatal(err)
	}
	got, err := GoScriptBindingRoots(context.Background(), dir, "GOFLAGS=-mod=vendor")
	if err != nil {
		t.Fatal(err)
	}

	// Verify only the module with qualifying siblings is returned.
	want, err := filepath.EvalSymlinks(withPB)
	if err != nil {
		t.Fatal(err)
	}
	if !slices.Equal(got, []string{want}) {
		t.Fatalf("binding roots = %v, want [%s]", got, want)
	}
}

func TestGoListImportPathPreservesEnv(t *testing.T) {
	// Write a js/wasm-only module for the environment-scoped go list.
	dir := t.TempDir()
	if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module example.com/goscriptenv\n\ngo 1.24\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	src := strings.Join([]string{
		"//go:build js && wasm",
		"",
		"package goscriptenv",
		"",
	}, "\n")
	if err := os.WriteFile(filepath.Join(dir, "browser.go"), []byte(src), 0o644); err != nil {
		t.Fatal(err)
	}

	// Resolve the import path with the js/wasm environment applied.
	importPath, err := GoListImportPath(context.Background(), dir, nil, "GOOS=js", "GOARCH=wasm")
	if err != nil {
		t.Fatal(err)
	}
	if importPath != "example.com/goscriptenv" {
		t.Fatalf("import path = %q, want example.com/goscriptenv", importPath)
	}
}

func writeGoScriptModule(t *testing.T, dir, modulePath string, files map[string]string) {
	// Write the module go.mod and its sorted source files.
	t.Helper()
	if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module "+modulePath+"\n\ngo 1.25.3\n"), 0o644); err != nil {
		t.Fatal(err)
	}
	names := make([]string, 0, len(files))
	for name := range files {
		names = append(names, name)
	}
	slices.Sort(names)
	for _, name := range names {
		path := filepath.Join(dir, name)
		if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
			t.Fatal(err)
		}
		if err := os.WriteFile(path, []byte(files[name]), 0o644); err != nil {
			t.Fatal(err)
		}
	}
}

func assertGoScriptOutputContains(t *testing.T, outputRoot, importPath, want string) {
	// Read the compiled output and check for the expected text.
	t.Helper()
	output := readGoScriptOutput(t, outputRoot, importPath)
	if !strings.Contains(output, want) {
		t.Fatalf("output missing %q:\n%s", want, output)
	}
}

func readGoScriptOutput(t *testing.T, outputRoot, importPath string) string {
	// Read the emitted index.ts or main.gs.ts for the import path.
	t.Helper()
	path := filepath.Join(outputRoot, "@goscript", filepath.FromSlash(importPath), "index.ts")
	if _, err := os.Stat(path); os.IsNotExist(err) {
		path = filepath.Join(outputRoot, "@goscript", filepath.FromSlash(importPath), "main.gs.ts")
	}
	data, err := os.ReadFile(path)
	if err != nil {
		t.Fatal(err)
	}
	return string(data)
}

func countGoScriptCacheManifests(t *testing.T, cacheRoot string) int {
	// Count manifest.json files under the cache root.
	t.Helper()
	count := 0
	err := filepath.WalkDir(cacheRoot, func(_ string, entry os.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if !entry.IsDir() && entry.Name() == "manifest.json" {
			count++
		}
		return nil
	})
	if err != nil {
		t.Fatal(err)
	}
	return count
}