Merge branch 'savefix' of stephen/caddy-hugo2 into master

master
stephen 7 years ago committed by Gogs
commit 84e4c70b47
  1. 18
      deltas.go
  2. 156
      doc_test.go
  3. 96
      helpers_test.go
  4. 1
      hugo.go

@ -35,16 +35,6 @@ func (ch *CaddyHugo) LTime() uint64 {
} }
func (ch *CaddyHugo) ShouldApply(ltime uint64) bool { func (ch *CaddyHugo) ShouldApply(ltime uint64) bool {
lowest := ch.LowestPendingConfirmation()
for _, c := range ch.Confirming() {
if lowest == 0 || c < lowest {
lowest = c
}
}
if ltime < lowest {
return false
}
ch.mtx.Lock() ch.mtx.Lock()
defer ch.mtx.Unlock() defer ch.mtx.Unlock()
@ -56,12 +46,15 @@ func (ch *CaddyHugo) ShouldApply(ltime uint64) bool {
return true return true
} }
// ConfirmLTime marks an ltime as something that should be confirmed with clients
func (ch *CaddyHugo) ConfirmLTime(ltime uint64) { func (ch *CaddyHugo) ConfirmLTime(ltime uint64) {
ch.mtx.Lock() ch.mtx.Lock()
defer ch.mtx.Unlock() defer ch.mtx.Unlock()
ch.confirmingToClient[ltime] = struct{}{} ch.confirmingToClient[ltime] = struct{}{}
} }
// Confirming returns the current list of LTimes that need to be confirmed
// to clients
func (ch *CaddyHugo) Confirming() []uint64 { func (ch *CaddyHugo) Confirming() []uint64 {
var times []uint64 var times []uint64
@ -74,6 +67,8 @@ func (ch *CaddyHugo) Confirming() []uint64 {
return times return times
} }
// LowestPendingConfirmation identifies the lowest LTime that needs to be
// confirmed to clients
func (ch *CaddyHugo) LowestPendingConfirmation() uint64 { func (ch *CaddyHugo) LowestPendingConfirmation() uint64 {
var lowest uint64 var lowest uint64
for _, c := range ch.Confirming() { for _, c := range ch.Confirming() {
@ -181,10 +176,11 @@ func (ch *CaddyHugo) handleDeltaConn(conn DeltaConn, doc *editSession) (int, err
errCh <- fmt.Errorf("error reading message from client conn: %v", err) errCh <- fmt.Errorf("error reading message from client conn: %v", err)
return return
} }
if message.LTime != 0 {
ch.ObserveLTime(message.LTime) ch.ObserveLTime(message.LTime)
}
if len(message.Deltas) == 0 { if len(message.Deltas) == 0 {
time.Sleep(10 * time.Microsecond)
continue continue
} }

@ -1,10 +1,6 @@
package caddyhugo package caddyhugo
import ( import (
"encoding/json"
"io/ioutil"
"os"
"os/exec"
"path" "path"
"sync" "sync"
"testing" "testing"
@ -13,136 +9,70 @@ import (
"git.stephensearles.com/stephen/acedoc" "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) { func TestEdits(t *testing.T) {
w := NewWorld(t) w := NewWorld(t)
defer w.Clean() defer w.Clean()
const title = "sometitle" const title = "sometitle"
var contentPath = path.Join("content", title+".md")
w.CH.NewContent(title, "") var (
mtx sync.Mutex
send := []acedoc.Delta{ contentPath = path.Join("content", title+".md")
// we will send these to the doc
send = []acedoc.Delta{
acedoc.Insert(0, 0, "hello"), acedoc.Insert(0, 0, "hello"),
acedoc.Insert(0, 5, " world"), acedoc.Insert(0, 5, " world"),
acedoc.Insert(0, 11, " world"), acedoc.Insert(0, 11, " world"),
} }
var mtx sync.Mutex
received := []acedoc.Delta{}
doc, err := w.CH.editSession(contentPath) // we will use this to track what we get out of the doc
received = []acedoc.Delta{}
)
// prepare a new post
w.CH.NewContent(title, "")
// start an edit session
es, err := w.CH.editSession(contentPath)
if err != nil { if err != nil {
t.Fatal("error creating document client:", err) t.Fatal("error creating document client:", err)
} }
doc.doc.Client(acedoc.DeltaHandlerFunc(func(ds []acedoc.Delta) error { // register a client that we will use to push the deltas. we dont need
// receive some deltas... // to actually do anything to receive deltas here, though.
c := es.doc.Client(acedoc.DeltaHandlerFunc(func(ds []acedoc.Delta) error {
return nil
}))
// register an *extra* client that just adds to the received delta
// slice we're tracking
es.doc.Client(acedoc.DeltaHandlerFunc(func(ds []acedoc.Delta) error {
mtx.Lock() mtx.Lock()
defer mtx.Unlock() defer mtx.Unlock()
received = append(received, ds...) received = append(received, ds...)
return nil return nil
})) }))
// make sure we have an edit session
_, ok := w.CH.hasEditSession(contentPath) _, ok := w.CH.hasEditSession(contentPath)
if !ok { if !ok {
t.Fatal("expected there to be an established client") t.Fatal("expected there to be an established client")
} }
doc.doc.Apply(send...) // push the deltas
c.PushDeltas(send...)
// wait...
<-time.After(5 * time.Second) <-time.After(5 * time.Second)
// be sure we got the correct number of deltas back
mtx.Lock() mtx.Lock()
defer mtx.Unlock() defer mtx.Unlock()
if len(received) != len(send) { if len(received) != len(send) {
t.Errorf("expected %d deltas, received %d; expected: %v, received: %v", len(send), len(received), send, received) 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
}
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
}
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")
}
if len(m.Deltas) == 0 {
return nil
}
ws.wroteMessages = append(ws.wroteMessages, m)
ws.wroteDeltas = append(ws.wroteDeltas, m.Deltas...)
return nil
}
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) { func TestDeltasSingle(t *testing.T) {
@ -158,12 +88,12 @@ func TestDeltasSingle(t *testing.T) {
client := new(WebsocketTester) client := new(WebsocketTester)
doc, err := w.CH.editSession("content/" + title + ".md") es, err := w.CH.editSession("content/" + title + ".md")
if err != nil { if err != nil {
t.Fatal("couldn't establish docref for client 0:", err) t.Fatal("couldn't establish docref for client 0:", err)
} }
go w.CH.handleDeltaConn(client, doc) go w.CH.handleDeltaConn(client, es)
a := acedoc.Insert(0, 0, "a") a := acedoc.Insert(0, 0, "a")
@ -174,9 +104,11 @@ func TestDeltasSingle(t *testing.T) {
time.Sleep(50 * time.Millisecond) time.Sleep(50 * time.Millisecond)
// we shouldn't have written back to the client, // we shouldn't have written back to the client,
// so we expect to have written 0 messages // so we expect to have written 0 *deltas*. (we may have written
if len(client.wroteMessages) != 0 { // empty messages without deltas because of the pings to the client)
t.Errorf("client wrote %d messages, should have written %d", len(client.wroteMessages), 0) if len(client.wroteDeltas) != 0 {
t.Errorf("client wrote %d deltas, should have written %d", len(client.wroteMessages), 0)
t.Logf("%v", client.wroteMessages)
} }
// we received one, so make sure that's counted properly // we received one, so make sure that's counted properly
@ -217,7 +149,7 @@ func TestDeltasDouble(t *testing.T) {
// so we expect clientA to have written 0 messages, and // so we expect clientA to have written 0 messages, and
// clientB to have written 1 // clientB to have written 1
if len(clientA.wroteMessages) != 0 || len(clientB.wroteMessages) != 1 { if len(clientA.wroteDeltas) != 0 || len(clientB.wroteDeltas) != 1 {
t.Errorf("clientA wrote %d messages, should have written 0. clientB wrote %d, should have written 1", len(clientA.wroteMessages), len(clientB.wroteMessages)) t.Errorf("clientA wrote %d messages, should have written 0. clientB wrote %d, should have written 1", len(clientA.wroteMessages), len(clientB.wroteMessages))
} }
@ -238,9 +170,9 @@ func TestDeltasDouble(t *testing.T) {
clientA.mtx.Lock() clientA.mtx.Lock()
clientB.mtx.Lock() clientB.mtx.Lock()
// so we expect clientA to have written 1 message this time, and // so we expect clientA to have written 1 delta this time, and
// clientB to have written nothing new, so 1 still // clientB to have written nothing new, so 1 still
if len(clientA.wroteMessages) != 1 || len(clientB.wroteMessages) != 1 { if len(clientA.wroteDeltas) != 1 || len(clientB.wroteDeltas) != 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)) 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))
} }
@ -268,13 +200,15 @@ func TestDeltasMulti(t *testing.T) {
doc, err := w.CH.editSession("content/" + title + ".md") doc, err := w.CH.editSession("content/" + title + ".md")
if err != nil { if err != nil {
t.Fatal("couldn't establish docref:", err) t.Fatal("couldn't establish edit session:", err)
} }
go w.CH.handleDeltaConn(clients[0], doc) go w.CH.handleDeltaConn(clients[0], doc)
go w.CH.handleDeltaConn(clients[1], doc) go w.CH.handleDeltaConn(clients[1], doc)
go w.CH.handleDeltaConn(clients[2], doc) go w.CH.handleDeltaConn(clients[2], doc)
time.Sleep(100 * time.Millisecond)
a := acedoc.Insert(0, 0, "a") a := acedoc.Insert(0, 0, "a")
b := acedoc.Insert(0, 0, "b") b := acedoc.Insert(0, 0, "b")
c := acedoc.Insert(0, 0, "c") c := acedoc.Insert(0, 0, "c")
@ -283,12 +217,12 @@ func TestDeltasMulti(t *testing.T) {
clients[1].ReceiveJSON(w.CH.Message(b)) clients[1].ReceiveJSON(w.CH.Message(b))
clients[2].ReceiveJSON(w.CH.Message(c)) clients[2].ReceiveJSON(w.CH.Message(c))
time.Sleep(400 * time.Millisecond) time.Sleep(1000 * time.Millisecond)
for i, client := range clients { for i, client := range clients {
client.mtx.Lock() client.mtx.Lock()
// all clients should have "written" 2 deltas (could be the same // all clients should have "written" 2 deltas out to their "browser"
// message) that came from the other clients // that came from the other clients
if len(client.wroteDeltas) != 2 { if len(client.wroteDeltas) != 2 {
t.Errorf("client %d wrote %d deltas, should have written 2", i, len(client.wroteDeltas)) t.Errorf("client %d wrote %d deltas, should have written 2", i, len(client.wroteDeltas))
} }

@ -0,0 +1,96 @@
package caddyhugo
import (
"encoding/json"
"io/ioutil"
"os"
"os/exec"
"sync"
"testing"
"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
}
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
}

@ -52,7 +52,6 @@ func writeThemeFiles(dir string) error {
if err != nil { if err != nil {
return err return err
} }
fmt.Println("writing", path.Join(dir, asset))
f, err := os.Create(path.Join(dir, asset)) f, err := os.Create(path.Join(dir, asset))
if err != nil { if err != nil {
return err return err

Loading…
Cancel
Save