package scheduler import ( "bytes" "fmt" "io/ioutil" "os" "testing" "time" "gitea.inhome.blapointe.com/local/firestormy/config" "gitea.inhome.blapointe.com/local/firestormy/config/ns" "gitea.inhome.blapointe.com/local/storage" ) func TestSchedulerAddRemove(t *testing.T) { config.Store, _ = storage.New(storage.MAP) s := New() j, err := NewJob(Bash, "@hourly", "hostname") if err != nil { t.Fatal(err) } if err := s.Add(j); err != nil { t.Fatal(err) } if list, err := s.List(); err != nil { t.Fatal(err) } else if len(list) != 1 { t.Fatal(err) } if err := s.Remove(j); err != nil { t.Fatal(err) } if list, err := s.List(); err != nil { t.Fatal(err) } else if len(list) != 0 { t.Fatal(err) } } func TestSchedulerStartStop(t *testing.T) { b, clean := captureLog() defer clean() config.Store, _ = storage.New(storage.MAP) s := New() for i := 0; i < 5; i++ { j, err := NewJob(Bash, "* * * * *", "hostname") if err != nil { t.Fatal(err) } if err := s.Add(j); err != nil { t.Fatal(err) } } if err := s.Start(); err != nil { t.Fatal(err) } if err := s.Stop(); err != nil { t.Fatal(err) } if n := bytes.Count(b.Bytes(), []byte("schedule")); n != 5 { t.Errorf("%v: %s", n, b.Bytes()) } } func TestSchedulerFromFile(t *testing.T) { was := config.Store defer func() { config.Store = was }() cases := map[string]struct { content string want int }{ "just a job": { content: `10 */12 * * * /bin/bash -c "hostname"`, want: 1, }, "just a job with seconds": { content: `*/2 10 */12 * * * /bin/bash -c "hostname"`, want: 1, }, "all wild": { content: `* * * * * /bin/bash -c "hostname"`, want: 1, }, "all single numbers": { content: `1 1 1 1 1 /bin/bash -c "hostname"`, want: 1, }, "all double numbers": { content: `10 10 10 10 2 /bin/bash -c "hostname"`, want: 1, }, "all /\\d+": { content: `*/11 */2 */3 */4 */1 /bin/bash -c "hostname"`, want: 1, }, "2 jobs with 1 comment no whitespace leading": { content: `# this is my comment 10 */12 * * * /bin/bash -c "hostname" 10 */12 * * * /bin/bash -c "hostname" `, want: 2, }, "2 jobs with 1 comment whitespace leading": { content: ` # this is my comment 10 */12 * * * /bin/bash -c "hostname" 10 */12 * * * /bin/bash -c "hostname" `, want: 2, }, "2 jobs with crazy whitespace between cron spec": { content: ` # this is my comment 10 */12 * * * /bin/bash -c "hostname" 10 */12 * * * /bin/bash -c "hostname" `, want: 2, }, "2 jobs with 2 comemnts and 2 empty lines": { content: ` # this is my comment # this is a second comment 10 */12 * * * /bin/bash -c "hostname" 10 */12 * * * /bin/bash -c "hostname" `, want: 2, }, } for name, c := range cases { t.Run(name, func(t *testing.T) { config.Store, _ = storage.New(storage.MAP) f, err := ioutil.TempFile(os.TempDir(), "testSchedulerFromFile") if err != nil { t.Fatal(err) } defer os.Remove(f.Name()) f.Write([]byte(c.content)) f.Close() s, err := NewFromFile(f.Name()) if err != nil { t.Fatal(err) } jobs, err := s.List() if err != nil { t.Fatal(err) } if len(jobs) != c.want { t.Fatalf("want %v, got %v jobs", c.want, jobs) } }) } } func TestSplitScheduleCommandTitle(t *testing.T) { cases := map[string]struct { in string schedule string command string title string }{ "invalid schedule": { in: "* * * cmd #title", }, "no title": { in: "* * * * * cmd ", schedule: "* * * * *", command: "cmd", }, "schedule, command, title": { in: "* * * * * cmd #title", schedule: "* * * * *", command: "cmd #title", title: "title", }, } for name, c := range cases { t.Run(name, func(t *testing.T) { s, a, l := splitScheduleCommandTitle([]byte(c.in)) if s != c.schedule { t.Error(s) } if a != c.command { t.Error(a) } if l != c.title { t.Error(l) } }) } } func TestSchedulerUpdate(t *testing.T) { config.Store, _ = storage.New(storage.MAP) was := config.Store defer func() { config.Store = was }() s := New() j, err := NewJob(Bash, "* * * * *", "hostname") if err != nil { t.Fatal(err) } if err := s.Add(j); err != nil { t.Fatal(err) } if list, err := s.List(); err != nil { t.Fatal(err) } else if len(list) != 1 { t.Fatal(err) } time.Sleep(time.Millisecond * 1500) config.Store.Set(j.Name, []byte("echo 2"), ns.JobsRaw...) j.Title = "title 2" if err := s.Update(j); err != nil { t.Fatal(err) } if list, err := s.List(); err != nil { t.Fatal(err) } else if len(list) != 1 { t.Fatal(err) } else if j, err := s.loadJobFromStore(j.Name); err != nil { t.Fatal(err) } else if j.Title != "title 2" { t.Error(j.Title) } else if entry := s.cron.Entry(s.running[j.Name]); entry == s.cron.Entry(-99) { t.Error(entry) } else if entries := s.cron.Entries(); len(entries) != 1 { t.Error(entries) } else if job, ok := entries[0].Job.(*Job); !ok { t.Error(fmt.Sprintf("%T", entries[0].Job)) } else if fmt.Sprintf("%+v", job) != fmt.Sprintf("%+v", j) { t.Error(job) } }