From 515bcdc536878ac703be9e63975cca4e8b5d2f36 Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 12 Nov 2013 14:58:47 -0800 Subject: [PATCH] godoc: update Index.{Write,Reader}, add tests R=golang-dev, adg CC=golang-dev https://golang.org/cl/24490043 --- godoc/index.go | 100 +++++++++++++++++++++++++++++++++++--------- godoc/index_test.go | 36 +++++++++++++++- 2 files changed, 115 insertions(+), 21 deletions(-) diff --git a/godoc/index.go b/godoc/index.go index c99b8e70..7371876e 100644 --- a/godoc/index.go +++ b/godoc/index.go @@ -880,11 +880,22 @@ func NewIndex(c *Corpus, dirnames <-chan string, fulltextIndex bool, throttle fl } } +var ErrFileIndexVersion = errors.New("file index version out of date") + +const fileIndexVersion = 2 + +// fileIndex is the subset of Index that's gob-encoded for use by +// Index.Write and Index.Read. type fileIndex struct { - Words map[string]*LookupResult - Alts map[string]*AltWords - Snippets []*Snippet - Fulltext bool + Version int + Words map[string]*LookupResult + Alts map[string]*AltWords + Snippets []*Snippet + Fulltext bool + Stats Statistics + ImportCount map[string]int + PackagePath map[string]map[string]bool + Exports map[string]map[string]SpotKind } func (x *fileIndex) Write(w io.Writer) error { @@ -895,63 +906,79 @@ func (x *fileIndex) Read(r io.Reader) error { return gob.NewDecoder(r).Decode(x) } -// Write writes the index x to w. -func (x *Index) Write(w io.Writer) error { +// WriteTo writes the index x to w. +func (x *Index) WriteTo(w io.Writer) (n int64, err error) { + w = countingWriter{&n, w} fulltext := false if x.suffixes != nil { fulltext = true } fx := fileIndex{ - x.words, - x.alts, - x.snippets, - fulltext, + Version: fileIndexVersion, + Words: x.words, + Alts: x.alts, + Snippets: x.snippets, + Fulltext: fulltext, + Stats: x.stats, + ImportCount: x.importCount, + PackagePath: x.packagePath, + Exports: x.exports, } if err := fx.Write(w); err != nil { - return err + return 0, err } if fulltext { encode := func(x interface{}) error { return gob.NewEncoder(w).Encode(x) } if err := x.fset.Write(encode); err != nil { - return err + return 0, err } if err := x.suffixes.Write(w); err != nil { - return err + return 0, err } } - return nil + return n, nil } // Read reads the index from r into x; x must not be nil. // If r does not also implement io.ByteReader, it will be wrapped in a bufio.Reader. -func (x *Index) Read(r io.Reader) error { +// If the index is from an old version, the error is ErrFileIndexVersion. +func (x *Index) ReadFrom(r io.Reader) (n int64, err error) { // We use the ability to read bytes as a plausible surrogate for buffering. if _, ok := r.(io.ByteReader); !ok { r = bufio.NewReader(r) } + r = countingReader{&n, r.(byteReader)} var fx fileIndex if err := fx.Read(r); err != nil { - return err + return n, err + } + if fx.Version != fileIndexVersion { + return 0, ErrFileIndexVersion } x.words = fx.Words x.alts = fx.Alts x.snippets = fx.Snippets + x.stats = fx.Stats + x.importCount = fx.ImportCount + x.packagePath = fx.PackagePath + x.exports = fx.Exports + if fx.Fulltext { x.fset = token.NewFileSet() decode := func(x interface{}) error { return gob.NewDecoder(r).Decode(x) } if err := x.fset.Read(decode); err != nil { - return err + return n, err } x.suffixes = new(suffixarray.Index) if err := x.suffixes.Read(r); err != nil { - return err + return n, err } } - return nil + return n, nil } // Stats returns index statistics. @@ -1204,7 +1231,7 @@ func (c *Corpus) readIndex(filenames string) error { files = append(files, f) } x := new(Index) - if err := x.Read(io.MultiReader(files...)); err != nil { + if _, err := x.ReadFrom(io.MultiReader(files...)); err != nil { return err } c.searchIndex.Set(x) @@ -1269,3 +1296,36 @@ func (c *Corpus) RunIndexer() { time.Sleep(delay) } } + +type countingWriter struct { + n *int64 + w io.Writer +} + +func (c countingWriter) Write(p []byte) (n int, err error) { + n, err = c.w.Write(p) + *c.n += int64(n) + return +} + +type byteReader interface { + io.Reader + io.ByteReader +} + +type countingReader struct { + n *int64 + r byteReader +} + +func (c countingReader) Read(p []byte) (n int, err error) { + n, err = c.r.Read(p) + *c.n += int64(n) + return +} + +func (c countingReader) ReadByte() (b byte, err error) { + b, err = c.r.ReadByte() + *c.n += 1 + return +} diff --git a/godoc/index_test.go b/godoc/index_test.go index 8a5a4737..c505e28f 100644 --- a/godoc/index_test.go +++ b/godoc/index_test.go @@ -5,6 +5,7 @@ package godoc import ( + "bytes" "reflect" "strings" "testing" @@ -12,7 +13,7 @@ import ( "code.google.com/p/go.tools/godoc/vfs/mapfs" ) -func TestIndex(t *testing.T) { +func newCorpus(t *testing.T) *Corpus { c := NewCorpus(mapfs.New(map[string]string{ "src/pkg/foo/foo.go": `// Package foo is an example. package foo @@ -46,13 +47,46 @@ func Skip() {} if err := c.Init(); err != nil { t.Fatal(err) } + return c +} + +func TestIndex(t *testing.T) { + c := newCorpus(t) c.UpdateIndex() ix, _ := c.CurrentIndex() if ix == nil { t.Fatal("no index") } t.Logf("Got: %#v", ix) + testIndex(t, ix) +} +func TestIndexWriteRead(t *testing.T) { + c := newCorpus(t) + c.UpdateIndex() + ix, _ := c.CurrentIndex() + if ix == nil { + t.Fatal("no index") + } + + var buf bytes.Buffer + nw, err := ix.WriteTo(&buf) + if err != nil { + t.Fatalf("Index.WriteTo: %v", err) + } + + ix2 := new(Index) + nr, err := ix2.ReadFrom(&buf) + if err != nil { + t.Fatalf("Index.ReadFrom: %v", err) + } + if nr != nw { + t.Errorf("Wrote %d bytes to index but read %d", nw, nr) + } + testIndex(t, ix2) +} + +func testIndex(t *testing.T, ix *Index) { wantStats := Statistics{Bytes: 256, Files: 3, Lines: 16, Words: 6, Spots: 9} if !reflect.DeepEqual(ix.Stats(), wantStats) { t.Errorf("Stats = %#v; want %#v", ix.Stats(), wantStats)