package caddyhugo import ( "encoding/json" "io/ioutil" "os" "os/exec" "path" "sync" "testing" "time" "git.stephensearles.com/stephen/acedoc" ) type World struct { CH *CaddyHugo BlogFolder string } func (w *World) Clean() { if w.BlogFolder != "" { os.RemoveAll(w.BlogFolder) } } func NewWorld(t *testing.T) *World { dir, err := ioutil.TempDir("", "caddy-hugo2-test-") if err != nil { t.Fatalf("error initializing test environment: %v", err) } w := &World{BlogFolder: dir} cmd := exec.Command("hugo", "new", "site", dir) cmd.Dir = dir out, err := cmd.CombinedOutput() if err != nil { t.Fatalf("error initializing test site: %v\n\n%v", err, string(out)) } w.CH = &CaddyHugo{} w.CH.Setup(dir) return w } func TestEdits(t *testing.T) { w := NewWorld(t) defer w.Clean() const title = "sometitle" var contentPath = path.Join("content", title+".md") w.CH.NewContent(title, "") send := []acedoc.Delta{ acedoc.Insert(0, 0, "hello"), acedoc.Insert(0, 5, " world"), acedoc.Insert(0, 11, " world"), } var mtx sync.Mutex received := []acedoc.Delta{} doc, err := w.CH.editSession(contentPath) if err != nil { t.Fatal("error creating document client:", err) } doc.doc.Client(acedoc.DeltaHandlerFunc(func(ds []acedoc.Delta) error { // receive some deltas... mtx.Lock() defer mtx.Unlock() received = append(received, ds...) return nil })) _, ok := w.CH.hasEditSession(contentPath) if !ok { t.Fatal("expected there to be an established client") } doc.doc.Apply(send...) <-time.After(5 * time.Second) mtx.Lock() defer mtx.Unlock() if len(received) != len(send) { t.Errorf("expected %d deltas, received %d; expected: %v, received: %v", len(send), len(received), send, received) } } type WebsocketTester struct { receivedPointer int received [][]byte wroteMessages []Message wroteDeltas []acedoc.Delta mtx sync.Mutex } // ReadJSON reads the next pending message from the "client" into v func (ws *WebsocketTester) ReadJSON(v interface{}) error { ws.mtx.Lock() defer ws.mtx.Unlock() if len(ws.received) <= ws.receivedPointer { return nil } err := json.Unmarshal(ws.received[ws.receivedPointer], v) ws.receivedPointer++ return err } // WriteJSON "sends" a message, v, to the "client" func (ws *WebsocketTester) WriteJSON(v interface{}) error { ws.mtx.Lock() defer ws.mtx.Unlock() m, ok := v.(Message) if !ok { panic("wrong type written to WebsocketTester") } ws.wroteMessages = append(ws.wroteMessages, m) ws.wroteDeltas = append(ws.wroteDeltas, m.Deltas...) return nil } // ReceiveJSON queues a message to be sent to the client func (ws *WebsocketTester) ReceiveJSON(v interface{}) error { ws.mtx.Lock() defer ws.mtx.Unlock() out, err := json.Marshal(v) if err != nil { return err } ws.received = append(ws.received, out) return nil } func TestDeltasSingle(t *testing.T) { w := NewWorld(t) defer w.Clean() const title = "test" _, err := w.CH.NewContent(title, "") if err != nil { t.Fatal("couldn't create new content:", err) } client := new(WebsocketTester) doc, err := w.CH.editSession("content/" + title + ".md") if err != nil { t.Fatal("couldn't establish docref for client 0:", err) } go w.CH.handleDeltaConn(client, doc) a := acedoc.Insert(0, 0, "a") // pretend to get one sent from the "browser" client.ReceiveJSON(w.CH.Message(a)) // wait to make sure it was processed time.Sleep(50 * time.Millisecond) // we shouldn't have written back to the client, // so we expect to have written 0 messages if len(client.wroteMessages) != 0 { t.Errorf("client wrote %d messages, should have written %d", len(client.wroteMessages), 0) t.Logf("%v", client.wroteMessages) } // we received one, so make sure that's counted properly if len(client.received) != 1 { t.Errorf("client has %d messages, should have received %d", len(client.received), 1) } } func TestDeltasDouble(t *testing.T) { w := NewWorld(t) defer w.Clean() const title = "test" _, err := w.CH.NewContent(title, "") if err != nil { t.Fatal("couldn't create new content:", err) } clientA := new(WebsocketTester) clientB := new(WebsocketTester) doc, err := w.CH.editSession("content/" + title + ".md") if err != nil { t.Fatal("couldn't establish docref for client 0:", err) } go w.CH.handleDeltaConn(clientA, doc) go w.CH.handleDeltaConn(clientB, doc) // send the first message, simulating the browser on clientA clientA.ReceiveJSON(w.CH.Message(acedoc.Insert(0, 0, "a"))) time.Sleep(100 * time.Millisecond) clientA.mtx.Lock() clientB.mtx.Lock() // so we expect clientA to have written 0 messages, and // clientB to have written 1 if len(clientA.wroteMessages) != 0 || len(clientB.wroteMessages) != 1 { t.Errorf("clientA wrote %d messages, should have written 0. clientB wrote %d, should have written 1", len(clientA.wroteMessages), len(clientB.wroteMessages)) } // we received one via clientA and zero via clientB, so make sure // that's counted properly if len(clientA.received) != 1 || len(clientB.received) != 0 { t.Errorf("clientA has %d messages, should have received 1; clientB has %d messages, should have received 0", len(clientA.received), len(clientB.received)) } clientA.mtx.Unlock() clientB.mtx.Unlock() // send the second message, via clientB clientB.ReceiveJSON(w.CH.Message(acedoc.Insert(0, 0, "b"))) time.Sleep(400 * time.Millisecond) clientA.mtx.Lock() clientB.mtx.Lock() // so we expect clientA to have written 1 message this time, and // clientB to have written nothing new, so 1 still if len(clientA.wroteMessages) != 1 || len(clientB.wroteMessages) != 1 { t.Errorf("clientA wrote %d messages, should have written 1. clientB wrote %d, should have written 1 (just from before)", len(clientA.wroteMessages), len(clientB.wroteMessages)) } // we received zero (new) via clientA and one via clientB, so make sure // that's counted properly if len(clientA.received) != 1 || len(clientB.received) != 1 { t.Errorf("clientA has %d messages, should have received 1; clientB has %d messages, should have received 1", len(clientA.received), len(clientB.received)) } clientA.mtx.Unlock() clientB.mtx.Unlock() } func TestDeltasMulti(t *testing.T) { w := NewWorld(t) defer w.Clean() const title = "test" _, err := w.CH.NewContent(title, "") if err != nil { t.Fatal("couldn't create new content:", err) } clients := []*WebsocketTester{{}, {}, {}} doc, err := w.CH.editSession("content/" + title + ".md") if err != nil { t.Fatal("couldn't establish edit session:", err) } go w.CH.handleDeltaConn(clients[0], doc) go w.CH.handleDeltaConn(clients[1], doc) go w.CH.handleDeltaConn(clients[2], doc) time.Sleep(100 * time.Millisecond) a := acedoc.Insert(0, 0, "a") b := acedoc.Insert(0, 0, "b") c := acedoc.Insert(0, 0, "c") clients[0].ReceiveJSON(w.CH.Message(a)) clients[1].ReceiveJSON(w.CH.Message(b)) clients[2].ReceiveJSON(w.CH.Message(c)) time.Sleep(1000 * time.Millisecond) for i, client := range clients { client.mtx.Lock() t.Logf("client %d exists", i) // all clients should have "written" 2 deltas out to their "browser" // that came from the other clients if len(client.wroteDeltas) != 2 { t.Errorf("client %d wrote %d deltas, should have written 2", i, len(client.wroteDeltas)) } // all clients "received" 1 message from the "browser" if len(client.received) != 1 { t.Errorf("client %d has %d messages, should have received 1", i, len(client.received)) } client.mtx.Unlock() } } func TestPagesInPagesOut(t *testing.T) { w := NewWorld(t) defer w.Clean() // check there's no content at first c, err := GetContent(w.BlogFolder, w.CH.HugoSites) if err != nil { t.Fatalf("couldn't get content from a blank test environment: %v", err) } if len(c) != 0 { t.Fatalf("expected a blank test environment, but saw %d pages", len(c)) } titles := []string{ "test1", "TEST 2!!", } found := map[string]bool{} // create some known content for i, title := range titles { w.CH.NewContent(title, "") c, err = GetContent(w.BlogFolder, w.CH.HugoSites) if err != nil { t.Fatalf("couldn't get content from the test environment: %v", err) } if len(c)-1 != i { t.Fatalf("expected %d page, but saw %d pages", i+1, len(c)) } } // make sure we get the content out that we just created c, err = GetContent(w.BlogFolder, w.CH.HugoSites) if err != nil { t.Fatalf("couldn't get content from the test environment: %v", err) } for _, content := range c { if content.Metadata == nil { t.Errorf("didn't see metadata for %q", content.Filename) } found[content.Filename] = true } var missingSomething bool for _, title := range titles { adjusted := path.Join("content", docname(title)+".md") if !found[adjusted] { missingSomething = true t.Errorf("expected to find title %q, but didn't see it", adjusted) } } if missingSomething { t.Logf("found titles: %v", found) } }