github.com/bilpay-tech/air@v0.0.0-20230514155040-b55f770a4ac6/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 TestRunBin(t *testing.T) { 189 engine, err := NewEngine("", true) 190 if err != nil { 191 t.Fatalf("Should not be fail: %s.", err) 192 } 193 194 err = engine.runBin() 195 if err != nil { 196 t.Fatalf("Should not be fail: %s.", err) 197 } 198 } 199 200 func GetPort() (int, func()) { 201 l, err := net.Listen("tcp", ":0") 202 port := l.Addr().(*net.TCPAddr).Port 203 if err != nil { 204 panic(err) 205 } 206 return port, func() { 207 _ = l.Close() 208 } 209 } 210 211 func TestRebuild(t *testing.T) { 212 // generate a random port 213 port, f := GetPort() 214 f() 215 t.Logf("port: %d", port) 216 217 tmpDir := initTestEnv(t, port) 218 // change dir to tmpDir 219 chdir(t, tmpDir) 220 engine, err := NewEngine("", true) 221 engine.config.Build.ExcludeUnchanged = true 222 if err != nil { 223 t.Fatalf("Should not be fail: %s.", err) 224 } 225 go func() { 226 engine.Run() 227 t.Logf("engine stopped") 228 }() 229 err = waitingPortReady(t, port, time.Second*10) 230 if err != nil { 231 t.Fatalf("Should not be fail: %s.", err) 232 } 233 t.Logf("port is ready") 234 235 // start rebuld 236 237 t.Logf("start change main.go") 238 // change file of main.go 239 // just append a new empty line to main.go 240 time.Sleep(time.Second * 2) 241 file, err := os.OpenFile("main.go", os.O_APPEND|os.O_WRONLY, 0o644) 242 if err != nil { 243 t.Fatalf("Should not be fail: %s.", err) 244 } 245 defer file.Close() 246 _, err = file.WriteString("\n") 247 if err != nil { 248 t.Fatalf("Should not be fail: %s.", err) 249 } 250 err = waitingPortConnectionRefused(t, port, time.Second*10) 251 if err != nil { 252 t.Fatalf("timeout: %s.", err) 253 } 254 t.Logf("connection refused") 255 time.Sleep(time.Second * 2) 256 err = waitingPortReady(t, port, time.Second*10) 257 if err != nil { 258 t.Fatalf("Should not be fail: %s.", err) 259 } 260 t.Logf("port is ready") 261 // stop engine 262 engine.Stop() 263 time.Sleep(time.Second * 1) 264 t.Logf("engine stopped") 265 assert.True(t, checkPortConnectionRefused(port)) 266 } 267 268 func waitingPortConnectionRefused(t *testing.T, port int, timeout time.Duration) error { 269 t.Logf("waiting port %d connection refused", port) 270 timer := time.NewTimer(timeout) 271 ticker := time.NewTicker(time.Millisecond * 100) 272 defer ticker.Stop() 273 defer timer.Stop() 274 for { 275 select { 276 case <-timer.C: 277 return fmt.Errorf("timeout") 278 case <-ticker.C: 279 print(".") 280 _, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port)) 281 if errors.Is(err, syscall.ECONNREFUSED) { 282 return nil 283 } 284 time.Sleep(time.Millisecond * 100) 285 } 286 } 287 } 288 289 func TestCtrlCWhenHaveKillDelay(t *testing.T) { 290 // fix https://github.com/bilpay-tech/air/issues/278 291 // generate a random port 292 data := []byte("[build]\n kill_delay = \"2s\"") 293 c := Config{} 294 if err := toml.Unmarshal(data, &c); err != nil { 295 t.Fatalf("Should not be fail: %s.", err) 296 } 297 298 port, f := GetPort() 299 f() 300 t.Logf("port: %d", port) 301 302 tmpDir := initTestEnv(t, port) 303 // change dir to tmpDir 304 chdir(t, tmpDir) 305 engine, err := NewEngine("", true) 306 if err != nil { 307 t.Fatalf("Should not be fail: %s.", err) 308 } 309 engine.config.Build.KillDelay = c.Build.KillDelay 310 engine.config.Build.Delay = 2000 311 engine.config.Build.SendInterrupt = true 312 engine.config.preprocess() 313 314 go func() { 315 engine.Run() 316 t.Logf("engine stopped") 317 }() 318 sigs := make(chan os.Signal, 1) 319 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 320 go func() { 321 <-sigs 322 engine.Stop() 323 t.Logf("engine stopped") 324 }() 325 if err := waitingPortReady(t, port, time.Second*10); err != nil { 326 t.Fatalf("Should not be fail: %s.", err) 327 } 328 sigs <- syscall.SIGINT 329 err = waitingPortConnectionRefused(t, port, time.Second*10) 330 if err != nil { 331 t.Fatalf("Should not be fail: %s.", err) 332 } 333 time.Sleep(time.Second * 3) 334 assert.False(t, engine.running) 335 } 336 337 func TestCtrlCWhenREngineIsRunning(t *testing.T) { 338 // generate a random port 339 port, f := GetPort() 340 f() 341 t.Logf("port: %d", port) 342 343 tmpDir := initTestEnv(t, port) 344 // change dir to tmpDir 345 chdir(t, tmpDir) 346 engine, err := NewEngine("", true) 347 if err != nil { 348 t.Fatalf("Should not be fail: %s.", err) 349 } 350 go func() { 351 engine.Run() 352 t.Logf("engine stopped") 353 }() 354 sigs := make(chan os.Signal, 1) 355 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 356 go func() { 357 <-sigs 358 engine.Stop() 359 t.Logf("engine stopped") 360 }() 361 if err := waitingPortReady(t, port, time.Second*10); err != nil { 362 t.Fatalf("Should not be fail: %s.", err) 363 } 364 sigs <- syscall.SIGINT 365 time.Sleep(time.Second * 1) 366 err = waitingPortConnectionRefused(t, port, time.Second*10) 367 if err != nil { 368 t.Fatalf("Should not be fail: %s.", err) 369 } 370 assert.False(t, engine.running) 371 } 372 373 func TestFixCloseOfChannelAfterCtrlC(t *testing.T) { 374 // fix https://github.com/bilpay-tech/air/issues/294 375 dir := initWithBuildFailedCode(t) 376 chdir(t, dir) 377 engine, err := NewEngine("", true) 378 if err != nil { 379 t.Fatalf("Should not be fail: %s.", err) 380 } 381 sigs := make(chan os.Signal, 1) 382 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 383 go func() { 384 engine.Run() 385 t.Logf("engine stopped") 386 }() 387 388 go func() { 389 <-sigs 390 engine.Stop() 391 t.Logf("engine stopped") 392 }() 393 // waiting for compile error 394 time.Sleep(time.Second * 3) 395 port, f := GetPort() 396 f() 397 // correct code 398 err = generateGoCode(dir, port) 399 if err != nil { 400 t.Fatalf("Should not be fail: %s.", err) 401 } 402 403 if err := waitingPortReady(t, port, time.Second*10); err != nil { 404 t.Fatalf("Should not be fail: %s.", err) 405 } 406 407 // ctrl + c 408 sigs <- syscall.SIGINT 409 time.Sleep(time.Second * 1) 410 if err := waitingPortConnectionRefused(t, port, time.Second*10); err != nil { 411 t.Fatalf("Should not be fail: %s.", err) 412 } 413 assert.False(t, engine.running) 414 } 415 416 func TestFixCloseOfChannelAfterTwoFailedBuild(t *testing.T) { 417 // fix https://github.com/bilpay-tech/air/issues/294 418 // happens after two failed builds 419 dir := initWithBuildFailedCode(t) 420 // change dir to tmpDir 421 chdir(t, dir) 422 engine, err := NewEngine("", true) 423 if err != nil { 424 t.Fatalf("Should not be fail: %s.", err) 425 } 426 sigs := make(chan os.Signal, 1) 427 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 428 go func() { 429 engine.Run() 430 t.Logf("engine stopped") 431 }() 432 433 go func() { 434 <-sigs 435 engine.Stop() 436 t.Logf("engine stopped") 437 }() 438 439 // waiting for compile error 440 time.Sleep(time.Second * 3) 441 442 // edit *.go file to create build error again 443 file, err := os.OpenFile("main.go", os.O_APPEND|os.O_WRONLY, 0o644) 444 if err != nil { 445 t.Fatalf("Should not be fail: %s.", err) 446 } 447 defer file.Close() 448 _, err = file.WriteString("\n") 449 if err != nil { 450 t.Fatalf("Should not be fail: %s.", err) 451 } 452 time.Sleep(time.Second * 3) 453 // ctrl + c 454 sigs <- syscall.SIGINT 455 time.Sleep(time.Second * 1) 456 assert.False(t, engine.running) 457 } 458 459 // waitingPortReady waits until the port is ready to be used. 460 func waitingPortReady(t *testing.T, port int, timeout time.Duration) error { 461 t.Logf("waiting port %d ready", port) 462 timeoutChan := time.After(timeout) 463 ticker := time.NewTicker(time.Millisecond * 100) 464 defer ticker.Stop() 465 for { 466 select { 467 case <-timeoutChan: 468 return fmt.Errorf("timeout") 469 case <-ticker.C: 470 conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port)) 471 if err == nil { 472 _ = conn.Close() 473 return nil 474 } 475 } 476 } 477 } 478 479 func TestRun(t *testing.T) { 480 // generate a random port 481 port, f := GetPort() 482 f() 483 t.Logf("port: %d", port) 484 485 tmpDir := initTestEnv(t, port) 486 // change dir to tmpDir 487 chdir(t, tmpDir) 488 engine, err := NewEngine("", true) 489 if err != nil { 490 t.Fatalf("Should not be fail: %s.", err) 491 } 492 493 go func() { 494 engine.Run() 495 }() 496 time.Sleep(time.Second * 2) 497 assert.True(t, checkPortHaveBeenUsed(port)) 498 t.Logf("try to stop") 499 engine.Stop() 500 time.Sleep(time.Second * 1) 501 assert.False(t, checkPortHaveBeenUsed(port)) 502 t.Logf("stoped") 503 } 504 505 func checkPortConnectionRefused(port int) bool { 506 conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port)) 507 defer func() { 508 if conn != nil { 509 _ = conn.Close() 510 } 511 }() 512 return errors.Is(err, syscall.ECONNREFUSED) 513 } 514 515 func checkPortHaveBeenUsed(port int) bool { 516 conn, err := net.Dial("tcp", fmt.Sprintf(":%d", port)) 517 if err != nil { 518 return false 519 } 520 _ = conn.Close() 521 return true 522 } 523 524 func initTestEnv(t *testing.T, port int) string { 525 tempDir := t.TempDir() 526 t.Logf("tempDir: %s", tempDir) 527 // generate golang code to tempdir 528 err := generateGoCode(tempDir, port) 529 if err != nil { 530 t.Fatalf("Should not be fail: %s.", err) 531 } 532 return tempDir 533 } 534 535 func initWithBuildFailedCode(t *testing.T) string { 536 tempDir := t.TempDir() 537 t.Logf("tempDir: %s", tempDir) 538 // generate golang code to tempdir 539 err := generateBuildErrorGoCode(tempDir) 540 if err != nil { 541 t.Fatalf("Should not be fail: %s.", err) 542 } 543 return tempDir 544 } 545 546 func initWithQuickExitGoCode(t *testing.T) string { 547 tempDir := t.TempDir() 548 t.Logf("tempDir: %s", tempDir) 549 // generate golang code to tempdir 550 err := generateQuickExitGoCode(tempDir) 551 if err != nil { 552 t.Fatalf("Should not be fail: %s.", err) 553 } 554 return tempDir 555 } 556 557 func generateQuickExitGoCode(dir string) error { 558 code := `package main 559 // You can edit this code! 560 // Click here and start typing. 561 562 import "fmt" 563 564 func main() { 565 fmt.Println("Hello, 世界") 566 } 567 ` 568 file, err := os.Create(dir + "/main.go") 569 if err != nil { 570 return err 571 } 572 _, err = file.WriteString(code) 573 if err != nil { 574 return err 575 } 576 577 // generate go mod file 578 mod := `module air.sample.com 579 580 go 1.17 581 ` 582 file, err = os.Create(dir + "/go.mod") 583 if err != nil { 584 return err 585 } 586 _, err = file.WriteString(mod) 587 if err != nil { 588 return err 589 } 590 return nil 591 } 592 593 func generateBuildErrorGoCode(dir string) error { 594 code := `package main 595 // You can edit this code! 596 // Click here and start typing. 597 598 func main() { 599 Println("Hello, 世界") 600 601 } 602 ` 603 file, err := os.Create(dir + "/main.go") 604 if err != nil { 605 return err 606 } 607 _, err = file.WriteString(code) 608 if err != nil { 609 return err 610 } 611 612 // generate go mod file 613 mod := `module air.sample.com 614 615 go 1.17 616 ` 617 file, err = os.Create(dir + "/go.mod") 618 if err != nil { 619 return err 620 } 621 _, err = file.WriteString(mod) 622 if err != nil { 623 return err 624 } 625 return nil 626 } 627 628 // generateGoCode generates golang code to tempdir 629 func generateGoCode(dir string, port int) error { 630 code := fmt.Sprintf(`package main 631 632 import ( 633 "log" 634 "net/http" 635 ) 636 637 func main() { 638 log.Fatal(http.ListenAndServe(":%v", nil)) 639 } 640 `, port) 641 file, err := os.Create(dir + "/main.go") 642 if err != nil { 643 return err 644 } 645 _, err = file.WriteString(code) 646 if err != nil { 647 return err 648 } 649 650 // generate go mod file 651 mod := `module air.sample.com 652 653 go 1.17 654 ` 655 file, err = os.Create(dir + "/go.mod") 656 if err != nil { 657 return err 658 } 659 _, err = file.WriteString(mod) 660 if err != nil { 661 return err 662 } 663 return nil 664 } 665 666 func TestRebuildWhenRunCmdUsingDLV(t *testing.T) { 667 // generate a random port 668 port, f := GetPort() 669 f() 670 t.Logf("port: %d", port) 671 tmpDir := initTestEnv(t, port) 672 // change dir to tmpDir 673 chdir(t, tmpDir) 674 engine, err := NewEngine("", true) 675 if err != nil { 676 t.Fatalf("Should not be fail: %s.", err) 677 } 678 engine.config.Build.Cmd = "go build -gcflags='all=-N -l' -o ./tmp/main ." 679 engine.config.Build.Bin = "" 680 dlvPort, f := GetPort() 681 f() 682 engine.config.Build.FullBin = fmt.Sprintf("dlv exec --accept-multiclient --log --headless --continue --listen :%d --api-version 2 ./tmp/main", dlvPort) 683 _ = engine.config.preprocess() 684 go func() { 685 engine.Run() 686 }() 687 if err := waitingPortReady(t, port, time.Second*40); err != nil { 688 t.Fatalf("Should not be fail: %s.", err) 689 } 690 691 t.Logf("start change main.go") 692 // change file of main.go 693 // just append a new empty line to main.go 694 time.Sleep(time.Second * 2) 695 go func() { 696 file, err := os.OpenFile("main.go", os.O_APPEND|os.O_WRONLY, 0o644) 697 if err != nil { 698 log.Fatalf("Should not be fail: %s.", err) 699 } 700 defer file.Close() 701 _, err = file.WriteString("\n") 702 if err != nil { 703 log.Fatalf("Should not be fail: %s.", err) 704 } 705 }() 706 err = waitingPortConnectionRefused(t, port, time.Second*10) 707 if err != nil { 708 t.Fatalf("timeout: %s.", err) 709 } 710 t.Logf("connection refused") 711 time.Sleep(time.Second * 2) 712 err = waitingPortReady(t, port, time.Second*40) 713 if err != nil { 714 t.Fatalf("Should not be fail: %s.", err) 715 } 716 t.Logf("port is ready") 717 // stop engine 718 engine.Stop() 719 time.Sleep(time.Second * 3) 720 t.Logf("engine stopped") 721 assert.True(t, checkPortConnectionRefused(port)) 722 } 723 724 func TestWriteDefaultConfig(t *testing.T) { 725 port, f := GetPort() 726 f() 727 t.Logf("port: %d", port) 728 729 tmpDir := initTestEnv(t, port) 730 // change dir to tmpDir 731 chdir(t, tmpDir) 732 writeDefaultConfig() 733 // check the file is exist 734 if _, err := os.Stat(dftTOML); err != nil { 735 t.Fatal(err) 736 } 737 738 // check the file content is right 739 actual, err := readConfig(dftTOML) 740 if err != nil { 741 t.Fatal(err) 742 } 743 expect := defaultConfig() 744 745 assert.Equal(t, expect, *actual) 746 } 747 748 func TestCheckNilSliceShouldBeenOverwrite(t *testing.T) { 749 port, f := GetPort() 750 f() 751 t.Logf("port: %d", port) 752 753 tmpDir := initTestEnv(t, port) 754 755 // change dir to tmpDir 756 chdir(t, tmpDir) 757 758 // write easy config file 759 760 config := ` 761 [build] 762 cmd = "go build ." 763 bin = "tmp/main" 764 exclude_regex = [] 765 exclude_dir = ["test"] 766 exclude_file = ["main.go"] 767 include_file = ["test/not_a_test.go"] 768 769 ` 770 if err := ioutil.WriteFile(dftTOML, []byte(config), 0o644); err != nil { 771 t.Fatal(err) 772 } 773 engine, err := NewEngine(".air.toml", true) 774 if err != nil { 775 t.Fatal(err) 776 } 777 assert.Equal(t, []string{"go", "tpl", "tmpl", "html"}, engine.config.Build.IncludeExt) 778 assert.Equal(t, []string{}, engine.config.Build.ExcludeRegex) 779 assert.Equal(t, []string{"test"}, engine.config.Build.ExcludeDir) 780 // add new config 781 assert.Equal(t, []string{"main.go"}, engine.config.Build.ExcludeFile) 782 assert.Equal(t, []string{"test/not_a_test.go"}, engine.config.Build.IncludeFile) 783 assert.Equal(t, "go build .", engine.config.Build.Cmd) 784 } 785 786 func TestShouldIncludeGoTestFile(t *testing.T) { 787 port, f := GetPort() 788 f() 789 t.Logf("port: %d", port) 790 791 tmpDir := initTestEnv(t, port) 792 // change dir to tmpDir 793 chdir(t, tmpDir) 794 writeDefaultConfig() 795 796 // write go test file 797 file, err := os.Create("main_test.go") 798 if err != nil { 799 t.Fatal(err) 800 } 801 _, err = file.WriteString(`package main 802 803 import "testing" 804 805 func Test(t *testing.T) { 806 t.Log("testing") 807 } 808 `) 809 // run sed 810 // check the file is exist 811 if _, err := os.Stat(dftTOML); err != nil { 812 t.Fatal(err) 813 } 814 // check is MacOS 815 var cmd *exec.Cmd 816 if runtime.GOOS == "darwin" { 817 cmd = exec.Command("gsed", "-i", "s/\"_test.*go\"//g", ".air.toml") 818 } else { 819 cmd = exec.Command("sed", "-i", "s/\"_test.*go\"//g", ".air.toml") 820 } 821 cmd.Stdout = os.Stdout 822 cmd.Stderr = os.Stderr 823 if err := cmd.Run(); err != nil { 824 t.Fatal(err) 825 } 826 827 time.Sleep(time.Second * 2) 828 engine, err := NewEngine(".air.toml", false) 829 if err != nil { 830 t.Fatal(err) 831 } 832 go func() { 833 engine.Run() 834 }() 835 836 t.Logf("start change main_test.go") 837 // change file of main_test.go 838 // just append a new empty line to main_test.go 839 if err = waitingPortReady(t, port, time.Second*40); err != nil { 840 t.Fatal(err) 841 } 842 go func() { 843 file, err = os.OpenFile("main_test.go", os.O_APPEND|os.O_WRONLY, 0o644) 844 assert.NoError(t, err) 845 defer file.Close() 846 _, err = file.WriteString("\n") 847 assert.NoError(t, err) 848 }() 849 // should Have rebuild 850 if err = waitingPortReady(t, port, time.Second*10); err != nil { 851 t.Fatal(err) 852 } 853 } 854 855 func TestCreateNewDir(t *testing.T) { 856 // generate a random port 857 port, f := GetPort() 858 f() 859 t.Logf("port: %d", port) 860 861 tmpDir := initTestEnv(t, port) 862 // change dir to tmpDir 863 chdir(t, tmpDir) 864 engine, err := NewEngine("", true) 865 if err != nil { 866 t.Fatalf("Should not be fail: %s.", err) 867 } 868 869 go func() { 870 engine.Run() 871 }() 872 time.Sleep(time.Second * 2) 873 assert.True(t, checkPortHaveBeenUsed(port)) 874 875 // create a new dir make dir 876 if err = os.Mkdir(tmpDir+"/dir", 0o644); err != nil { 877 t.Fatal(err) 878 } 879 880 // no need reload 881 if err = waitingPortConnectionRefused(t, port, 3*time.Second); err == nil { 882 t.Fatal("should raise a error") 883 } 884 engine.Stop() 885 time.Sleep(2 * time.Second) 886 } 887 888 func TestShouldIncludeIncludedFile(t *testing.T) { 889 port, f := GetPort() 890 f() 891 t.Logf("port: %d", port) 892 893 tmpDir := initTestEnv(t, port) 894 895 chdir(t, tmpDir) 896 897 config := ` 898 [build] 899 cmd = "true" # do nothing 900 full_bin = "sh main.sh" 901 include_ext = ["sh"] 902 include_dir = ["nonexist"] # prevent default "." watch from taking effect 903 include_file = ["main.sh"] 904 ` 905 if err := ioutil.WriteFile(dftTOML, []byte(config), 0o644); err != nil { 906 t.Fatal(err) 907 } 908 909 err := os.WriteFile("main.sh", []byte("#!/bin/sh\nprintf original > output"), 0o755) 910 if err != nil { 911 t.Fatal(err) 912 } 913 914 engine, err := NewEngine(dftTOML, false) 915 if err != nil { 916 t.Fatal(err) 917 } 918 go func() { 919 engine.Run() 920 }() 921 922 time.Sleep(time.Second * 1) 923 924 bytes, err := os.ReadFile("output") 925 if err != nil { 926 t.Fatal(err) 927 } 928 assert.Equal(t, []byte("original"), bytes) 929 930 t.Logf("start change main.sh") 931 go func() { 932 err := os.WriteFile("main.sh", []byte("#!/bin/sh\nprintf modified > output"), 0o755) 933 if err != nil { 934 log.Fatalf("Error updating file: %s.", err) 935 } 936 }() 937 938 time.Sleep(time.Second * 3) 939 940 bytes, err = os.ReadFile("output") 941 if err != nil { 942 t.Fatal(err) 943 } 944 assert.Equal(t, []byte("modified"), bytes) 945 }