package arthur import ( "io/ioutil" "net" "os" "testing" "time" "git.csclub.uwaterloo.ca/public/merlin/config" "git.csclub.uwaterloo.ca/public/merlin/logger" ) func TestStatusCommand(t *testing.T) { r, w := net.Pipe() go func() { // will only finish write when EOF is sent // only way to send EOF is to close w.Write([]byte("status")) w.Close() }() command, repoName := GetCommand(r) if command != "status" { t.Errorf("command for status should be \"status\", got " + command) } else if repoName != "" { t.Errorf("status should return an empty string for the repoName, got " + repoName) } } func TestSyncCommand(t *testing.T) { r, w := net.Pipe() go func() { w.Write([]byte("sync:ubuntu")) w.Close() }() command, repoName := GetCommand(r) r.Close() if command != "sync" { t.Errorf("command for sync:ubuntu should be \"sync\", got " + command) } else if repoName != "ubuntu" { t.Errorf("name of repo for sync:ubuntu should be \"ubuntu\", got " + repoName) } } func TestSendStatus(t *testing.T) { saveRepoMap := config.RepoMap defer func() { config.RepoMap = saveRepoMap }() repoMap := make(map[string]*config.Repo) repoMap["eeeee"] = &config.Repo{ Frequency: 30 * 86400, State: config.RepoState{ IsRunning: true, LastAttemptStartTime: 1600000000, }, } repoMap["alinux"] = &config.Repo{ Frequency: 7*86400 + 3, State: config.RepoState{ IsRunning: true, LastAttemptStartTime: 1620000000, }, } repoMap["lnux"] = &config.Repo{ Frequency: 86400, State: config.RepoState{ IsRunning: false, LastAttemptStartTime: 1640000000, }, } config.RepoMap = repoMap r, w := net.Pipe() go func() { SendStatus(w) w.Close() }() msg, err := ioutil.ReadAll(r) r.Close() if err != nil { t.Errorf(err.Error()) } expected := `Repository Last Synced Next Expected Sync Running alinux Sun, 02 May 2021 20:00:00 EDT Sun, 09 May 2021 20:00:03 EDT true eeeee Sun, 13 Sep 2020 08:26:40 EDT Tue, 13 Oct 2020 08:26:40 EDT true lnux Mon, 20 Dec 2021 06:33:20 EST Tue, 21 Dec 2021 06:33:20 EST false ` if expected != string(msg) { t.Errorf("Expected:\n" + expected + "\nGot:\n" + string(msg)) } } func TestForceSync(t *testing.T) { saveRepos := config.Repos saveRepoMap := config.RepoMap doneChan := make(chan config.SyncResult) defer func() { config.Repos = saveRepos config.RepoMap = saveRepoMap close(doneChan) }() // Part 1: run a dummy sync repo := config.Repo{ Name: "nux", SyncType: "csc-sync-dummy", Frequency: 7 * 86400, MaxTime: 30, Logger: logger.NewLogger("nux", "/tmp/merlin_force_sync_test_logs"), StateFile: "/tmp/merlin_force_sync_test_state", DoneChan: doneChan, State: config.RepoState{ IsRunning: false, LastAttemptStartTime: 0, LastAttemptRunTime: 0, LastAttemptExit: config.NOT_RUN_YET, }, } config.Repos = nil config.Repos = append(config.Repos, &repo) config.RepoMap = make(map[string]*config.Repo) config.RepoMap["nux"] = &repo r, w := net.Pipe() go func() { if !ForceSync(w, "nux") { t.Errorf("Sync for nux did not start") } w.Close() }() msg, err := ioutil.ReadAll(r) r.Close() if err != nil { t.Errorf(err.Error()) } expected := "Forced sync for nux" if expected != string(msg) { t.Errorf("Expected:\n" + expected + "\nGot:\n" + string(msg)) } select { case result := <-doneChan: if result.Exit != config.SUCCESS { t.Errorf("Sync should exit with SUCCESS, got %d", result.Exit) } case <-time.After(3 * time.Second): t.Errorf("Dummy sync should be done in 1 second, waited 3 seconds") } // Part 2: attempt the same thing but with repo.State.IsRunning = true r, w = net.Pipe() go func() { if ForceSync(w, "nux") { t.Errorf("Sync for nux should not have started") } w.Close() }() msg, err = ioutil.ReadAll(r) r.Close() if err != nil { t.Errorf(err.Error()) } expected = "Could not force sync: nux is already syncing." if expected != string(msg) { t.Errorf("Expected:\n" + expected + "\nGot:\n" + string(msg)) } select { case <-doneChan: t.Errorf("Sync for nux should not have been started") case <-time.After(2 * time.Second): } // Part 3: attempt a force sync with a repo that does not exist r, w = net.Pipe() go func() { if ForceSync(w, "nixx") { t.Errorf("Sync for nixx should not have started") } w.Close() }() msg, err = ioutil.ReadAll(r) r.Close() if err != nil { t.Errorf(err.Error()) } expected = "nixx is not tracked so cannot sync" if expected != string(msg) { t.Errorf("Expected:\n" + expected + "\nGot:\n" + string(msg)) } } func TestStartListener(t *testing.T) { saveConf := config.Conf connChan := make(chan net.Conn) stopLisChan := make(chan struct{}) wait := make(chan struct{}) defer func() { config.Conf = saveConf close(connChan) close(stopLisChan) }() config.Conf = config.Config{ SockPath: "/tmp/merlin_listener_test.sock", } // Test 1: check that closing/sending something to stopLisChan will stop the listener // and that a new listener can be created after stopping the old one go func() { StartListener(connChan, stopLisChan) wait <- struct{}{} }() stopLisChan <- struct{}{} select { case <-wait: case <-time.After(3 * time.Second): t.Errorf("StartListener should stop when struct{}{} is sent to stopLisChan") } go func() { StartListener(connChan, stopLisChan) wait <- struct{}{} }() close(stopLisChan) select { case <-wait: case <-time.After(3 * time.Second): t.Errorf("StartListener should stop when stopLisChan is closed") } close(wait) // Test 2: check that connections can be made to the unix socket // this test does not appear to be very stable (I think there is a race condition somewhere) stopLisChan = make(chan struct{}) go StartListener(connChan, stopLisChan) waitForMsg := func(expected string) { select { case conn := <-connChan: msg, err := ioutil.ReadAll(conn) if err != nil { t.Errorf(err.Error()) } else if expected != string(msg) { t.Errorf("Message expected was " + expected + " got " + string(msg)) } conn.Close() case <-time.After(3 * time.Second): t.Errorf("StartListener should stop when struct{}{} is sent to stopLisChan") } } sendMsg := func(msg string) { <-time.After(500 * time.Millisecond) send, err := net.Dial("unix", "/tmp/merlin_listener_test.sock") if err != nil { panic(err) } _, err = send.Write([]byte(msg)) if err != nil { t.Errorf(err.Error()) } send.Close() } go func() { waitForMsg("status") }() sendMsg("status") go func() { waitForMsg("sync:uuunix") }() sendMsg("sync:uuunix") go func() { waitForMsg("$UPz2L2yWsE^UY8iG9JX@^dBb@5yb*") }() sendMsg("$UPz2L2yWsE^UY8iG9JX@^dBb@5yb*") // unsure why I can't put this in the defer os.Remove("/tmp/merlin_listener_test.sock") }