emit_test.go raw

   1  package emit
   2  
   3  import (
   4  	"fmt"
   5  	"go/parser"
   6  	"go/token"
   7  	"sort"
   8  	"strings"
   9  	"testing"
  10  
  11  	"git.mleku.dev/mleku/dendrite/pkg/axiom"
  12  	"git.mleku.dev/mleku/dendrite/pkg/enzyme"
  13  	"git.mleku.dev/mleku/dendrite/pkg/lattice"
  14  )
  15  
  16  type tagConstraint struct{ tag string }
  17  
  18  func (c tagConstraint) Tag() string                 { return c.tag }
  19  func (c tagConstraint) Admits(e axiom.Element) bool  { return e.Type() == c.tag }
  20  
  21  // bondAll ingests Go source into a lattice and bonds elements to matching sites.
  22  func bondAll(t *testing.T, l *lattice.Lattice, src string) int {
  23  	t.Helper()
  24  	ge := enzyme.GoSource{}
  25  	elements := ge.Digest(strings.NewReader(src))
  26  	bonded := 0
  27  	for e := range elements {
  28  		for _, n := range l.Nodes() {
  29  			if !n.Occupied() && n.Admits(e) {
  30  				n.Bond(e)
  31  				bonded++
  32  				break
  33  			}
  34  		}
  35  	}
  36  	return bonded
  37  }
  38  
  39  // makeLattice creates a lattice with sites for all Go AST element types.
  40  func makeLattice(t *testing.T) *lattice.Lattice {
  41  	t.Helper()
  42  	l := lattice.New()
  43  	tags := []string{
  44  		"package", "import", "type", "struct", "interface", "field",
  45  		"func", "method", "comment",
  46  		"assign", "return", "if", "for", "switch", "select",
  47  		"go", "send", "expr", "defer", "decl", "branch", "case", "comm",
  48  		// Declaration-level ident subtypes.
  49  		"ident:func-name", "ident:method-name", "ident:type-name",
  50  		"ident:field-name", "ident:param", "ident:result",
  51  		"ident:receiver", "ident:var-name",
  52  	}
  53  	for _, tag := range tags {
  54  		for range 20 {
  55  			n := l.AddNode([]axiom.Constraint{tagConstraint{tag}})
  56  			n.SetEnergy(true)
  57  		}
  58  	}
  59  	// Wire up neighbors.
  60  	nodes := l.Nodes()
  61  	for i := range nodes {
  62  		if i > 0 {
  63  			l.Connect(nodes[i-1], nodes[i])
  64  		}
  65  	}
  66  	return l
  67  }
  68  
  69  func TestHarvestAndEmit(t *testing.T) {
  70  	l := lattice.New()
  71  
  72  	tags := []string{"package", "import", "type", "struct", "field",
  73  		"func", "ident:var-name", "ident:func-name", "method", "return"}
  74  	nodes := make(map[string]*lattice.Node)
  75  	for _, tag := range tags {
  76  		n := l.AddNode([]axiom.Constraint{tagConstraint{tag}})
  77  		nodes[tag] = n
  78  	}
  79  	for range 5 {
  80  		l.AddNode([]axiom.Constraint{tagConstraint{"ident:var-name"}})
  81  	}
  82  
  83  	nodes["package"].Bond(enzyme.Elem("package", "main"))
  84  	nodes["import"].Bond(enzyme.Elem("import", `"fmt"`))
  85  	nodes["type"].Bond(enzyme.Elem("type", "MyType"))
  86  	nodes["struct"].Bond(enzyme.Elem("struct", "MyType"))
  87  	nodes["field"].Bond(enzyme.Elem("field", "MyType\x00Name string"))
  88  	nodes["func"].Bond(enzyme.Elem("func", "main()"))
  89  	nodes["ident:var-name"].Bond(enzyme.Elem("ident:var-name", "foo"))
  90  	nodes["ident:func-name"].Bond(enzyme.Elem("ident:func-name", "main"))
  91  	nodes["method"].Bond(enzyme.Elem("method", "MyType.Run()"))
  92  	nodes["return"].Bond(enzyme.Elem("return", ""))
  93  
  94  	files := Harvest(l)
  95  	frags := files[""]
  96  	t.Logf("harvested %d fragments", len(frags))
  97  
  98  	var buf strings.Builder
  99  	err := EmitGo(frags, &buf)
 100  	if err != nil {
 101  		t.Fatal(err)
 102  	}
 103  
 104  	src := buf.String()
 105  	t.Log("--- emitted source ---")
 106  	t.Log(src)
 107  
 108  	if !strings.Contains(src, "package main") {
 109  		t.Error("missing package declaration")
 110  	}
 111  	if !strings.Contains(src, "type MyType struct") {
 112  		t.Error("missing type declaration")
 113  	}
 114  	if !strings.Contains(src, "func main()") {
 115  		t.Error("missing func declaration")
 116  	}
 117  }
 118  
 119  func TestEmitFromSelfIngest(t *testing.T) {
 120  	l := makeLattice(t)
 121  
 122  	src := `package main
 123  
 124  import "fmt"
 125  
 126  type Thing struct {
 127  	Name string
 128  }
 129  
 130  func main() {
 131  	f := Thing{Name: "world"}
 132  	fmt.Println(f.Name)
 133  }
 134  `
 135  	bonded := bondAll(t, l, src)
 136  	t.Logf("bonded %d elements", bonded)
 137  
 138  	files := Harvest(l)
 139  	frags := files[""]
 140  
 141  	var buf strings.Builder
 142  	err := EmitGo(frags, &buf)
 143  	if err != nil {
 144  		t.Fatal(err)
 145  	}
 146  
 147  	emitted := buf.String()
 148  	t.Log("--- emitted from self-ingest ---")
 149  	t.Log(emitted)
 150  
 151  	if !strings.Contains(emitted, "package main") {
 152  		t.Error("missing package declaration")
 153  	}
 154  	if !strings.Contains(emitted, "func main()") {
 155  		t.Error("expected main function")
 156  	}
 157  	// Should have the assignment in main's body.
 158  	if !strings.Contains(emitted, "Thing{") {
 159  		t.Error("expected Thing struct literal in main body")
 160  	}
 161  	// Should have the fmt.Println call.
 162  	if !strings.Contains(emitted, "fmt.Println") {
 163  		t.Error("expected fmt.Println call in body")
 164  	}
 165  	// Should have fmt import.
 166  	if !strings.Contains(emitted, `"fmt"`) {
 167  		t.Error("expected fmt import")
 168  	}
 169  }
 170  
 171  func TestEmitReconstructsBody(t *testing.T) {
 172  	l := makeLattice(t)
 173  
 174  	src := `package main
 175  
 176  import "fmt"
 177  
 178  func greet(name string) string {
 179  	if name == "" {
 180  		return "hello, stranger"
 181  	}
 182  	return fmt.Sprintf("hello, %s", name)
 183  }
 184  
 185  func main() {
 186  	msg := greet("world")
 187  	fmt.Println(msg)
 188  }
 189  `
 190  	bonded := bondAll(t, l, src)
 191  	t.Logf("bonded %d elements", bonded)
 192  
 193  	files := Harvest(l)
 194  	frags := files[""]
 195  
 196  	var buf strings.Builder
 197  	err := EmitGo(frags, &buf)
 198  	if err != nil {
 199  		t.Fatal(err)
 200  	}
 201  
 202  	emitted := buf.String()
 203  	t.Log("--- emitted with body reconstruction ---")
 204  	t.Log(emitted)
 205  
 206  	// Check function declarations include signatures.
 207  	if !strings.Contains(emitted, "func greet(") {
 208  		t.Error("expected greet function with parameters")
 209  	}
 210  	// Check greet body has the if statement.
 211  	if !strings.Contains(emitted, `name == ""`) {
 212  		t.Error("expected if condition in greet body")
 213  	}
 214  	// Check return statement.
 215  	if !strings.Contains(emitted, `return fmt.Sprintf`) || !strings.Contains(emitted, `return "hello, stranger"`) {
 216  		t.Error("expected return statements in greet body")
 217  	}
 218  	// Check main body has assignment.
 219  	if !strings.Contains(emitted, `greet("world")`) {
 220  		t.Error("expected greet call in main body")
 221  	}
 222  }
 223  
 224  func TestEmitMethodReconstruction(t *testing.T) {
 225  	l := makeLattice(t)
 226  
 227  	src := `package main
 228  
 229  import "fmt"
 230  
 231  type Server struct {
 232  	Port int
 233  }
 234  
 235  func (s Server) Start() error {
 236  	fmt.Printf("listening on %d\n", s.Port)
 237  	return nil
 238  }
 239  
 240  func main() {
 241  	s := Server{Port: 8080}
 242  	s.Start()
 243  }
 244  `
 245  	bonded := bondAll(t, l, src)
 246  	t.Logf("bonded %d elements", bonded)
 247  
 248  	files := Harvest(l)
 249  	frags := files[""]
 250  
 251  	var buf strings.Builder
 252  	err := EmitGo(frags, &buf)
 253  	if err != nil {
 254  		t.Fatal(err)
 255  	}
 256  
 257  	emitted := buf.String()
 258  	t.Log("--- emitted with methods ---")
 259  	t.Log(emitted)
 260  
 261  	// Method should have receiver with original variable name.
 262  	if !strings.Contains(emitted, "func (s Server) Start()") {
 263  		t.Error("expected method with receiver")
 264  	}
 265  	// Method body should have the Printf call.
 266  	if !strings.Contains(emitted, "fmt.Printf") {
 267  		t.Error("expected fmt.Printf in method body")
 268  	}
 269  	// Struct declaration.
 270  	if !strings.Contains(emitted, "type Server struct") {
 271  		t.Error("expected Server struct")
 272  	}
 273  }
 274  
 275  func TestEmitCompilesClean(t *testing.T) {
 276  	l := makeLattice(t)
 277  
 278  	src := `package main
 279  
 280  import "fmt"
 281  
 282  type Score struct {
 283  	Value int
 284  }
 285  
 286  func compute() int {
 287  	return 42
 288  }
 289  
 290  func main() {
 291  	x := compute()
 292  	fmt.Printf("score: %d\n", x)
 293  }
 294  `
 295  	bondAll(t, l, src)
 296  
 297  	files := Harvest(l)
 298  	frags := files[""]
 299  
 300  	var buf strings.Builder
 301  	err := EmitGo(frags, &buf)
 302  	if err != nil {
 303  		t.Fatal(err)
 304  	}
 305  
 306  	emitted := buf.String()
 307  	t.Log("--- emitted source ---")
 308  	t.Log(emitted)
 309  
 310  	// Verify it parses as valid Go.
 311  	fset := token.NewFileSet()
 312  	_, parseErr := parser.ParseFile(fset, "emitted.go", emitted, parser.SkipObjectResolution)
 313  	if parseErr != nil {
 314  		t.Errorf("emitted source does not parse as valid Go: %v\nsource:\n%s", parseErr, emitted)
 315  	}
 316  }
 317  
 318  func TestEmitLegacyMode(t *testing.T) {
 319  	// Test that the legacy self-logging mode still works when only
 320  	// declarations and literals are present (no body elements).
 321  	l := lattice.New()
 322  
 323  	nodes := make(map[string]*lattice.Node)
 324  	for _, tag := range []string{"package", "type", "struct", "field", "func", "literal:string", "method"} {
 325  		n := l.AddNode([]axiom.Constraint{tagConstraint{tag}})
 326  		nodes[tag] = n
 327  	}
 328  
 329  	nodes["package"].Bond(enzyme.Elem("package", "main"))
 330  	nodes["type"].Bond(enzyme.Elem("type", "Score"))
 331  	nodes["struct"].Bond(enzyme.Elem("struct", "Score"))
 332  	nodes["field"].Bond(enzyme.Elem("field", "Score\x00Value int"))
 333  	nodes["func"].Bond(enzyme.Elem("func", "main()"))
 334  	nodes["literal:string"].Bond(enzyme.Elem("literal:string", `"score: %d nodes\n"`))
 335  	nodes["method"].Bond(enzyme.Elem("method", "Score.Compute()"))
 336  
 337  	files := Harvest(l)
 338  	frags := files[""]
 339  
 340  	var buf strings.Builder
 341  	err := EmitGo(frags, &buf)
 342  	if err != nil {
 343  		t.Fatal(err)
 344  	}
 345  
 346  	emitted := buf.String()
 347  	t.Log("--- legacy emitted source ---")
 348  	t.Log(emitted)
 349  
 350  	// Should have self-knowledge in legacy mode.
 351  	if !strings.Contains(emitted, "nTypes") {
 352  		t.Error("expected self-knowledge declaration in legacy mode")
 353  	}
 354  	if !strings.Contains(emitted, "fmt.Printf") {
 355  		t.Error("expected fmt.Printf for format strings in legacy mode")
 356  	}
 357  }
 358  
 359  func TestParseBodyValue(t *testing.T) {
 360  	parent, source := parseBodyValue("main\x00x := 42")
 361  	if parent != "main" {
 362  		t.Errorf("expected parent 'main', got %q", parent)
 363  	}
 364  	if source != "x := 42" {
 365  		t.Errorf("expected source 'x := 42', got %q", source)
 366  	}
 367  
 368  	// Legacy element with no separator.
 369  	parent, source = parseBodyValue("")
 370  	if parent != "" {
 371  		t.Errorf("expected empty parent, got %q", parent)
 372  	}
 373  }
 374  
 375  func TestParseFuncName(t *testing.T) {
 376  	tests := []struct {
 377  		val      string
 378  		isMethod bool
 379  		want     string
 380  	}{
 381  		{"main()", false, "main"},
 382  		{"greet(name string) string", false, "greet"},
 383  		{"Foo.Hello() string", true, "Hello"},
 384  		{"*Server.Start() error", true, "Start"},
 385  	}
 386  	for _, tt := range tests {
 387  		got := parseFuncName(tt.val, tt.isMethod)
 388  		if got != tt.want {
 389  			t.Errorf("parseFuncName(%q, %v) = %q, want %q", tt.val, tt.isMethod, got, tt.want)
 390  		}
 391  	}
 392  }
 393  
 394  func TestEmitMultipleStructs(t *testing.T) {
 395  	l := makeLattice(t)
 396  
 397  	src := `package main
 398  
 399  import "fmt"
 400  
 401  type Server struct {
 402  	Port int
 403  	Host string
 404  }
 405  
 406  type Client struct {
 407  	Name    string
 408  	Timeout int
 409  }
 410  
 411  func main() {
 412  	s := Server{Port: 8080, Host: "localhost"}
 413  	c := Client{Name: "test", Timeout: 30}
 414  	fmt.Println(s, c)
 415  }
 416  `
 417  	bonded := bondAll(t, l, src)
 418  	t.Logf("bonded %d elements", bonded)
 419  
 420  	files := Harvest(l)
 421  	frags := files[""]
 422  
 423  	var buf strings.Builder
 424  	err := EmitGo(frags, &buf)
 425  	if err != nil {
 426  		t.Fatal(err)
 427  	}
 428  
 429  	emitted := buf.String()
 430  	t.Log("--- emitted with multiple structs ---")
 431  	t.Log(emitted)
 432  
 433  	// Server struct should have Port and Host but NOT Name or Timeout.
 434  	if !strings.Contains(emitted, "type Server struct") {
 435  		t.Error("expected Server struct")
 436  	}
 437  	if !strings.Contains(emitted, "type Client struct") {
 438  		t.Error("expected Client struct")
 439  	}
 440  
 441  	// Find Server struct block and verify its fields.
 442  	serverIdx := strings.Index(emitted, "type Server struct")
 443  	clientIdx := strings.Index(emitted, "type Client struct")
 444  	if serverIdx < 0 || clientIdx < 0 {
 445  		t.Fatal("missing struct declarations")
 446  	}
 447  
 448  	// Extract the Server struct block.
 449  	serverEnd := strings.Index(emitted[serverIdx:], "}\n")
 450  	serverBlock := emitted[serverIdx : serverIdx+serverEnd+2]
 451  	t.Logf("Server block: %q", serverBlock)
 452  
 453  	if !strings.Contains(serverBlock, "Port") {
 454  		t.Error("Server should contain Port field")
 455  	}
 456  	if !strings.Contains(serverBlock, "Host") {
 457  		t.Error("Server should contain Host field")
 458  	}
 459  	if strings.Contains(serverBlock, "Name") {
 460  		t.Error("Server should NOT contain Name field (belongs to Client)")
 461  	}
 462  	if strings.Contains(serverBlock, "Timeout") {
 463  		t.Error("Server should NOT contain Timeout field (belongs to Client)")
 464  	}
 465  
 466  	// Extract the Client struct block.
 467  	clientEnd := strings.Index(emitted[clientIdx:], "}\n")
 468  	clientBlock := emitted[clientIdx : clientIdx+clientEnd+2]
 469  	t.Logf("Client block: %q", clientBlock)
 470  
 471  	if !strings.Contains(clientBlock, "Name") {
 472  		t.Error("Client should contain Name field")
 473  	}
 474  	if !strings.Contains(clientBlock, "Timeout") {
 475  		t.Error("Client should contain Timeout field")
 476  	}
 477  	if strings.Contains(clientBlock, "Port") {
 478  		t.Error("Client should NOT contain Port field (belongs to Server)")
 479  	}
 480  
 481  	// Verify it parses as valid Go.
 482  	fset := token.NewFileSet()
 483  	_, parseErr := parser.ParseFile(fset, "emitted.go", emitted, parser.SkipObjectResolution)
 484  	if parseErr != nil {
 485  		t.Errorf("emitted source does not parse: %v\nsource:\n%s", parseErr, emitted)
 486  	}
 487  }
 488  
 489  func TestEmitDirective(t *testing.T) {
 490  	l := makeLattice(t)
 491  	// Add sites for directive and var element types.
 492  	for range 4 {
 493  		n := l.AddNode([]axiom.Constraint{tagConstraint{"directive"}})
 494  		n.SetEnergy(true)
 495  	}
 496  	for range 4 {
 497  		n := l.AddNode([]axiom.Constraint{tagConstraint{"var"}})
 498  		n.SetEnergy(true)
 499  	}
 500  
 501  	src := `package main
 502  
 503  import "embed"
 504  
 505  //go:embed hello.txt
 506  var content string
 507  
 508  func main() {
 509  	println(content)
 510  }
 511  `
 512  	bonded := bondAll(t, l, src)
 513  	t.Logf("bonded %d elements", bonded)
 514  
 515  	files := Harvest(l)
 516  	frags := files[""]
 517  
 518  	var buf strings.Builder
 519  	err := EmitGo(frags, &buf)
 520  	if err != nil {
 521  		t.Fatal(err)
 522  	}
 523  
 524  	emitted := buf.String()
 525  	t.Log("--- emitted with directive ---")
 526  	t.Log(emitted)
 527  
 528  	// The //go:embed directive should appear in the output.
 529  	if !strings.Contains(emitted, "//go:embed") {
 530  		t.Error("expected //go:embed directive in emitted source")
 531  	}
 532  
 533  	// The var declaration should appear.
 534  	if !strings.Contains(emitted, "var content") {
 535  		t.Error("expected var content declaration in emitted source")
 536  	}
 537  
 538  	// The directive should appear before the var declaration.
 539  	embedIdx := strings.Index(emitted, "//go:embed")
 540  	varIdx := strings.Index(emitted, "var content")
 541  	if embedIdx >= 0 && varIdx >= 0 && embedIdx > varIdx {
 542  		t.Error("//go:embed directive should appear before var declaration")
 543  	}
 544  }
 545  
 546  func TestImportPruning(t *testing.T) {
 547  	// Verify unused imports are pruned.
 548  	stmts := []bodyStmt{
 549  		{Source: `fmt.Println("hello")`},
 550  	}
 551  	imports := []Fragment{
 552  		{Type: "import", Value: `"fmt"`},
 553  		{Type: "import", Value: `"os"`},       // unused — should be pruned
 554  		{Type: "import", Value: `"strings"`},   // unused — should be pruned
 555  	}
 556  	result := inferImports(stmts, imports, nil, nil, nil)
 557  	if len(result) != 1 || result[0] != `"fmt"` {
 558  		t.Errorf("expected only fmt import, got %v", result)
 559  	}
 560  }
 561  
 562  func TestRejectUntrustedImport(t *testing.T) {
 563  	// External imports must be rejected by trust policy.
 564  	stmts := []bodyStmt{
 565  		{Source: `evil.DoSomething()`},
 566  		{Source: `axiom.NewElement()`},
 567  	}
 568  	imports := []Fragment{
 569  		{Type: "import", Value: `"github.com/evil/pkg"`},                       // untrusted
 570  		{Type: "import", Value: `"git.mleku.dev/mleku/dendrite/pkg/axiom"`},        // trusted self-import
 571  		{Type: "import", Value: `"fmt"`},                                        // trusted stdlib (unused)
 572  	}
 573  	result := inferImports(stmts, imports, nil, nil, nil)
 574  
 575  	// Only the self-import should survive (used + trusted).
 576  	// fmt is trusted but unused, evil is used but untrusted.
 577  	found := make(map[string]bool)
 578  	for _, imp := range result {
 579  		found[imp] = true
 580  	}
 581  
 582  	if found[`"github.com/evil/pkg"`] {
 583  		t.Error("untrusted external import should have been rejected")
 584  	}
 585  	if !found[`"git.mleku.dev/mleku/dendrite/pkg/axiom"`] {
 586  		t.Error("trusted self-import should have been kept")
 587  	}
 588  	if found[`"fmt"`] {
 589  		t.Error("unused import should have been pruned")
 590  	}
 591  }
 592  
 593  func TestInferImportsFromSignatures(t *testing.T) {
 594  	// Imports should be inferred from function signatures and var declarations,
 595  	// not just body statements.
 596  	stmts := []bodyStmt{
 597  		{Source: `fmt.Println("hello")`},
 598  	}
 599  	funcs := []funcDecl{
 600  		{Name: "foo", Signature: "foo(s *spore.Spore) ratio.Ratio", IsMethod: false},
 601  	}
 602  	vars := []Fragment{
 603  		{Type: "var", Value: `var Name = reflect.TypeOf(Block{}).Name()`},
 604  	}
 605  	result := inferImports(stmts, nil, funcs, vars, nil)
 606  	found := make(map[string]bool)
 607  	for _, imp := range result {
 608  		found[imp] = true
 609  	}
 610  	if !found[`"fmt"`] {
 611  		t.Error("expected fmt import from body, got", result)
 612  	}
 613  	if !found[`"reflect"`] {
 614  		t.Error("expected reflect import from var declaration, got", result)
 615  	}
 616  	if !found[`"git.mleku.dev/mleku/dendrite/pkg/spore"`] {
 617  		t.Error("expected spore import from func signature, got", result)
 618  	}
 619  	if !found[`"git.mleku.dev/mleku/dendrite/pkg/ratio"`] {
 620  		t.Error("expected ratio import from func signature, got", result)
 621  	}
 622  }
 623  
 624  func TestDependencyOrdering(t *testing.T) {
 625  	// Strict dependency chain: x → y → Println.
 626  	// x must come before y, y before Println.
 627  	stmts := []bodyStmt{
 628  		{Source: `fmt.Println(y)`, Tag: "expr"},
 629  		{Source: `y := x + 1`, Tag: "assign"},
 630  		{Source: `x := 1`, Tag: "assign"},
 631  	}
 632  
 633  	for range 10 {
 634  		ordered := orderBody(stmts)
 635  		xIdx, yIdx, printIdx := -1, -1, -1
 636  		for i, s := range ordered {
 637  			switch {
 638  			case strings.Contains(s.Source, "x := 1"):
 639  				xIdx = i
 640  			case strings.Contains(s.Source, "y := x"):
 641  				yIdx = i
 642  			case strings.Contains(s.Source, "Println"):
 643  				printIdx = i
 644  			}
 645  		}
 646  		if xIdx > yIdx {
 647  			t.Errorf("x := 1 (idx %d) should come before y := x + 1 (idx %d)", xIdx, yIdx)
 648  		}
 649  		if yIdx > printIdx {
 650  			t.Errorf("y := x + 1 (idx %d) should come before Println(y) (idx %d)", yIdx, printIdx)
 651  		}
 652  	}
 653  }
 654  
 655  func TestNonDependentShuffling(t *testing.T) {
 656  	// Three independent assignments + one dependent Println.
 657  	// a, b, c have no dependencies on each other — should be shuffled.
 658  	// Println depends on all three — must come last.
 659  	stmts := []bodyStmt{
 660  		{Source: `a := 1`, Tag: "assign"},
 661  		{Source: `b := 2`, Tag: "assign"},
 662  		{Source: `c := 3`, Tag: "assign"},
 663  		{Source: `fmt.Println(a, b, c)`, Tag: "expr"},
 664  	}
 665  
 666  	orderings := make(map[string]bool)
 667  	for range 100 {
 668  		ordered := orderBody(stmts)
 669  
 670  		// Println must always be last.
 671  		last := ordered[len(ordered)-1]
 672  		if !strings.Contains(last.Source, "Println") {
 673  			t.Fatal("Println must always be last (depends on a, b, c)")
 674  		}
 675  
 676  		// Collect the order of a, b, c.
 677  		var order string
 678  		for _, s := range ordered[:3] {
 679  			switch {
 680  			case strings.Contains(s.Source, "a :="):
 681  				order += "a"
 682  			case strings.Contains(s.Source, "b :="):
 683  				order += "b"
 684  			case strings.Contains(s.Source, "c :="):
 685  				order += "c"
 686  			}
 687  		}
 688  		orderings[order] = true
 689  	}
 690  
 691  	if len(orderings) < 2 {
 692  		t.Errorf("expected at least 2 distinct orderings of independent statements, got %d: %v",
 693  			len(orderings), orderings)
 694  	}
 695  	t.Logf("observed %d distinct orderings: %v", len(orderings), orderings)
 696  }
 697  
 698  func TestEmitProject(t *testing.T) {
 699  	// Create fragments for two files: main package and a library package.
 700  	mainFrags := []Fragment{
 701  		{Type: "package", Value: "main"},
 702  		{Type: "import", Value: `"fmt"`},
 703  		{Type: "import", Value: `"git.mleku.dev/mleku/dendrite/pkg/axiom"`},
 704  		{Type: "func", Value: "main()"},
 705  	}
 706  	// Simulate body content so imports get resolved.
 707  	mainFrags = append(mainFrags, Fragment{Type: "expr", Value: "main\x00fmt.Println(axiom.New())"})
 708  
 709  	libFrags := []Fragment{
 710  		{Type: "package", Value: "axiom"},
 711  		{Type: "type", Value: "Element"},
 712  		{Type: "interface", Value: "Element"},
 713  		{Type: "func", Value: "New() Element"},
 714  	}
 715  
 716  	files := map[string][]Fragment{
 717  		"main.go":       mainFrags,
 718  		"axiom/axiom.go": libFrags,
 719  	}
 720  
 721  	project := EmitProject(files, "git.mleku.dev/mleku/dendrite")
 722  
 723  	// Should have 3 files: main.go, axiom/axiom.go, go.mod.
 724  	if len(project) != 3 {
 725  		t.Errorf("expected 3 files, got %d: %v", len(project), keys(project))
 726  	}
 727  
 728  	// Check go.mod exists and has replace directive.
 729  	gomod := project["go.mod"]
 730  	if gomod == "" {
 731  		t.Fatal("missing go.mod")
 732  	}
 733  	t.Log("--- go.mod ---")
 734  	t.Log(gomod)
 735  
 736  	if !strings.Contains(gomod, "module git.mleku.dev/mleku/dendrite") {
 737  		t.Error("go.mod missing module declaration")
 738  	}
 739  	if !strings.Contains(gomod, "git.mleku.dev/mleku/dendrite/axiom => ./axiom") {
 740  		t.Error("go.mod missing replace directive for axiom")
 741  	}
 742  
 743  	// Check main.go has package main.
 744  	mainSrc := project["main.go"]
 745  	if !strings.Contains(mainSrc, "package main") {
 746  		t.Error("main.go missing package main")
 747  	}
 748  	t.Log("--- main.go ---")
 749  	t.Log(mainSrc)
 750  
 751  	// Check axiom/axiom.go has package axiom.
 752  	axiomSrc := project["axiom/axiom.go"]
 753  	if !strings.Contains(axiomSrc, "package axiom") {
 754  		t.Error("axiom/axiom.go missing package axiom")
 755  	}
 756  	t.Log("--- axiom/axiom.go ---")
 757  	t.Log(axiomSrc)
 758  }
 759  
 760  func TestGoModReplace(t *testing.T) {
 761  	internalPkgs := map[string]bool{
 762  		"axiom":   true,
 763  		"emit":    true,
 764  		"enzyme":  true,
 765  		"lattice": true,
 766  	}
 767  	gomod := emitGoMod("git.mleku.dev/mleku/dendrite", internalPkgs)
 768  	t.Log(gomod)
 769  
 770  	if !strings.Contains(gomod, "module git.mleku.dev/mleku/dendrite") {
 771  		t.Error("missing module declaration")
 772  	}
 773  	if !strings.Contains(gomod, "go 1.24") {
 774  		t.Error("missing go version")
 775  	}
 776  
 777  	// All internal packages should have replace directives.
 778  	for pkg := range internalPkgs {
 779  		expected := fmt.Sprintf("git.mleku.dev/mleku/dendrite/%s => ./%s", pkg, pkg)
 780  		if !strings.Contains(gomod, expected) {
 781  			t.Errorf("missing replace directive for %s", pkg)
 782  		}
 783  	}
 784  }
 785  
 786  func keys(m map[string]string) []string {
 787  	var ks []string
 788  	for k := range m {
 789  		ks = append(ks, k)
 790  	}
 791  	sort.Strings(ks)
 792  	return ks
 793  }
 794  
 795  func TestEmitGoPackage(t *testing.T) {
 796  	// Verify non-main package emission doesn't force a main function.
 797  	frags := []Fragment{
 798  		{Type: "package", Value: "axiom"},
 799  		{Type: "type", Value: "Element"},
 800  		{Type: "interface", Value: "Element"},
 801  	}
 802  
 803  	var buf strings.Builder
 804  	err := EmitGoPackage(frags, &buf, "axiom")
 805  	if err != nil {
 806  		t.Fatal(err)
 807  	}
 808  
 809  	emitted := buf.String()
 810  	t.Log("--- non-main package ---")
 811  	t.Log(emitted)
 812  
 813  	if !strings.Contains(emitted, "package axiom") {
 814  		t.Error("expected package axiom")
 815  	}
 816  	if strings.Contains(emitted, "func main()") {
 817  		t.Error("non-main package should NOT have func main()")
 818  	}
 819  }
 820