github.com/connorvict/air@v0.0.0-20231005162537-279bf07db0d5/runner/engine_test.go (about) 1 package runner 2 3 import ( 4 "errors" 5 "fmt" 6 "io/ioutil" 7 "log" 8 "net" 9 "os" 10 "os/exec" 11 "os/signal" 12 "runtime" 13 "strings" 14 "sync/atomic" 15 "syscall" 16 "testing" 17 "time" 18 19 "github.com/pelletier/go-toml" 20 "github.com/stretchr/testify/assert" 21 ) 22 23 func TestNewEngine(t *testing.T) { 24 _ = os.Unsetenv(airWd) 25 engine, err := NewEngine("", true) 26 if err != nil { 27 t.Fatalf("Should not be fail: %s.", err) 28 } 29 if engine.logger == nil { 30 t.Fatal("logger should not be nil") 31 } 32 if engine.config == nil { 33 t.Fatal("Config should not be nil") 34 } 35 if engine.watcher == nil { 36 t.Fatal("watcher should not be nil") 37 } 38 } 39 40 func TestCheckRunEnv(t *testing.T) { 41 _ = os.Unsetenv(airWd) 42 engine, err := NewEngine("", true) 43 if err != nil { 44 t.Fatalf("Should not be fail: %s.", err) 45 } 46 err = engine.checkRunEnv() 47 if err == nil { 48 t.Fatal("should throw a err") 49 } 50 } 51 52 func TestWatching(t *testing.T) { 53 engine, err := NewEngine("", true) 54 if err != nil { 55 t.Fatalf("Should not be fail: %s.", err) 56 } 57 path, err := os.Getwd() 58 if err != nil { 59 t.Fatalf("Should not be fail: %s.", err) 60 } 61 path = strings.Replace(path, "_testdata/toml", "", 1) 62 err = engine.watching(path + "/_testdata/watching") 63 if err != nil { 64 t.Fatalf("Should not be fail: %s.", err) 65 } 66 } 67 68 func TestRegexes(t *testing.T) { 69 engine, err := NewEngine("", true) 70 if err != nil { 71 t.Fatalf("Should not be fail: %s.", err) 72 } 73 engine.config.Build.ExcludeRegex = []string{"foo\\.html$", "bar", "_test\\.go"} 74 75 result, err := engine.isExcludeRegex("./test/foo.html") 76 if err != nil { 77 t.Fatalf("Should not be fail: %s.", err) 78 } 79 if result != true { 80 t.Errorf("expected '%t' but got '%t'", true, result) 81 } 82 83 result, err = engine.isExcludeRegex("./test/bar/index.html") 84 if err != nil { 85 t.Fatalf("Should not be fail: %s.", err) 86 } 87 if result != true { 88 t.Errorf("expected '%t' but got '%t'", true, result) 89 } 90 91 result, err = engine.isExcludeRegex("./test/unrelated.html") 92 if err != nil { 93 t.Fatalf("Should not be fail: %s.", err) 94 } 95 if result { 96 t.Errorf("expected '%t' but got '%t'", false, result) 97 } 98 99 result, err = engine.isExcludeRegex("./myPackage/goFile_testxgo") 100 if err != nil { 101 t.Fatalf("Should not be fail: %s.", err) 102 } 103 if result { 104 t.Errorf("expected '%t' but got '%t'", false, result) 105 } 106 result, err = engine.isExcludeRegex("./myPackage/goFile_test.go") 107 if err != nil { 108 t.Fatalf("Should not be fail: %s.", err) 109 } 110 if result != true { 111 t.Errorf("expected '%t' but got '%t'", true, result) 112 } 113 } 114 115 func TestRerun(t *testing.T) { 116 tmpDir := initWithQuickExitGoCode(t) 117 // change dir to tmpDir 118 chdir(t, tmpDir) 119 engine, err := NewEngine("", true) 120 engine.config.Build.ExcludeUnchanged = true 121 engine.config.Build.Rerun = true 122 engine.config.Build.RerunDelay = 100 123 if err != nil { 124 t.Fatalf("Should not be fail: %s.", err) 125 } 126 go func() { 127 engine.Run() 128 t.Logf("engine run") 129 }() 130 131 time.Sleep(time.Second * 1) 132 133 // stop engine 134 engine.Stop() 135 time.Sleep(time.Second * 1) 136 t.Logf("engine stopped") 137 138 if atomic.LoadUint64(&engine.round) <= 1 { 139 t.Fatalf("The engine did't rerun") 140 } 141 } 142 143 func TestRerunWhenFileChanged(t *testing.T) { 144 tmpDir := initWithQuickExitGoCode(t) 145 // change dir to tmpDir 146 chdir(t, tmpDir) 147 engine, err := NewEngine("", true) 148 engine.config.Build.ExcludeUnchanged = true 149 engine.config.Build.Rerun = true 150 engine.config.Build.RerunDelay = 100 151 if err != nil { 152 t.Fatalf("Should not be fail: %s.", err) 153 } 154 go func() { 155 engine.Run() 156 t.Logf("engine run") 157 }() 158 time.Sleep(time.Second * 1) 159 160 roundBeforeChange := atomic.LoadUint64(&engine.round) 161 162 t.Logf("start change main.go") 163 // change file of main.go 164 // just append a new empty line to main.go 165 time.Sleep(time.Second * 2) 166 file, err := os.OpenFile("main.go", os.O_APPEND|os.O_WRONLY, 0o644) 167 if err != nil { 168 t.Fatalf("Should not be fail: %s.", err) 169 } 170 defer file.Close() 171 _, err = file.WriteString("\n") 172 if err != nil { 173 t.Fatalf("Should not be fail: %s.", err) 174 } 175 176 time.Sleep(time.Second * 1) 177 // stop engine 178 engine.Stop() 179 time.Sleep(time.Second * 1) 180 t.Logf("engine stopped") 181 182 roundAfterChange := atomic.LoadUint64(&engine.round) 183 if roundBeforeChange+1 >= roundAfterChange { 184 t.Fatalf("The engine didn't rerun") 185 } 186 } 187 188 func TestRunCommand(t *testing.T) { 189 // generate a random port 190 port, f := GetPort() 191 f() 192 t.Logf("port: %d", port) 193 tmpDir := initTestEnv(t, port) 194 // change dir to tmpDir 195 chdir(t, tmpDir) 196 engine, err := NewEngine("", true) 197 if err != nil { 198 t.Fatalf("Should not be fail: %s.", err) 199 } 200 err = engine.runCommand("touch test.txt") 201 if err != nil { 202 t.Fatalf("Should not be fail: %s.", err) 203 } 204 if _, err := os.Stat("./test.txt"); err != nil { 205 if os.IsNotExist(err) { 206 t.Fatalf("Should not be fail: %s.", err) 207 } 208 } 209 } 210 211 func TestRunPreCmd(t *testing.T) { 212 // generate a random port 213 port, f := GetPort() 214 f() 215 t.Logf("port: %d", port) 216 tmpDir := initTestEnv(t, port) 217 // change dir to tmpDir 218 chdir(t, tmpDir) 219 engine, err := NewEngine("", true) 220 if err != nil { 221 t.Fatalf("Should not be fail: %s.", err) 222 } 223 err = engine.runPreCmd() 224 if err != nil { 225 t.Fatalf("Should not be fail: %s.", err) 226 } 227 if _, err := os.Stat("./pre_cmd.txt"); err != nil { 228 if os.IsNotExist(err) { 229 t.Fatalf("Should not be fail: %s.", err) 230 } 231 } 232 } 233 234 func TestRunPostCmd(t *testing.T) { 235 // generate a random port 236 port, f := GetPort() 237 f() 238 t.Logf("port: %d", port) 239 tmpDir := initTestEnv(t, port) 240 // change dir to tmpDir 241 chdir(t, tmpDir) 242 243 engine, err := NewEngine("", true) 244 if err != nil { 245 t.Fatalf("Should not be fail: %s.", err) 246 } 247 248 err = engine.runPostCmd() 249 if err != nil { 250 t.Fatalf("Should not be fail: %s.", err) 251 } 252 253 if _, err := os.Stat("./post_cmd.txt"); err != nil { 254 if os.IsNotExist(err) { 255 t.Fatalf("Should not be fail: %s.", err) 256 } 257 } 258 } 259 260 func TestRunBin(t *testing.T) { 261 engine, err := NewEngine("", true) 262 if err != nil { 263 t.Fatalf("Should not be fail: %s.", err) 264 } 265 266 err = engine.runBin() 267 if err != nil { 268 t.Fatalf("Should not be fail: %s.", err) 269 } 270 } 271 272 func GetPort() (int, func()) { 273 l, err := net.Listen("tcp", ":0") 274 port := l.Addr().(*net.TCPAddr).Port 275 if err != nil { 276 panic(err) 277 } 278 return port, func() { 279 _ = l.Close() 280 } 281 } 282 283 func TestRebuild(t *testing.T) { 284 // generate a random port 285 port, f := GetPort() 286 f() 287 t.Logf("port: %d", port) 288 289 tmpDir := initTestEnv(t, port) 290 // change dir to tmpDir 291 chdir(t, tmpDir) 292 engine, err := NewEngine("", true) 293 engine.config.Build.ExcludeUnchanged = true 294 if err != nil { 295 t.Fatalf("Should not be fail: %s.", err) 296 } 297 go func() { 298 engine.Run() 299 t.Logf("engine stopped") 300 }() 301 err = waitingPortReady(t, port, time.Second*10) 302 if err != nil { 303 t.Fatalf("Should not be fail: %s.", err) 304 } 305 t.Logf("port is ready") 306 307 // start rebuld 308 309 t.Logf("start change main.go") 310 // change file of main.go 311 // just append a new empty line to main.go 312 time.Sleep(time.Second * 2) 313 file, err := os.OpenFile("main.go", os.O_APPEND|os.O_WRONLY, 0o644) 314 if err != nil { 315 t.Fatalf("Should not be fail: %s.", err) 316 } 317 defer file.Close() 318 _, err = file.WriteString("\n") 319 if err != nil { 320 t.Fatalf("Should not be fail: %s.", err) 321 } 322 err = waitingPortConnectionRefused(t, port, time.Second*10) 323 if err != nil { 324 t.Fatalf("timeout: %s.", err) 325 } 326 t.Logf("connection refused") 327 time.Sleep(time.Second * 2) 328 err = waitingPortReady(t, port, time.Second*10) 329 if err != nil { 330 t.Fatalf("Should not be fail: %s.", err) 331 } 332 t.Logf("port is ready") 333 // stop engine 334 engine.Stop() 335 time.Sleep(time.Second * 1) 336 t.Logf("engine stopped") 337 assert.True(t, checkPortConnectionRefused(port)) 338 } 339 340 func waitingPortConnectionRefused(t *testing.T, port int, timeout time.Duration) error { 341 t.Logf("waiting port %d connection refused", port) 342 timer := time.NewTimer(timeout) 343 ticker := time.NewTicker(time.Millisecond * 100) 344 defer ticker.Stop() 345 defer timer.Stop() 346 for { 347 select { 348 case <-timer.C: 349 return fmt.Errorf("timeout") 350 case <-ticker.C: 351 print(".") 352 _, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port)) 353 if errors.Is(err, syscall.ECONNREFUSED) { 354 return nil 355 } 356 time.Sleep(time.Millisecond * 100) 357 } 358 } 359 } 360 361 func TestCtrlCWhenHaveKillDelay(t *testing.T) { 362 // fix https://github.com/cosmtrek/air/issues/278 363 // generate a random port 364 data := []byte("[build]\n kill_delay = \"2s\"") 365 c := Config{} 366 if err := toml.Unmarshal(data, &c); err != nil { 367 t.Fatalf("Should not be fail: %s.", err) 368 } 369 370 port, f := GetPort() 371 f() 372 t.Logf("port: %d", port) 373 374 tmpDir := initTestEnv(t, port) 375 // change dir to tmpDir 376 chdir(t, tmpDir) 377 engine, err := NewEngine("", true) 378 if err != nil { 379 t.Fatalf("Should not be fail: %s.", err) 380 } 381 engine.config.Build.KillDelay = c.Build.KillDelay 382 engine.config.Build.Delay = 2000 383 engine.config.Build.SendInterrupt = true 384 engine.config.preprocess() 385 386 go func() { 387 engine.Run() 388 t.Logf("engine stopped") 389 }() 390 sigs := make(chan os.Signal, 1) 391 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) 392 go func() { 393 <-sigs 394 engine.Stop() 395 t.Logf("engine stopped") 396 }() 397 if err := waitingPortReady(t, port, time.Second*10); err != nil { 398 t.Fatalf("Should not be fail: %s.", err) 399 } 400 sigs <- syscall.SIGINT 401 err = waitingPortConnectionRefused(t, port, time.Second*10) 402 if err != nil { 403 t.Fatalf("Should not be fail: %s.", err) 404 } 405 time.Sleep(time.Second * 3) 406 assert.False(t, engine.running) 407 } 408 409 func TestCtrlCWhenREngineIsRunning(t *testing.T) { 410 // generate a random port 411 port, f := GetPort() 412 f() 413 t.Logf("port: %d", port) 414 415 tmpDir := initTestEnv(t, port) 416 // change dir to tmpDir 417 chdir(t, tmpDir) 418 engine, err := NewEngine("", true) 419 if err != nil { 420 t.Fatalf("Should not be fail: %s.", err) 421 } 422 go func() { 423 engine.Run() 424 t.Logf("engine stopped") 425 }() 426 sigs := make(chan os.Signal, 1) 427 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 428 go func() { 429 <-sigs 430 engine.Stop() 431 t.Logf("engine stopped") 432 }() 433 if err := waitingPortReady(t, port, time.Second*10); err != nil { 434 t.Fatalf("Should not be fail: %s.", err) 435 } 436 sigs <- syscall.SIGINT 437 time.Sleep(time.Second * 1) 438 err = waitingPortConnectionRefused(t, port, time.Second*10) 439 if err != nil { 440 t.Fatalf("Should not be fail: %s.", err) 441 } 442 assert.False(t, engine.running) 443 } 444 445 func TestFixCloseOfChannelAfterCtrlC(t *testing.T) { 446 // fix https://github.com/cosmtrek/air/issues/294 447 dir := initWithBuildFailedCode(t) 448 chdir(t, dir) 449 engine, err := NewEngine("", true) 450 if err != nil { 451 t.Fatalf("Should not be fail: %s.", err) 452 } 453 sigs := make(chan os.Signal, 1) 454 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 455 go func() { 456 engine.Run() 457 t.Logf("engine stopped") 458 }() 459 460 go func() { 461 <-sigs 462 engine.Stop() 463 t.Logf("engine stopped") 464 }() 465 // waiting for compile error 466 time.Sleep(time.Second * 3) 467 port, f := GetPort() 468 f() 469 // correct code 470 err = generateGoCode(dir, port) 471 if err != nil { 472 t.Fatalf("Should not be fail: %s.", err) 473 } 474 475 if err := waitingPortReady(t, port, time.Second*10); err != nil { 476 t.Fatalf("Should not be fail: %s.", err) 477 } 478 479 // ctrl + c 480 sigs <- syscall.SIGINT 481 time.Sleep(time.Second * 1) 482 if err := waitingPortConnectionRefused(t, port, time.Second*10); err != nil { 483 t.Fatalf("Should not be fail: %s.", err) 484 } 485 assert.False(t, engine.running) 486 } 487 488 func TestFixCloseOfChannelAfterTwoFailedBuild(t *testing.T) { 489 // fix https://github.com/cosmtrek/air/issues/294 490 // happens after two failed builds 491 dir := initWithBuildFailedCode(t) 492 // change dir to tmpDir 493 chdir(t, dir) 494 engine, err := NewEngine("", true) 495 if err != nil { 496 t.Fatalf("Should not be fail: %s.", err) 497 } 498 sigs := make(chan os.Signal, 1) 499 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 500 go func() { 501 engine.Run() 502 t.Logf("engine stopped") 503 }() 504 505 go func() { 506 <-sigs 507 engine.Stop() 508 t.Logf("engine stopped") 509 }() 510 511 // waiting for compile error 512 time.Sleep(time.Second * 3) 513 514 // edit *.go file to create build error again 515 file, err := os.OpenFile("main.go", os.O_APPEND|os.O_WRONLY, 0o644) 516 if err != nil { 517 t.Fatalf("Should not be fail: %s.", err) 518 } 519 defer file.Close() 520 _, err = file.WriteString("\n") 521 if err != nil { 522 t.Fatalf("Should not be fail: %s.", err) 523 } 524 time.Sleep(time.Second * 3) 525 // ctrl + c 526 sigs <- syscall.SIGINT 527 time.Sleep(time.Second * 1) 528 assert.False(t, engine.running) 529 } 530 531 // waitingPortReady waits until the port is ready to be used. 532 func waitingPortReady(t *testing.T, port int, timeout time.Duration) error { 533 t.Logf("waiting port %d ready", port) 534 timeoutChan := time.After(timeout) 535 ticker := time.NewTicker(time.Millisecond * 100) 536 defer ticker.Stop() 537 for { 538 select { 539 case <-timeoutChan: 540 return fmt.Errorf("timeout") 541 case <-ticker.C: 542 conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port)) 543 if err == nil { 544 _ = conn.Close() 545 return nil 546 } 547 } 548 } 549 } 550 551 func TestRun(t *testing.T) { 552 // generate a random port 553 port, f := GetPort() 554 f() 555 t.Logf("port: %d", port) 556 557 tmpDir := initTestEnv(t, port) 558 // change dir to tmpDir 559 chdir(t, tmpDir) 560 engine, err := NewEngine("", true) 561 if err != nil { 562 t.Fatalf("Should not be fail: %s.", err) 563 } 564 565 go func() { 566 engine.Run() 567 }() 568 time.Sleep(time.Second * 2) 569 assert.True(t, checkPortHaveBeenUsed(port)) 570 t.Logf("try to stop") 571 engine.Stop() 572 time.Sleep(time.Second * 1) 573 assert.False(t, checkPortHaveBeenUsed(port)) 574 t.Logf("stoped") 575 } 576 577 func checkPortConnectionRefused(port int) bool { 578 conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port)) 579 defer func() { 580 if conn != nil { 581 _ = conn.Close() 582 } 583 }() 584 return errors.Is(err, syscall.ECONNREFUSED) 585 } 586 587 func checkPortHaveBeenUsed(port int) bool { 588 conn, err := net.Dial("tcp", fmt.Sprintf(":%d", port)) 589 if err != nil { 590 return false 591 } 592 _ = conn.Close() 593 return true 594 } 595 596 func initTestEnv(t *testing.T, port int) string { 597 tempDir := t.TempDir() 598 t.Logf("tempDir: %s", tempDir) 599 // generate golang code to tempdir 600 err := generateGoCode(tempDir, port) 601 if err != nil { 602 t.Fatalf("Should not be fail: %s.", err) 603 } 604 return tempDir 605 } 606 607 func initWithBuildFailedCode(t *testing.T) string { 608 tempDir := t.TempDir() 609 t.Logf("tempDir: %s", tempDir) 610 // generate golang code to tempdir 611 err := generateBuildErrorGoCode(tempDir) 612 if err != nil { 613 t.Fatalf("Should not be fail: %s.", err) 614 } 615 return tempDir 616 } 617 618 func initWithQuickExitGoCode(t *testing.T) string { 619 tempDir := t.TempDir() 620 t.Logf("tempDir: %s", tempDir) 621 // generate golang code to tempdir 622 err := generateQuickExitGoCode(tempDir) 623 if err != nil { 624 t.Fatalf("Should not be fail: %s.", err) 625 } 626 return tempDir 627 } 628 629 func generateQuickExitGoCode(dir string) error { 630 code := `package main 631 // You can edit this code! 632 // Click here and start typing. 633 634 import "fmt" 635 636 func main() { 637 fmt.Println("Hello, 世界") 638 } 639 ` 640 file, err := os.Create(dir + "/main.go") 641 if err != nil { 642 return err 643 } 644 _, err = file.WriteString(code) 645 if err != nil { 646 return err 647 } 648 649 // generate go mod file 650 mod := `module air.sample.com 651 652 go 1.17 653 ` 654 file, err = os.Create(dir + "/go.mod") 655 if err != nil { 656 return err 657 } 658 _, err = file.WriteString(mod) 659 if err != nil { 660 return err 661 } 662 return nil 663 } 664 665 func generateBuildErrorGoCode(dir string) error { 666 code := `package main 667 // You can edit this code! 668 // Click here and start typing. 669 670 func main() { 671 Println("Hello, 世界") 672 673 } 674 ` 675 file, err := os.Create(dir + "/main.go") 676 if err != nil { 677 return err 678 } 679 _, err = file.WriteString(code) 680 if err != nil { 681 return err 682 } 683 684 // generate go mod file 685 mod := `module air.sample.com 686 687 go 1.17 688 ` 689 file, err = os.Create(dir + "/go.mod") 690 if err != nil { 691 return err 692 } 693 _, err = file.WriteString(mod) 694 if err != nil { 695 return err 696 } 697 return nil 698 } 699 700 // generateGoCode generates golang code to tempdir 701 func generateGoCode(dir string, port int) error { 702 code := fmt.Sprintf(`package main 703 704 import ( 705 "log" 706 "net/http" 707 ) 708 709 func main() { 710 log.Fatal(http.ListenAndServe(":%v", nil)) 711 } 712 `, port) 713 file, err := os.Create(dir + "/main.go") 714 if err != nil { 715 return err 716 } 717 _, err = file.WriteString(code) 718 if err != nil { 719 return err 720 } 721 722 // generate go mod file 723 mod := `module air.sample.com 724 725 go 1.17 726 ` 727 file, err = os.Create(dir + "/go.mod") 728 if err != nil { 729 return err 730 } 731 _, err = file.WriteString(mod) 732 if err != nil { 733 return err 734 } 735 return nil 736 } 737 738 func TestRebuildWhenRunCmdUsingDLV(t *testing.T) { 739 // generate a random port 740 port, f := GetPort() 741 f() 742 t.Logf("port: %d", port) 743 tmpDir := initTestEnv(t, port) 744 // change dir to tmpDir 745 chdir(t, tmpDir) 746 engine, err := NewEngine("", true) 747 if err != nil { 748 t.Fatalf("Should not be fail: %s.", err) 749 } 750 engine.config.Build.Cmd = "go build -gcflags='all=-N -l' -o ./tmp/main ." 751 engine.config.Build.Bin = "" 752 dlvPort, f := GetPort() 753 f() 754 engine.config.Build.FullBin = fmt.Sprintf("dlv exec --accept-multiclient --log --headless --continue --listen :%d --api-version 2 ./tmp/main", dlvPort) 755 _ = engine.config.preprocess() 756 go func() { 757 engine.Run() 758 }() 759 if err := waitingPortReady(t, port, time.Second*40); err != nil { 760 t.Fatalf("Should not be fail: %s.", err) 761 } 762 763 t.Logf("start change main.go") 764 // change file of main.go 765 // just append a new empty line to main.go 766 time.Sleep(time.Second * 2) 767 go func() { 768 file, err := os.OpenFile("main.go", os.O_APPEND|os.O_WRONLY, 0o644) 769 if err != nil { 770 log.Fatalf("Should not be fail: %s.", err) 771 } 772 defer file.Close() 773 _, err = file.WriteString("\n") 774 if err != nil { 775 log.Fatalf("Should not be fail: %s.", err) 776 } 777 }() 778 err = waitingPortConnectionRefused(t, port, time.Second*10) 779 if err != nil { 780 t.Fatalf("timeout: %s.", err) 781 } 782 t.Logf("connection refused") 783 time.Sleep(time.Second * 2) 784 err = waitingPortReady(t, port, time.Second*40) 785 if err != nil { 786 t.Fatalf("Should not be fail: %s.", err) 787 } 788 t.Logf("port is ready") 789 // stop engine 790 engine.Stop() 791 time.Sleep(time.Second * 3) 792 t.Logf("engine stopped") 793 assert.True(t, checkPortConnectionRefused(port)) 794 } 795 796 func TestWriteDefaultConfig(t *testing.T) { 797 port, f := GetPort() 798 f() 799 t.Logf("port: %d", port) 800 801 tmpDir := initTestEnv(t, port) 802 // change dir to tmpDir 803 chdir(t, tmpDir) 804 writeDefaultConfig() 805 // check the file is exist 806 if _, err := os.Stat(dftTOML); err != nil { 807 t.Fatal(err) 808 } 809 810 // check the file content is right 811 actual, err := readConfig(dftTOML) 812 if err != nil { 813 t.Fatal(err) 814 } 815 expect := defaultConfig() 816 817 assert.Equal(t, expect, *actual) 818 } 819 820 func TestCheckNilSliceShouldBeenOverwrite(t *testing.T) { 821 port, f := GetPort() 822 f() 823 t.Logf("port: %d", port) 824 825 tmpDir := initTestEnv(t, port) 826 827 // change dir to tmpDir 828 chdir(t, tmpDir) 829 830 // write easy config file 831 832 config := ` 833 [build] 834 cmd = "go build ." 835 bin = "tmp/main" 836 exclude_regex = [] 837 exclude_dir = ["test"] 838 exclude_file = ["main.go"] 839 include_file = ["test/not_a_test.go"] 840 841 ` 842 if err := ioutil.WriteFile(dftTOML, []byte(config), 0o644); err != nil { 843 t.Fatal(err) 844 } 845 engine, err := NewEngine(".air.toml", true) 846 if err != nil { 847 t.Fatal(err) 848 } 849 assert.Equal(t, []string{"go", "tpl", "tmpl", "html"}, engine.config.Build.IncludeExt) 850 assert.Equal(t, []string{}, engine.config.Build.ExcludeRegex) 851 assert.Equal(t, []string{"test"}, engine.config.Build.ExcludeDir) 852 // add new config 853 assert.Equal(t, []string{"main.go"}, engine.config.Build.ExcludeFile) 854 assert.Equal(t, []string{"test/not_a_test.go"}, engine.config.Build.IncludeFile) 855 assert.Equal(t, "go build .", engine.config.Build.Cmd) 856 } 857 858 func TestShouldIncludeGoTestFile(t *testing.T) { 859 port, f := GetPort() 860 f() 861 t.Logf("port: %d", port) 862 863 tmpDir := initTestEnv(t, port) 864 // change dir to tmpDir 865 chdir(t, tmpDir) 866 writeDefaultConfig() 867 868 // write go test file 869 file, err := os.Create("main_test.go") 870 if err != nil { 871 t.Fatal(err) 872 } 873 _, err = file.WriteString(`package main 874 875 import "testing" 876 877 func Test(t *testing.T) { 878 t.Log("testing") 879 } 880 `) 881 // run sed 882 // check the file is exist 883 if _, err := os.Stat(dftTOML); err != nil { 884 t.Fatal(err) 885 } 886 // check is MacOS 887 var cmd *exec.Cmd 888 if runtime.GOOS == "darwin" { 889 cmd = exec.Command("gsed", "-i", "s/\"_test.*go\"//g", ".air.toml") 890 } else { 891 cmd = exec.Command("sed", "-i", "s/\"_test.*go\"//g", ".air.toml") 892 } 893 cmd.Stdout = os.Stdout 894 cmd.Stderr = os.Stderr 895 if err := cmd.Run(); err != nil { 896 t.Fatal(err) 897 } 898 899 time.Sleep(time.Second * 2) 900 engine, err := NewEngine(".air.toml", false) 901 if err != nil { 902 t.Fatal(err) 903 } 904 go func() { 905 engine.Run() 906 }() 907 908 t.Logf("start change main_test.go") 909 // change file of main_test.go 910 // just append a new empty line to main_test.go 911 if err = waitingPortReady(t, port, time.Second*40); err != nil { 912 t.Fatal(err) 913 } 914 go func() { 915 file, err = os.OpenFile("main_test.go", os.O_APPEND|os.O_WRONLY, 0o644) 916 assert.NoError(t, err) 917 defer file.Close() 918 _, err = file.WriteString("\n") 919 assert.NoError(t, err) 920 }() 921 // should Have rebuild 922 if err = waitingPortReady(t, port, time.Second*10); err != nil { 923 t.Fatal(err) 924 } 925 } 926 927 func TestCreateNewDir(t *testing.T) { 928 // generate a random port 929 port, f := GetPort() 930 f() 931 t.Logf("port: %d", port) 932 933 tmpDir := initTestEnv(t, port) 934 // change dir to tmpDir 935 chdir(t, tmpDir) 936 engine, err := NewEngine("", true) 937 if err != nil { 938 t.Fatalf("Should not be fail: %s.", err) 939 } 940 941 go func() { 942 engine.Run() 943 }() 944 time.Sleep(time.Second * 2) 945 assert.True(t, checkPortHaveBeenUsed(port)) 946 947 // create a new dir make dir 948 if err = os.Mkdir(tmpDir+"/dir", 0o644); err != nil { 949 t.Fatal(err) 950 } 951 952 // no need reload 953 if err = waitingPortConnectionRefused(t, port, 3*time.Second); err == nil { 954 t.Fatal("should raise a error") 955 } 956 engine.Stop() 957 time.Sleep(2 * time.Second) 958 } 959 960 func TestShouldIncludeIncludedFile(t *testing.T) { 961 port, f := GetPort() 962 f() 963 t.Logf("port: %d", port) 964 965 tmpDir := initTestEnv(t, port) 966 967 chdir(t, tmpDir) 968 969 config := ` 970 [build] 971 cmd = "true" # do nothing 972 full_bin = "sh main.sh" 973 include_ext = ["sh"] 974 include_dir = ["nonexist"] # prevent default "." watch from taking effect 975 include_file = ["main.sh"] 976 ` 977 if err := ioutil.WriteFile(dftTOML, []byte(config), 0o644); err != nil { 978 t.Fatal(err) 979 } 980 981 err := os.WriteFile("main.sh", []byte("#!/bin/sh\nprintf original > output"), 0o755) 982 if err != nil { 983 t.Fatal(err) 984 } 985 986 engine, err := NewEngine(dftTOML, false) 987 if err != nil { 988 t.Fatal(err) 989 } 990 go func() { 991 engine.Run() 992 }() 993 994 time.Sleep(time.Second * 1) 995 996 bytes, err := os.ReadFile("output") 997 if err != nil { 998 t.Fatal(err) 999 } 1000 assert.Equal(t, []byte("original"), bytes) 1001 1002 t.Logf("start change main.sh") 1003 go func() { 1004 err := os.WriteFile("main.sh", []byte("#!/bin/sh\nprintf modified > output"), 0o755) 1005 if err != nil { 1006 log.Fatalf("Error updating file: %s.", err) 1007 } 1008 }() 1009 1010 time.Sleep(time.Second * 3) 1011 1012 bytes, err = os.ReadFile("output") 1013 if err != nil { 1014 t.Fatal(err) 1015 } 1016 assert.Equal(t, []byte("modified"), bytes) 1017 }