github.com/bytebase/air@v0.0.0-20221010164341-87187cc8b920/runner/engine_test.go (about)

     1  package runner
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"net"
     8  	"os"
     9  	"os/exec"
    10  	"os/signal"
    11  	"runtime"
    12  	"strings"
    13  	"syscall"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/pelletier/go-toml"
    18  	"github.com/stretchr/testify/assert"
    19  )
    20  
    21  func TestNewEngine(t *testing.T) {
    22  	_ = os.Unsetenv(airWd)
    23  	engine, err := NewEngine("", true)
    24  	if err != nil {
    25  		t.Fatalf("Should not be fail: %s.", err)
    26  	}
    27  	if engine.logger == nil {
    28  		t.Fatal("logger should not be nil")
    29  	}
    30  	if engine.config == nil {
    31  		t.Fatal("Config should not be nil")
    32  	}
    33  	if engine.watcher == nil {
    34  		t.Fatal("watcher should not be nil")
    35  	}
    36  }
    37  
    38  func TestCheckRunEnv(t *testing.T) {
    39  	_ = os.Unsetenv(airWd)
    40  	engine, err := NewEngine("", true)
    41  	if err != nil {
    42  		t.Fatalf("Should not be fail: %s.", err)
    43  	}
    44  	err = engine.checkRunEnv()
    45  	if err == nil {
    46  		t.Fatal("should throw a err")
    47  	}
    48  }
    49  
    50  func TestWatching(t *testing.T) {
    51  	engine, err := NewEngine("", true)
    52  	if err != nil {
    53  		t.Fatalf("Should not be fail: %s.", err)
    54  	}
    55  	path, err := os.Getwd()
    56  	if err != nil {
    57  		t.Fatalf("Should not be fail: %s.", err)
    58  	}
    59  	path = strings.Replace(path, "_testdata/toml", "", 1)
    60  	err = engine.watching(path + "/_testdata/watching")
    61  	if err != nil {
    62  		t.Fatalf("Should not be fail: %s.", err)
    63  	}
    64  }
    65  
    66  func TestRegexes(t *testing.T) {
    67  	engine, err := NewEngine("", true)
    68  	if err != nil {
    69  		t.Fatalf("Should not be fail: %s.", err)
    70  	}
    71  	engine.config.Build.ExcludeRegex = []string{"foo\\.html$", "bar", "_test\\.go"}
    72  
    73  	result, err := engine.isExcludeRegex("./test/foo.html")
    74  	if err != nil {
    75  		t.Fatalf("Should not be fail: %s.", err)
    76  	}
    77  	if result != true {
    78  		t.Errorf("expected '%t' but got '%t'", true, result)
    79  	}
    80  
    81  	result, err = engine.isExcludeRegex("./test/bar/index.html")
    82  	if err != nil {
    83  		t.Fatalf("Should not be fail: %s.", err)
    84  	}
    85  	if result != true {
    86  		t.Errorf("expected '%t' but got '%t'", true, result)
    87  	}
    88  
    89  	result, err = engine.isExcludeRegex("./test/unrelated.html")
    90  	if err != nil {
    91  		t.Fatalf("Should not be fail: %s.", err)
    92  	}
    93  	if result {
    94  		t.Errorf("expected '%t' but got '%t'", false, result)
    95  	}
    96  
    97  	result, err = engine.isExcludeRegex("./myPackage/goFile_testxgo")
    98  	if err != nil {
    99  		t.Fatalf("Should not be fail: %s.", err)
   100  	}
   101  	if result {
   102  		t.Errorf("expected '%t' but got '%t'", false, result)
   103  	}
   104  	result, err = engine.isExcludeRegex("./myPackage/goFile_test.go")
   105  	if err != nil {
   106  		t.Fatalf("Should not be fail: %s.", err)
   107  	}
   108  	if result != true {
   109  		t.Errorf("expected '%t' but got '%t'", true, result)
   110  	}
   111  }
   112  
   113  func TestRunBin(t *testing.T) {
   114  	engine, err := NewEngine("", true)
   115  	if err != nil {
   116  		t.Fatalf("Should not be fail: %s.", err)
   117  	}
   118  
   119  	err = engine.runBin()
   120  	if err != nil {
   121  		t.Fatalf("Should not be fail: %s.", err)
   122  	}
   123  }
   124  
   125  func GetPort() (int, func()) {
   126  	l, err := net.Listen("tcp", ":0")
   127  	port := l.Addr().(*net.TCPAddr).Port
   128  	if err != nil {
   129  		panic(err)
   130  	}
   131  	return port, func() {
   132  		_ = l.Close()
   133  	}
   134  }
   135  
   136  func TestRebuild(t *testing.T) {
   137  	// generate a random port
   138  	port, f := GetPort()
   139  	f()
   140  	t.Logf("port: %d", port)
   141  
   142  	tmpDir := initTestEnv(t, port)
   143  	// change dir to tmpDir
   144  	err := os.Chdir(tmpDir)
   145  	if err != nil {
   146  		t.Fatalf("Should not be fail: %s.", err)
   147  	}
   148  	engine, err := NewEngine("", true)
   149  	engine.config.Build.ExcludeUnchanged = true
   150  	if err != nil {
   151  		t.Fatalf("Should not be fail: %s.", err)
   152  	}
   153  	go func() {
   154  		engine.Run()
   155  		t.Logf("engine stopped")
   156  	}()
   157  	err = waitingPortReady(t, port, time.Second*10)
   158  	if err != nil {
   159  		t.Fatalf("Should not be fail: %s.", err)
   160  	}
   161  	t.Logf("port is ready")
   162  
   163  	// start rebuld
   164  
   165  	t.Logf("start change main.go")
   166  	// change file of main.go
   167  	// just append a new empty line to main.go
   168  	time.Sleep(time.Second * 2)
   169  	file, err := os.OpenFile("main.go", os.O_APPEND|os.O_WRONLY, 0644)
   170  	if err != nil {
   171  		t.Fatalf("Should not be fail: %s.", err)
   172  	}
   173  	defer file.Close()
   174  	_, err = file.WriteString("\n")
   175  	if err != nil {
   176  		t.Fatalf("Should not be fail: %s.", err)
   177  	}
   178  	err = waitingPortConnectionRefused(t, port, time.Second*10)
   179  	if err != nil {
   180  		t.Fatalf("timeout: %s.", err)
   181  	}
   182  	t.Logf("connection refused")
   183  	time.Sleep(time.Second * 2)
   184  	err = waitingPortReady(t, port, time.Second*10)
   185  	if err != nil {
   186  		t.Fatalf("Should not be fail: %s.", err)
   187  	}
   188  	t.Logf("port is ready")
   189  	// stop engine
   190  	engine.Stop()
   191  	time.Sleep(time.Second * 1)
   192  	t.Logf("engine stopped")
   193  	assert.True(t, checkPortConnectionRefused(port))
   194  }
   195  
   196  func waitingPortConnectionRefused(t *testing.T, port int, timeout time.Duration) error {
   197  	t.Logf("waiting port %d connection refused", port)
   198  	timer := time.NewTimer(timeout)
   199  	ticker := time.NewTicker(time.Millisecond * 100)
   200  	defer ticker.Stop()
   201  	defer timer.Stop()
   202  	for {
   203  		select {
   204  		case <-timer.C:
   205  			return fmt.Errorf("timeout")
   206  		case <-ticker.C:
   207  			print(".")
   208  			_, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port))
   209  			if errors.Is(err, syscall.ECONNREFUSED) {
   210  				return nil
   211  			}
   212  			time.Sleep(time.Millisecond * 100)
   213  		}
   214  	}
   215  }
   216  
   217  func TestCtrlCWhenHaveKillDelay(t *testing.T) {
   218  	// fix https://github.com/cosmtrek/air/issues/278
   219  	// generate a random port
   220  	data := []byte("[build]\n  kill_delay = \"2s\"")
   221  	c := Config{}
   222  	if err := toml.Unmarshal(data, &c); err != nil {
   223  		t.Fatalf("Should not be fail: %s.", err)
   224  	}
   225  
   226  	port, f := GetPort()
   227  	f()
   228  	t.Logf("port: %d", port)
   229  
   230  	tmpDir := initTestEnv(t, port)
   231  	// change dir to tmpDir
   232  	err := os.Chdir(tmpDir)
   233  	if err != nil {
   234  		t.Fatalf("Should not be fail: %s.", err)
   235  	}
   236  	engine, err := NewEngine("", true)
   237  	if err != nil {
   238  		t.Fatalf("Should not be fail: %s.", err)
   239  	}
   240  	engine.config.Build.KillDelay = c.Build.KillDelay
   241  	engine.config.Build.Delay = 2000
   242  	engine.config.Build.SendInterrupt = true
   243  	engine.config.preprocess()
   244  
   245  	go func() {
   246  		engine.Run()
   247  		t.Logf("engine stopped")
   248  	}()
   249  	sigs := make(chan os.Signal, 1)
   250  	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
   251  	go func() {
   252  		<-sigs
   253  		engine.Stop()
   254  		t.Logf("engine stopped")
   255  	}()
   256  	if err := waitingPortReady(t, port, time.Second*10); err != nil {
   257  		t.Fatalf("Should not be fail: %s.", err)
   258  	}
   259  	sigs <- syscall.SIGINT
   260  	err = waitingPortConnectionRefused(t, port, time.Second*10)
   261  	if err != nil {
   262  		t.Fatalf("Should not be fail: %s.", err)
   263  	}
   264  	time.Sleep(time.Second * 3)
   265  }
   266  
   267  func TestCtrlCWhenREngineIsRunning(t *testing.T) {
   268  	// generate a random port
   269  	port, f := GetPort()
   270  	f()
   271  	t.Logf("port: %d", port)
   272  
   273  	tmpDir := initTestEnv(t, port)
   274  	// change dir to tmpDir
   275  	err := os.Chdir(tmpDir)
   276  	if err != nil {
   277  		t.Fatalf("Should not be fail: %s.", err)
   278  	}
   279  	engine, err := NewEngine("", true)
   280  	if err != nil {
   281  		t.Fatalf("Should not be fail: %s.", err)
   282  	}
   283  	go func() {
   284  		engine.Run()
   285  		t.Logf("engine stopped")
   286  	}()
   287  	sigs := make(chan os.Signal, 1)
   288  	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
   289  	go func() {
   290  		<-sigs
   291  		engine.Stop()
   292  		t.Logf("engine stopped")
   293  	}()
   294  	if err := waitingPortReady(t, port, time.Second*10); err != nil {
   295  		t.Fatalf("Should not be fail: %s.", err)
   296  	}
   297  	sigs <- syscall.SIGINT
   298  	time.Sleep(time.Second * 1)
   299  	err = waitingPortConnectionRefused(t, port, time.Second*10)
   300  	if err != nil {
   301  		t.Fatalf("Should not be fail: %s.", err)
   302  	}
   303  }
   304  
   305  func TestFixCloseOfChannelAfterCtrlC(t *testing.T) {
   306  	// fix https://github.com/cosmtrek/air/issues/294
   307  	dir := initWithBuildFailedCode(t)
   308  
   309  	err := os.Chdir(dir)
   310  	if err != nil {
   311  		t.Fatalf("Should not be fail: %s.", err)
   312  	}
   313  	engine, err := NewEngine("", true)
   314  	if err != nil {
   315  		t.Fatalf("Should not be fail: %s.", err)
   316  	}
   317  	sigs := make(chan os.Signal, 1)
   318  	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
   319  	go func() {
   320  		engine.Run()
   321  		t.Logf("engine stopped")
   322  	}()
   323  
   324  	go func() {
   325  		<-sigs
   326  		engine.Stop()
   327  		t.Logf("engine stopped")
   328  	}()
   329  	// waiting for compile error
   330  	time.Sleep(time.Second * 3)
   331  	port, f := GetPort()
   332  	f()
   333  	// correct code
   334  	err = generateGoCode(dir, port)
   335  	if err != nil {
   336  		t.Fatalf("Should not be fail: %s.", err)
   337  	}
   338  
   339  	if err := waitingPortReady(t, port, time.Second*10); err != nil {
   340  		t.Fatalf("Should not be fail: %s.", err)
   341  	}
   342  
   343  	// ctrl + c
   344  	sigs <- syscall.SIGINT
   345  	time.Sleep(time.Second * 1)
   346  	if err := waitingPortConnectionRefused(t, port, time.Second*10); err != nil {
   347  		t.Fatalf("Should not be fail: %s.", err)
   348  	}
   349  }
   350  
   351  func TestFixCloseOfChannelAfterTwoFailedBuild(t *testing.T) {
   352  	// fix https://github.com/cosmtrek/air/issues/294
   353  	// happens after two failed builds
   354  	dir := initWithBuildFailedCode(t)
   355  	// change dir to tmpDir
   356  	err := os.Chdir(dir)
   357  	if err != nil {
   358  		t.Fatalf("Should not be fail: %s.", err)
   359  	}
   360  	engine, err := NewEngine("", true)
   361  	if err != nil {
   362  		t.Fatalf("Should not be fail: %s.", err)
   363  	}
   364  	sigs := make(chan os.Signal, 1)
   365  	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
   366  	go func() {
   367  		engine.Run()
   368  		t.Logf("engine stopped")
   369  	}()
   370  
   371  	go func() {
   372  		<-sigs
   373  		engine.Stop()
   374  		t.Logf("engine stopped")
   375  	}()
   376  
   377  	// waiting for compile error
   378  	time.Sleep(time.Second * 3)
   379  
   380  	// edit *.go file to create build error again
   381  	file, err := os.OpenFile("main.go", os.O_APPEND|os.O_WRONLY, 0644)
   382  	if err != nil {
   383  		t.Fatalf("Should not be fail: %s.", err)
   384  	}
   385  	defer file.Close()
   386  	_, err = file.WriteString("\n")
   387  	if err != nil {
   388  		t.Fatalf("Should not be fail: %s.", err)
   389  	}
   390  	time.Sleep(time.Second * 3)
   391  	// ctrl + c
   392  	sigs <- syscall.SIGINT
   393  	time.Sleep(time.Second * 1)
   394  }
   395  
   396  // waitingPortReady waits until the port is ready to be used.
   397  func waitingPortReady(t *testing.T, port int, timeout time.Duration) error {
   398  	t.Logf("waiting port %d ready", port)
   399  	timeoutChan := time.After(timeout)
   400  	ticker := time.NewTicker(time.Millisecond * 100)
   401  	defer ticker.Stop()
   402  	for {
   403  		select {
   404  		case <-timeoutChan:
   405  			return fmt.Errorf("timeout")
   406  		case <-ticker.C:
   407  			conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port))
   408  			if err == nil {
   409  				_ = conn.Close()
   410  				return nil
   411  			}
   412  		}
   413  	}
   414  }
   415  
   416  func TestRun(t *testing.T) {
   417  	// generate a random port
   418  	port, f := GetPort()
   419  	f()
   420  	t.Logf("port: %d", port)
   421  
   422  	tmpDir := initTestEnv(t, port)
   423  	// change dir to tmpDir
   424  	err := os.Chdir(tmpDir)
   425  	if err != nil {
   426  		t.Fatalf("Should not be fail: %s.", err)
   427  	}
   428  	engine, err := NewEngine("", true)
   429  	if err != nil {
   430  		t.Fatalf("Should not be fail: %s.", err)
   431  	}
   432  
   433  	go func() {
   434  		engine.Run()
   435  	}()
   436  	time.Sleep(time.Second * 2)
   437  	assert.True(t, checkPortHaveBeenUsed(port))
   438  	t.Logf("try to stop")
   439  	engine.Stop()
   440  	time.Sleep(time.Second * 1)
   441  	assert.False(t, checkPortHaveBeenUsed(port))
   442  	t.Logf("stoped")
   443  }
   444  
   445  func checkPortConnectionRefused(port int) bool {
   446  	conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%d", port))
   447  	defer func() {
   448  		if conn != nil {
   449  			_ = conn.Close()
   450  		}
   451  	}()
   452  	if errors.Is(err, syscall.ECONNREFUSED) {
   453  		return true
   454  	}
   455  	return false
   456  }
   457  
   458  func checkPortHaveBeenUsed(port int) bool {
   459  	conn, err := net.Dial("tcp", fmt.Sprintf(":%d", port))
   460  	if err != nil {
   461  		return false
   462  	}
   463  	_ = conn.Close()
   464  	return true
   465  }
   466  
   467  func initTestEnv(t *testing.T, port int) string {
   468  	tempDir := t.TempDir()
   469  	t.Logf("tempDir: %s", tempDir)
   470  	// generate golang code to tempdir
   471  	err := generateGoCode(tempDir, port)
   472  	if err != nil {
   473  		t.Fatalf("Should not be fail: %s.", err)
   474  	}
   475  	return tempDir
   476  }
   477  
   478  func initWithBuildFailedCode(t *testing.T) string {
   479  	tempDir := t.TempDir()
   480  	t.Logf("tempDir: %s", tempDir)
   481  	// generate golang code to tempdir
   482  	err := generateBuildErrorGoCode(tempDir)
   483  	if err != nil {
   484  		t.Fatalf("Should not be fail: %s.", err)
   485  	}
   486  	return tempDir
   487  }
   488  
   489  func generateBuildErrorGoCode(dir string) error {
   490  	code := `package main
   491  // You can edit this code!
   492  // Click here and start typing.
   493  
   494  func main() {
   495  	Println("Hello, 世界")
   496  
   497  }
   498  `
   499  	file, err := os.Create(dir + "/main.go")
   500  	if err != nil {
   501  		return err
   502  	}
   503  	_, err = file.WriteString(code)
   504  
   505  	// generate go mod file
   506  	mod := `module air.sample.com
   507  
   508  go 1.17
   509  `
   510  	file, err = os.Create(dir + "/go.mod")
   511  	if err != nil {
   512  		return err
   513  	}
   514  	_, err = file.WriteString(mod)
   515  	if err != nil {
   516  		return err
   517  	}
   518  	return nil
   519  }
   520  
   521  // generateGoCode generates golang code to tempdir
   522  func generateGoCode(dir string, port int) error {
   523  
   524  	code := fmt.Sprintf(`package main
   525  
   526  import (
   527  	"log"
   528  	"net/http"
   529  )
   530  
   531  func main() {
   532  	log.Fatal(http.ListenAndServe(":%v", nil))
   533  }
   534  `, port)
   535  	file, err := os.Create(dir + "/main.go")
   536  	if err != nil {
   537  		return err
   538  	}
   539  	_, err = file.WriteString(code)
   540  
   541  	// generate go mod file
   542  	mod := `module air.sample.com
   543  
   544  go 1.17
   545  `
   546  	file, err = os.Create(dir + "/go.mod")
   547  	if err != nil {
   548  		return err
   549  	}
   550  	_, err = file.WriteString(mod)
   551  	if err != nil {
   552  		return err
   553  	}
   554  	return nil
   555  }
   556  
   557  func TestRebuildWhenRunCmdUsingDLV(t *testing.T) {
   558  	// generate a random port
   559  	port, f := GetPort()
   560  	f()
   561  	t.Logf("port: %d", port)
   562  	tmpDir := initTestEnv(t, port)
   563  	// change dir to tmpDir
   564  	err := os.Chdir(tmpDir)
   565  	if err != nil {
   566  		t.Fatalf("Should not be fail: %s.", err)
   567  	}
   568  	engine, err := NewEngine("", true)
   569  	if err != nil {
   570  		t.Fatalf("Should not be fail: %s.", err)
   571  	}
   572  	engine.config.Build.Cmd = "go build -gcflags='all=-N -l' -o ./tmp/main ."
   573  	engine.config.Build.Bin = ""
   574  	engine.config.Build.FullBin = "dlv exec --accept-multiclient --log --headless --continue --listen :2345 --api-version 2 ./tmp/main"
   575  	_ = engine.config.preprocess()
   576  	go func() {
   577  		engine.Run()
   578  	}()
   579  	if err := waitingPortReady(t, port, time.Second*40); err != nil {
   580  		t.Fatalf("Should not be fail: %s.", err)
   581  	}
   582  
   583  	t.Logf("start change main.go")
   584  	// change file of main.go
   585  	// just append a new empty line to main.go
   586  	time.Sleep(time.Second * 2)
   587  	go func() {
   588  		file, err := os.OpenFile("main.go", os.O_APPEND|os.O_WRONLY, 0644)
   589  		if err != nil {
   590  			t.Fatalf("Should not be fail: %s.", err)
   591  		}
   592  		defer file.Close()
   593  		_, err = file.WriteString("\n")
   594  		if err != nil {
   595  			t.Fatalf("Should not be fail: %s.", err)
   596  		}
   597  	}()
   598  	err = waitingPortConnectionRefused(t, port, time.Second*10)
   599  	if err != nil {
   600  		t.Fatalf("timeout: %s.", err)
   601  	}
   602  	t.Logf("connection refused")
   603  	time.Sleep(time.Second * 2)
   604  	err = waitingPortReady(t, port, time.Second*40)
   605  	if err != nil {
   606  		t.Fatalf("Should not be fail: %s.", err)
   607  	}
   608  	t.Logf("port is ready")
   609  	// stop engine
   610  	engine.Stop()
   611  	time.Sleep(time.Second * 3)
   612  	t.Logf("engine stopped")
   613  	assert.True(t, checkPortConnectionRefused(port))
   614  }
   615  
   616  func TestWriteDefaultConfig(t *testing.T) {
   617  	port, f := GetPort()
   618  	f()
   619  	t.Logf("port: %d", port)
   620  
   621  	tmpDir := initTestEnv(t, port)
   622  	// change dir to tmpDir
   623  	if err := os.Chdir(tmpDir); err != nil {
   624  		t.Fatal(err)
   625  	}
   626  	writeDefaultConfig()
   627  	// check the file is exist
   628  	if _, err := os.Stat(dftTOML); err != nil {
   629  		t.Fatal(err)
   630  	}
   631  
   632  	// check the file content is right
   633  	actual, err := readConfig(dftTOML)
   634  	if err != nil {
   635  		t.Fatal(err)
   636  	}
   637  	expect := defaultConfig()
   638  
   639  	assert.Equal(t, expect, *actual)
   640  }
   641  
   642  func TestCheckNilSliceShouldBeenOverwrite(t *testing.T) {
   643  	port, f := GetPort()
   644  	f()
   645  	t.Logf("port: %d", port)
   646  
   647  	tmpDir := initTestEnv(t, port)
   648  
   649  	// change dir to tmpDir
   650  	if err := os.Chdir(tmpDir); err != nil {
   651  		t.Fatal(err)
   652  	}
   653  
   654  	// write easy config file
   655  
   656  	config := `
   657  [build]
   658  cmd = "go build ."
   659  bin = "tmp/main"
   660  exclude_regex = []
   661  exclude_dir = ["test"]
   662  exclude_file = ["main.go"]
   663  
   664  `
   665  	if err := ioutil.WriteFile(dftTOML, []byte(config), 0644); err != nil {
   666  		t.Fatal(err)
   667  	}
   668  	engine, err := NewEngine(".air.toml", true)
   669  	if err != nil {
   670  		t.Fatal(err)
   671  	}
   672  	assert.Equal(t, []string{"go", "tpl", "tmpl", "html"}, engine.config.Build.IncludeExt)
   673  	assert.Equal(t, []string{}, engine.config.Build.ExcludeRegex)
   674  	assert.Equal(t, []string{"test"}, engine.config.Build.ExcludeDir)
   675  	// add new config
   676  	assert.Equal(t, []string{"main.go"}, engine.config.Build.ExcludeFile)
   677  	assert.Equal(t, "go build .", engine.config.Build.Cmd)
   678  
   679  }
   680  
   681  func TestShouldIncludeGoTestFile(t *testing.T) {
   682  	port, f := GetPort()
   683  	f()
   684  	t.Logf("port: %d", port)
   685  
   686  	tmpDir := initTestEnv(t, port)
   687  	// change dir to tmpDir
   688  	if err := os.Chdir(tmpDir); err != nil {
   689  		t.Fatal(err)
   690  	}
   691  	writeDefaultConfig()
   692  
   693  	// write go test file
   694  	file, err := os.Create("main_test.go")
   695  	if err != nil {
   696  		t.Fatal(err)
   697  	}
   698  	_, err = file.WriteString(`package main
   699  
   700  import "testing"
   701  
   702  func Test(t *testing.T) {
   703  	t.Log("testing")
   704  }
   705  `)
   706  	// run sed
   707  	// check the file is exist
   708  	if _, err := os.Stat(dftTOML); err != nil {
   709  		t.Fatal(err)
   710  	}
   711  	// check is MacOS
   712  	var cmd *exec.Cmd
   713  	if runtime.GOOS == "darwin" {
   714  		cmd = exec.Command("gsed", "-i", "s/\"_test.*go\"//g", ".air.toml")
   715  	} else {
   716  		cmd = exec.Command("sed", "-i", "s/\"_test.*go\"//g", ".air.toml")
   717  	}
   718  	cmd.Stdout = os.Stdout
   719  	cmd.Stderr = os.Stderr
   720  	if err := cmd.Run(); err != nil {
   721  		t.Fatal(err)
   722  	}
   723  
   724  	time.Sleep(time.Second * 3)
   725  	engine, err := NewEngine(".air.toml", false)
   726  	if err != nil {
   727  		t.Fatal(err)
   728  	}
   729  	go func() {
   730  		engine.Run()
   731  	}()
   732  
   733  	t.Logf("start change main_test.go")
   734  	// change file of main_test.go
   735  	// just append a new empty line to main_test.go
   736  	if err = waitingPortReady(t, port, time.Second*40); err != nil {
   737  		t.Fatal(err)
   738  	}
   739  	go func() {
   740  		file, err := os.OpenFile("main_test.go", os.O_APPEND|os.O_WRONLY, 0644)
   741  		if err != nil {
   742  			t.Fatalf("Should not be fail: %s.", err)
   743  		}
   744  		defer file.Close()
   745  		_, err = file.WriteString("\n")
   746  		if err != nil {
   747  			t.Fatalf("Should not be fail: %s.", err)
   748  		}
   749  	}()
   750  	// should Have rebuild
   751  	if err = waitingPortConnectionRefused(t, port, time.Second*10); err != nil {
   752  		t.Fatal(err)
   753  	}
   754  }
   755  
   756  func TestCreateNewDir(t *testing.T) {
   757  	// generate a random port
   758  	port, f := GetPort()
   759  	f()
   760  	t.Logf("port: %d", port)
   761  
   762  	tmpDir := initTestEnv(t, port)
   763  	// change dir to tmpDir
   764  	err := os.Chdir(tmpDir)
   765  	if err != nil {
   766  		t.Fatalf("Should not be fail: %s.", err)
   767  	}
   768  	engine, err := NewEngine("", true)
   769  	if err != nil {
   770  		t.Fatalf("Should not be fail: %s.", err)
   771  	}
   772  
   773  	go func() {
   774  		engine.Run()
   775  	}()
   776  	time.Sleep(time.Second * 2)
   777  	assert.True(t, checkPortHaveBeenUsed(port))
   778  
   779  	// create a new dir make dir
   780  	if err = os.Mkdir(tmpDir+"/dir", 0644); err != nil {
   781  		t.Fatal(err)
   782  	}
   783  
   784  	// no need reload
   785  	if err = waitingPortConnectionRefused(t, port, 3*time.Second); err == nil {
   786  		t.Fatal("should raise a error")
   787  	}
   788  	engine.Stop()
   789  	time.Sleep(2 * time.Second)
   790  
   791  }