Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(2221)

Unified Diff: appengine/logdog/coordinator/logView/view_test.go

Issue 1672833003: LogDog: Add log rendering view. Base URL: https://github.com/luci/luci-go@master
Patch Set: Clean up, add tests, little reorg. Created 4 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « appengine/logdog/coordinator/logView/view.go ('k') | appengine/logdog/coordinator/service.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: appengine/logdog/coordinator/logView/view_test.go
diff --git a/appengine/logdog/coordinator/logView/view_test.go b/appengine/logdog/coordinator/logView/view_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..5dd81a639b1c2bc593f69aa2ff0ce5519a5093b5
--- /dev/null
+++ b/appengine/logdog/coordinator/logView/view_test.go
@@ -0,0 +1,285 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package logView
+
+import (
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/golang/protobuf/proto"
+ "github.com/julienschmidt/httprouter"
+ "github.com/luci/gae/impl/memory"
+ ds "github.com/luci/gae/service/datastore"
+ "github.com/luci/luci-go/appengine/logdog/coordinator"
+ ct "github.com/luci/luci-go/appengine/logdog/coordinator/coordinatorTest"
+ "github.com/luci/luci-go/common/clock"
+ "github.com/luci/luci-go/common/clock/testclock"
+ "github.com/luci/luci-go/common/logdog/types"
+ "github.com/luci/luci-go/common/logging"
+ "github.com/luci/luci-go/common/logging/gologger"
+ "github.com/luci/luci-go/common/proto/logdog/logpb"
+ "github.com/luci/luci-go/common/proto/logdog/svcconfig"
+ "github.com/luci/luci-go/server/auth"
+ "github.com/luci/luci-go/server/auth/authtest"
+ "github.com/luci/luci-go/server/logdog/storage"
+ "github.com/luci/luci-go/server/middleware"
+ "golang.org/x/net/context"
+
+ . "github.com/smartystreets/goconvey/convey"
+)
+
+type simulatedStorageInsn struct {
+ idx int64
+ d [][]byte
+ err error
+}
+
+type simulatedStorage struct {
+ storage.Storage
+
+ insnC chan *simulatedStorageInsn
+ gotC chan struct{}
+ closedC chan struct{}
+}
+
+func (s *simulatedStorage) Get(req *storage.GetRequest, cb storage.GetCallback) error {
+ insn := <-s.insnC
+ if insn.err != nil {
+ return insn.err
+ }
+
+ defer func() {
+ if s.gotC != nil {
+ s.gotC <- struct{}{}
+ }
+ }()
+
+ for i, d := range insn.d {
+ if !cb(types.MessageIndex(insn.idx+int64(i)), d) {
+ return nil
+ }
+ }
+ return nil
+}
+
+func (s *simulatedStorage) Close() {
+ close(s.insnC)
+ close(s.closedC)
+}
+
+func (s *simulatedStorage) err(err error) {
+ s.insnC <- &simulatedStorageInsn{err: err}
+}
+
+func (s *simulatedStorage) text(startIdx int64, lines ...string) {
+ d := make([][]byte, len(lines))
+ for i, v := range lines {
+ line := logpb.Text_Line{
+ Value: v,
+ }
+ if strings.HasSuffix(line.Value, "\n") {
+ line.Value = strings.TrimSuffix(line.Value, "\n")
+ line.Delimiter = "\n"
+ }
+
+ pb := logpb.LogEntry{
+ StreamIndex: uint64(startIdx + int64(i)),
+ Content: &logpb.LogEntry_Text{
+ Text: &logpb.Text{
+ Lines: []*logpb.Text_Line{
+ &line,
+ },
+ },
+ },
+ }
+
+ data, err := proto.Marshal(&pb)
+ require(err)
+ d[i] = data
+ }
+ s.data(startIdx, d...)
+}
+
+func (s *simulatedStorage) data(startIdx int64, d ...[]byte) {
+ s.insnC <- &simulatedStorageInsn{idx: startIdx, d: d}
+}
+
+// testBase is a middleware.Base which uses its current Context as the base
+// context.
+type testBase struct {
+ context.Context
+}
+
+func (t *testBase) base(h middleware.Handler) httprouter.Handle {
+ return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
+ h(t.Context, w, r, p)
+ }
+}
+
+func TestLogView(t *testing.T) {
+ Convey(`With a testing configuration, a LogView handler`, t, func() {
+ c, tc := testclock.UseTime(context.Background(), testclock.TestTimeLocal)
+ c = memory.Use(c)
+ c = gologger.Use(c) // XXX: Remove me.
+ c = logging.SetLevel(c, logging.Debug)
+
+ fs := authtest.FakeState{}
+ c = auth.WithState(c, &fs)
+
+ c = ct.UseConfig(c, &svcconfig.Coordinator{
+ AdminAuthGroup: "test-administrators",
+ })
+
+ st := simulatedStorage{
+ insnC: make(chan *simulatedStorageInsn, 1),
+ closedC: make(chan struct{}),
+ }
+
+ desc := ct.TestLogStreamDescriptor(c, "foo/bar")
+ ls, err := ct.TestLogStream(c, desc)
+ require(err)
+
+ err = ls.Put(ds.Get(c))
+ require(err)
+
+ h := Handler{
+ Service: coordinator.Service{
+ IntermediateStorageFunc: func(c context.Context) (storage.Storage, error) {
+ return &st, nil
+ },
+ },
+ }
+
+ r := httprouter.New()
+ h.InstallHandlers(r, func(h middleware.Handler) httprouter.Handle {
+ return func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
+ h(c, w, r, p)
+ }
+ })
+
+ srv := httptest.NewServer(r)
+ defer srv.Close()
+
+ // Executes a view request for the named stream/hash against our server.
+ view := func(v string) (string, int) {
+ u, err := url.Parse(srv.URL)
+ require(err)
+
+ u.Path = fmt.Sprintf("/logs/view/%s", v)
+ resp, err := http.Get(u.String())
+ require(err)
+ defer resp.Body.Close()
+
+ b, err := ioutil.ReadAll(resp.Body)
+ require(err)
+ return string(b), resp.StatusCode
+ }
+
+ Convey(`Will return Bad Request for an invalid log stream path/hash.`, func() {
+ _, err := view("!!!invalid!!!")
+ So(err, ShouldEqual, http.StatusBadRequest)
+ })
+
+ Convey(`Will return Not Found for a non-existent log stream.`, func() {
+ _, err := view("does/not/+/exist")
+ So(err, ShouldEqual, http.StatusNotFound)
+ })
+
+ Convey(`When Storage has a finished log with "Line0\nLine1\nLine2\n"`, func() {
+ st.text(0, "Line0\n", "Line1\n", "Line2\n")
+ ls.TerminalIndex = 2
+ require(ls.Put(ds.Get(c)))
+
+ Convey(`Can read the log stream.`, func() {
+ txt, err := view(string(ls.Path()))
+ So(err, ShouldEqual, http.StatusOK)
+ So(txt, ShouldEqual, "Line0\nLine1\nLine2\n")
+ })
+
+ Convey(`When a log stream has been purged`, func() {
+ ls.Purged = true
+ require(ls.Put(ds.Get(c)))
+
+ Convey(`Will return Not Found if the user is not admin.`, func() {
+ _, err := view(string(ls.Path()))
+ So(err, ShouldEqual, http.StatusNotFound)
+ })
+
+ Convey(`Will return the log stream if the user is admin.`, func() {
+ fs.IdentityGroups = []string{"test-administrators"}
+
+ txt, err := view(string(ls.Path()))
+ So(err, ShouldEqual, http.StatusOK)
+ So(txt, ShouldEqual, "Line0\nLine1\nLine2\n")
+ })
+ })
+ })
+
+ Convey(`Can stream a non-terminal log.`, func() {
+ st.gotC = make(chan struct{})
+
+ go func() {
+ // Put initial log record.
+ st.text(0, "Line0\n")
+ <-st.gotC
+
+ // Skip a log record. This should be ignored since it's non-contiguous.
+ st.text(2, "Line2\n")
+
+ // When we sleep pending a new log, add log line #1. This will happen
+ // AFTER the following "gotC" block. We do this to ensure that the
+ // callback is in place when the timer needs it.
+ tc.SetTimerCallback(func(d time.Duration, t clock.Timer) {
+ st.text(1, "Line1\n")
+ tc.SetTimerCallback(nil)
+ tc.Add(d)
+ })
+
+ <-st.gotC
+
+ // The log stream should sleep pending new logs. When it does, our timer
+ // callback will add the missing log (once).
+ <-st.gotC
+
+ // Mark the log stream as terminal @3.
+ ls.TerminalIndex = 3
+ require(ls.Put(ds.Get(c)))
+
+ // Add the missing logs.
+ st.text(2, "Line2\n", "Line3\n")
+ <-st.gotC
+ }()
+
+ txt, err := view(string(ls.Path()))
+ So(err, ShouldEqual, http.StatusOK)
+ So(txt, ShouldEqual, "Line0\nLine1\nLine2\nLine3\n")
+ })
+
+ Convey(`Will return InternalServerError if our storage returns an error.`, func() {
+ st.err(errors.New("test error"))
+ _, err := view(string(ls.Path()))
+ So(err, ShouldEqual, http.StatusInternalServerError)
+ })
+
+ Convey(`Will return InternalServerError if our storage returns junk log data.`, func() {
+ st.data(0, []byte{0x00})
+ _, err := view(string(ls.Path()))
+ So(err, ShouldEqual, http.StatusInternalServerError)
+ })
+ })
+}
+
+func require(err error) {
+ if err != nil {
+ panic(err)
+ }
+}
« no previous file with comments | « appengine/logdog/coordinator/logView/view.go ('k') | appengine/logdog/coordinator/service.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698