Addressing go vet / golint

This commit is contained in:
Stephen McQuay 2016-02-15 01:10:14 -08:00
parent c5c1dbc4a9
commit 9e9bb74c4a
5 changed files with 203 additions and 63 deletions

View File

@ -1,7 +1,9 @@
package vain package vain
import ( import (
"bytes"
"fmt" "fmt"
"io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"strings" "strings"
@ -20,8 +22,8 @@ func TestAdd(t *testing.T) {
t.Errorf("couldn't GET: %v", err) t.Errorf("couldn't GET: %v", err)
} }
resp.Body.Close() resp.Body.Close()
if len(s.storage.p) != 0 { if len(ms.p) != 0 {
t.Errorf("started with something in it; got %d, want %d", len(s.storage.p), 0) t.Errorf("started with something in it; got %d, want %d", len(ms.p), 0)
} }
bad := ts.URL bad := ts.URL
@ -30,8 +32,8 @@ func TestAdd(t *testing.T) {
t.Errorf("couldn't POST: %v", err) t.Errorf("couldn't POST: %v", err)
} }
resp.Body.Close() resp.Body.Close()
if len(s.storage.p) != 0 { if len(ms.p) != 0 {
t.Errorf("started with something in it; got %d, want %d", len(s.storage.p), 0) t.Errorf("started with something in it; got %d, want %d", len(ms.p), 0)
} }
good := fmt.Sprintf("%s/foo", ts.URL) good := fmt.Sprintf("%s/foo", ts.URL)
@ -40,16 +42,16 @@ func TestAdd(t *testing.T) {
t.Errorf("couldn't POST: %v", err) t.Errorf("couldn't POST: %v", err)
} }
if len(s.storage.p) != 1 { if len(ms.p) != 1 {
t.Errorf("storage should have something in it; got %d, want %d", len(s.storage.p), 1) t.Errorf("storage should have something in it; got %d, want %d", len(ms.p), 1)
} }
p, ok := s.storage.p[good] p, ok := ms.p[good]
if !ok { if !ok {
t.Fatalf("did not find package for %s; should have posted a valid package", good) t.Fatalf("did not find package for %s; should have posted a valid package", good)
} }
if p.Path != good { if p.path != good {
t.Errorf("package name did not go through as expected; got %q, want %q", p.Path, good) t.Errorf("package name did not go through as expected; got %q, want %q", p.path, good)
} }
if want := "https://s.mcquay.me/sm/vain"; p.Repo != want { if want := "https://s.mcquay.me/sm/vain"; p.Repo != want {
t.Errorf("repo did not go through as expected; got %q, want %q", p.Repo, want) t.Errorf("repo did not go through as expected; got %q, want %q", p.Repo, want)
@ -57,6 +59,22 @@ func TestAdd(t *testing.T) {
if want := Git; p.Vcs != want { if want := Git; p.Vcs != want {
t.Errorf("Vcs did not go through as expected; got %q, want %q", p.Vcs, want) t.Errorf("Vcs did not go through as expected; got %q, want %q", p.Vcs, want)
} }
resp, err = http.Get(ts.URL)
if err != nil {
t.Errorf("couldn't GET: %v", err)
}
defer resp.Body.Close()
if want := http.StatusOK; resp.StatusCode != want {
t.Errorf("Should have succeeded to fetch /; got %s, want %s", resp.Status, http.StatusText(want))
}
buf := &bytes.Buffer{}
if _, err := io.Copy(buf, resp.Body); err != nil {
t.Errorf("couldn't read content from server: %v", err)
}
if got, want := strings.Count(buf.String(), "meta"), 1; got != want {
t.Errorf("did not find all the tags I need; got %d, want %d", got, want)
}
} }
func TestInvalidPath(t *testing.T) { func TestInvalidPath(t *testing.T) {
@ -71,11 +89,11 @@ func TestInvalidPath(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("couldn't POST: %v", err) t.Errorf("couldn't POST: %v", err)
} }
if len(s.storage.p) != 0 { if len(ms.p) != 0 {
t.Errorf("should have failed to insert; got %d, want %d", len(s.storage.p), 0) t.Errorf("should have failed to insert; got %d, want %d", len(ms.p), 0)
} }
if resp.StatusCode != http.StatusBadRequest { if want := http.StatusBadRequest; resp.StatusCode != want {
t.Errorf("should have failed to post at bad route; got %s, want %s", resp.Status, http.StatusText(http.StatusBadRequest)) t.Errorf("should have failed to post at bad route; got %s, want %s", resp.Status, http.StatusText(want))
} }
} }
@ -131,3 +149,86 @@ func TestCannotAddExistingSubPath(t *testing.T) {
t.Errorf("initial post should have worked; got %s, want %s", resp.Status, http.StatusText(want)) t.Errorf("initial post should have worked; got %s, want %s", resp.Status, http.StatusText(want))
} }
} }
func TestMissingRepo(t *testing.T) {
ms := NewMemStore("")
s := &Server{
storage: ms,
}
ts := httptest.NewServer(s)
s.hostname = ts.URL
url := fmt.Sprintf("%s/foo", ts.URL)
resp, err := http.Post(url, "application/json", strings.NewReader(`{}`))
if err != nil {
t.Errorf("couldn't POST: %v", err)
}
if len(ms.p) != 0 {
t.Errorf("should have failed to insert; got %d, want %d", len(ms.p), 0)
}
if want := http.StatusBadRequest; resp.StatusCode != want {
t.Errorf("should have failed to post at bad route; got %s, want %s", resp.Status, http.StatusText(want))
}
}
func TestBadJson(t *testing.T) {
ms := NewMemStore("")
s := &Server{
storage: ms,
}
ts := httptest.NewServer(s)
s.hostname = ts.URL
url := fmt.Sprintf("%s/foo", ts.URL)
resp, err := http.Post(url, "application/json", strings.NewReader(`{`))
if err != nil {
t.Errorf("couldn't POST: %v", err)
}
if len(ms.p) != 0 {
t.Errorf("should have failed to insert; got %d, want %d", len(ms.p), 0)
}
if want := http.StatusBadRequest; resp.StatusCode != want {
t.Errorf("should have failed to post at bad route; got %s, want %s", resp.Status, http.StatusText(want))
}
}
func TestUnsupportedMethod(t *testing.T) {
ms := NewMemStore("")
s := &Server{
storage: ms,
}
ts := httptest.NewServer(s)
s.hostname = ts.URL
url := fmt.Sprintf("%s/foo", ts.URL)
client := &http.Client{}
req, err := http.NewRequest("PUT", url, nil)
resp, err := client.Do(req)
if err != nil {
t.Errorf("couldn't POST: %v", err)
}
if len(ms.p) != 0 {
t.Errorf("should have failed to insert; got %d, want %d", len(ms.p), 0)
}
if want := http.StatusMethodNotAllowed; resp.StatusCode != want {
t.Errorf("should have failed to post at bad route; got %s, want %s", resp.Status, http.StatusText(want))
}
}
func TestNewServer(t *testing.T) {
ms := NewMemStore("")
sm := http.NewServeMux()
s := NewServer(sm, ms, "foo")
ts := httptest.NewServer(s)
s.hostname = ts.URL
url := fmt.Sprintf("%s/foo", ts.URL)
client := &http.Client{}
req, err := http.NewRequest("PUT", url, nil)
resp, err := client.Do(req)
if err != nil {
t.Errorf("couldn't POST: %v", err)
}
if len(ms.p) != 0 {
t.Errorf("should have failed to insert; got %d, want %d", len(ms.p), 0)
}
if want := http.StatusMethodNotAllowed; resp.StatusCode != want {
t.Errorf("should have failed to post at bad route; got %s, want %s", resp.Status, http.StatusText(want))
}
}

View File

@ -7,9 +7,20 @@ import (
"strings" "strings"
) )
// NewServer populates a server, adds the routes, and returns it for use.
func NewServer(sm *http.ServeMux, store Storage, hostname string) *Server {
s := &Server{
storage: store,
hostname: hostname,
}
sm.Handle("/", s)
return s
}
// Server serves up the http.
type Server struct { type Server struct {
hostname string hostname string
storage *MemStore storage Storage
} }
func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) { func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
@ -27,11 +38,15 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
} }
p := Package{} p := Package{}
if err := json.NewDecoder(req.Body).Decode(&p); err != nil { if err := json.NewDecoder(req.Body).Decode(&p); err != nil {
http.Error(w, fmt.Sprintf("unable to parse json from body: %v", err), http.StatusInternalServerError) http.Error(w, fmt.Sprintf("unable to parse json from body: %v", err), http.StatusBadRequest)
return return
} }
p.Path = fmt.Sprintf("%s/%s", s.hostname, strings.Trim(req.URL.Path, "/")) if p.Repo == "" {
if !Valid(p.Path, s.storage.All()) { http.Error(w, fmt.Sprintf("invalid repository %q", req.URL.Path), http.StatusBadRequest)
return
}
p.path = fmt.Sprintf("%s/%s", s.hostname, strings.Trim(req.URL.Path, "/"))
if !Valid(p.path, s.storage.All()) {
http.Error(w, fmt.Sprintf("invalid path; prefix already taken %q", req.URL.Path), http.StatusConflict) http.Error(w, fmt.Sprintf("invalid path; prefix already taken %q", req.URL.Path), http.StatusConflict)
return return
} }
@ -39,23 +54,7 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
http.Error(w, fmt.Sprintf("unable to add package: %v", err), http.StatusInternalServerError) http.Error(w, fmt.Sprintf("unable to add package: %v", err), http.StatusInternalServerError)
return return
} }
if err := s.storage.Save(); err != nil {
http.Error(w, fmt.Sprintf("unable to store db: %v", err), http.StatusInternalServerError)
if err := s.storage.Remove(p.Path); err != nil {
fmt.Fprintf(w, "to add insult to injury, could not delete package: %v\n", err)
}
return
}
default: default:
http.Error(w, fmt.Sprintf("unsupported method %q; accepted: POST, GET", req.Method), http.StatusMethodNotAllowed) http.Error(w, fmt.Sprintf("unsupported method %q; accepted: POST, GET", req.Method), http.StatusMethodNotAllowed)
} }
} }
func NewServer(sm *http.ServeMux, ms *MemStore, hostname string) *Server {
s := &Server{
storage: ms,
hostname: hostname,
}
sm.Handle("/", s)
return s
}

View File

@ -2,21 +2,32 @@ package vain
import ( import (
"encoding/json" "encoding/json"
"errors"
"fmt"
"os" "os"
"strings" "strings"
"sync" "sync"
) )
// Valid checks that p will not confuse the go tool if added to packages.
func Valid(p string, packages []Package) bool { func Valid(p string, packages []Package) bool {
for _, pkg := range packages { for _, pkg := range packages {
if strings.HasPrefix(pkg.Path, p) { if strings.HasPrefix(pkg.path, p) {
return false return false
} }
} }
return true return true
} }
type MemStore struct { // Storage is a shim to allow for alternate storage types.
type Storage interface {
Add(p Package) error
Remove(path string) error
All() []Package
}
// SimpleStore implements a simple json on-disk storage.
type SimpleStore struct {
l sync.RWMutex l sync.RWMutex
p map[string]Package p map[string]Package
@ -24,28 +35,51 @@ type MemStore struct {
path string path string
} }
func NewMemStore(path string) *MemStore { // NewMemStore returns a ready-to-use SimpleStore storing json at path.
return &MemStore{ func NewMemStore(path string) *SimpleStore {
return &SimpleStore{
path: path, path: path,
p: make(map[string]Package), p: make(map[string]Package),
} }
} }
func (ms *MemStore) Add(p Package) error { // Add adds p to the SimpleStore.
func (ms *SimpleStore) Add(p Package) error {
ms.l.Lock() ms.l.Lock()
ms.p[p.Path] = p ms.p[p.path] = p
ms.l.Unlock() ms.l.Unlock()
m := ""
if err := ms.Save(); err != nil {
m = fmt.Sprintf("unable to store db: %v", err)
if err := ms.Remove(p.path); err != nil {
m = fmt.Sprintf("%s\nto add insult to injury, could not delete package: %v\n", m, err)
}
return errors.New(m)
}
return nil return nil
} }
func (ms *MemStore) Remove(path string) error { // Remove removes p from the SimpleStore.
func (ms *SimpleStore) Remove(path string) error {
ms.l.Lock() ms.l.Lock()
delete(ms.p, path) delete(ms.p, path)
ms.l.Unlock() ms.l.Unlock()
return nil return nil
} }
func (ms *MemStore) Save() error { // All returns all current packages.
func (ms *SimpleStore) All() []Package {
r := []Package{}
ms.l.RLock()
for _, p := range ms.p {
r = append(r, p)
}
ms.l.RUnlock()
return r
}
// Save writes the db to disk.
func (ms *SimpleStore) Save() error {
// running in-memory only // running in-memory only
if ms.path == "" { if ms.path == "" {
return nil return nil
@ -60,7 +94,8 @@ func (ms *MemStore) Save() error {
return json.NewEncoder(f).Encode(ms.p) return json.NewEncoder(f).Encode(ms.p)
} }
func (ms *MemStore) Load() error { // Load reads the db from disk and populates ms.
func (ms *SimpleStore) Load() error {
// running in-memory only // running in-memory only
if ms.path == "" { if ms.path == "" {
return nil return nil
@ -74,13 +109,3 @@ func (ms *MemStore) Load() error {
defer f.Close() defer f.Close()
return json.NewDecoder(f).Decode(&ms.p) return json.NewDecoder(f).Decode(&ms.p)
} }
func (ms *MemStore) All() []Package {
r := []Package{}
ms.l.RLock()
for _, p := range ms.p {
r = append(r, p)
}
ms.l.RUnlock()
return r
}

19
vain.go
View File

@ -1,3 +1,6 @@
// Package vain implements a vanity service for use by the the go tool.
//
// The executable, cmd/ysvd, is located in the respective subdirectory.
package vain package vain
import "fmt" import "fmt"
@ -5,7 +8,10 @@ import "fmt"
type vcs int type vcs int
const ( const (
// Git is the default Vcs.
Git vcs = iota Git vcs = iota
// Hg is mercurial
Hg Hg
) )
@ -23,16 +29,25 @@ var labelToVcs = map[string]vcs{
// String returns the name of the vcs ("git", "mercurial", ...). // String returns the name of the vcs ("git", "mercurial", ...).
func (v vcs) String() string { return vcss[v] } func (v vcs) String() string { return vcss[v] }
// Package stores the three pieces of information needed to create the meta
// tag. Two of these (Vcs and Repo) are stored explicitly, and the third is
// determined implicitly by the path POSTed to. For more information refer to
// the documentation for the go tool:
//
// https://golang.org/cmd/go/#hdr-Remote_import_paths
type Package struct { type Package struct {
//Vcs (version control system) supported: "git", "mercurial"
Vcs vcs `json:"vcs"` Vcs vcs `json:"vcs"`
Path string `json:"path"` // Repo: the remote repository url
Repo string `json:"repo"` Repo string `json:"repo"`
path string
} }
func (p Package) String() string { func (p Package) String() string {
return fmt.Sprintf( return fmt.Sprintf(
"<meta name=\"go-import\" content=\"%s %s %s\">", "<meta name=\"go-import\" content=\"%s %s %s\">",
p.Path, p.path,
p.Vcs, p.Vcs,
p.Repo, p.Repo,
) )

View File

@ -7,7 +7,7 @@ import (
func TestString(t *testing.T) { func TestString(t *testing.T) {
p := Package{ p := Package{
Path: "mcquay.me/bps", path: "mcquay.me/bps",
Repo: "https://s.mcquay.me/sm/bps", Repo: "https://s.mcquay.me/sm/bps",
} }
got := fmt.Sprintf("%s", p) got := fmt.Sprintf("%s", p)
@ -49,37 +49,37 @@ func TestValid(t *testing.T) {
}, },
{ {
pkgs: []Package{ pkgs: []Package{
{Path: ""}, {path: ""},
}, },
in: "bobo", in: "bobo",
want: true, want: true,
}, },
{ {
pkgs: []Package{ pkgs: []Package{
{Path: "bobo"}, {path: "bobo"},
}, },
in: "bobo", in: "bobo",
want: false, want: false,
}, },
{ {
pkgs: []Package{ pkgs: []Package{
{Path: "a/b/c"}, {path: "a/b/c"},
}, },
in: "a/b/c", in: "a/b/c",
want: false, want: false,
}, },
{ {
pkgs: []Package{ pkgs: []Package{
{Path: "foo/bar"}, {path: "foo/bar"},
{Path: "foo/baz"}, {path: "foo/baz"},
}, },
in: "foo", in: "foo",
want: false, want: false,
}, },
{ {
pkgs: []Package{ pkgs: []Package{
{Path: "bilbo"}, {path: "bilbo"},
{Path: "frodo"}, {path: "frodo"},
}, },
in: "foo/bar/baz", in: "foo/bar/baz",
want: true, want: true,