From c6db3974bc4b42085e7b8479e028a054eb757510 Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Thu, 21 Jun 2018 12:20:31 -0500 Subject: [PATCH 01/16] WIP API refactor --- api.go | 31 +++++++++++- cmd/server_test.go | 4 +- ctl/export_test.go | 2 + ctl/import_test.go | 5 ++ executor_test.go | 10 ++++ handler.go | 17 +++---- holder_test.go | 2 + http/client_test.go | 8 +++ http/handler.go | 38 ++++++++++++--- http/handler_test.go | 105 ++++++++++++++++++++++++++++++++-------- http/translator_test.go | 2 + server.go | 58 +++++----------------- server/server.go | 55 +++++++++++++-------- test/handler.go | 35 ++++++++------ 14 files changed, 252 insertions(+), 120 deletions(-) diff --git a/api.go b/api.go index 7adff1d1b..4fdb0de6f 100644 --- a/api.go +++ b/api.go @@ -46,16 +46,43 @@ type API struct { Cluster *Cluster TranslateStore TranslateStore Logger Logger + server *Server +} + +// APIOption is a functional option type for pilosa.API +type APIOption func(s *API) error + +func OptAPIServer(s *Server) APIOption { + return func(a *API) error { + a.server = s + a.Executor = s.executor + a.TranslateStore = s.translateFile + a.Holder = s.holder + a.Broadcaster = s + a.BroadcastHandler = s + a.StatusHandler = s + a.Cluster = s.Cluster + a.Logger = s.logger + return nil + } } // NewAPI returns a new API instance. -func NewAPI() *API { - return &API{ +func NewAPI(opts ...APIOption) (*API, error) { + api := &API{ Broadcaster: NopBroadcaster, //BroadcastHandler: NopBroadcastHandler, // TODO: implement the nop //StatusHandler: NopStatusHandler, // TODO: implement the nop Logger: NopLogger, } + + for _, opt := range opts { + err := opt(api) + if err != nil { + return nil, errors.Wrap(err, "applying option") + } + } + return api, nil } // validAPIMethods specifies the api methods that are valid for each diff --git a/cmd/server_test.go b/cmd/server_test.go index abbe8d7a4..989c8de6b 100644 --- a/cmd/server_test.go +++ b/cmd/server_test.go @@ -15,7 +15,6 @@ package cmd_test import ( - "errors" "io/ioutil" "strings" "testing" @@ -24,6 +23,7 @@ import ( "github.com/pilosa/pilosa/cmd" _ "github.com/pilosa/pilosa/test" "github.com/pilosa/pilosa/toml" + "github.com/pkg/errors" ) func TestServerHelp(t *testing.T) { @@ -35,6 +35,8 @@ func TestServerHelp(t *testing.T) { } func TestServerConfig(t *testing.T) { + t.Skip() // Until test.NewServer() works + actualDataDir, err := ioutil.TempDir("", "") failErr(t, err, "making data dir") logFile, err := ioutil.TempFile("", "") diff --git a/ctl/export_test.go b/ctl/export_test.go index 5e87334d9..d405c92c4 100644 --- a/ctl/export_test.go +++ b/ctl/export_test.go @@ -44,6 +44,8 @@ func TestExportCommand_Validation(t *testing.T) { } func TestExportCommand_Run(t *testing.T) { + t.Skip() // Until test.NewServer() works + buf := bytes.Buffer{} stdin, stdout, stderr := GetIO(buf) cm := NewExportCommand(stdin, stdout, stderr) diff --git a/ctl/import_test.go b/ctl/import_test.go index 2284ca873..df2b9a5a6 100644 --- a/ctl/import_test.go +++ b/ctl/import_test.go @@ -29,6 +29,8 @@ import ( ) func TestImportCommand_Validation(t *testing.T) { + t.Skip() // Until test.NewServer() works + buf := bytes.Buffer{} stdin, stdout, stderr := GetIO(buf) cm := NewImportCommand(stdin, stdout, stderr) @@ -51,6 +53,7 @@ func TestImportCommand_Validation(t *testing.T) { } func TestImportCommand_Run(t *testing.T) { + t.Skip() // Until test.NewServer() works buf := bytes.Buffer{} stdin, stdout, stderr := GetIO(buf) @@ -84,6 +87,7 @@ func TestImportCommand_Run(t *testing.T) { // Ensure that the ImportValue path runs. func TestImportCommand_RunValue(t *testing.T) { + t.Skip() // Until test.NewServer() works buf := bytes.Buffer{} stdin, stdout, stderr := GetIO(buf) @@ -118,6 +122,7 @@ func TestImportCommand_RunValue(t *testing.T) { } func TestImportCommand_InvalidFile(t *testing.T) { + t.Skip() // Until test.NewServer() works hldr := test.MustOpenHolder() defer hldr.Close() diff --git a/executor_test.go b/executor_test.go index 164133ecb..f50c139f1 100644 --- a/executor_test.go +++ b/executor_test.go @@ -1026,6 +1026,8 @@ func TestExecutor_Execute_Range(t *testing.T) { // Ensure a remote query can return a row. func TestExecutor_Execute_Remote_Row(t *testing.T) { + t.Skip() // Until test.NewServer() works + c := pilosa.NewTestCluster(2) // Create secondary server and update second cluster node. @@ -1074,6 +1076,8 @@ func TestExecutor_Execute_Remote_Row(t *testing.T) { // Ensure a remote query can return a count. func TestExecutor_Execute_Remote_Count(t *testing.T) { + t.Skip() // Until test.NewServer() works + c := pilosa.NewTestCluster(2) // Create secondary server and update second cluster node. @@ -1109,6 +1113,8 @@ func TestExecutor_Execute_Remote_Count(t *testing.T) { // Ensure a remote query can set columns on multiple nodes. func TestExecutor_Execute_Remote_SetBit(t *testing.T) { + t.Skip() // Until test.NewServer() works + c := pilosa.NewTestCluster(2) c.ReplicaN = 2 @@ -1161,6 +1167,8 @@ func TestExecutor_Execute_Remote_SetBit(t *testing.T) { // Ensure a remote query can set columns on multiple nodes. func TestExecutor_Execute_Remote_SetBit_With_Timestamp(t *testing.T) { + t.Skip() // Until test.NewServer() works + c := pilosa.NewTestCluster(2) c.ReplicaN = 2 @@ -1215,6 +1223,8 @@ func TestExecutor_Execute_Remote_SetBit_With_Timestamp(t *testing.T) { // Ensure a remote query can return a top-n query. func TestExecutor_Execute_Remote_TopN(t *testing.T) { + t.Skip() // Until test.NewServer() works + c := pilosa.NewTestCluster(2) // Create secondary server and update second cluster node. diff --git a/handler.go b/handler.go index 7c2b76a3f..c9a476e13 100644 --- a/handler.go +++ b/handler.go @@ -2,7 +2,6 @@ package pilosa import ( "encoding/json" - "net" ) // QueryRequest represent a request to process a query. @@ -61,18 +60,18 @@ func (resp *QueryResponse) MarshalJSON() ([]byte, error) { } type Handler interface { - Serve(ln net.Listener, closing <-chan struct{}) - GetAPI() *API + Serve() error + Close() error } -type NopHandler struct{} +type nopHandler struct{} -func (n *NopHandler) Serve(ln net.Listener, closing <-chan struct{}) {} - -func (n *NopHandler) GetAPI() *API { +func (n nopHandler) Serve() error { return nil } -func NewNopHandler() Handler { - return &NopHandler{} +func (n nopHandler) Close() error { + return nil } + +var NopHandler Handler = nopHandler{} diff --git a/holder_test.go b/holder_test.go index c70ffcca6..9b3e21c12 100644 --- a/holder_test.go +++ b/holder_test.go @@ -350,6 +350,8 @@ func TestHolder_DeleteIndex(t *testing.T) { // Ensure holder can sync with a remote holder. func TestHolderSyncer_SyncHolder(t *testing.T) { + t.Skip() // Until test.NewServer() works + s := test.NewServer() defer s.Close() diff --git a/http/client_test.go b/http/client_test.go index 185a3c378..46f9ce619 100644 --- a/http/client_test.go +++ b/http/client_test.go @@ -52,6 +52,8 @@ func init() { // Test distributed TopN Row count across 3 nodes. func TestClient_MultiNode(t *testing.T) { + t.Skip() // Until test.NewServer() works + cluster := test.NewCluster(3) s, hldr := createCluster(cluster) @@ -217,6 +219,8 @@ func TestClient_MultiNode(t *testing.T) { // Ensure client can bulk import data. func TestClient_Import(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -251,6 +255,8 @@ func TestClient_Import(t *testing.T) { // Ensure client can bulk import value data. func TestClient_ImportValue(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -328,6 +334,8 @@ func TestClient_ImportValue(t *testing.T) { // Ensure client can retrieve a list of all checksums for blocks in a fragment. func TestClient_FragmentBlocks(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() diff --git a/http/handler.go b/http/handler.go index 674381905..9b3d17789 100644 --- a/http/handler.go +++ b/http/handler.go @@ -15,6 +15,7 @@ package http import ( + "context" "crypto/tls" "encoding/json" "expvar" @@ -53,6 +54,10 @@ type Handler struct { API *pilosa.API AllowedOrigins []string + + ln net.Listener + + server *http.Server } // externalPrefixFlag denotes endpoints that are intended to be exposed to clients. @@ -99,6 +104,13 @@ func OptHandlerLogger(logger pilosa.Logger) HandlerOption { } } +func OptHandlerListener(ln net.Listener) HandlerOption { + return func(h *Handler) error { + h.ln = ln + return nil + } +} + // NewHandler returns a new instance of Handler with a default logger. func NewHandler(opts ...HandlerOption) (*Handler, error) { handler := &Handler{ @@ -114,19 +126,31 @@ func NewHandler(opts ...HandlerOption) (*Handler, error) { } } + if handler.API == nil { + return nil, errors.New("must pass OptHandlerAPI") + } + + if handler.ln == nil { + return nil, errors.New("must pass OptHandlerListener") + } + return handler, nil } -func (h *Handler) Serve(ln net.Listener, closing <-chan struct{}) { - server := &http.Server{Handler: h} - go func() { - <-closing - server.Close() - }() - err := server.Serve(ln) +func (h *Handler) Serve() error { + h.server = &http.Server{Handler: h} + err := h.server.Serve(h.ln) if err != nil && err.Error() != "http: Server closed" { h.Logger.Printf("HTTP handler terminated with error: %s\n", err) + return errors.Wrap(err, "serve http") } + return nil +} + +func (h *Handler) Close() error { + // TODO: timeout? + err := h.server.Shutdown(context.Background()) + return errors.Wrap(err, "shutdown http server") } func (h *Handler) populateValidators() { diff --git a/http/handler_test.go b/http/handler_test.go index ddedef49a..f249750c2 100644 --- a/http/handler_test.go +++ b/http/handler_test.go @@ -17,50 +17,54 @@ package http_test import ( "bytes" "context" - "errors" "fmt" "io" "io/ioutil" - gohttp "net/http" + "net" "net/http/httptest" "reflect" "strings" "testing" + gohttp "net/http" + "github.com/gogo/protobuf/proto" "github.com/pilosa/pilosa" "github.com/pilosa/pilosa/http" "github.com/pilosa/pilosa/internal" "github.com/pilosa/pilosa/pql" "github.com/pilosa/pilosa/test" + "github.com/pkg/errors" ) -func TestHandlerPanics(t *testing.T) { - h := test.MustNewHandler() - bufLogger := test.NewBufferLogger() - h.Handler.Logger = bufLogger - - w := httptest.NewRecorder() - // will panic since Handler has no Holder set up - h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/index/taxi", nil)) - bufbytes, err := bufLogger.ReadAll() +func TestHandlerOptions(t *testing.T) { + _, err := http.NewHandler() + if err == nil { + t.Fatalf("expected error making handler without options, got nil") + } + _, err = http.NewHandler(http.OptHandlerAPI(&pilosa.API{})) + if err == nil { + t.Fatalf("expected error making handler without options, got nil") + } + ln, err := net.Listen("tcp", ":0") if err != nil { - t.Fatalf("reading all logoutput: %v", err) + t.Fatal(err) } - if !bytes.Contains(bufbytes, []byte("PANIC: runtime error: invalid memory address or nil pointer dereference")) { - t.Fatalf("expected panic in log, but got: %s", bufbytes) - } - if w.Code != gohttp.StatusInternalServerError { - t.Fatalf("expected internal server error, but got: %v", w.Code) - } - bodyBytes := w.Body.Bytes() - if !bytes.Contains(bodyBytes, []byte("PANIC: runtime error: invalid memory address or nil pointer dereference")) { - t.Fatalf("response to client should have panic, but got %s", bodyBytes) + _, err = http.NewHandler(http.OptHandlerListener(ln)) + if err == nil { + t.Fatalf("expected error making handler without options, got nil") } } +func TestHandler_Endpoints(t *testing.T) { + mains := test.MustRunMainWithCluster(t, 1) + _ = mains[0] +} + // Ensure the handler returns "not found" for invalid paths. func TestHandler_NotFound(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -77,6 +81,8 @@ func TestHandler_NotFound(t *testing.T) { // Ensure the handler can return the schema. func TestHandler_Schema(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -112,6 +118,8 @@ func TestHandler_Schema(t *testing.T) { // Ensure the handler can return the status. func TestHandler_Status(t *testing.T) { + t.Skip() // Until test.NewServer() works + s := test.NewServer() hldr := test.MustOpenHolder() defer s.Close() @@ -151,6 +159,8 @@ func TestHandler_Status(t *testing.T) { } func TestHandler_Info(t *testing.T) { + t.Skip() // Until test.NewServer() works + s := test.NewServer() defer s.Close() h := test.MustNewHandler() @@ -166,6 +176,7 @@ func TestHandler_Info(t *testing.T) { // Ensure the handler can abort a cluster resize. func TestHandler_ClusterResizeAbort(t *testing.T) { + t.Skip() // Until test.NewServer() works t.Run("No resize job", func(t *testing.T) { h := test.MustNewHandler() @@ -186,6 +197,8 @@ func TestHandler_ClusterResizeAbort(t *testing.T) { // Ensure the handler can return the maxslice map. func TestHandler_MaxSlices(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -211,6 +224,8 @@ func TestHandler_MaxSlices(t *testing.T) { // Ensure the handler can accept URL arguments. func TestHandler_Query_Args_URL(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -239,6 +254,8 @@ func TestHandler_Query_Args_URL(t *testing.T) { // Ensure the handler can accept arguments via protobufs. func TestHandler_Query_Args_Protobuf(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -278,6 +295,8 @@ func TestHandler_Query_Args_Protobuf(t *testing.T) { // Ensure the handler returns an error when parsing bad arguments. func TestHandler_Query_Args_Err(t *testing.T) { + t.Skip() // Until test.NewServer() works + w := httptest.NewRecorder() hldr := test.MustOpenHolder() defer hldr.Close() @@ -294,6 +313,8 @@ func TestHandler_Query_Args_Err(t *testing.T) { } } func TestHandler_Query_Params_Err(t *testing.T) { + t.Skip() // Until test.NewServer() works + w := httptest.NewRecorder() test.MustNewHandler().ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/idx0/query?slices=0,1&db=sample", strings.NewReader("Bitmap(id=100)"))) if w.Code != gohttp.StatusBadRequest { @@ -306,6 +327,8 @@ func TestHandler_Query_Params_Err(t *testing.T) { // Ensure the handler can execute a query with a uint64 response as JSON. func TestHandler_Query_Uint64_JSON(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -327,6 +350,8 @@ func TestHandler_Query_Uint64_JSON(t *testing.T) { // Ensure the handler can execute a query with a uint64 response as protobufs. func TestHandler_Query_Uint64_Protobuf(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -357,6 +382,8 @@ func TestHandler_Query_Uint64_Protobuf(t *testing.T) { // Ensure the handler can execute a query that returns a bitmap as JSON. func TestHandler_Query_Bitmap_JSON(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -380,6 +407,8 @@ func TestHandler_Query_Bitmap_JSON(t *testing.T) { // Ensure the handler can execute a query that returns a row with column attributes as JSON. func TestHandler_Query_Row_ColumnAttrs_JSON(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.NewHolder() defer hldr.Close() @@ -413,6 +442,8 @@ func TestHandler_Query_Row_ColumnAttrs_JSON(t *testing.T) { // Ensure the handler can execute a query that returns a row as protobuf. func TestHandler_Query_Row_Protobuf(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -453,6 +484,8 @@ func TestHandler_Query_Row_Protobuf(t *testing.T) { // Ensure the handler can execute a query that returns a row with column attributes as protobuf. func TestHandler_Query_Row_ColumnAttrs_Protobuf(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.NewHolder() defer hldr.Close() @@ -522,6 +555,8 @@ func TestHandler_Query_Row_ColumnAttrs_Protobuf(t *testing.T) { // Ensure the handler can execute a query that returns pairs as JSON. func TestHandler_Query_Pairs_JSON(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -546,6 +581,8 @@ func TestHandler_Query_Pairs_JSON(t *testing.T) { // Ensure the handler can execute a query that returns pairs as protobuf. func TestHandler_Query_Pairs_Protobuf(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -579,6 +616,8 @@ func TestHandler_Query_Pairs_Protobuf(t *testing.T) { // Ensure the handler can return an error as JSON. func TestHandler_Query_Err_JSON(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -600,6 +639,8 @@ func TestHandler_Query_Err_JSON(t *testing.T) { // Ensure the handler can return an error as protobuf. func TestHandler_Query_Err_Protobuf(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -628,6 +669,8 @@ func TestHandler_Query_Err_Protobuf(t *testing.T) { // Ensure the handler returns "method not allowed" for non-POST queries. func TestHandler_Query_MethodNotAllowed(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -643,6 +686,8 @@ func TestHandler_Query_MethodNotAllowed(t *testing.T) { // Ensure the handler returns an error if there is a parsing error.. func TestHandler_Query_ErrParse(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -660,6 +705,8 @@ func TestHandler_Query_ErrParse(t *testing.T) { // Ensure the handler can delete an index. func TestHandler_Index_Delete(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -696,6 +743,8 @@ func TestHandler_Index_Delete(t *testing.T) { // Ensure handler can delete a field. func TestHandler_DeleteField(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() i0 := hldr.MustCreateIndexIfNotExists("i0", pilosa.IndexOptions{}) @@ -719,6 +768,8 @@ func TestHandler_DeleteField(t *testing.T) { // Ensure the handler can return data in differing blocks for an index. func TestHandler_Index_AttrStore_Diff(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -774,6 +825,8 @@ func TestHandler_Index_AttrStore_Diff(t *testing.T) { // Ensure the handler can return data in differing blocks for a field. func TestHandler_Field_AttrStore_Diff(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -830,6 +883,8 @@ func TestHandler_Field_AttrStore_Diff(t *testing.T) { // Ensure the handler can retrieve the version. func TestHandler_Version(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -853,6 +908,8 @@ func TestHandler_Version(t *testing.T) { // Ensure the handler can return a list of nodes for a fragment. func TestHandler_Fragment_Nodes(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -889,6 +946,8 @@ func TestHandler_Fragment_Nodes(t *testing.T) { // Ensure the handler can return expvars without panicking. func TestHandler_Expvars(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -912,6 +971,8 @@ func MustReadAll(r io.Reader) []byte { } func TestHandler_RecalculateCaches(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() @@ -928,6 +989,8 @@ func TestHandler_RecalculateCaches(t *testing.T) { } func TestHandler_CORS(t *testing.T) { + t.Skip() // Until test.NewServer() works + hldr := test.MustOpenHolder() defer hldr.Close() diff --git a/http/translator_test.go b/http/translator_test.go index 3378ddc58..8bedf22cd 100644 --- a/http/translator_test.go +++ b/http/translator_test.go @@ -15,6 +15,8 @@ import ( ) func TestTranslateStore_Reader(t *testing.T) { + t.Skip() // Until test.NewServer() works + // Ensure client can connect and stream the translate store data. t.Run("OK", func(t *testing.T) { t.Run("ServerDisconnect", func(t *testing.T) { diff --git a/server.go b/server.go index b6c4e7996..c575d5a2c 100644 --- a/server.go +++ b/server.go @@ -61,12 +61,10 @@ type Server struct { clusterDisabled bool // External - handler Handler BroadcastReceiver BroadcastReceiver systemInfo SystemInfo gcNotifier GCNotifier logger Logger - ln net.Listener NodeID string URI URI @@ -126,13 +124,6 @@ func OptServerLongQueryTime(dur time.Duration) ServerOption { } } -func OptServerHandler(h Handler) ServerOption { - return func(s *Server) error { - s.handler = h - return nil - } -} - func OptServerMaxWritesPerRequest(n int) ServerOption { return func(s *Server) error { s.maxWritesPerRequest = n @@ -191,14 +182,6 @@ func OptServerDiagnosticsInterval(dur time.Duration) ServerOption { } } -func OptServerListener(ln net.Listener) ServerOption { - return func(s *Server) error { - s.ln = ln - - return nil - } -} - func OptServerURI(uri *URI) ServerOption { return func(s *Server) error { s.URI = *uri @@ -264,11 +247,6 @@ func NewServer(opts ...ServerOption) (*Server, error) { return nil, err } - // update URI port with actual listener port. TODO this should probably be done outside of here. - if s.URI.Port() == 0 { - s.URI.SetPort(uint16(s.ln.Addr().(*net.TCPAddr).Port)) - } - // Get or create NodeID. s.NodeID = s.LoadNodeID() // Set Cluster Node. @@ -293,8 +271,6 @@ func NewServer(opts ...ServerOption) (*Server, error) { s.executor.Cluster = s.Cluster s.executor.TranslateStore = s.translateFile s.executor.MaxWritesPerRequest = s.maxWritesPerRequest - s.handler.GetAPI().Executor = s.executor - s.handler.GetAPI().TranslateStore = s.translateFile return s, nil } @@ -302,9 +278,6 @@ func NewServer(opts ...ServerOption) (*Server, error) { // Open opens and initializes the server. func (s *Server) Open() error { s.logger.Printf("open server") - if s.ln == nil { - return errors.New("must pass a listener option to NewServer") - } // Log startup err := s.holder.logStartup() @@ -316,20 +289,9 @@ func (s *Server) Open() error { s.Cluster.Broadcaster = s s.Cluster.MaxWritesPerRequest = s.maxWritesPerRequest - // Initialize HTTP handler. - api := s.handler.GetAPI() - api.Holder = s.holder - api.Broadcaster = s - api.BroadcastHandler = s - api.StatusHandler = s - api.Cluster = s.Cluster - // Initialize Holder. s.holder.Broadcaster = s - // Serve handler. - go s.handler.Serve(s.ln, s.closing) - // Start the BroadcastReceiver. if err := s.BroadcastReceiver.Start(s); err != nil { return fmt.Errorf("starting BroadcastReceiver: %v", err) @@ -370,9 +332,6 @@ func (s *Server) Close() error { close(s.closing) s.wg.Wait() - if s.ln != nil { - s.ln.Close() - } if s.Cluster != nil { s.Cluster.close() } @@ -400,12 +359,21 @@ func (s *Server) LoadNodeID() string { return nodeID } +type pilosaAddr URI + +func (p pilosaAddr) String() string { + uri := URI(p) + return uri.HostPort() + +} + +func (pilosaAddr) Network() string { + return "tcp" +} + // Addr returns the address of the listener. func (s *Server) Addr() net.Addr { - if s.ln == nil { - return nil - } - return s.ln.Addr() + return pilosaAddr(s.URI) } func (s *Server) monitorAntiEntropy() { diff --git a/server/server.go b/server/server.go index d97e36520..e61ba6fad 100644 --- a/server/server.go +++ b/server/server.go @@ -73,6 +73,9 @@ type Command struct { // Passed to the Gossip implementation. logOutput io.Writer logger loggerLogger + + handler pilosa.Handler + ln net.Listener } // NewCommand returns a new instance of Main. @@ -108,6 +111,13 @@ func (m *Command) Start() (err error) { return errors.Wrap(err, "opening server") } + go func() { + err := m.handler.Serve() + if err != nil { + m.logger.Printf("Handler serve error: %v", err) + } + }() + m.logger.Printf("Listening as %s\n", m.Server.URI) return nil @@ -164,18 +174,6 @@ func (m *Command) SetupServer() error { } m.logger.Printf("%s %s, build time %s\n", productName, pilosa.Version, pilosa.BuildTime) - api := pilosa.NewAPI() - api.Logger = m.logger - - handler, err := http.NewHandler( - http.OptHandlerAllowedOrigins(m.Config.Handler.AllowedOrigins), - http.OptHandlerAPI(api), - http.OptHandlerLogger(m.logger), - ) - if err != nil { - return errors.Wrap(err, "wrapping handler") - } - uri, err := pilosa.AddressWithDefaults(m.Config.Bind) if err != nil { return errors.Wrap(err, "processing bind address") @@ -210,11 +208,16 @@ func (m *Command) SetupServer() error { return errors.Wrap(err, "new stats client") } - ln, err := getListener(*uri, TLSConfig) + m.ln, err = getListener(*uri, TLSConfig) if err != nil { return errors.Wrap(err, "getting listener") } + // If port is 0, get auto-allocated port from listener + if uri.Port() == 0 { + uri.SetPort(uint16(m.ln.Addr().(*net.TCPAddr).Port)) + } + c := http.GetHTTPClient(TLSConfig) // Setup connection to primary store if this is a replica. @@ -234,17 +237,30 @@ func (m *Command) SetupServer() error { pilosa.OptServerLogger(m.logger), pilosa.OptServerAttrStoreFunc(boltdb.NewAttrStore), - pilosa.OptServerHandler(handler), pilosa.OptServerSystemInfo(gopsutil.NewSystemInfo()), pilosa.OptServerGCNotifier(gcnotify.NewActiveGCNotifier()), pilosa.OptServerStatsClient(statsClient), - pilosa.OptServerListener(ln), pilosa.OptServerURI(uri), pilosa.OptServerInternalClient(http.NewInternalClientFromURI(uri, c)), pilosa.OptServerPrimaryTranslateStore(primaryTranslateStore), pilosa.OptServerClusterDisabled(m.Config.Cluster.Disabled, m.Config.Cluster.Hosts), ) + api, err := pilosa.NewAPI(pilosa.OptAPIServer(m.Server)) + if err != nil { + return errors.Wrap(err, "new api") + } + + m.handler, err = http.NewHandler( + http.OptHandlerAllowedOrigins(m.Config.Handler.AllowedOrigins), + http.OptHandlerAPI(api), + http.OptHandlerLogger(m.logger), + http.OptHandlerListener(m.ln), + ) + if err != nil { + return errors.Wrap(err, "new handler") + } + return errors.Wrap(err, "new server") } @@ -300,17 +316,16 @@ func (m *Command) SetupNetworking() error { // Close shuts down the server. func (m *Command) Close() error { var logErr error + handlerErr := m.handler.Close() serveErr := m.Server.Close() if closer, ok := m.logOutput.(io.Closer); ok { logErr = closer.Close() } close(m.done) - if serveErr != nil && logErr != nil { - return fmt.Errorf("closing server: '%v', closing logs: '%v'", serveErr, logErr) - } else if logErr != nil { - return logErr + if serveErr != nil || logErr != nil || handlerErr != nil { + return fmt.Errorf("closing server: '%v', closing logs: '%v', closing handler: '%v'", serveErr, logErr, handlerErr) } - return serveErr + return nil } // NewStatsClient creates a stats client from the config diff --git a/test/handler.go b/test/handler.go index 5256413b5..8b58d5a6e 100644 --- a/test/handler.go +++ b/test/handler.go @@ -45,7 +45,11 @@ func NewHandler(opts ...http.HandlerOption) (*Handler, error) { h := &Handler{ Handler: handler, } - h.API = pilosa.NewAPI() + + //h.API, err = pilosa.NewAPI(OptAPIServer(s)) + if err != nil { + return nil, err + } h.Handler.API = h.API h.Handler.API.Executor = &h.Executor @@ -84,22 +88,23 @@ type Server struct { // NewServer returns a test server running on a random port. func NewServer() *Server { - handler, err := NewHandler() - if err != nil { - panic(err) - } - s := &Server{ - Handler: handler, - } - s.Server = httptest.NewServer(s.Handler.Handler) + return &Server{} + //handler, err := pilosa.NewHandler() + //if err != nil { + // panic(err) + //} + //s := &Server{ + // Handler: handler, + //} + //s.Server = httptest.NewServer(s.Handler.Handler) - // Handler test messages can no-op. - s.Handler.API.Broadcaster = pilosa.NopBroadcaster - // Create a default cluster on the handler - s.Handler.API.Cluster = NewCluster(1) - s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() + //// Handler test messages can no-op. + //s.Handler.API.Broadcaster = pilosa.NopBroadcaster + //// Create a default cluster on the handler + //s.Handler.API.Cluster = NewCluster(1) + //s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() - return s + //return s } // LocalStatus exists so that test.Server implements StatusHandler. From c9479afe8c02ec7c5bed293799b04f86b7e5c224 Mon Sep 17 00:00:00 2001 From: Matt Jaffee Date: Thu, 21 Jun 2018 13:56:46 -0500 Subject: [PATCH 02/16] start handler before server.Open to avoid stall in cluster.open cluster.open waits for node to join cluster if it is not the coordinator, and currently this relies on having the http handler able to receive messages, so handler needs to be started first. --- cluster.go | 2 +- server/server.go | 27 +++++++++++++-------------- server_test.go | 4 ++-- stats_test.go | 4 ++++ test/pilosa.go | 8 ++++++++ 5 files changed, 28 insertions(+), 17 deletions(-) diff --git a/cluster.go b/cluster.go index 0f998507e..2883b739e 100644 --- a/cluster.go +++ b/cluster.go @@ -914,7 +914,7 @@ func (c *Cluster) open() error { return fmt.Errorf("sending restart NodeJoin: %v", err) } - c.Logger.Printf("wait for joining to complete") + c.Logger.Printf("%v wait for joining to complete", c.Node.ID) <-c.joining c.Logger.Printf("joining has completed") } diff --git a/server/server.go b/server/server.go index e61ba6fad..ef9f46ca3 100644 --- a/server/server.go +++ b/server/server.go @@ -74,7 +74,7 @@ type Command struct { logOutput io.Writer logger loggerLogger - handler pilosa.Handler + Handler pilosa.Handler ln net.Listener } @@ -105,19 +105,18 @@ func (m *Command) Start() (err error) { if err != nil { return errors.Wrap(err, "setting up networking") } + go func() { + err := m.Handler.Serve() + if err != nil { + m.logger.Printf("Handler serve error: %v", err) + } + }() // Initialize server. if err = m.Server.Open(); err != nil { return errors.Wrap(err, "opening server") } - go func() { - err := m.handler.Serve() - if err != nil { - m.logger.Printf("Handler serve error: %v", err) - } - }() - m.logger.Printf("Listening as %s\n", m.Server.URI) return nil @@ -245,23 +244,23 @@ func (m *Command) SetupServer() error { pilosa.OptServerPrimaryTranslateStore(primaryTranslateStore), pilosa.OptServerClusterDisabled(m.Config.Cluster.Disabled, m.Config.Cluster.Hosts), ) + if err != nil { + return errors.Wrap(err, "new server") + } api, err := pilosa.NewAPI(pilosa.OptAPIServer(m.Server)) if err != nil { return errors.Wrap(err, "new api") } - m.handler, err = http.NewHandler( + m.Handler, err = http.NewHandler( http.OptHandlerAllowedOrigins(m.Config.Handler.AllowedOrigins), http.OptHandlerAPI(api), http.OptHandlerLogger(m.logger), http.OptHandlerListener(m.ln), ) - if err != nil { - return errors.Wrap(err, "new handler") - } + return errors.Wrap(err, "new handler") - return errors.Wrap(err, "new server") } // SetupNetworking sets up internode communication based on the configuration. @@ -316,7 +315,7 @@ func (m *Command) SetupNetworking() error { // Close shuts down the server. func (m *Command) Close() error { var logErr error - handlerErr := m.handler.Close() + handlerErr := m.Handler.Close() serveErr := m.Server.Close() if closer, ok := m.logOutput.(io.Closer); ok { logErr = closer.Close() diff --git a/server_test.go b/server_test.go index 2f1003592..402d4de7d 100644 --- a/server_test.go +++ b/server_test.go @@ -27,7 +27,7 @@ import ( // pilosa.Server was not having its remoteClient field set by an option and so // it was using a nil client in monitorAntiEntropy. func TestMonitorAntiEntropy(t *testing.T) { - cluster := test.MustRunMainWithCluster(t, 3, test.OptAntiEntropyInterval(time.Millisecond*1)) + cluster := test.MustRunMainWithCluster(t, 3, test.OptAntiEntropyInterval(time.Millisecond*20)) client := cluster[1].Client() err := client.CreateIndex(context.Background(), "balh", pilosa.IndexOptions{}) if err != nil { @@ -38,7 +38,7 @@ func TestMonitorAntiEntropy(t *testing.T) { t.Fatalf("creating field: %v", err) } - time.Sleep(time.Millisecond * 2) + time.Sleep(time.Millisecond * 40) for _, m := range cluster { err := m.Close() if err != nil { diff --git a/stats_test.go b/stats_test.go index 6644786cf..1f23dd3f8 100644 --- a/stats_test.go +++ b/stats_test.go @@ -208,6 +208,7 @@ func TestStatsCount_SetProfileAttrs(t *testing.T) { } func TestStatsCount_CreateIndex(t *testing.T) { + t.Skip() hldr := test.MustOpenHolder() defer hldr.Close() s := test.NewServer() @@ -230,6 +231,7 @@ func TestStatsCount_CreateIndex(t *testing.T) { } func TestStatsCount_DeleteIndex(t *testing.T) { + t.Skip() hldr := test.MustOpenHolder() defer hldr.Close() @@ -258,6 +260,7 @@ func TestStatsCount_DeleteIndex(t *testing.T) { } func TestStatsCount_CreateField(t *testing.T) { + t.Skip() hldr := test.MustOpenHolder() defer hldr.Close() @@ -289,6 +292,7 @@ func TestStatsCount_CreateField(t *testing.T) { } func TestStatsCount_DeleteField(t *testing.T) { + t.Skip() hldr := test.MustOpenHolder() defer hldr.Close() diff --git a/test/pilosa.go b/test/pilosa.go index 757e5a96c..22e89a5df 100644 --- a/test/pilosa.go +++ b/test/pilosa.go @@ -19,6 +19,7 @@ import ( "fmt" "io" "io/ioutil" + "log" gohttp "net/http" "os" "strings" @@ -221,6 +222,13 @@ func (m *Main) RunWithTransport(host string, bindPort int, joinSeeds []string) ( m.Server.Cluster.Static = false + go func() { + err := m.Handler.Serve() + if err != nil { + log.Printf("Handler serve error: %v", err) + } + }() + // Initialize server. err = m.Server.Open() if err != nil { From a0337714120769cdd520962c119db4bca9931a91 Mon Sep 17 00:00:00 2001 From: Matt Jaffee Date: Thu, 21 Jun 2018 14:05:00 -0500 Subject: [PATCH 03/16] move handler tests which are actually testing everything to server package --- http/handler_test.go | 987 --------------------------------------- server/handler_test.go | 1009 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1009 insertions(+), 987 deletions(-) create mode 100644 server/handler_test.go diff --git a/http/handler_test.go b/http/handler_test.go index f249750c2..49d24ffec 100644 --- a/http/handler_test.go +++ b/http/handler_test.go @@ -15,26 +15,11 @@ package http_test import ( - "bytes" - "context" - "fmt" - "io" - "io/ioutil" "net" - "net/http/httptest" - "reflect" - "strings" "testing" - gohttp "net/http" - - "github.com/gogo/protobuf/proto" "github.com/pilosa/pilosa" "github.com/pilosa/pilosa/http" - "github.com/pilosa/pilosa/internal" - "github.com/pilosa/pilosa/pql" - "github.com/pilosa/pilosa/test" - "github.com/pkg/errors" ) func TestHandlerOptions(t *testing.T) { @@ -55,975 +40,3 @@ func TestHandlerOptions(t *testing.T) { t.Fatalf("expected error making handler without options, got nil") } } - -func TestHandler_Endpoints(t *testing.T) { - mains := test.MustRunMainWithCluster(t, 1) - _ = mains[0] -} - -// Ensure the handler returns "not found" for invalid paths. -func TestHandler_NotFound(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/no_such_path", nil)) - if w.Code != gohttp.StatusNotFound { - t.Fatalf("invalid status: %d", w.Code) - } -} - -// Ensure the handler can return the schema. -func TestHandler_Schema(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - i0 := hldr.MustCreateIndexIfNotExists("i0", pilosa.IndexOptions{}) - i1 := hldr.MustCreateIndexIfNotExists("i1", pilosa.IndexOptions{}) - - if f, err := i0.CreateFieldIfNotExists("f1", pilosa.FieldOptions{}); err != nil { - t.Fatal(err) - } else if _, err := f.SetBit(0, 0, nil); err != nil { - t.Fatal(err) - } - if f, err := i1.CreateFieldIfNotExists("f0", pilosa.FieldOptions{}); err != nil { - t.Fatal(err) - } else if _, err := f.SetBit(0, 0, nil); err != nil { - t.Fatal(err) - } - if _, err := i0.CreateFieldIfNotExists("f0", pilosa.FieldOptions{}); err != nil { - t.Fatal(err) - } - - h := test.MustNewHandler() - h.API.Holder = hldr.Holder - h.API.Cluster = test.NewCluster(1) - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/schema", nil)) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"indexes":[{"name":"i0","fields":[{"name":"f0"},{"name":"f1","views":[{"name":"standard"}]}]},{"name":"i1","fields":[{"name":"f0","views":[{"name":"standard"}]}]}]}`+"\n" { - } else if body := w.Body.String(); body != `{"indexes":[{"name":"i0","fields":[{"name":"f0","options":{"cacheType":"ranked","cacheSize":50000}},{"name":"f1","options":{"cacheType":"ranked","cacheSize":50000},"views":[{"name":"standard"}]}]},{"name":"i1","fields":[{"name":"f0","options":{"cacheType":"ranked","cacheSize":50000},"views":[{"name":"standard"}]}]}]}`+"\n" { - t.Fatalf("unexpected body: %s", body) - } -} - -// Ensure the handler can return the status. -func TestHandler_Status(t *testing.T) { - t.Skip() // Until test.NewServer() works - - s := test.NewServer() - hldr := test.MustOpenHolder() - defer s.Close() - defer hldr.Close() - - i0 := hldr.MustCreateIndexIfNotExists("i0", pilosa.IndexOptions{}) - i1 := hldr.MustCreateIndexIfNotExists("i1", pilosa.IndexOptions{}) - - if f, err := i0.CreateFieldIfNotExists("f1", pilosa.FieldOptions{}); err != nil { - t.Fatal(err) - } else if _, err := f.SetBit(0, 0, nil); err != nil { - t.Fatal(err) - } - if f, err := i1.CreateFieldIfNotExists("f0", pilosa.FieldOptions{}); err != nil { - t.Fatal(err) - } else if _, err := f.SetBit(0, 0, nil); err != nil { - t.Fatal(err) - } - if _, err := i0.CreateFieldIfNotExists("f0", pilosa.FieldOptions{}); err != nil { - t.Fatal(err) - } - - h := test.MustNewHandler() - h.API.Holder = hldr.Holder - h.API.Cluster = test.NewCluster(1) - h.API.Cluster.SetState(pilosa.ClusterStateNormal) - h.API.StatusHandler = s - s.Handler = h - - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/status", nil)) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"state":"NORMAL","nodes":[{"id":"node0","uri":{"scheme":"http","host":"host0"},"isCoordinator":false}],"localID":"node0"}`+"\n" { - t.Fatalf("unexpected body: %s", body) - } -} - -func TestHandler_Info(t *testing.T) { - t.Skip() // Until test.NewServer() works - - s := test.NewServer() - defer s.Close() - h := test.MustNewHandler() - - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/info", nil)) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != fmt.Sprintf("{\"sliceWidth\":%d}\n", pilosa.SliceWidth) { - t.Fatalf("unexpected body: %s", body) - } -} - -// Ensure the handler can abort a cluster resize. -func TestHandler_ClusterResizeAbort(t *testing.T) { - t.Skip() // Until test.NewServer() works - - t.Run("No resize job", func(t *testing.T) { - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Cluster.SetState(pilosa.ClusterStateResizing) - - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/cluster/resize/abort", nil)) - if w.Code != gohttp.StatusOK { - bod, err := ioutil.ReadAll(w.Body) - t.Fatalf("unexpected status code: %d, bod: %s, readerr: %v", w.Code, bod, err) - } else if body := w.Body.String(); body != `{"info":"complete current job: no resize job currently running"}`+"\n" { - t.Fatalf("unexpected body: %s", body) - } - }) - -} - -// Ensure the handler can return the maxslice map. -func TestHandler_MaxSlices(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - hldr.SetBit("i0", "f0", 30, (1*pilosa.SliceWidth)+1) - hldr.SetBit("i0", "f0", 30, (1*pilosa.SliceWidth)+2) - hldr.SetBit("i0", "f0", 30, (3*pilosa.SliceWidth)+4) - - hldr.SetBit("i1", "f1", 40, (0*pilosa.SliceWidth)+1) - hldr.SetBit("i1", "f1", 40, (0*pilosa.SliceWidth)+2) - hldr.SetBit("i1", "f1", 40, (0*pilosa.SliceWidth)+8) - - h := test.MustNewHandler() - h.API.Holder = hldr.Holder - h.API.Cluster = test.NewCluster(1) - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/slices/max", nil)) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"standard":{"i0":3,"i1":0}}`+"\n" { - t.Fatalf("unexpected body: %s", body) - } -} - -// Ensure the handler can accept URL arguments. -func TestHandler_Query_Args_URL(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - if index != "idx0" { - t.Fatalf("unexpected index: %s", index) - } else if query.String() != `Count(Bitmap(id=100))` { - t.Fatalf("unexpected query: %s", query.String()) - } else if !reflect.DeepEqual(slices, []uint64{0, 1}) { - t.Fatalf("unexpected slices: %+v", slices) - } - return []interface{}{uint64(100)}, nil - } - - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/idx0/query?slices=0,1", strings.NewReader("Count( Bitmap( id=100))"))) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d %s", w.Code, w.Body.String()) - } else if body := w.Body.String(); body != `{"results":[100]}`+"\n" { - t.Fatalf("unexpected body: %q", body) - } -} - -// Ensure the handler can accept arguments via protobufs. -func TestHandler_Query_Args_Protobuf(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - if index != "idx0" { - t.Fatalf("unexpected index: %s", index) - } else if query.String() != `Count(Bitmap(id=100))` { - t.Fatalf("unexpected query: %s", query.String()) - } else if !reflect.DeepEqual(slices, []uint64{0, 1}) { - t.Fatalf("unexpected slices: %+v", slices) - } - return []interface{}{uint64(100)}, nil - } - - // Generate request body. - reqBody, err := proto.Marshal(&internal.QueryRequest{ - Query: "Count(Bitmap(id=100))", - Slices: []uint64{0, 1}, - }) - if err != nil { - t.Fatal(err) - } - - // Generate protobuf request. - req := test.MustNewHTTPRequest("POST", "/index/idx0/query", bytes.NewReader(reqBody)) - req.Header.Set("Content-Type", "application/x-protobuf") - - w := httptest.NewRecorder() - h.ServeHTTP(w, req) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } -} - -// Ensure the handler returns an error when parsing bad arguments. -func TestHandler_Query_Args_Err(t *testing.T) { - t.Skip() // Until test.NewServer() works - - w := httptest.NewRecorder() - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/idx0/query?slices=a,b", strings.NewReader("Bitmap(id=100)"))) - if w.Code != gohttp.StatusBadRequest { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"error":"invalid slice argument"}`+"\n" { - t.Fatalf("unexpected body: %q", body) - } -} -func TestHandler_Query_Params_Err(t *testing.T) { - t.Skip() // Until test.NewServer() works - - w := httptest.NewRecorder() - test.MustNewHandler().ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/idx0/query?slices=0,1&db=sample", strings.NewReader("Bitmap(id=100)"))) - if w.Code != gohttp.StatusBadRequest { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"error":"db is not a valid argument"}`+"\n" { - t.Fatalf("unexpected body: %q", body) - } - -} - -// Ensure the handler can execute a query with a uint64 response as JSON. -func TestHandler_Query_Uint64_JSON(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - return []interface{}{uint64(100)}, nil - } - - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/idx0/query?slices=0,1", strings.NewReader("Count( Bitmap( id=100))"))) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"results":[100]}`+"\n" { - t.Fatalf("unexpected body: %q", body) - } -} - -// Ensure the handler can execute a query with a uint64 response as protobufs. -func TestHandler_Query_Uint64_Protobuf(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - return []interface{}{uint64(100)}, nil - } - - w := httptest.NewRecorder() - r := test.MustNewHTTPRequest("POST", "/index/i/query", strings.NewReader("Count(Bitmap(id=100))")) - r.Header.Set("Accept", "application/x-protobuf") - h.ServeHTTP(w, r) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } - - var resp internal.QueryResponse - if err := proto.Unmarshal(w.Body.Bytes(), &resp); err != nil { - t.Fatal(err) - } else if rt := resp.Results[0].Type; rt != http.QueryResultTypeUint64 { - t.Fatalf("unexpected response type: %d", resp.Results[0].Type) - } else if n := resp.Results[0].N; n != 100 { - t.Fatalf("unexpected n: %d", n) - } -} - -// Ensure the handler can execute a query that returns a bitmap as JSON. -func TestHandler_Query_Bitmap_JSON(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - r := pilosa.NewRow(1, 3, 66, pilosa.SliceWidth+1) - r.Attrs = map[string]interface{}{"a": "b", "c": 1, "d": true} - return []interface{}{r}, nil - } - - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i/query", strings.NewReader("Bitmap(id=100)"))) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"results":[{"attrs":{"a":"b","c":1,"d":true},"columns":[1,3,66,1048577]}]}`+"\n" { - t.Fatalf("unexpected body: %s", body) - } -} - -// Ensure the handler can execute a query that returns a row with column attributes as JSON. -func TestHandler_Query_Row_ColumnAttrs_JSON(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.NewHolder() - defer hldr.Close() - - // Create index and set column attributes. - index, err := hldr.CreateIndexIfNotExists("i", pilosa.IndexOptions{}) - if err != nil { - t.Fatal(err) - } else if err := index.ColumnAttrStore().SetAttrs(3, map[string]interface{}{"x": "y"}); err != nil { - t.Fatal(err) - } else if err := index.ColumnAttrStore().SetAttrs(66, map[string]interface{}{"y": 123, "z": false}); err != nil { - t.Fatal(err) - } - - h := test.MustNewHandler() - h.API.Holder = hldr.Holder - h.API.Cluster = test.NewCluster(1) - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - r := pilosa.NewRow(1, 3, 66, pilosa.SliceWidth+1) - r.Attrs = map[string]interface{}{"a": "b", "c": 1, "d": true} - return []interface{}{r}, nil - } - - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i/query?columnAttrs=true", strings.NewReader("Bitmap(id=100)"))) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"results":[{"attrs":{"a":"b","c":1,"d":true},"columns":[1,3,66,1048577]}],"columnAttrs":[{"id":3,"attrs":{"x":"y"}},{"id":66,"attrs":{"y":123,"z":false}}]}`+"\n" { - t.Fatalf("unexpected body: %s", body) - } -} - -// Ensure the handler can execute a query that returns a row as protobuf. -func TestHandler_Query_Row_Protobuf(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - r := pilosa.NewRow(1, pilosa.SliceWidth+1) - r.Attrs = map[string]interface{}{"a": "b", "c": int64(1), "d": true} - return []interface{}{r}, nil - } - - w := httptest.NewRecorder() - r := test.MustNewHTTPRequest("POST", "/index/i/query", strings.NewReader("Bitmap(id=100)")) - r.Header.Set("Accept", "application/x-protobuf") - h.ServeHTTP(w, r) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } - - var resp internal.QueryResponse - if err := proto.Unmarshal(w.Body.Bytes(), &resp); err != nil { - t.Fatal(err) - } else if rt := resp.Results[0].Type; rt != http.QueryResultTypeRow { - t.Fatalf("unexpected response type: %d", resp.Results[0].Type) - } else if columns := resp.Results[0].Row.Columns; !reflect.DeepEqual(columns, []uint64{1, pilosa.SliceWidth + 1}) { - t.Fatalf("unexpected columns: %+v", columns) - } else if attrs := resp.Results[0].Row.Attrs; len(attrs) != 3 { - t.Fatalf("unexpected attr length: %d", len(attrs)) - } else if k, v := attrs[0].Key, attrs[0].StringValue; k != "a" || v != "b" { - t.Fatalf("unexpected attr[0]: %s=%v", k, v) - } else if k, v := attrs[1].Key, attrs[1].IntValue; k != "c" || v != int64(1) { - t.Fatalf("unexpected attr[1]: %s=%v", k, v) - } else if k, v := attrs[2].Key, attrs[2].BoolValue; k != "d" || !v { - t.Fatalf("unexpected attr[2]: %s=%v", k, v) - } -} - -// Ensure the handler can execute a query that returns a row with column attributes as protobuf. -func TestHandler_Query_Row_ColumnAttrs_Protobuf(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.NewHolder() - defer hldr.Close() - - // Create index and set column attributes. - index, err := hldr.CreateIndexIfNotExists("i", pilosa.IndexOptions{}) - if err != nil { - t.Fatal(err) - } else if err := index.ColumnAttrStore().SetAttrs(1, map[string]interface{}{"x": "y"}); err != nil { - t.Fatal(err) - } - - h := test.MustNewHandler() - h.API.Holder = hldr.Holder - h.API.Cluster = test.NewCluster(1) - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - r := pilosa.NewRow(1, pilosa.SliceWidth+1) - r.Attrs = map[string]interface{}{"a": "b", "c": int64(1), "d": true} - return []interface{}{r}, nil - } - - // Encode request body. - buf, err := proto.Marshal(&internal.QueryRequest{ - Query: "Bitmap(id=100)", - ColumnAttrs: true, - }) - if err != nil { - t.Fatal(err) - } - - w := httptest.NewRecorder() - r := test.MustNewHTTPRequest("POST", "/index/i/query", bytes.NewReader(buf)) - r.Header.Set("Content-Type", "application/x-protobuf") - r.Header.Set("Accept", "application/x-protobuf") - h.ServeHTTP(w, r) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } - - var resp internal.QueryResponse - if err := proto.Unmarshal(w.Body.Bytes(), &resp); err != nil { - t.Fatal(err) - } - if columns := resp.Results[0].Row.Columns; !reflect.DeepEqual(columns, []uint64{1, pilosa.SliceWidth + 1}) { - t.Fatalf("unexpected columns: %+v", columns) - } else if rt := resp.Results[0].Type; rt != http.QueryResultTypeRow { - t.Fatalf("unexpected response type: %d", resp.Results[0].Type) - } else if attrs := resp.Results[0].Row.Attrs; len(attrs) != 3 { - t.Fatalf("unexpected attr length: %d", len(attrs)) - } else if k, v := attrs[0].Key, attrs[0].StringValue; k != "a" || v != "b" { - t.Fatalf("unexpected attr[0]: %s=%v", k, v) - } else if k, v := attrs[1].Key, attrs[1].IntValue; k != "c" || v != int64(1) { - t.Fatalf("unexpected attr[1]: %s=%v", k, v) - } else if k, v := attrs[2].Key, attrs[2].BoolValue; k != "d" || !v { - t.Fatalf("unexpected attr[2]: %s=%v", k, v) - } - - if a := resp.ColumnAttrSets; len(a) != 1 { - t.Fatalf("unexpected column attributes length: %d", len(a)) - } else if a[0].ID != 1 { - t.Fatalf("unexpected id: %d", a[0].ID) - } else if len(a[0].Attrs) != 1 { - t.Fatalf("unexpected column attr length: %d", len(a)) - } else if k, v := a[0].Attrs[0].Key, a[0].Attrs[0].StringValue; k != "x" || v != "y" { - t.Fatalf("unexpected attr[0]: %s=%v", k, v) - } -} - -// Ensure the handler can execute a query that returns pairs as JSON. -func TestHandler_Query_Pairs_JSON(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - return []interface{}{[]pilosa.Pair{ - {ID: 1, Count: 2}, - {ID: 3, Count: 4}, - }}, nil - } - - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i/query", strings.NewReader(`TopN(field=x, n=2)`))) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"results":[[{"id":1,"count":2},{"id":3,"count":4}]]}`+"\n" { - t.Fatalf("unexpected body: %q", body) - } -} - -// Ensure the handler can execute a query that returns pairs as protobuf. -func TestHandler_Query_Pairs_Protobuf(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - return []interface{}{[]pilosa.Pair{ - {ID: 1, Count: 2}, - {ID: 3, Count: 4}, - }}, nil - } - - w := httptest.NewRecorder() - r := test.MustNewHTTPRequest("POST", "/index/i/query", strings.NewReader(`TopN(field=x, n=2)`)) - r.Header.Set("Accept", "application/x-protobuf") - h.ServeHTTP(w, r) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } - - var resp internal.QueryResponse - if err := proto.Unmarshal(w.Body.Bytes(), &resp); err != nil { - t.Fatal(err) - } else if rt := resp.Results[0].Type; rt != http.QueryResultTypePairs { - t.Fatalf("unexpected response type: %d", resp.Results[0].Type) - } else if a := resp.Results[0].GetPairs(); len(a) != 2 { - t.Fatalf("unexpected pair length: %d", len(a)) - } -} - -// Ensure the handler can return an error as JSON. -func TestHandler_Query_Err_JSON(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - return nil, errors.New("marker") - } - - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i/query", strings.NewReader(`Bitmap(id=100)`))) - if w.Code != gohttp.StatusBadRequest { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"error":"executing: marker"}`+"\n" { - t.Fatalf("unexpected body: %q", body) - } -} - -// Ensure the handler can return an error as protobuf. -func TestHandler_Query_Err_Protobuf(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - return nil, errors.New("marker") - } - - w := httptest.NewRecorder() - r := test.MustNewHTTPRequest("POST", "/index/i/query", strings.NewReader(`TopN(field=x, n=2)`)) - r.Header.Set("Accept", "application/x-protobuf") - h.ServeHTTP(w, r) - if w.Code != gohttp.StatusBadRequest { - t.Fatalf("unexpected status code: %d", w.Code) - } - - var resp internal.QueryResponse - if err := proto.Unmarshal(w.Body.Bytes(), &resp); err != nil { - t.Fatal(err) - } else if s := resp.Err; s != `executing: marker` { - t.Fatalf("unexpected error: %s", s) - } -} - -// Ensure the handler returns "method not allowed" for non-POST queries. -func TestHandler_Query_MethodNotAllowed(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/index/i/query", nil)) - if w.Code != gohttp.StatusMethodNotAllowed { - t.Fatalf("invalid status: %d", w.Code) - } -} - -// Ensure the handler returns an error if there is a parsing error.. -func TestHandler_Query_ErrParse(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/idx0/query?slices=0,1", strings.NewReader("bad_fn("))) - if w.Code != gohttp.StatusBadRequest { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"error":"parsing: expected comma, right paren, or identifier, found \"\" occurred at line 1, char 8"}`+"\n" { - t.Fatalf("unexpected body: %s", body) - } -} - -// Ensure the handler can delete an index. -func TestHandler_Index_Delete(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - s := test.NewServer() - s.Handler.API.Holder = hldr.Holder - defer s.Close() - - // Create index. - if _, err := hldr.CreateIndexIfNotExists("i", pilosa.IndexOptions{}); err != nil { - t.Fatal(err) - } - - // Send request to delete index. - resp, err := gohttp.DefaultClient.Do(test.MustNewHTTPRequest("DELETE", s.URL+"/index/i", strings.NewReader(""))) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - - // Verify body response. - if resp.StatusCode != gohttp.StatusOK { - t.Fatalf("unexpected status: %d", resp.StatusCode) - } else if buf, err := ioutil.ReadAll(resp.Body); err != nil { - t.Fatal(err) - } else if string(buf) != "{}\n" { - t.Fatalf("unexpected response body: %s", buf) - } - - // Verify index is gone. - if hldr.Index("i") != nil { - t.Fatal("expected nil index") - } -} - -// Ensure handler can delete a field. -func TestHandler_DeleteField(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - i0 := hldr.MustCreateIndexIfNotExists("i0", pilosa.IndexOptions{}) - if _, err := i0.CreateFieldIfNotExists("f1", pilosa.FieldOptions{}); err != nil { - t.Fatal(err) - } - - h := test.MustNewHandler() - h.API.Holder = hldr.Holder - h.API.Cluster = test.NewCluster(1) - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("DELETE", "/index/i0/field/f1", strings.NewReader(""))) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{}`+"\n" { - t.Fatalf("unexpected body: %s", body) - } else if f := hldr.Index("i0").Field("f1"); f != nil { - t.Fatal("expected nil field") - } -} - -// Ensure the handler can return data in differing blocks for an index. -func TestHandler_Index_AttrStore_Diff(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - s := test.NewServer() - s.Handler.API.Holder = hldr.Holder - defer s.Close() - - // Set attributes on the index. - index, err := hldr.CreateIndexIfNotExists("i", pilosa.IndexOptions{}) - if err != nil { - t.Fatal(err) - } - if err := index.ColumnAttrStore().SetAttrs(1, map[string]interface{}{"foo": 1, "bar": 2}); err != nil { - t.Fatal(err) - } else if err := index.ColumnAttrStore().SetAttrs(100, map[string]interface{}{"x": "y"}); err != nil { - t.Fatal(err) - } else if err := index.ColumnAttrStore().SetAttrs(200, map[string]interface{}{"snowman": "☃"}); err != nil { - t.Fatal(err) - } - - // Retrieve block checksums. - blks, err := index.ColumnAttrStore().Blocks() - if err != nil { - t.Fatal(err) - } - - // Remove block #0 and alter block 2's checksum. - blks = blks[1:] - blks[1].Checksum = []byte("MISMATCHED_CHECKSUM") - - // Send block checksums to determine diff. - req, err := gohttp.NewRequest( - "POST", - s.URL+"/index/i/attr/diff", - strings.NewReader(`{"blocks":`+string(test.MustMarshalJSON(blks))+`}`), - ) - - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Accept", "application/json") - - client := &gohttp.Client{} - resp, err := client.Do(req) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - - // Read and validate body. - if body := string(test.MustReadAll(resp.Body)); body != `{"attrs":{"1":{"bar":2,"foo":1},"200":{"snowman":"☃"}}}`+"\n" { - t.Fatalf("unexpected body: %s", body) - } -} - -// Ensure the handler can return data in differing blocks for a field. -func TestHandler_Field_AttrStore_Diff(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - s := test.NewServer() - s.Handler.API.Holder = hldr.Holder - defer s.Close() - - // Set attributes on the index. - idx := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{}) - f, err := idx.CreateFieldIfNotExists("meta", pilosa.FieldOptions{}) - if err != nil { - t.Fatal(err) - } - if err := f.RowAttrStore().SetAttrs(1, map[string]interface{}{"foo": 1, "bar": 2}); err != nil { - t.Fatal(err) - } else if err := f.RowAttrStore().SetAttrs(100, map[string]interface{}{"x": "y"}); err != nil { - t.Fatal(err) - } else if err := f.RowAttrStore().SetAttrs(200, map[string]interface{}{"snowman": "☃"}); err != nil { - t.Fatal(err) - } - - // Retrieve block checksums. - blks, err := f.RowAttrStore().Blocks() - if err != nil { - t.Fatal(err) - } - - // Remove block #0 and alter block 2's checksum. - blks = blks[1:] - blks[1].Checksum = []byte("MISMATCHED_CHECKSUM") - - // Send block checksums to determine diff. - req, err := gohttp.NewRequest( - "POST", - s.URL+"/index/i/field/meta/attr/diff", - strings.NewReader(`{"blocks":`+string(test.MustMarshalJSON(blks))+`}`), - ) - - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Accept", "application/json") - - client := &gohttp.Client{} - resp, err := client.Do(req) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - - // Read and validate body. - if body := string(test.MustReadAll(resp.Body)); body != `{"attrs":{"1":{"bar":2,"foo":1},"200":{"snowman":"☃"}}}`+"\n" { - t.Fatalf("unexpected body: %s", body) - } -} - -// Ensure the handler can retrieve the version. -func TestHandler_Version(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - - w := httptest.NewRecorder() - r := test.MustNewHTTPRequest("GET", "/version", nil) - h.ServeHTTP(w, r) - version := pilosa.Version - if strings.HasPrefix(version, "v") { - version = version[1:] - } - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } else if w.Body.String() != `{"version":"`+version+`"}`+"\n" { - t.Fatalf("unexpected body: %q", w.Body.String()) - } -} - -// Ensure the handler can return a list of nodes for a fragment. -func TestHandler_Fragment_Nodes(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Holder = hldr.Holder - h.API.Cluster = test.NewCluster(3) - h.API.Cluster.ReplicaN = 2 - - w := httptest.NewRecorder() - r := test.MustNewHTTPRequest("GET", "/fragment/nodes?index=X&slice=0", nil) - h.ServeHTTP(w, r) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `[{"id":"node2","uri":{"scheme":"http","host":"host2"},"isCoordinator":false},{"id":"node0","uri":{"scheme":"http","host":"host0"},"isCoordinator":false}]`+"\n" { - t.Fatalf("unexpected body: %q", body) - } - - // invalid argument should return BadRequest - w = httptest.NewRecorder() - r = test.MustNewHTTPRequest("GET", "/fragment/nodes?db=X&slice=0", nil) - h.ServeHTTP(w, r) - if w.Code != gohttp.StatusBadRequest { - t.Fatalf("unexpected status code: %d", w.Code) - } - - // index is required - w = httptest.NewRecorder() - r = test.MustNewHTTPRequest("GET", "/fragment/nodes?slice=0", nil) - h.ServeHTTP(w, r) - if w.Code != gohttp.StatusBadRequest { - t.Fatalf("unexpected status code: %d", w.Code) - } -} - -// Ensure the handler can return expvars without panicking. -func TestHandler_Expvars(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - w := httptest.NewRecorder() - r := test.MustNewHTTPRequest("GET", "/debug/vars", nil) - h.ServeHTTP(w, r) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } -} - -func MustReadAll(r io.Reader) []byte { - buf, err := ioutil.ReadAll(r) - if err != nil { - panic(err) - } - return buf -} - -func TestHandler_RecalculateCaches(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Holder = hldr.Holder - h.API.Cluster = test.NewCluster(1) - - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/recalculate-caches", nil)) - if w.Code != gohttp.StatusNoContent { - t.Fatalf("unexpected status code: %d", w.Code) - } - -} - -func TestHandler_CORS(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - s := test.NewServer() - s.Handler.API.Holder = hldr.Holder - defer s.Close() - - // No CORS config present, so should fail - handler := test.MustNewHandler() - - req := test.MustNewHTTPRequest("OPTIONS", "/index/foo/query", nil) - req.Header.Add("Origin", "http://test/") - req.Header.Add("Access-Control-Request-Method", "POST") - - w := httptest.NewRecorder() - handler.ServeHTTP(w, req) - result := w.Result() - - // This handler does not support CORS, return Method Not Allowed (405) - if result.StatusCode != 405 { - t.Fatalf("CORS preflight status should be 405, but is %v", result.StatusCode) - } - - // CORS config should allow preflight response - handler = test.MustNewHandler(http.OptHandlerAllowedOrigins([]string{"http://test/"})) - w = httptest.NewRecorder() - handler.ServeHTTP(w, req) - result = w.Result() - - if result.StatusCode != 200 { - t.Fatalf("CORS preflight status should be 200, but is %v", result.StatusCode) - } - if w.HeaderMap["Access-Control-Allow-Origin"][0] != "http://test/" { - t.Fatal("CORS header not present") - } -} diff --git a/server/handler_test.go b/server/handler_test.go new file mode 100644 index 000000000..fb4048f65 --- /dev/null +++ b/server/handler_test.go @@ -0,0 +1,1009 @@ +// Copyright 2017 Pilosa Corp. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server_test + +import ( + "bytes" + "context" + "fmt" + "io" + "io/ioutil" + "net/http/httptest" + "reflect" + "strings" + "testing" + + gohttp "net/http" + + "github.com/gogo/protobuf/proto" + "github.com/pilosa/pilosa" + "github.com/pilosa/pilosa/http" + "github.com/pilosa/pilosa/internal" + "github.com/pilosa/pilosa/pql" + "github.com/pilosa/pilosa/test" + "github.com/pkg/errors" +) + +func TestHandler_Endpoints(t *testing.T) { + mains := test.MustRunMainWithCluster(t, 1) + _ = mains[0] +} + +// Ensure the handler returns "not found" for invalid paths. +func TestHandler_NotFound(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + + h := test.MustNewHandler() + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/no_such_path", nil)) + if w.Code != gohttp.StatusNotFound { + t.Fatalf("invalid status: %d", w.Code) + } +} + +// Ensure the handler can return the schema. +func TestHandler_Schema(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + + i0 := hldr.MustCreateIndexIfNotExists("i0", pilosa.IndexOptions{}) + i1 := hldr.MustCreateIndexIfNotExists("i1", pilosa.IndexOptions{}) + + if f, err := i0.CreateFieldIfNotExists("f1", pilosa.FieldOptions{}); err != nil { + t.Fatal(err) + } else if _, err := f.SetBit(0, 0, nil); err != nil { + t.Fatal(err) + } + if f, err := i1.CreateFieldIfNotExists("f0", pilosa.FieldOptions{}); err != nil { + t.Fatal(err) + } else if _, err := f.SetBit(0, 0, nil); err != nil { + t.Fatal(err) + } + if _, err := i0.CreateFieldIfNotExists("f0", pilosa.FieldOptions{}); err != nil { + t.Fatal(err) + } + + h := test.MustNewHandler() + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/schema", nil)) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `{"indexes":[{"name":"i0","fields":[{"name":"f0"},{"name":"f1","views":[{"name":"standard"}]}]},{"name":"i1","fields":[{"name":"f0","views":[{"name":"standard"}]}]}]}`+"\n" { + } else if body := w.Body.String(); body != `{"indexes":[{"name":"i0","fields":[{"name":"f0","options":{"cacheType":"ranked","cacheSize":50000}},{"name":"f1","options":{"cacheType":"ranked","cacheSize":50000},"views":[{"name":"standard"}]}]},{"name":"i1","fields":[{"name":"f0","options":{"cacheType":"ranked","cacheSize":50000},"views":[{"name":"standard"}]}]}]}`+"\n" { + t.Fatalf("unexpected body: %s", body) + } +} + +// Ensure the handler can return the status. +func TestHandler_Status(t *testing.T) { + t.Skip() // Until test.NewServer() works + + s := test.NewServer() + hldr := test.MustOpenHolder() + defer s.Close() + defer hldr.Close() + + i0 := hldr.MustCreateIndexIfNotExists("i0", pilosa.IndexOptions{}) + i1 := hldr.MustCreateIndexIfNotExists("i1", pilosa.IndexOptions{}) + + if f, err := i0.CreateFieldIfNotExists("f1", pilosa.FieldOptions{}); err != nil { + t.Fatal(err) + } else if _, err := f.SetBit(0, 0, nil); err != nil { + t.Fatal(err) + } + if f, err := i1.CreateFieldIfNotExists("f0", pilosa.FieldOptions{}); err != nil { + t.Fatal(err) + } else if _, err := f.SetBit(0, 0, nil); err != nil { + t.Fatal(err) + } + if _, err := i0.CreateFieldIfNotExists("f0", pilosa.FieldOptions{}); err != nil { + t.Fatal(err) + } + + h := test.MustNewHandler() + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + h.API.Cluster.SetState(pilosa.ClusterStateNormal) + h.API.StatusHandler = s + s.Handler = h + + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/status", nil)) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `{"state":"NORMAL","nodes":[{"id":"node0","uri":{"scheme":"http","host":"host0"},"isCoordinator":false}],"localID":"node0"}`+"\n" { + t.Fatalf("unexpected body: %s", body) + } +} + +func TestHandler_Info(t *testing.T) { + t.Skip() // Until test.NewServer() works + + s := test.NewServer() + defer s.Close() + h := test.MustNewHandler() + + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/info", nil)) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != fmt.Sprintf("{\"sliceWidth\":%d}\n", pilosa.SliceWidth) { + t.Fatalf("unexpected body: %s", body) + } +} + +// Ensure the handler can abort a cluster resize. +func TestHandler_ClusterResizeAbort(t *testing.T) { + t.Skip() // Until test.NewServer() works + + t.Run("No resize job", func(t *testing.T) { + h := test.MustNewHandler() + h.API.Cluster = test.NewCluster(1) + h.API.Cluster.SetState(pilosa.ClusterStateResizing) + + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/cluster/resize/abort", nil)) + if w.Code != gohttp.StatusOK { + bod, err := ioutil.ReadAll(w.Body) + t.Fatalf("unexpected status code: %d, bod: %s, readerr: %v", w.Code, bod, err) + } else if body := w.Body.String(); body != `{"info":"complete current job: no resize job currently running"}`+"\n" { + t.Fatalf("unexpected body: %s", body) + } + }) + +} + +// Ensure the handler can return the maxslice map. +func TestHandler_MaxSlices(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + + hldr.SetBit("i0", "f0", 30, (1*pilosa.SliceWidth)+1) + hldr.SetBit("i0", "f0", 30, (1*pilosa.SliceWidth)+2) + hldr.SetBit("i0", "f0", 30, (3*pilosa.SliceWidth)+4) + + hldr.SetBit("i1", "f1", 40, (0*pilosa.SliceWidth)+1) + hldr.SetBit("i1", "f1", 40, (0*pilosa.SliceWidth)+2) + hldr.SetBit("i1", "f1", 40, (0*pilosa.SliceWidth)+8) + + h := test.MustNewHandler() + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/slices/max", nil)) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `{"standard":{"i0":3,"i1":0}}`+"\n" { + t.Fatalf("unexpected body: %s", body) + } +} + +// Ensure the handler can accept URL arguments. +func TestHandler_Query_Args_URL(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + + h := test.MustNewHandler() + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { + if index != "idx0" { + t.Fatalf("unexpected index: %s", index) + } else if query.String() != `Count(Bitmap(id=100))` { + t.Fatalf("unexpected query: %s", query.String()) + } else if !reflect.DeepEqual(slices, []uint64{0, 1}) { + t.Fatalf("unexpected slices: %+v", slices) + } + return []interface{}{uint64(100)}, nil + } + + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/idx0/query?slices=0,1", strings.NewReader("Count( Bitmap( id=100))"))) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d %s", w.Code, w.Body.String()) + } else if body := w.Body.String(); body != `{"results":[100]}`+"\n" { + t.Fatalf("unexpected body: %q", body) + } +} + +// Ensure the handler can accept arguments via protobufs. +func TestHandler_Query_Args_Protobuf(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + + h := test.MustNewHandler() + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { + if index != "idx0" { + t.Fatalf("unexpected index: %s", index) + } else if query.String() != `Count(Bitmap(id=100))` { + t.Fatalf("unexpected query: %s", query.String()) + } else if !reflect.DeepEqual(slices, []uint64{0, 1}) { + t.Fatalf("unexpected slices: %+v", slices) + } + return []interface{}{uint64(100)}, nil + } + + // Generate request body. + reqBody, err := proto.Marshal(&internal.QueryRequest{ + Query: "Count(Bitmap(id=100))", + Slices: []uint64{0, 1}, + }) + if err != nil { + t.Fatal(err) + } + + // Generate protobuf request. + req := test.MustNewHTTPRequest("POST", "/index/idx0/query", bytes.NewReader(reqBody)) + req.Header.Set("Content-Type", "application/x-protobuf") + + w := httptest.NewRecorder() + h.ServeHTTP(w, req) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } +} + +// Ensure the handler returns an error when parsing bad arguments. +func TestHandler_Query_Args_Err(t *testing.T) { + t.Skip() // Until test.NewServer() works + + w := httptest.NewRecorder() + hldr := test.MustOpenHolder() + defer hldr.Close() + + h := test.MustNewHandler() + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/idx0/query?slices=a,b", strings.NewReader("Bitmap(id=100)"))) + if w.Code != gohttp.StatusBadRequest { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `{"error":"invalid slice argument"}`+"\n" { + t.Fatalf("unexpected body: %q", body) + } +} +func TestHandler_Query_Params_Err(t *testing.T) { + t.Skip() // Until test.NewServer() works + + w := httptest.NewRecorder() + test.MustNewHandler().ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/idx0/query?slices=0,1&db=sample", strings.NewReader("Bitmap(id=100)"))) + if w.Code != gohttp.StatusBadRequest { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `{"error":"db is not a valid argument"}`+"\n" { + t.Fatalf("unexpected body: %q", body) + } + +} + +// Ensure the handler can execute a query with a uint64 response as JSON. +func TestHandler_Query_Uint64_JSON(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + + h := test.MustNewHandler() + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { + return []interface{}{uint64(100)}, nil + } + + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/idx0/query?slices=0,1", strings.NewReader("Count( Bitmap( id=100))"))) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `{"results":[100]}`+"\n" { + t.Fatalf("unexpected body: %q", body) + } +} + +// Ensure the handler can execute a query with a uint64 response as protobufs. +func TestHandler_Query_Uint64_Protobuf(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + + h := test.MustNewHandler() + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { + return []interface{}{uint64(100)}, nil + } + + w := httptest.NewRecorder() + r := test.MustNewHTTPRequest("POST", "/index/i/query", strings.NewReader("Count(Bitmap(id=100))")) + r.Header.Set("Accept", "application/x-protobuf") + h.ServeHTTP(w, r) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } + + var resp internal.QueryResponse + if err := proto.Unmarshal(w.Body.Bytes(), &resp); err != nil { + t.Fatal(err) + } else if rt := resp.Results[0].Type; rt != http.QueryResultTypeUint64 { + t.Fatalf("unexpected response type: %d", resp.Results[0].Type) + } else if n := resp.Results[0].N; n != 100 { + t.Fatalf("unexpected n: %d", n) + } +} + +// Ensure the handler can execute a query that returns a bitmap as JSON. +func TestHandler_Query_Bitmap_JSON(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + + h := test.MustNewHandler() + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { + r := pilosa.NewRow(1, 3, 66, pilosa.SliceWidth+1) + r.Attrs = map[string]interface{}{"a": "b", "c": 1, "d": true} + return []interface{}{r}, nil + } + + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i/query", strings.NewReader("Bitmap(id=100)"))) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `{"results":[{"attrs":{"a":"b","c":1,"d":true},"columns":[1,3,66,1048577]}]}`+"\n" { + t.Fatalf("unexpected body: %s", body) + } +} + +// Ensure the handler can execute a query that returns a row with column attributes as JSON. +func TestHandler_Query_Row_ColumnAttrs_JSON(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.NewHolder() + defer hldr.Close() + + // Create index and set column attributes. + index, err := hldr.CreateIndexIfNotExists("i", pilosa.IndexOptions{}) + if err != nil { + t.Fatal(err) + } else if err := index.ColumnAttrStore().SetAttrs(3, map[string]interface{}{"x": "y"}); err != nil { + t.Fatal(err) + } else if err := index.ColumnAttrStore().SetAttrs(66, map[string]interface{}{"y": 123, "z": false}); err != nil { + t.Fatal(err) + } + + h := test.MustNewHandler() + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { + r := pilosa.NewRow(1, 3, 66, pilosa.SliceWidth+1) + r.Attrs = map[string]interface{}{"a": "b", "c": 1, "d": true} + return []interface{}{r}, nil + } + + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i/query?columnAttrs=true", strings.NewReader("Bitmap(id=100)"))) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `{"results":[{"attrs":{"a":"b","c":1,"d":true},"columns":[1,3,66,1048577]}],"columnAttrs":[{"id":3,"attrs":{"x":"y"}},{"id":66,"attrs":{"y":123,"z":false}}]}`+"\n" { + t.Fatalf("unexpected body: %s", body) + } +} + +// Ensure the handler can execute a query that returns a row as protobuf. +func TestHandler_Query_Row_Protobuf(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + + h := test.MustNewHandler() + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { + r := pilosa.NewRow(1, pilosa.SliceWidth+1) + r.Attrs = map[string]interface{}{"a": "b", "c": int64(1), "d": true} + return []interface{}{r}, nil + } + + w := httptest.NewRecorder() + r := test.MustNewHTTPRequest("POST", "/index/i/query", strings.NewReader("Bitmap(id=100)")) + r.Header.Set("Accept", "application/x-protobuf") + h.ServeHTTP(w, r) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } + + var resp internal.QueryResponse + if err := proto.Unmarshal(w.Body.Bytes(), &resp); err != nil { + t.Fatal(err) + } else if rt := resp.Results[0].Type; rt != http.QueryResultTypeRow { + t.Fatalf("unexpected response type: %d", resp.Results[0].Type) + } else if columns := resp.Results[0].Row.Columns; !reflect.DeepEqual(columns, []uint64{1, pilosa.SliceWidth + 1}) { + t.Fatalf("unexpected columns: %+v", columns) + } else if attrs := resp.Results[0].Row.Attrs; len(attrs) != 3 { + t.Fatalf("unexpected attr length: %d", len(attrs)) + } else if k, v := attrs[0].Key, attrs[0].StringValue; k != "a" || v != "b" { + t.Fatalf("unexpected attr[0]: %s=%v", k, v) + } else if k, v := attrs[1].Key, attrs[1].IntValue; k != "c" || v != int64(1) { + t.Fatalf("unexpected attr[1]: %s=%v", k, v) + } else if k, v := attrs[2].Key, attrs[2].BoolValue; k != "d" || !v { + t.Fatalf("unexpected attr[2]: %s=%v", k, v) + } +} + +// Ensure the handler can execute a query that returns a row with column attributes as protobuf. +func TestHandler_Query_Row_ColumnAttrs_Protobuf(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.NewHolder() + defer hldr.Close() + + // Create index and set column attributes. + index, err := hldr.CreateIndexIfNotExists("i", pilosa.IndexOptions{}) + if err != nil { + t.Fatal(err) + } else if err := index.ColumnAttrStore().SetAttrs(1, map[string]interface{}{"x": "y"}); err != nil { + t.Fatal(err) + } + + h := test.MustNewHandler() + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { + r := pilosa.NewRow(1, pilosa.SliceWidth+1) + r.Attrs = map[string]interface{}{"a": "b", "c": int64(1), "d": true} + return []interface{}{r}, nil + } + + // Encode request body. + buf, err := proto.Marshal(&internal.QueryRequest{ + Query: "Bitmap(id=100)", + ColumnAttrs: true, + }) + if err != nil { + t.Fatal(err) + } + + w := httptest.NewRecorder() + r := test.MustNewHTTPRequest("POST", "/index/i/query", bytes.NewReader(buf)) + r.Header.Set("Content-Type", "application/x-protobuf") + r.Header.Set("Accept", "application/x-protobuf") + h.ServeHTTP(w, r) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } + + var resp internal.QueryResponse + if err := proto.Unmarshal(w.Body.Bytes(), &resp); err != nil { + t.Fatal(err) + } + if columns := resp.Results[0].Row.Columns; !reflect.DeepEqual(columns, []uint64{1, pilosa.SliceWidth + 1}) { + t.Fatalf("unexpected columns: %+v", columns) + } else if rt := resp.Results[0].Type; rt != http.QueryResultTypeRow { + t.Fatalf("unexpected response type: %d", resp.Results[0].Type) + } else if attrs := resp.Results[0].Row.Attrs; len(attrs) != 3 { + t.Fatalf("unexpected attr length: %d", len(attrs)) + } else if k, v := attrs[0].Key, attrs[0].StringValue; k != "a" || v != "b" { + t.Fatalf("unexpected attr[0]: %s=%v", k, v) + } else if k, v := attrs[1].Key, attrs[1].IntValue; k != "c" || v != int64(1) { + t.Fatalf("unexpected attr[1]: %s=%v", k, v) + } else if k, v := attrs[2].Key, attrs[2].BoolValue; k != "d" || !v { + t.Fatalf("unexpected attr[2]: %s=%v", k, v) + } + + if a := resp.ColumnAttrSets; len(a) != 1 { + t.Fatalf("unexpected column attributes length: %d", len(a)) + } else if a[0].ID != 1 { + t.Fatalf("unexpected id: %d", a[0].ID) + } else if len(a[0].Attrs) != 1 { + t.Fatalf("unexpected column attr length: %d", len(a)) + } else if k, v := a[0].Attrs[0].Key, a[0].Attrs[0].StringValue; k != "x" || v != "y" { + t.Fatalf("unexpected attr[0]: %s=%v", k, v) + } +} + +// Ensure the handler can execute a query that returns pairs as JSON. +func TestHandler_Query_Pairs_JSON(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + + h := test.MustNewHandler() + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { + return []interface{}{[]pilosa.Pair{ + {ID: 1, Count: 2}, + {ID: 3, Count: 4}, + }}, nil + } + + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i/query", strings.NewReader(`TopN(field=x, n=2)`))) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `{"results":[[{"id":1,"count":2},{"id":3,"count":4}]]}`+"\n" { + t.Fatalf("unexpected body: %q", body) + } +} + +// Ensure the handler can execute a query that returns pairs as protobuf. +func TestHandler_Query_Pairs_Protobuf(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + + h := test.MustNewHandler() + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { + return []interface{}{[]pilosa.Pair{ + {ID: 1, Count: 2}, + {ID: 3, Count: 4}, + }}, nil + } + + w := httptest.NewRecorder() + r := test.MustNewHTTPRequest("POST", "/index/i/query", strings.NewReader(`TopN(field=x, n=2)`)) + r.Header.Set("Accept", "application/x-protobuf") + h.ServeHTTP(w, r) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } + + var resp internal.QueryResponse + if err := proto.Unmarshal(w.Body.Bytes(), &resp); err != nil { + t.Fatal(err) + } else if rt := resp.Results[0].Type; rt != http.QueryResultTypePairs { + t.Fatalf("unexpected response type: %d", resp.Results[0].Type) + } else if a := resp.Results[0].GetPairs(); len(a) != 2 { + t.Fatalf("unexpected pair length: %d", len(a)) + } +} + +// Ensure the handler can return an error as JSON. +func TestHandler_Query_Err_JSON(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + + h := test.MustNewHandler() + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { + return nil, errors.New("marker") + } + + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i/query", strings.NewReader(`Bitmap(id=100)`))) + if w.Code != gohttp.StatusBadRequest { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `{"error":"executing: marker"}`+"\n" { + t.Fatalf("unexpected body: %q", body) + } +} + +// Ensure the handler can return an error as protobuf. +func TestHandler_Query_Err_Protobuf(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + + h := test.MustNewHandler() + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { + return nil, errors.New("marker") + } + + w := httptest.NewRecorder() + r := test.MustNewHTTPRequest("POST", "/index/i/query", strings.NewReader(`TopN(field=x, n=2)`)) + r.Header.Set("Accept", "application/x-protobuf") + h.ServeHTTP(w, r) + if w.Code != gohttp.StatusBadRequest { + t.Fatalf("unexpected status code: %d", w.Code) + } + + var resp internal.QueryResponse + if err := proto.Unmarshal(w.Body.Bytes(), &resp); err != nil { + t.Fatal(err) + } else if s := resp.Err; s != `executing: marker` { + t.Fatalf("unexpected error: %s", s) + } +} + +// Ensure the handler returns "method not allowed" for non-POST queries. +func TestHandler_Query_MethodNotAllowed(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + + h := test.MustNewHandler() + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/index/i/query", nil)) + if w.Code != gohttp.StatusMethodNotAllowed { + t.Fatalf("invalid status: %d", w.Code) + } +} + +// Ensure the handler returns an error if there is a parsing error.. +func TestHandler_Query_ErrParse(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + + h := test.MustNewHandler() + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/idx0/query?slices=0,1", strings.NewReader("bad_fn("))) + if w.Code != gohttp.StatusBadRequest { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `{"error":"parsing: expected comma, right paren, or identifier, found \"\" occurred at line 1, char 8"}`+"\n" { + t.Fatalf("unexpected body: %s", body) + } +} + +// Ensure the handler can delete an index. +func TestHandler_Index_Delete(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + + s := test.NewServer() + s.Handler.API.Holder = hldr.Holder + defer s.Close() + + // Create index. + if _, err := hldr.CreateIndexIfNotExists("i", pilosa.IndexOptions{}); err != nil { + t.Fatal(err) + } + + // Send request to delete index. + resp, err := gohttp.DefaultClient.Do(test.MustNewHTTPRequest("DELETE", s.URL+"/index/i", strings.NewReader(""))) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + + // Verify body response. + if resp.StatusCode != gohttp.StatusOK { + t.Fatalf("unexpected status: %d", resp.StatusCode) + } else if buf, err := ioutil.ReadAll(resp.Body); err != nil { + t.Fatal(err) + } else if string(buf) != "{}\n" { + t.Fatalf("unexpected response body: %s", buf) + } + + // Verify index is gone. + if hldr.Index("i") != nil { + t.Fatal("expected nil index") + } +} + +// Ensure handler can delete a field. +func TestHandler_DeleteField(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + i0 := hldr.MustCreateIndexIfNotExists("i0", pilosa.IndexOptions{}) + if _, err := i0.CreateFieldIfNotExists("f1", pilosa.FieldOptions{}); err != nil { + t.Fatal(err) + } + + h := test.MustNewHandler() + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("DELETE", "/index/i0/field/f1", strings.NewReader(""))) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `{}`+"\n" { + t.Fatalf("unexpected body: %s", body) + } else if f := hldr.Index("i0").Field("f1"); f != nil { + t.Fatal("expected nil field") + } +} + +// Ensure the handler can return data in differing blocks for an index. +func TestHandler_Index_AttrStore_Diff(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + + s := test.NewServer() + s.Handler.API.Holder = hldr.Holder + defer s.Close() + + // Set attributes on the index. + index, err := hldr.CreateIndexIfNotExists("i", pilosa.IndexOptions{}) + if err != nil { + t.Fatal(err) + } + if err := index.ColumnAttrStore().SetAttrs(1, map[string]interface{}{"foo": 1, "bar": 2}); err != nil { + t.Fatal(err) + } else if err := index.ColumnAttrStore().SetAttrs(100, map[string]interface{}{"x": "y"}); err != nil { + t.Fatal(err) + } else if err := index.ColumnAttrStore().SetAttrs(200, map[string]interface{}{"snowman": "☃"}); err != nil { + t.Fatal(err) + } + + // Retrieve block checksums. + blks, err := index.ColumnAttrStore().Blocks() + if err != nil { + t.Fatal(err) + } + + // Remove block #0 and alter block 2's checksum. + blks = blks[1:] + blks[1].Checksum = []byte("MISMATCHED_CHECKSUM") + + // Send block checksums to determine diff. + req, err := gohttp.NewRequest( + "POST", + s.URL+"/index/i/attr/diff", + strings.NewReader(`{"blocks":`+string(test.MustMarshalJSON(blks))+`}`), + ) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + + client := &gohttp.Client{} + resp, err := client.Do(req) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + + // Read and validate body. + if body := string(test.MustReadAll(resp.Body)); body != `{"attrs":{"1":{"bar":2,"foo":1},"200":{"snowman":"☃"}}}`+"\n" { + t.Fatalf("unexpected body: %s", body) + } +} + +// Ensure the handler can return data in differing blocks for a field. +func TestHandler_Field_AttrStore_Diff(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + + s := test.NewServer() + s.Handler.API.Holder = hldr.Holder + defer s.Close() + + // Set attributes on the index. + idx := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{}) + f, err := idx.CreateFieldIfNotExists("meta", pilosa.FieldOptions{}) + if err != nil { + t.Fatal(err) + } + if err := f.RowAttrStore().SetAttrs(1, map[string]interface{}{"foo": 1, "bar": 2}); err != nil { + t.Fatal(err) + } else if err := f.RowAttrStore().SetAttrs(100, map[string]interface{}{"x": "y"}); err != nil { + t.Fatal(err) + } else if err := f.RowAttrStore().SetAttrs(200, map[string]interface{}{"snowman": "☃"}); err != nil { + t.Fatal(err) + } + + // Retrieve block checksums. + blks, err := f.RowAttrStore().Blocks() + if err != nil { + t.Fatal(err) + } + + // Remove block #0 and alter block 2's checksum. + blks = blks[1:] + blks[1].Checksum = []byte("MISMATCHED_CHECKSUM") + + // Send block checksums to determine diff. + req, err := gohttp.NewRequest( + "POST", + s.URL+"/index/i/field/meta/attr/diff", + strings.NewReader(`{"blocks":`+string(test.MustMarshalJSON(blks))+`}`), + ) + + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + + client := &gohttp.Client{} + resp, err := client.Do(req) + if err != nil { + t.Fatal(err) + } + defer resp.Body.Close() + + // Read and validate body. + if body := string(test.MustReadAll(resp.Body)); body != `{"attrs":{"1":{"bar":2,"foo":1},"200":{"snowman":"☃"}}}`+"\n" { + t.Fatalf("unexpected body: %s", body) + } +} + +// Ensure the handler can retrieve the version. +func TestHandler_Version(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + + h := test.MustNewHandler() + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + + w := httptest.NewRecorder() + r := test.MustNewHTTPRequest("GET", "/version", nil) + h.ServeHTTP(w, r) + version := pilosa.Version + if strings.HasPrefix(version, "v") { + version = version[1:] + } + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } else if w.Body.String() != `{"version":"`+version+`"}`+"\n" { + t.Fatalf("unexpected body: %q", w.Body.String()) + } +} + +// Ensure the handler can return a list of nodes for a fragment. +func TestHandler_Fragment_Nodes(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + + h := test.MustNewHandler() + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(3) + h.API.Cluster.ReplicaN = 2 + + w := httptest.NewRecorder() + r := test.MustNewHTTPRequest("GET", "/fragment/nodes?index=X&slice=0", nil) + h.ServeHTTP(w, r) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `[{"id":"node2","uri":{"scheme":"http","host":"host2"},"isCoordinator":false},{"id":"node0","uri":{"scheme":"http","host":"host0"},"isCoordinator":false}]`+"\n" { + t.Fatalf("unexpected body: %q", body) + } + + // invalid argument should return BadRequest + w = httptest.NewRecorder() + r = test.MustNewHTTPRequest("GET", "/fragment/nodes?db=X&slice=0", nil) + h.ServeHTTP(w, r) + if w.Code != gohttp.StatusBadRequest { + t.Fatalf("unexpected status code: %d", w.Code) + } + + // index is required + w = httptest.NewRecorder() + r = test.MustNewHTTPRequest("GET", "/fragment/nodes?slice=0", nil) + h.ServeHTTP(w, r) + if w.Code != gohttp.StatusBadRequest { + t.Fatalf("unexpected status code: %d", w.Code) + } +} + +// Ensure the handler can return expvars without panicking. +func TestHandler_Expvars(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + + h := test.MustNewHandler() + h.API.Cluster = test.NewCluster(1) + h.API.Holder = hldr.Holder + w := httptest.NewRecorder() + r := test.MustNewHTTPRequest("GET", "/debug/vars", nil) + h.ServeHTTP(w, r) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } +} + +func MustReadAll(r io.Reader) []byte { + buf, err := ioutil.ReadAll(r) + if err != nil { + panic(err) + } + return buf +} + +func TestHandler_RecalculateCaches(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + + h := test.MustNewHandler() + h.API.Holder = hldr.Holder + h.API.Cluster = test.NewCluster(1) + + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/recalculate-caches", nil)) + if w.Code != gohttp.StatusNoContent { + t.Fatalf("unexpected status code: %d", w.Code) + } + +} + +func TestHandler_CORS(t *testing.T) { + t.Skip() // Until test.NewServer() works + + hldr := test.MustOpenHolder() + defer hldr.Close() + + s := test.NewServer() + s.Handler.API.Holder = hldr.Holder + defer s.Close() + + // No CORS config present, so should fail + handler := test.MustNewHandler() + + req := test.MustNewHTTPRequest("OPTIONS", "/index/foo/query", nil) + req.Header.Add("Origin", "http://test/") + req.Header.Add("Access-Control-Request-Method", "POST") + + w := httptest.NewRecorder() + handler.ServeHTTP(w, req) + result := w.Result() + + // This handler does not support CORS, return Method Not Allowed (405) + if result.StatusCode != 405 { + t.Fatalf("CORS preflight status should be 405, but is %v", result.StatusCode) + } + + // CORS config should allow preflight response + handler = test.MustNewHandler(http.OptHandlerAllowedOrigins([]string{"http://test/"})) + w = httptest.NewRecorder() + handler.ServeHTTP(w, req) + result = w.Result() + + if result.StatusCode != 200 { + t.Fatalf("CORS preflight status should be 200, but is %v", result.StatusCode) + } + if w.HeaderMap["Access-Control-Allow-Origin"][0] != "http://test/" { + t.Fatal("CORS header not present") + } +} From 504309e59484154b41bdfa1fbce5ea961797735f Mon Sep 17 00:00:00 2001 From: Matt Jaffee Date: Thu, 21 Jun 2018 15:06:22 -0500 Subject: [PATCH 04/16] rewrite some tests to not be skipped --- server.go | 5 + server/handler_test.go | 473 ++++++++++++++--------------------------- 2 files changed, 162 insertions(+), 316 deletions(-) diff --git a/server.go b/server.go index c575d5a2c..84a09a01d 100644 --- a/server.go +++ b/server.go @@ -79,6 +79,11 @@ type Server struct { dataDir string } +// TODO: have this return an interface for Holder instead of concrete object? +func (s *Server) Holder() *Holder { + return s.holder +} + // ServerOption is a functional option type for pilosa.Server type ServerOption func(s *Server) error diff --git a/server/handler_test.go b/server/handler_test.go index fb4048f65..79ca26572 100644 --- a/server/handler_test.go +++ b/server/handler_test.go @@ -17,6 +17,7 @@ package server_test import ( "bytes" "context" + "encoding/json" "fmt" "io" "io/ioutil" @@ -36,151 +37,82 @@ import ( "github.com/pkg/errors" ) -func TestHandler_Endpoints(t *testing.T) { - mains := test.MustRunMainWithCluster(t, 1) - _ = mains[0] -} - // Ensure the handler returns "not found" for invalid paths. -func TestHandler_NotFound(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/no_such_path", nil)) - if w.Code != gohttp.StatusNotFound { - t.Fatalf("invalid status: %d", w.Code) - } -} - -// Ensure the handler can return the schema. -func TestHandler_Schema(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - i0 := hldr.MustCreateIndexIfNotExists("i0", pilosa.IndexOptions{}) - i1 := hldr.MustCreateIndexIfNotExists("i1", pilosa.IndexOptions{}) - - if f, err := i0.CreateFieldIfNotExists("f1", pilosa.FieldOptions{}); err != nil { - t.Fatal(err) - } else if _, err := f.SetBit(0, 0, nil); err != nil { - t.Fatal(err) - } - if f, err := i1.CreateFieldIfNotExists("f0", pilosa.FieldOptions{}); err != nil { - t.Fatal(err) - } else if _, err := f.SetBit(0, 0, nil); err != nil { - t.Fatal(err) - } - if _, err := i0.CreateFieldIfNotExists("f0", pilosa.FieldOptions{}); err != nil { - t.Fatal(err) - } - - h := test.MustNewHandler() - h.API.Holder = hldr.Holder - h.API.Cluster = test.NewCluster(1) - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/schema", nil)) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"indexes":[{"name":"i0","fields":[{"name":"f0"},{"name":"f1","views":[{"name":"standard"}]}]},{"name":"i1","fields":[{"name":"f0","views":[{"name":"standard"}]}]}]}`+"\n" { - } else if body := w.Body.String(); body != `{"indexes":[{"name":"i0","fields":[{"name":"f0","options":{"cacheType":"ranked","cacheSize":50000}},{"name":"f1","options":{"cacheType":"ranked","cacheSize":50000},"views":[{"name":"standard"}]}]},{"name":"i1","fields":[{"name":"f0","options":{"cacheType":"ranked","cacheSize":50000},"views":[{"name":"standard"}]}]}]}`+"\n" { - t.Fatalf("unexpected body: %s", body) - } -} - -// Ensure the handler can return the status. -func TestHandler_Status(t *testing.T) { - t.Skip() // Until test.NewServer() works - - s := test.NewServer() - hldr := test.MustOpenHolder() - defer s.Close() - defer hldr.Close() - - i0 := hldr.MustCreateIndexIfNotExists("i0", pilosa.IndexOptions{}) - i1 := hldr.MustCreateIndexIfNotExists("i1", pilosa.IndexOptions{}) - - if f, err := i0.CreateFieldIfNotExists("f1", pilosa.FieldOptions{}); err != nil { - t.Fatal(err) - } else if _, err := f.SetBit(0, 0, nil); err != nil { - t.Fatal(err) - } - if f, err := i1.CreateFieldIfNotExists("f0", pilosa.FieldOptions{}); err != nil { - t.Fatal(err) - } else if _, err := f.SetBit(0, 0, nil); err != nil { - t.Fatal(err) - } - if _, err := i0.CreateFieldIfNotExists("f0", pilosa.FieldOptions{}); err != nil { - t.Fatal(err) - } - - h := test.MustNewHandler() - h.API.Holder = hldr.Holder - h.API.Cluster = test.NewCluster(1) - h.API.Cluster.SetState(pilosa.ClusterStateNormal) - h.API.StatusHandler = s - s.Handler = h - - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/status", nil)) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"state":"NORMAL","nodes":[{"id":"node0","uri":{"scheme":"http","host":"host0"},"isCoordinator":false}],"localID":"node0"}`+"\n" { - t.Fatalf("unexpected body: %s", body) - } -} - -func TestHandler_Info(t *testing.T) { - t.Skip() // Until test.NewServer() works - - s := test.NewServer() - defer s.Close() - h := test.MustNewHandler() - - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/info", nil)) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != fmt.Sprintf("{\"sliceWidth\":%d}\n", pilosa.SliceWidth) { - t.Fatalf("unexpected body: %s", body) - } -} - -// Ensure the handler can abort a cluster resize. -func TestHandler_ClusterResizeAbort(t *testing.T) { - t.Skip() // Until test.NewServer() works - - t.Run("No resize job", func(t *testing.T) { - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Cluster.SetState(pilosa.ClusterStateResizing) +func TestHandler_Endpoints(t *testing.T) { + cmd := test.MustRunMainWithCluster(t, 1)[0] + h := cmd.Handler.(*http.Handler).Handler + t.Run("Not Found", func(t *testing.T) { w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/cluster/resize/abort", nil)) + h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/no_such_path", nil)) + if w.Code != gohttp.StatusNotFound { + t.Fatalf("invalid status: %d", w.Code) + } + }) + + t.Run("Info", func(t *testing.T) { + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/info", nil)) if w.Code != gohttp.StatusOK { - bod, err := ioutil.ReadAll(w.Body) - t.Fatalf("unexpected status code: %d, bod: %s, readerr: %v", w.Code, bod, err) - } else if body := w.Body.String(); body != `{"info":"complete current job: no resize job currently running"}`+"\n" { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != fmt.Sprintf("{\"sliceWidth\":%d}\n", pilosa.SliceWidth) { t.Fatalf("unexpected body: %s", body) } }) -} + holder := cmd.Server.Holder() + hldr := test.Holder{holder} + i0 := hldr.MustCreateIndexIfNotExists("i0", pilosa.IndexOptions{}) + i1 := hldr.MustCreateIndexIfNotExists("i1", pilosa.IndexOptions{}) + if f, err := i0.CreateFieldIfNotExists("f1", pilosa.FieldOptions{}); err != nil { + t.Fatal(err) + } else if _, err := f.SetBit(0, 0, nil); err != nil { + t.Fatal(err) + } + if f, err := i1.CreateFieldIfNotExists("f0", pilosa.FieldOptions{}); err != nil { + t.Fatal(err) + } else if _, err := f.SetBit(0, 0, nil); err != nil { + t.Fatal(err) + } + if _, err := i0.CreateFieldIfNotExists("f0", pilosa.FieldOptions{}); err != nil { + t.Fatal(err) + } -// Ensure the handler can return the maxslice map. -func TestHandler_MaxSlices(t *testing.T) { - t.Skip() // Until test.NewServer() works + t.Run("Schema", func(t *testing.T) { + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/schema", nil)) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `{"indexes":[{"name":"i0","fields":[{"name":"f0"},{"name":"f1","views":[{"name":"standard"}]}]},{"name":"i1","fields":[{"name":"f0","views":[{"name":"standard"}]}]}]}`+"\n" { + } else if body := w.Body.String(); body != `{"indexes":[{"name":"i0","fields":[{"name":"f0","options":{"cacheType":"ranked","cacheSize":50000}},{"name":"f1","options":{"cacheType":"ranked","cacheSize":50000},"views":[{"name":"standard"}]}]},{"name":"i1","fields":[{"name":"f0","options":{"cacheType":"ranked","cacheSize":50000},"views":[{"name":"standard"}]}]}]}`+"\n" { + t.Fatalf("unexpected body: %s", body) + } + }) - hldr := test.MustOpenHolder() - defer hldr.Close() + t.Run("Status", func(t *testing.T) { + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/status", nil)) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } + ret := mustJSONDecode(t, w.Body) + if ret["state"].(string) != "NORMAL" { + t.Fatalf("wrong state from /status: %#v", ret) + } + if len(ret["nodes"].([]interface{})) != 1 { + t.Fatalf("wrong length nodes list: %#v", ret) + } + }) + + t.Run("Abort no resize job", func(t *testing.T) { + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/cluster/resize/abort", nil)) + if w.Code != gohttp.StatusInternalServerError { + bod, err := ioutil.ReadAll(w.Body) + t.Fatalf("unexpected status code: %d, bod: %s, readerr: %v", w.Code, bod, err) + } + // TODO need to test aborting a cluster resize job. this may not be the right place + }) hldr.SetBit("i0", "f0", 30, (1*pilosa.SliceWidth)+1) hldr.SetBit("i0", "f0", 30, (1*pilosa.SliceWidth)+2) @@ -190,199 +122,99 @@ func TestHandler_MaxSlices(t *testing.T) { hldr.SetBit("i1", "f1", 40, (0*pilosa.SliceWidth)+2) hldr.SetBit("i1", "f1", 40, (0*pilosa.SliceWidth)+8) - h := test.MustNewHandler() - h.API.Holder = hldr.Holder - h.API.Cluster = test.NewCluster(1) - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/slices/max", nil)) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"standard":{"i0":3,"i1":0}}`+"\n" { - t.Fatalf("unexpected body: %s", body) - } -} - -// Ensure the handler can accept URL arguments. -func TestHandler_Query_Args_URL(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - if index != "idx0" { - t.Fatalf("unexpected index: %s", index) - } else if query.String() != `Count(Bitmap(id=100))` { - t.Fatalf("unexpected query: %s", query.String()) - } else if !reflect.DeepEqual(slices, []uint64{0, 1}) { - t.Fatalf("unexpected slices: %+v", slices) + t.Run("Max Slice", func(t *testing.T) { + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/slices/max", nil)) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `{"standard":{"i0":3,"i1":0}}`+"\n" { + t.Fatalf("unexpected body: %s", body) } - return []interface{}{uint64(100)}, nil - } - - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/idx0/query?slices=0,1", strings.NewReader("Count( Bitmap( id=100))"))) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d %s", w.Code, w.Body.String()) - } else if body := w.Body.String(); body != `{"results":[100]}`+"\n" { - t.Fatalf("unexpected body: %q", body) - } -} - -// Ensure the handler can accept arguments via protobufs. -func TestHandler_Query_Args_Protobuf(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - if index != "idx0" { - t.Fatalf("unexpected index: %s", index) - } else if query.String() != `Count(Bitmap(id=100))` { - t.Fatalf("unexpected query: %s", query.String()) - } else if !reflect.DeepEqual(slices, []uint64{0, 1}) { - t.Fatalf("unexpected slices: %+v", slices) - } - return []interface{}{uint64(100)}, nil - } - - // Generate request body. - reqBody, err := proto.Marshal(&internal.QueryRequest{ - Query: "Count(Bitmap(id=100))", - Slices: []uint64{0, 1}, }) - if err != nil { - t.Fatal(err) - } - // Generate protobuf request. - req := test.MustNewHTTPRequest("POST", "/index/idx0/query", bytes.NewReader(reqBody)) - req.Header.Set("Content-Type", "application/x-protobuf") + t.Run("Slices args", func(t *testing.T) { + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/query?slices=0,1", strings.NewReader("Count(Bitmap(field=f0, row=30))"))) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d %s", w.Code, w.Body.String()) + } else if body := w.Body.String(); body != `{"results":[2]}`+"\n" { + t.Fatalf("unexpected body: %q", body) + } + }) - w := httptest.NewRecorder() - h.ServeHTTP(w, req) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } -} + t.Run("Slices args protobuf", func(t *testing.T) { + // Generate request body. + reqBody, err := proto.Marshal(&internal.QueryRequest{ + Query: "Count(Bitmap(field=f0, row=30))", + Slices: []uint64{0, 1}, + }) + if err != nil { + t.Fatal(err) + } -// Ensure the handler returns an error when parsing bad arguments. -func TestHandler_Query_Args_Err(t *testing.T) { - t.Skip() // Until test.NewServer() works + // Generate protobuf request. + req := test.MustNewHTTPRequest("POST", "/index/i0/query", bytes.NewReader(reqBody)) + req.Header.Set("Content-Type", "application/x-protobuf") + req.Header.Set("Accept", "application/json") - w := httptest.NewRecorder() - hldr := test.MustOpenHolder() - defer hldr.Close() + w := httptest.NewRecorder() + h.ServeHTTP(w, req) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `{"results":[2]}`+"\n" { + t.Fatalf("unexpected body: %q", body) + } - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder + }) - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/idx0/query?slices=a,b", strings.NewReader("Bitmap(id=100)"))) - if w.Code != gohttp.StatusBadRequest { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"error":"invalid slice argument"}`+"\n" { - t.Fatalf("unexpected body: %q", body) - } -} -func TestHandler_Query_Params_Err(t *testing.T) { - t.Skip() // Until test.NewServer() works + t.Run("Query args error", func(t *testing.T) { + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/query?slices=a,b", strings.NewReader("Count(Bitmap(field=f0, row=30))"))) + if w.Code != gohttp.StatusBadRequest { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `{"error":"invalid slice argument"}`+"\n" { + t.Fatalf("unexpected body: %q", body) + } + }) - w := httptest.NewRecorder() - test.MustNewHandler().ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/idx0/query?slices=0,1&db=sample", strings.NewReader("Bitmap(id=100)"))) - if w.Code != gohttp.StatusBadRequest { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"error":"db is not a valid argument"}`+"\n" { - t.Fatalf("unexpected body: %q", body) - } + t.Run("Query params err", func(t *testing.T) { + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/query?slices=0,1&db=sample", strings.NewReader("Count(Bitmap(field=f0, row=30))"))) + if w.Code != gohttp.StatusBadRequest { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `{"error":"db is not a valid argument"}`+"\n" { + t.Fatalf("unexpected body: %q", body) + } + }) -} + t.Run("Uint64 protobuf", func(t *testing.T) { + w := httptest.NewRecorder() + r := test.MustNewHTTPRequest("POST", "/index/i0/query", strings.NewReader("Count(Bitmap(field=f0, row=30))")) + r.Header.Set("Accept", "application/x-protobuf") + h.ServeHTTP(w, r) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } -// Ensure the handler can execute a query with a uint64 response as JSON. -func TestHandler_Query_Uint64_JSON(t *testing.T) { - t.Skip() // Until test.NewServer() works + var resp internal.QueryResponse + if err := proto.Unmarshal(w.Body.Bytes(), &resp); err != nil { + t.Fatal(err) + } else if rt := resp.Results[0].Type; rt != http.QueryResultTypeUint64 { + t.Fatalf("unexpected response type: %d", resp.Results[0].Type) + } else if n := resp.Results[0].N; n != 3 { + t.Fatalf("unexpected n: %d", n) + } + }) - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - return []interface{}{uint64(100)}, nil - } - - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/idx0/query?slices=0,1", strings.NewReader("Count( Bitmap( id=100))"))) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"results":[100]}`+"\n" { - t.Fatalf("unexpected body: %q", body) - } -} - -// Ensure the handler can execute a query with a uint64 response as protobufs. -func TestHandler_Query_Uint64_Protobuf(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - return []interface{}{uint64(100)}, nil - } - - w := httptest.NewRecorder() - r := test.MustNewHTTPRequest("POST", "/index/i/query", strings.NewReader("Count(Bitmap(id=100))")) - r.Header.Set("Accept", "application/x-protobuf") - h.ServeHTTP(w, r) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } - - var resp internal.QueryResponse - if err := proto.Unmarshal(w.Body.Bytes(), &resp); err != nil { - t.Fatal(err) - } else if rt := resp.Results[0].Type; rt != http.QueryResultTypeUint64 { - t.Fatalf("unexpected response type: %d", resp.Results[0].Type) - } else if n := resp.Results[0].N; n != 100 { - t.Fatalf("unexpected n: %d", n) - } -} - -// Ensure the handler can execute a query that returns a bitmap as JSON. -func TestHandler_Query_Bitmap_JSON(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - r := pilosa.NewRow(1, 3, 66, pilosa.SliceWidth+1) - r.Attrs = map[string]interface{}{"a": "b", "c": 1, "d": true} - return []interface{}{r}, nil - } - - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i/query", strings.NewReader("Bitmap(id=100)"))) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"results":[{"attrs":{"a":"b","c":1,"d":true},"columns":[1,3,66,1048577]}]}`+"\n" { - t.Fatalf("unexpected body: %s", body) - } + t.Run("Bitmap JSON", func(t *testing.T) { + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/query", strings.NewReader("Bitmap(field=f0, row=30)"))) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `{"results":[{"attrs":{},"columns":[1048577,1048578,3145732]}]}`+"\n" { + t.Fatalf("unexpected body: %s", body) + } + }) } // Ensure the handler can execute a query that returns a row with column attributes as JSON. @@ -1007,3 +839,12 @@ func TestHandler_CORS(t *testing.T) { t.Fatal("CORS header not present") } } + +func mustJSONDecode(t *testing.T, r io.Reader) (ret map[string]interface{}) { + dec := json.NewDecoder(r) + err := dec.Decode(&ret) + if err != nil { + t.Fatalf("decoding response: %v", err) + } + return ret +} From 526bdae280ab51ddd1a661e537f38ea80d2b2418 Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Thu, 21 Jun 2018 15:41:34 -0500 Subject: [PATCH 05/16] Remove unneccessary t.Skip() --- cmd/server_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmd/server_test.go b/cmd/server_test.go index 989c8de6b..e58f8af4d 100644 --- a/cmd/server_test.go +++ b/cmd/server_test.go @@ -35,8 +35,6 @@ func TestServerHelp(t *testing.T) { } func TestServerConfig(t *testing.T) { - t.Skip() // Until test.NewServer() works - actualDataDir, err := ioutil.TempDir("", "") failErr(t, err, "making data dir") logFile, err := ioutil.TempFile("", "") From e2512ec58dba1da99f5376907ddabefa8ff747c0 Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Thu, 21 Jun 2018 16:26:34 -0500 Subject: [PATCH 06/16] Use test.MustRunMainWithCluster in ctl tests --- ctl/export_test.go | 18 +++++------------- ctl/import_test.go | 46 +++++++++------------------------------------- 2 files changed, 14 insertions(+), 50 deletions(-) diff --git a/ctl/export_test.go b/ctl/export_test.go index d405c92c4..8960702f6 100644 --- a/ctl/export_test.go +++ b/ctl/export_test.go @@ -44,24 +44,16 @@ func TestExportCommand_Validation(t *testing.T) { } func TestExportCommand_Run(t *testing.T) { - t.Skip() // Until test.NewServer() works + cmd := test.MustRunMainWithCluster(t, 1)[0] buf := bytes.Buffer{} stdin, stdout, stderr := GetIO(buf) cm := NewExportCommand(stdin, stdout, stderr) + hostport := cmd.Server.URI.HostPort() + cm.Host = hostport - hldr := test.MustOpenHolder() - defer hldr.Close() - s := test.NewServer() - defer s.Close() - - s.Handler.API.Cluster = test.NewCluster(1) - s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() - s.Handler.API.Holder = hldr.Holder - cm.Host = s.Host() - - http.DefaultClient.Do(test.MustNewHTTPRequest("POST", s.URL+"/index/i", strings.NewReader(""))) - http.DefaultClient.Do(test.MustNewHTTPRequest("POST", s.URL+"/index/i/field/f", strings.NewReader(""))) + http.DefaultClient.Do(test.MustNewHTTPRequest("POST", "http://"+hostport+"/index/i", strings.NewReader(""))) + http.DefaultClient.Do(test.MustNewHTTPRequest("POST", "http://"+hostport+"/index/i/field/f", strings.NewReader(""))) cm.Index = "i" cm.Field = "f" diff --git a/ctl/import_test.go b/ctl/import_test.go index df2b9a5a6..2cf03a126 100644 --- a/ctl/import_test.go +++ b/ctl/import_test.go @@ -29,8 +29,6 @@ import ( ) func TestImportCommand_Validation(t *testing.T) { - t.Skip() // Until test.NewServer() works - buf := bytes.Buffer{} stdin, stdout, stderr := GetIO(buf) cm := NewImportCommand(stdin, stdout, stderr) @@ -53,8 +51,6 @@ func TestImportCommand_Validation(t *testing.T) { } func TestImportCommand_Run(t *testing.T) { - t.Skip() // Until test.NewServer() works - buf := bytes.Buffer{} stdin, stdout, stderr := GetIO(buf) cm := NewImportCommand(stdin, stdout, stderr) @@ -65,15 +61,8 @@ func TestImportCommand_Run(t *testing.T) { t.Fatal(err) } - hldr := test.MustOpenHolder() - defer hldr.Close() - s := test.NewServer() - defer s.Close() - - s.Handler.API.Cluster = test.NewCluster(1) - s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() - s.Handler.API.Holder = hldr.Holder - cm.Host = s.Host() + cmd := test.MustRunMainWithCluster(t, 1)[0] + cm.Host = cmd.Server.URI.HostPort() cm.Index = "i" cm.Field = "f" @@ -87,8 +76,6 @@ func TestImportCommand_Run(t *testing.T) { // Ensure that the ImportValue path runs. func TestImportCommand_RunValue(t *testing.T) { - t.Skip() // Until test.NewServer() works - buf := bytes.Buffer{} stdin, stdout, stderr := GetIO(buf) cm := NewImportCommand(stdin, stdout, stderr) @@ -99,18 +86,12 @@ func TestImportCommand_RunValue(t *testing.T) { t.Fatal(err) } - hldr := test.MustOpenHolder() - defer hldr.Close() - s := test.NewServer() - defer s.Close() + cmd := test.MustRunMainWithCluster(t, 1)[0] + hostport := cmd.Server.URI.HostPort() + cm.Host = hostport - s.Handler.API.Cluster = test.NewCluster(1) - s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() - s.Handler.API.Holder = hldr.Holder - cm.Host = s.Host() - - http.DefaultClient.Do(MustNewHTTPRequest("POST", s.URL+"/index/i", strings.NewReader(""))) - http.DefaultClient.Do(MustNewHTTPRequest("POST", s.URL+"/index/i/field/f", strings.NewReader(`{"options":{"type": "int", "min": 0, "max": 100}}`))) + http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+hostport+"/index/i", strings.NewReader(""))) + http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+hostport+"/index/i/field/f", strings.NewReader(`{"options":{"type": "int", "min": 0, "max": 100}}`))) cm.Index = "i" cm.Field = "f" @@ -122,21 +103,12 @@ func TestImportCommand_RunValue(t *testing.T) { } func TestImportCommand_InvalidFile(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - s := test.NewServer() - defer s.Close() - - s.Handler.API.Cluster = test.NewCluster(1) - s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() - s.Handler.API.Holder = hldr.Holder + cmd := test.MustRunMainWithCluster(t, 1)[0] buf := bytes.Buffer{} stdin, stdout, stderr := GetIO(buf) cm := NewImportCommand(stdin, stdout, stderr) - cm.Host = s.Host() + cm.Host = cmd.Server.URI.HostPort() cm.Index = "i" cm.Field = "f" file, err := ioutil.TempFile("", "import.csv") From 016dbac6ca1cb82f2274960a807a14c29585fcfc Mon Sep 17 00:00:00 2001 From: Matt Jaffee Date: Thu, 21 Jun 2018 19:24:17 -0500 Subject: [PATCH 07/16] convert a bunch more tests --- server/handler_test.go | 523 ++++++++++++++--------------------------- 1 file changed, 180 insertions(+), 343 deletions(-) diff --git a/server/handler_test.go b/server/handler_test.go index 79ca26572..5dde9b20f 100644 --- a/server/handler_test.go +++ b/server/handler_test.go @@ -16,7 +16,6 @@ package server_test import ( "bytes" - "context" "encoding/json" "fmt" "io" @@ -32,9 +31,7 @@ import ( "github.com/pilosa/pilosa" "github.com/pilosa/pilosa/http" "github.com/pilosa/pilosa/internal" - "github.com/pilosa/pilosa/pql" "github.com/pilosa/pilosa/test" - "github.com/pkg/errors" ) // Ensure the handler returns "not found" for invalid paths. @@ -118,6 +115,8 @@ func TestHandler_Endpoints(t *testing.T) { hldr.SetBit("i0", "f0", 30, (1*pilosa.SliceWidth)+2) hldr.SetBit("i0", "f0", 30, (3*pilosa.SliceWidth)+4) + hldr.SetBit("i0", "f0", 31, 1) + hldr.SetBit("i1", "f1", 40, (0*pilosa.SliceWidth)+1) hldr.SetBit("i1", "f1", 40, (0*pilosa.SliceWidth)+2) hldr.SetBit("i1", "f1", 40, (0*pilosa.SliceWidth)+8) @@ -215,367 +214,205 @@ func TestHandler_Endpoints(t *testing.T) { t.Fatalf("unexpected body: %s", body) } }) -} -// Ensure the handler can execute a query that returns a row with column attributes as JSON. -func TestHandler_Query_Row_ColumnAttrs_JSON(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.NewHolder() - defer hldr.Close() - - // Create index and set column attributes. - index, err := hldr.CreateIndexIfNotExists("i", pilosa.IndexOptions{}) - if err != nil { + f0 := i0.Field("f0") + if err := i0.ColumnAttrStore().SetAttrs((1*pilosa.SliceWidth)+1, map[string]interface{}{"x": "y"}); err != nil { t.Fatal(err) - } else if err := index.ColumnAttrStore().SetAttrs(3, map[string]interface{}{"x": "y"}); err != nil { + } else if err := i0.ColumnAttrStore().SetAttrs((1*pilosa.SliceWidth)+2, map[string]interface{}{"y": 123, "z": false}); err != nil { t.Fatal(err) - } else if err := index.ColumnAttrStore().SetAttrs(66, map[string]interface{}{"y": 123, "z": false}); err != nil { + } else if err := f0.RowAttrStore().SetAttrs(30, map[string]interface{}{"a": "b", "c": 1, "d": true}); err != nil { t.Fatal(err) } - h := test.MustNewHandler() - h.API.Holder = hldr.Holder - h.API.Cluster = test.NewCluster(1) - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - r := pilosa.NewRow(1, 3, 66, pilosa.SliceWidth+1) - r.Attrs = map[string]interface{}{"a": "b", "c": 1, "d": true} - return []interface{}{r}, nil - } - - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i/query?columnAttrs=true", strings.NewReader("Bitmap(id=100)"))) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"results":[{"attrs":{"a":"b","c":1,"d":true},"columns":[1,3,66,1048577]}],"columnAttrs":[{"id":3,"attrs":{"x":"y"}},{"id":66,"attrs":{"y":123,"z":false}}]}`+"\n" { - t.Fatalf("unexpected body: %s", body) - } -} - -// Ensure the handler can execute a query that returns a row as protobuf. -func TestHandler_Query_Row_Protobuf(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - r := pilosa.NewRow(1, pilosa.SliceWidth+1) - r.Attrs = map[string]interface{}{"a": "b", "c": int64(1), "d": true} - return []interface{}{r}, nil - } - - w := httptest.NewRecorder() - r := test.MustNewHTTPRequest("POST", "/index/i/query", strings.NewReader("Bitmap(id=100)")) - r.Header.Set("Accept", "application/x-protobuf") - h.ServeHTTP(w, r) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } - - var resp internal.QueryResponse - if err := proto.Unmarshal(w.Body.Bytes(), &resp); err != nil { - t.Fatal(err) - } else if rt := resp.Results[0].Type; rt != http.QueryResultTypeRow { - t.Fatalf("unexpected response type: %d", resp.Results[0].Type) - } else if columns := resp.Results[0].Row.Columns; !reflect.DeepEqual(columns, []uint64{1, pilosa.SliceWidth + 1}) { - t.Fatalf("unexpected columns: %+v", columns) - } else if attrs := resp.Results[0].Row.Attrs; len(attrs) != 3 { - t.Fatalf("unexpected attr length: %d", len(attrs)) - } else if k, v := attrs[0].Key, attrs[0].StringValue; k != "a" || v != "b" { - t.Fatalf("unexpected attr[0]: %s=%v", k, v) - } else if k, v := attrs[1].Key, attrs[1].IntValue; k != "c" || v != int64(1) { - t.Fatalf("unexpected attr[1]: %s=%v", k, v) - } else if k, v := attrs[2].Key, attrs[2].BoolValue; k != "d" || !v { - t.Fatalf("unexpected attr[2]: %s=%v", k, v) - } -} - -// Ensure the handler can execute a query that returns a row with column attributes as protobuf. -func TestHandler_Query_Row_ColumnAttrs_Protobuf(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.NewHolder() - defer hldr.Close() - - // Create index and set column attributes. - index, err := hldr.CreateIndexIfNotExists("i", pilosa.IndexOptions{}) - if err != nil { - t.Fatal(err) - } else if err := index.ColumnAttrStore().SetAttrs(1, map[string]interface{}{"x": "y"}); err != nil { - t.Fatal(err) - } - - h := test.MustNewHandler() - h.API.Holder = hldr.Holder - h.API.Cluster = test.NewCluster(1) - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - r := pilosa.NewRow(1, pilosa.SliceWidth+1) - r.Attrs = map[string]interface{}{"a": "b", "c": int64(1), "d": true} - return []interface{}{r}, nil - } - - // Encode request body. - buf, err := proto.Marshal(&internal.QueryRequest{ - Query: "Bitmap(id=100)", - ColumnAttrs: true, + t.Run("ColumnAttrs_JSON", func(t *testing.T) { + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/query?columnAttrs=true", strings.NewReader("Bitmap(field=f0, row=30)"))) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d. body: %s", w.Code, w.Body.String()) + } else if body := w.Body.String(); body != `{"results":[{"attrs":{"a":"b","c":1,"d":true},"columns":[1048577,1048578,3145732]}],"columnAttrs":[{"id":1048577,"attrs":{"x":"y"}},{"id":1048578,"attrs":{"y":123,"z":false}}]}`+"\n" { + t.Fatalf("unexpected body: %s", body) + } }) - if err != nil { - t.Fatal(err) - } - w := httptest.NewRecorder() - r := test.MustNewHTTPRequest("POST", "/index/i/query", bytes.NewReader(buf)) - r.Header.Set("Content-Type", "application/x-protobuf") - r.Header.Set("Accept", "application/x-protobuf") - h.ServeHTTP(w, r) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } + t.Run("Row pbuf", func(t *testing.T) { + w := httptest.NewRecorder() + r := test.MustNewHTTPRequest("POST", "/index/i0/query", strings.NewReader("Bitmap(field=f0, row=30)")) + r.Header.Set("Accept", "application/x-protobuf") + h.ServeHTTP(w, r) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } - var resp internal.QueryResponse - if err := proto.Unmarshal(w.Body.Bytes(), &resp); err != nil { - t.Fatal(err) - } - if columns := resp.Results[0].Row.Columns; !reflect.DeepEqual(columns, []uint64{1, pilosa.SliceWidth + 1}) { - t.Fatalf("unexpected columns: %+v", columns) - } else if rt := resp.Results[0].Type; rt != http.QueryResultTypeRow { - t.Fatalf("unexpected response type: %d", resp.Results[0].Type) - } else if attrs := resp.Results[0].Row.Attrs; len(attrs) != 3 { - t.Fatalf("unexpected attr length: %d", len(attrs)) - } else if k, v := attrs[0].Key, attrs[0].StringValue; k != "a" || v != "b" { - t.Fatalf("unexpected attr[0]: %s=%v", k, v) - } else if k, v := attrs[1].Key, attrs[1].IntValue; k != "c" || v != int64(1) { - t.Fatalf("unexpected attr[1]: %s=%v", k, v) - } else if k, v := attrs[2].Key, attrs[2].BoolValue; k != "d" || !v { - t.Fatalf("unexpected attr[2]: %s=%v", k, v) - } + var resp internal.QueryResponse + if err := proto.Unmarshal(w.Body.Bytes(), &resp); err != nil { + t.Fatal(err) + } else if rt := resp.Results[0].Type; rt != http.QueryResultTypeRow { + t.Fatalf("unexpected response type: %d", resp.Results[0].Type) + } else if columns := resp.Results[0].Row.Columns; !reflect.DeepEqual(columns, []uint64{pilosa.SliceWidth + 1, pilosa.SliceWidth + 2, (3 * pilosa.SliceWidth) + 4}) { + t.Fatalf("unexpected columns: %+v", columns) + } else if attrs := resp.Results[0].Row.Attrs; len(attrs) != 3 { + t.Fatalf("unexpected attr length: %d", len(attrs)) + } else if k, v := attrs[0].Key, attrs[0].StringValue; k != "a" || v != "b" { + t.Fatalf("unexpected attr[0]: %s=%v", k, v) + } else if k, v := attrs[1].Key, attrs[1].IntValue; k != "c" || v != int64(1) { + t.Fatalf("unexpected attr[1]: %s=%v", k, v) + } else if k, v := attrs[2].Key, attrs[2].BoolValue; k != "d" || !v { + t.Fatalf("unexpected attr[2]: %s=%v", k, v) + } + }) - if a := resp.ColumnAttrSets; len(a) != 1 { - t.Fatalf("unexpected column attributes length: %d", len(a)) - } else if a[0].ID != 1 { - t.Fatalf("unexpected id: %d", a[0].ID) - } else if len(a[0].Attrs) != 1 { - t.Fatalf("unexpected column attr length: %d", len(a)) - } else if k, v := a[0].Attrs[0].Key, a[0].Attrs[0].StringValue; k != "x" || v != "y" { - t.Fatalf("unexpected attr[0]: %s=%v", k, v) - } -} + t.Run("Row columnattrs protobuf", func(t *testing.T) { + // Encode request body. + buf, err := proto.Marshal(&internal.QueryRequest{ + Query: "Bitmap(field=f0, row=30)", + ColumnAttrs: true, + }) + if err != nil { + t.Fatal(err) + } -// Ensure the handler can execute a query that returns pairs as JSON. -func TestHandler_Query_Pairs_JSON(t *testing.T) { - t.Skip() // Until test.NewServer() works + w := httptest.NewRecorder() + r := test.MustNewHTTPRequest("POST", "/index/i0/query", bytes.NewReader(buf)) + r.Header.Set("Content-Type", "application/x-protobuf") + r.Header.Set("Accept", "application/x-protobuf") + h.ServeHTTP(w, r) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } - hldr := test.MustOpenHolder() - defer hldr.Close() + var resp internal.QueryResponse + if err := proto.Unmarshal(w.Body.Bytes(), &resp); err != nil { + t.Fatal(err) + } + if columns := resp.Results[0].Row.Columns; !reflect.DeepEqual(columns, []uint64{pilosa.SliceWidth + 1, pilosa.SliceWidth + 2, (3 * pilosa.SliceWidth) + 4}) { + t.Fatalf("unexpected columns: %+v", columns) + } else if rt := resp.Results[0].Type; rt != http.QueryResultTypeRow { + t.Fatalf("unexpected response type: %d", resp.Results[0].Type) + } else if attrs := resp.Results[0].Row.Attrs; len(attrs) != 3 { + t.Fatalf("unexpected attr length: %d", len(attrs)) + } else if k, v := attrs[0].Key, attrs[0].StringValue; k != "a" || v != "b" { + t.Fatalf("unexpected attr[0]: %s=%v", k, v) + } else if k, v := attrs[1].Key, attrs[1].IntValue; k != "c" || v != int64(1) { + t.Fatalf("unexpected attr[1]: %s=%v", k, v) + } else if k, v := attrs[2].Key, attrs[2].BoolValue; k != "d" || !v { + t.Fatalf("unexpected attr[2]: %s=%v", k, v) + } - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - return []interface{}{[]pilosa.Pair{ - {ID: 1, Count: 2}, - {ID: 3, Count: 4}, - }}, nil - } + if a := resp.ColumnAttrSets; len(a) != 2 { + t.Fatalf("unexpected column attributes length: %d", len(a)) + } else if a[0].ID != pilosa.SliceWidth+1 { + t.Fatalf("unexpected id: %d", a[0].ID) + } else if len(a[0].Attrs) != 1 { + t.Fatalf("unexpected column attr length: %d", len(a)) + } else if k, v := a[0].Attrs[0].Key, a[0].Attrs[0].StringValue; k != "x" || v != "y" { + t.Fatalf("unexpected attr[0]: %s=%v", k, v) + } + }) - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i/query", strings.NewReader(`TopN(field=x, n=2)`))) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"results":[[{"id":1,"count":2},{"id":3,"count":4}]]}`+"\n" { - t.Fatalf("unexpected body: %q", body) - } -} + t.Run("Query Pairs JSON", func(t *testing.T) { + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/query", strings.NewReader(`TopN(field=f0, n=2)`))) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `{"results":[[{"id":30,"count":3},{"id":31,"count":1}]]}`+"\n" { + t.Fatalf("unexpected body: %q", body) + } + }) -// Ensure the handler can execute a query that returns pairs as protobuf. -func TestHandler_Query_Pairs_Protobuf(t *testing.T) { - t.Skip() // Until test.NewServer() works + t.Run("Query Pairs protobuf", func(t *testing.T) { + w := httptest.NewRecorder() + r := test.MustNewHTTPRequest("POST", "/index/i0/query", strings.NewReader(`TopN(field=f0, n=2)`)) + r.Header.Set("Accept", "application/x-protobuf") + h.ServeHTTP(w, r) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } - hldr := test.MustOpenHolder() - defer hldr.Close() + var resp internal.QueryResponse + if err := proto.Unmarshal(w.Body.Bytes(), &resp); err != nil { + t.Fatal(err) + } else if rt := resp.Results[0].Type; rt != http.QueryResultTypePairs { + t.Fatalf("unexpected response type: %d", resp.Results[0].Type) + } else if a := resp.Results[0].GetPairs(); len(a) != 2 { + t.Fatalf("unexpected pair length: %d", len(a)) + } + }) - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - return []interface{}{[]pilosa.Pair{ - {ID: 1, Count: 2}, - {ID: 3, Count: 4}, - }}, nil - } + t.Run("Query err JSON", func(t *testing.T) { + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/query", strings.NewReader(`Bitmap(row=30)`))) + if w.Code != gohttp.StatusBadRequest { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `{"error":"executing: field not found"}`+"\n" { + t.Fatalf("unexpected body: %q", body) + } + }) - w := httptest.NewRecorder() - r := test.MustNewHTTPRequest("POST", "/index/i/query", strings.NewReader(`TopN(field=x, n=2)`)) - r.Header.Set("Accept", "application/x-protobuf") - h.ServeHTTP(w, r) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } + t.Run("Query err protobuf", func(t *testing.T) { + w := httptest.NewRecorder() + r := test.MustNewHTTPRequest("POST", "/index/i0/query", strings.NewReader(`Bitmap(row=30)`)) + r.Header.Set("Accept", "application/x-protobuf") + h.ServeHTTP(w, r) + if w.Code != gohttp.StatusBadRequest { + t.Fatalf("unexpected status code: %d", w.Code) + } - var resp internal.QueryResponse - if err := proto.Unmarshal(w.Body.Bytes(), &resp); err != nil { - t.Fatal(err) - } else if rt := resp.Results[0].Type; rt != http.QueryResultTypePairs { - t.Fatalf("unexpected response type: %d", resp.Results[0].Type) - } else if a := resp.Results[0].GetPairs(); len(a) != 2 { - t.Fatalf("unexpected pair length: %d", len(a)) - } -} + var resp internal.QueryResponse + if err := proto.Unmarshal(w.Body.Bytes(), &resp); err != nil { + t.Fatal(err) + } else if s := resp.Err; s != `executing: field not found` { + t.Fatalf("unexpected error: %s", s) + } + }) -// Ensure the handler can return an error as JSON. -func TestHandler_Query_Err_JSON(t *testing.T) { - t.Skip() // Until test.NewServer() works + t.Run("Method not allowed", func(t *testing.T) { + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/index/i0/query", nil)) + if w.Code != gohttp.StatusMethodNotAllowed { + t.Fatalf("invalid status: %d", w.Code) + } + }) - hldr := test.MustOpenHolder() - defer hldr.Close() + t.Run("Err Parse", func(t *testing.T) { + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/idx0/query?slices=0,1", strings.NewReader("bad_fn("))) + if w.Code != gohttp.StatusBadRequest { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `{"error":"parsing: expected comma, right paren, or identifier, found \"\" occurred at line 1, char 8"}`+"\n" { + t.Fatalf("unexpected body: %s", body) + } + }) - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - return nil, errors.New("marker") - } + t.Run("delete index", func(t *testing.T) { + hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{}) + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("DELETE", "/index/i", strings.NewReader(""))) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d, body: %s", w.Code, w.Body.String()) + } else if w.Body.String() != "{}\n" { + t.Fatalf("unexpected response body: %s", w.Body.String()) + } + // Verify index is gone. + if hldr.Index("i") != nil { + t.Fatal("expected nil index") + } + }) - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i/query", strings.NewReader(`Bitmap(id=100)`))) - if w.Code != gohttp.StatusBadRequest { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"error":"executing: marker"}`+"\n" { - t.Fatalf("unexpected body: %q", body) - } -} - -// Ensure the handler can return an error as protobuf. -func TestHandler_Query_Err_Protobuf(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - h.Executor.ExecuteFn = func(ctx context.Context, index string, query *pql.Query, slices []uint64, opt *pilosa.ExecOptions) ([]interface{}, error) { - return nil, errors.New("marker") - } - - w := httptest.NewRecorder() - r := test.MustNewHTTPRequest("POST", "/index/i/query", strings.NewReader(`TopN(field=x, n=2)`)) - r.Header.Set("Accept", "application/x-protobuf") - h.ServeHTTP(w, r) - if w.Code != gohttp.StatusBadRequest { - t.Fatalf("unexpected status code: %d", w.Code) - } - - var resp internal.QueryResponse - if err := proto.Unmarshal(w.Body.Bytes(), &resp); err != nil { - t.Fatal(err) - } else if s := resp.Err; s != `executing: marker` { - t.Fatalf("unexpected error: %s", s) - } -} - -// Ensure the handler returns "method not allowed" for non-POST queries. -func TestHandler_Query_MethodNotAllowed(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("GET", "/index/i/query", nil)) - if w.Code != gohttp.StatusMethodNotAllowed { - t.Fatalf("invalid status: %d", w.Code) - } -} - -// Ensure the handler returns an error if there is a parsing error.. -func TestHandler_Query_ErrParse(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/idx0/query?slices=0,1", strings.NewReader("bad_fn("))) - if w.Code != gohttp.StatusBadRequest { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"error":"parsing: expected comma, right paren, or identifier, found \"\" occurred at line 1, char 8"}`+"\n" { - t.Fatalf("unexpected body: %s", body) - } -} - -// Ensure the handler can delete an index. -func TestHandler_Index_Delete(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - s := test.NewServer() - s.Handler.API.Holder = hldr.Holder - defer s.Close() - - // Create index. - if _, err := hldr.CreateIndexIfNotExists("i", pilosa.IndexOptions{}); err != nil { - t.Fatal(err) - } - - // Send request to delete index. - resp, err := gohttp.DefaultClient.Do(test.MustNewHTTPRequest("DELETE", s.URL+"/index/i", strings.NewReader(""))) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - - // Verify body response. - if resp.StatusCode != gohttp.StatusOK { - t.Fatalf("unexpected status: %d", resp.StatusCode) - } else if buf, err := ioutil.ReadAll(resp.Body); err != nil { - t.Fatal(err) - } else if string(buf) != "{}\n" { - t.Fatalf("unexpected response body: %s", buf) - } - - // Verify index is gone. - if hldr.Index("i") != nil { - t.Fatal("expected nil index") - } -} - -// Ensure handler can delete a field. -func TestHandler_DeleteField(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - i0 := hldr.MustCreateIndexIfNotExists("i0", pilosa.IndexOptions{}) - if _, err := i0.CreateFieldIfNotExists("f1", pilosa.FieldOptions{}); err != nil { - t.Fatal(err) - } - - h := test.MustNewHandler() - h.API.Holder = hldr.Holder - h.API.Cluster = test.NewCluster(1) - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("DELETE", "/index/i0/field/f1", strings.NewReader(""))) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{}`+"\n" { - t.Fatalf("unexpected body: %s", body) - } else if f := hldr.Index("i0").Field("f1"); f != nil { - t.Fatal("expected nil field") - } + t.Run("Field delete", func(t *testing.T) { + i := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{}) + if _, err := i.CreateFieldIfNotExists("f1", pilosa.FieldOptions{}); err != nil { + t.Fatal(err) + } + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("DELETE", "/index/i/field/f1", strings.NewReader(""))) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } else if body := w.Body.String(); body != `{}`+"\n" { + t.Fatalf("unexpected body: %s", body) + } else if f := hldr.Index("i").Field("f1"); f != nil { + t.Fatal("expected nil field") + } + }) } // Ensure the handler can return data in differing blocks for an index. @@ -615,7 +452,7 @@ func TestHandler_Index_AttrStore_Diff(t *testing.T) { // Send block checksums to determine diff. req, err := gohttp.NewRequest( "POST", - s.URL+"/index/i/attr/diff", + s.URL+"/index/i0/attr/diff", strings.NewReader(`{"blocks":`+string(test.MustMarshalJSON(blks))+`}`), ) @@ -673,7 +510,7 @@ func TestHandler_Field_AttrStore_Diff(t *testing.T) { // Send block checksums to determine diff. req, err := gohttp.NewRequest( "POST", - s.URL+"/index/i/field/meta/attr/diff", + s.URL+"/index/i0/field/meta/attr/diff", strings.NewReader(`{"blocks":`+string(test.MustMarshalJSON(blks))+`}`), ) From 8809751a23a15a58e23fa375e9ac5a9c7550575d Mon Sep 17 00:00:00 2001 From: Matt Jaffee Date: Fri, 22 Jun 2018 07:41:39 -0500 Subject: [PATCH 08/16] convert all tests except for CORS --- server/handler_test.go | 329 ++++++++++++++++------------------------- 1 file changed, 127 insertions(+), 202 deletions(-) diff --git a/server/handler_test.go b/server/handler_test.go index 5dde9b20f..9ac0f8931 100644 --- a/server/handler_test.go +++ b/server/handler_test.go @@ -406,234 +406,150 @@ func TestHandler_Endpoints(t *testing.T) { w := httptest.NewRecorder() h.ServeHTTP(w, test.MustNewHTTPRequest("DELETE", "/index/i/field/f1", strings.NewReader(""))) if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) + t.Fatalf("unexpected status code: %d, body: %s", w.Code, w.Body.String()) } else if body := w.Body.String(); body != `{}`+"\n" { t.Fatalf("unexpected body: %s", body) } else if f := hldr.Index("i").Field("f1"); f != nil { t.Fatal("expected nil field") } }) -} -// Ensure the handler can return data in differing blocks for an index. -func TestHandler_Index_AttrStore_Diff(t *testing.T) { - t.Skip() // Until test.NewServer() works + i := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{}) + if err := i.ColumnAttrStore().SetAttrs(1, map[string]interface{}{"foo": 1, "bar": 2}); err != nil { + t.Fatal(err) + } else if err := i.ColumnAttrStore().SetAttrs(100, map[string]interface{}{"x": "y"}); err != nil { + t.Fatal(err) + } else if err := i.ColumnAttrStore().SetAttrs(200, map[string]interface{}{"snowman": "☃"}); err != nil { + t.Fatal(err) + } - hldr := test.MustOpenHolder() - defer hldr.Close() + t.Run("AttrStore Diff", func(t *testing.T) { + blks, err := i.ColumnAttrStore().Blocks() + if err != nil { + t.Fatal(err) + } - s := test.NewServer() - s.Handler.API.Holder = hldr.Holder - defer s.Close() + blks = blks[1:] + blks[1].Checksum = []byte("MISMATCHED_CHECKSUM") - // Set attributes on the index. - index, err := hldr.CreateIndexIfNotExists("i", pilosa.IndexOptions{}) + // Send block checksums to determine diff. + req := test.MustNewHTTPRequest( + "POST", + "/index/i/attr/diff", + strings.NewReader(`{"blocks":`+string(test.MustMarshalJSON(blks))+`}`), + ) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + w := httptest.NewRecorder() + h.ServeHTTP(w, req) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d, body: %s", w.Code, w.Body.String()) + } + + // Read and validate body. + if w.Body.String() != `{"attrs":{"1":{"bar":2,"foo":1},"200":{"snowman":"☃"}}}`+"\n" { + t.Fatalf("unexpected body: %s", w.Body.String()) + } + }) + + meta, err := i.CreateFieldIfNotExists("meta", pilosa.FieldOptions{}) if err != nil { t.Fatal(err) } - if err := index.ColumnAttrStore().SetAttrs(1, map[string]interface{}{"foo": 1, "bar": 2}); err != nil { + if err := meta.RowAttrStore().SetAttrs(1, map[string]interface{}{"foo": 1, "bar": 2}); err != nil { t.Fatal(err) - } else if err := index.ColumnAttrStore().SetAttrs(100, map[string]interface{}{"x": "y"}); err != nil { + } else if err := meta.RowAttrStore().SetAttrs(100, map[string]interface{}{"x": "y"}); err != nil { t.Fatal(err) - } else if err := index.ColumnAttrStore().SetAttrs(200, map[string]interface{}{"snowman": "☃"}); err != nil { + } else if err := meta.RowAttrStore().SetAttrs(200, map[string]interface{}{"snowman": "☃"}); err != nil { t.Fatal(err) } - // Retrieve block checksums. - blks, err := index.ColumnAttrStore().Blocks() - if err != nil { - t.Fatal(err) - } + t.Run("field attrstore diff", func(t *testing.T) { + blks, err := meta.RowAttrStore().Blocks() + if err != nil { + t.Fatal(err) + } + blks = blks[1:] + blks[1].Checksum = []byte("MISMATCHED_CHECKSUM") - // Remove block #0 and alter block 2's checksum. - blks = blks[1:] - blks[1].Checksum = []byte("MISMATCHED_CHECKSUM") + // Send block checksums to determine diff. + req := test.MustNewHTTPRequest( + "POST", + "/index/i/field/meta/attr/diff", + strings.NewReader(`{"blocks":`+string(test.MustMarshalJSON(blks))+`}`), + ) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("Accept", "application/json") + w := httptest.NewRecorder() + h.ServeHTTP(w, req) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d, body: %s", w.Code, w.Body.String()) + } - // Send block checksums to determine diff. - req, err := gohttp.NewRequest( - "POST", - s.URL+"/index/i0/attr/diff", - strings.NewReader(`{"blocks":`+string(test.MustMarshalJSON(blks))+`}`), - ) + // Read and validate body. + if w.Body.String() != `{"attrs":{"1":{"bar":2,"foo":1},"200":{"snowman":"☃"}}}`+"\n" { + t.Fatalf("unexpected body: %s", w.Body.String()) + } + }) - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Accept", "application/json") + t.Run("Version", func(t *testing.T) { + w := httptest.NewRecorder() + r := test.MustNewHTTPRequest("GET", "/version", nil) + h.ServeHTTP(w, r) + version := strings.TrimPrefix(pilosa.Version, "v") + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } else if w.Body.String() != `{"version":"`+version+`"}`+"\n" { + t.Fatalf("unexpected body: %q", w.Body.String()) + } + }) - client := &gohttp.Client{} - resp, err := client.Do(req) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() + t.Run("Fragment Nodes", func(t *testing.T) { + w := httptest.NewRecorder() + r := test.MustNewHTTPRequest("GET", "/fragment/nodes?index=i&slice=0", nil) + h.ServeHTTP(w, r) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } + body := mustJSONDecodeSlice(t, w.Body) + bmap := body[0].(map[string]interface{}) + if bmap["isCoordinator"] != true { + t.Fatalf("expected true coordinator") + } - // Read and validate body. - if body := string(test.MustReadAll(resp.Body)); body != `{"attrs":{"1":{"bar":2,"foo":1},"200":{"snowman":"☃"}}}`+"\n" { - t.Fatalf("unexpected body: %s", body) - } -} + // invalid argument should return BadRequest + w = httptest.NewRecorder() + r = test.MustNewHTTPRequest("GET", "/fragment/nodes?db=X&slice=0", nil) + h.ServeHTTP(w, r) + if w.Code != gohttp.StatusBadRequest { + t.Fatalf("unexpected status code: %d", w.Code) + } -// Ensure the handler can return data in differing blocks for a field. -func TestHandler_Field_AttrStore_Diff(t *testing.T) { - t.Skip() // Until test.NewServer() works + // index is required + w = httptest.NewRecorder() + r = test.MustNewHTTPRequest("GET", "/fragment/nodes?slice=0", nil) + h.ServeHTTP(w, r) + if w.Code != gohttp.StatusBadRequest { + t.Fatalf("unexpected status code: %d", w.Code) + } + }) - hldr := test.MustOpenHolder() - defer hldr.Close() + t.Run("Expvars", func(t *testing.T) { + w := httptest.NewRecorder() + r := test.MustNewHTTPRequest("GET", "/debug/vars", nil) + h.ServeHTTP(w, r) + if w.Code != gohttp.StatusOK { + t.Fatalf("unexpected status code: %d", w.Code) + } + }) - s := test.NewServer() - s.Handler.API.Holder = hldr.Holder - defer s.Close() - - // Set attributes on the index. - idx := hldr.MustCreateIndexIfNotExists("i", pilosa.IndexOptions{}) - f, err := idx.CreateFieldIfNotExists("meta", pilosa.FieldOptions{}) - if err != nil { - t.Fatal(err) - } - if err := f.RowAttrStore().SetAttrs(1, map[string]interface{}{"foo": 1, "bar": 2}); err != nil { - t.Fatal(err) - } else if err := f.RowAttrStore().SetAttrs(100, map[string]interface{}{"x": "y"}); err != nil { - t.Fatal(err) - } else if err := f.RowAttrStore().SetAttrs(200, map[string]interface{}{"snowman": "☃"}); err != nil { - t.Fatal(err) - } - - // Retrieve block checksums. - blks, err := f.RowAttrStore().Blocks() - if err != nil { - t.Fatal(err) - } - - // Remove block #0 and alter block 2's checksum. - blks = blks[1:] - blks[1].Checksum = []byte("MISMATCHED_CHECKSUM") - - // Send block checksums to determine diff. - req, err := gohttp.NewRequest( - "POST", - s.URL+"/index/i0/field/meta/attr/diff", - strings.NewReader(`{"blocks":`+string(test.MustMarshalJSON(blks))+`}`), - ) - - req.Header.Set("Content-Type", "application/json") - req.Header.Set("Accept", "application/json") - - client := &gohttp.Client{} - resp, err := client.Do(req) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - - // Read and validate body. - if body := string(test.MustReadAll(resp.Body)); body != `{"attrs":{"1":{"bar":2,"foo":1},"200":{"snowman":"☃"}}}`+"\n" { - t.Fatalf("unexpected body: %s", body) - } -} - -// Ensure the handler can retrieve the version. -func TestHandler_Version(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - - w := httptest.NewRecorder() - r := test.MustNewHTTPRequest("GET", "/version", nil) - h.ServeHTTP(w, r) - version := pilosa.Version - if strings.HasPrefix(version, "v") { - version = version[1:] - } - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } else if w.Body.String() != `{"version":"`+version+`"}`+"\n" { - t.Fatalf("unexpected body: %q", w.Body.String()) - } -} - -// Ensure the handler can return a list of nodes for a fragment. -func TestHandler_Fragment_Nodes(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Holder = hldr.Holder - h.API.Cluster = test.NewCluster(3) - h.API.Cluster.ReplicaN = 2 - - w := httptest.NewRecorder() - r := test.MustNewHTTPRequest("GET", "/fragment/nodes?index=X&slice=0", nil) - h.ServeHTTP(w, r) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `[{"id":"node2","uri":{"scheme":"http","host":"host2"},"isCoordinator":false},{"id":"node0","uri":{"scheme":"http","host":"host0"},"isCoordinator":false}]`+"\n" { - t.Fatalf("unexpected body: %q", body) - } - - // invalid argument should return BadRequest - w = httptest.NewRecorder() - r = test.MustNewHTTPRequest("GET", "/fragment/nodes?db=X&slice=0", nil) - h.ServeHTTP(w, r) - if w.Code != gohttp.StatusBadRequest { - t.Fatalf("unexpected status code: %d", w.Code) - } - - // index is required - w = httptest.NewRecorder() - r = test.MustNewHTTPRequest("GET", "/fragment/nodes?slice=0", nil) - h.ServeHTTP(w, r) - if w.Code != gohttp.StatusBadRequest { - t.Fatalf("unexpected status code: %d", w.Code) - } -} - -// Ensure the handler can return expvars without panicking. -func TestHandler_Expvars(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Cluster = test.NewCluster(1) - h.API.Holder = hldr.Holder - w := httptest.NewRecorder() - r := test.MustNewHTTPRequest("GET", "/debug/vars", nil) - h.ServeHTTP(w, r) - if w.Code != gohttp.StatusOK { - t.Fatalf("unexpected status code: %d", w.Code) - } -} - -func MustReadAll(r io.Reader) []byte { - buf, err := ioutil.ReadAll(r) - if err != nil { - panic(err) - } - return buf -} - -func TestHandler_RecalculateCaches(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() - - h := test.MustNewHandler() - h.API.Holder = hldr.Holder - h.API.Cluster = test.NewCluster(1) - - w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/recalculate-caches", nil)) - if w.Code != gohttp.StatusNoContent { - t.Fatalf("unexpected status code: %d", w.Code) - } + t.Run("Recalculate Caches", func(t *testing.T) { + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/recalculate-caches", nil)) + if w.Code != gohttp.StatusNoContent { + t.Fatalf("unexpected status code: %d", w.Code) + } + }) } @@ -685,3 +601,12 @@ func mustJSONDecode(t *testing.T, r io.Reader) (ret map[string]interface{}) { } return ret } + +func mustJSONDecodeSlice(t *testing.T, r io.Reader) (ret []interface{}) { + dec := json.NewDecoder(r) + err := dec.Decode(&ret) + if err != nil { + t.Fatalf("decoding response: %v", err) + } + return ret +} From 3c4ba82a4ab3b4047a3c969969ac528b285be6f4 Mon Sep 17 00:00:00 2001 From: Matt Jaffee Date: Fri, 22 Jun 2018 08:08:53 -0500 Subject: [PATCH 09/16] finish conversion of handler tests --- server/handler_test.go | 59 ++++++++++++++++-------------------------- test/pilosa.go | 7 +++++ 2 files changed, 30 insertions(+), 36 deletions(-) diff --git a/server/handler_test.go b/server/handler_test.go index 9ac0f8931..8d001b529 100644 --- a/server/handler_test.go +++ b/server/handler_test.go @@ -551,46 +551,33 @@ func TestHandler_Endpoints(t *testing.T) { } }) -} + t.Run("CORS", func(t *testing.T) { + req := test.MustNewHTTPRequest("OPTIONS", "/index/foo/query", nil) + req.Header.Add("Origin", "http://test/") + req.Header.Add("Access-Control-Request-Method", "POST") -func TestHandler_CORS(t *testing.T) { - t.Skip() // Until test.NewServer() works + w := httptest.NewRecorder() + h.ServeHTTP(w, req) + result := w.Result() - hldr := test.MustOpenHolder() - defer hldr.Close() + // This handler does not support CORS, return Method Not Allowed (405) + if result.StatusCode != 405 { + t.Fatalf("CORS preflight status should be 405, but is %v", result.StatusCode) + } - s := test.NewServer() - s.Handler.API.Holder = hldr.Holder - defer s.Close() + clus := test.MustRunMainWithCluster(t, 1, test.OptAllowedOrigins([]string{"http://test/"})) + w = httptest.NewRecorder() + h := clus[0].Handler.(*http.Handler).Handler + h.ServeHTTP(w, req) + result = w.Result() - // No CORS config present, so should fail - handler := test.MustNewHandler() - - req := test.MustNewHTTPRequest("OPTIONS", "/index/foo/query", nil) - req.Header.Add("Origin", "http://test/") - req.Header.Add("Access-Control-Request-Method", "POST") - - w := httptest.NewRecorder() - handler.ServeHTTP(w, req) - result := w.Result() - - // This handler does not support CORS, return Method Not Allowed (405) - if result.StatusCode != 405 { - t.Fatalf("CORS preflight status should be 405, but is %v", result.StatusCode) - } - - // CORS config should allow preflight response - handler = test.MustNewHandler(http.OptHandlerAllowedOrigins([]string{"http://test/"})) - w = httptest.NewRecorder() - handler.ServeHTTP(w, req) - result = w.Result() - - if result.StatusCode != 200 { - t.Fatalf("CORS preflight status should be 200, but is %v", result.StatusCode) - } - if w.HeaderMap["Access-Control-Allow-Origin"][0] != "http://test/" { - t.Fatal("CORS header not present") - } + if result.StatusCode != 200 { + t.Fatalf("CORS preflight status should be 200, but is %v", result.StatusCode) + } + if w.HeaderMap["Access-Control-Allow-Origin"][0] != "http://test/" { + t.Fatal("CORS header not present") + } + }) } func mustJSONDecode(t *testing.T, r io.Reader) (ret map[string]interface{}) { diff --git a/test/pilosa.go b/test/pilosa.go index 22e89a5df..0f5cdd0d5 100644 --- a/test/pilosa.go +++ b/test/pilosa.go @@ -52,6 +52,13 @@ func OptAntiEntropyInterval(dur time.Duration) MainOpt { } } +func OptAllowedOrigins(origins []string) MainOpt { + return func(m *Main) error { + m.Config.Handler.AllowedOrigins = origins + return nil + } +} + // NewMain returns a new instance of Main with a temporary data directory and random port. func NewMain(opts ...MainOpt) *Main { path, err := ioutil.TempDir("", "pilosa-") From 6dd8b9adc637b9683ed4840ab3e12d03c0a6d0f1 Mon Sep 17 00:00:00 2001 From: Cody Soyland Date: Fri, 22 Jun 2018 10:20:52 -0500 Subject: [PATCH 10/16] Move server initialization to prevent race condition --- http/handler.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/http/handler.go b/http/handler.go index 9b3d17789..58fdcb8b6 100644 --- a/http/handler.go +++ b/http/handler.go @@ -134,11 +134,12 @@ func NewHandler(opts ...HandlerOption) (*Handler, error) { return nil, errors.New("must pass OptHandlerListener") } + handler.server = &http.Server{Handler: handler} + return handler, nil } func (h *Handler) Serve() error { - h.server = &http.Server{Handler: h} err := h.server.Serve(h.ln) if err != nil && err.Error() != "http: Server closed" { h.Logger.Printf("HTTP handler terminated with error: %s\n", err) From 6b57369b511ec53cc9df3b3a9d3ca4118df1081c Mon Sep 17 00:00:00 2001 From: Matt Jaffee Date: Fri, 22 Jun 2018 10:44:36 -0500 Subject: [PATCH 11/16] fix stats tests --- server/handler_test.go | 4 +- stats_test.go | 184 +++++++++++++++++------------------------ 2 files changed, 79 insertions(+), 109 deletions(-) diff --git a/server/handler_test.go b/server/handler_test.go index 8d001b529..2b0801585 100644 --- a/server/handler_test.go +++ b/server/handler_test.go @@ -38,6 +38,8 @@ import ( func TestHandler_Endpoints(t *testing.T) { cmd := test.MustRunMainWithCluster(t, 1)[0] h := cmd.Handler.(*http.Handler).Handler + holder := cmd.Server.Holder() + hldr := test.Holder{holder} t.Run("Not Found", func(t *testing.T) { w := httptest.NewRecorder() @@ -57,8 +59,6 @@ func TestHandler_Endpoints(t *testing.T) { } }) - holder := cmd.Server.Holder() - hldr := test.Holder{holder} i0 := hldr.MustCreateIndexIfNotExists("i0", pilosa.IndexOptions{}) i1 := hldr.MustCreateIndexIfNotExists("i1", pilosa.IndexOptions{}) if f, err := i0.CreateFieldIfNotExists("f1", pilosa.FieldOptions{}); err != nil { diff --git a/stats_test.go b/stats_test.go index 1f23dd3f8..7abcfa9c3 100644 --- a/stats_test.go +++ b/stats_test.go @@ -16,12 +16,13 @@ package pilosa_test import ( "context" - "net/http" + "net/http/httptest" "strings" "testing" "time" "github.com/pilosa/pilosa" + "github.com/pilosa/pilosa/http" "github.com/pilosa/pilosa/test" ) @@ -207,120 +208,89 @@ func TestStatsCount_SetProfileAttrs(t *testing.T) { } } -func TestStatsCount_CreateIndex(t *testing.T) { - t.Skip() - hldr := test.MustOpenHolder() - defer hldr.Close() - s := test.NewServer() - s.Handler.API.Holder = hldr.Holder - defer s.Close() - called := false - s.Handler.API.Holder.Stats = &MockStats{ - mockCount: func(name string, value int64, rate float64) { - if name != "createIndex" { - t.Errorf("Expected createIndex, Results %s", name) - } +func TestStatsCount_APICalls(t *testing.T) { + cmd := test.MustRunMainWithCluster(t, 1)[0] + h := cmd.Handler.(*http.Handler).Handler + holder := cmd.Server.Holder() + hldr := test.Holder{Holder: holder} - called = true - }, - } - http.DefaultClient.Do(test.MustNewHTTPRequest("POST", s.URL+"/index/i", nil)) - if !called { - t.Error("Count isn't called") - } -} + t.Run("create index", func(t *testing.T) { + called := false + hldr.Stats = &MockStats{ + mockCount: func(name string, value int64, rate float64) { + if name != "createIndex" { + t.Errorf("Expected createIndex, Results %s", name) + } + called = true + }, + } + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i", strings.NewReader(""))) + if !called { + t.Error("Count isn't called") + } + }) -func TestStatsCount_DeleteIndex(t *testing.T) { - t.Skip() - hldr := test.MustOpenHolder() - defer hldr.Close() + t.Run("create field", func(t *testing.T) { + called := false + hldr.Stats = &MockStats{ + mockCountWithTags: func(name string, value int64, rate float64, index []string) { + if name != "createField" { + t.Errorf("Expected createField, Results %s", name) + } + if index[0] != "index:i" { + t.Errorf("Expected index:i, Results %s", index) + } - s := test.NewServer() - s.Handler.API.Holder = hldr.Holder - defer s.Close() + called = true + }, + } + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i/field/f", strings.NewReader(""))) + if !called { + t.Error("Count isn't called") + } + }) - // Create index. - if _, err := hldr.CreateIndexIfNotExists("i", pilosa.IndexOptions{}); err != nil { - t.Fatal(err) - } - called := false - s.Handler.API.Holder.Stats = &MockStats{ - mockCount: func(name string, value int64, rate float64) { - if name != "deleteIndex" { - t.Errorf("Expected deleteIndex, Results %s", name) - } + t.Run("delete field", func(t *testing.T) { + called := false + hldr.Stats = &MockStats{ + mockCountWithTags: func(name string, value int64, rate float64, index []string) { + if name != "deleteField" { + t.Errorf("Expected deleteField, Results %s", name) + } + if index[0] != "index:i" { + t.Errorf("Expected index:i, Results %s", index) + } - called = true - }, - } - http.DefaultClient.Do(test.MustNewHTTPRequest("DELETE", s.URL+"/index/i", strings.NewReader(""))) - if !called { - t.Error("Count isn't called") - } -} + called = true + }, + } + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("DELETE", "/index/i/field/f", strings.NewReader(""))) + if !called { + t.Error("Count isn't called") + } + }) -func TestStatsCount_CreateField(t *testing.T) { - t.Skip() - hldr := test.MustOpenHolder() - defer hldr.Close() + t.Run("delete index", func(t *testing.T) { + called := false + hldr.Stats = &MockStats{ + mockCount: func(name string, value int64, rate float64) { + if name != "deleteIndex" { + t.Errorf("Expected deleteIndex, Results %s", name) + } - s := test.NewServer() - s.Handler.API.Holder = hldr.Holder - defer s.Close() + called = true + }, + } + w := httptest.NewRecorder() + h.ServeHTTP(w, test.MustNewHTTPRequest("DELETE", "/index/i", strings.NewReader(""))) + if !called { + t.Error("Count isn't called") + } + }) - // Create index. - if _, err := hldr.CreateIndexIfNotExists("i", pilosa.IndexOptions{}); err != nil { - t.Fatal(err) - } - called := false - s.Handler.API.Holder.Stats = &MockStats{ - mockCountWithTags: func(name string, value int64, rate float64, index []string) { - if name != "createField" { - t.Errorf("Expected createField, Results %s", name) - } - if index[0] != "index:i" { - t.Errorf("Expected index:i, Results %s", index) - } - - called = true - }, - } - http.DefaultClient.Do(test.MustNewHTTPRequest("POST", s.URL+"/index/i/field/f", nil)) - if !called { - t.Error("Count isn't called") - } -} - -func TestStatsCount_DeleteField(t *testing.T) { - t.Skip() - hldr := test.MustOpenHolder() - defer hldr.Close() - - s := test.NewServer() - s.Handler.API.Holder = hldr.Holder - defer s.Close() - called := false - // Create index. - indx, _ := hldr.CreateIndexIfNotExists("i", pilosa.IndexOptions{}) - if _, err := indx.CreateFieldIfNotExists("test", pilosa.FieldOptions{}); err != nil { - t.Fatal(err) - } - s.Handler.API.Holder.Stats = &MockStats{ - mockCountWithTags: func(name string, value int64, rate float64, index []string) { - if name != "deleteField" { - t.Errorf("Expected deleteField, Results %s", name) - } - if index[0] != "index:i" { - t.Errorf("Expected index:i, Results %s", index) - } - - called = true - }, - } - http.DefaultClient.Do(test.MustNewHTTPRequest("DELETE", s.URL+"/index/i/field/f", strings.NewReader(""))) - if !called { - t.Error("Count isn't called") - } } type MockStats struct { From ba9112507daff1450f693e79da4e6fb5314e0fde Mon Sep 17 00:00:00 2001 From: Matt Jaffee Date: Fri, 22 Jun 2018 12:57:28 -0500 Subject: [PATCH 12/16] fix server/handler_test.go for newpql --- executor.go | 6 ++++++ server/handler_test.go | 30 +++++++++++++++--------------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/executor.go b/executor.go index 027bf50ad..1fbf56960 100644 --- a/executor.go +++ b/executor.go @@ -1618,6 +1618,9 @@ func (e *Executor) translateCall(index string, idx *Index, c *pql.Call) error { // Translate row key, if field is specified & key exists. if fieldName != "" { field := idx.Field(fieldName) + if field == nil { + return ErrFieldNotFound + } if field.Keys() { if value := callArgString(c, rowKey); value != "" { ids, err := e.TranslateStore.TranslateRowsToUint64(index, fieldName, []string{value}) @@ -1659,6 +1662,9 @@ func (e *Executor) translateResult(index string, idx *Index, call *pql.Call, res case []Pair: if fieldName := callArgString(call, "_field"); fieldName != "" { field := idx.Field(fieldName) + if field == nil { + return nil, ErrFieldNotFound + } if field.Keys() { other := make([]Pair, len(result)) for i := range result { diff --git a/server/handler_test.go b/server/handler_test.go index 2b0801585..0320b6f09 100644 --- a/server/handler_test.go +++ b/server/handler_test.go @@ -133,7 +133,7 @@ func TestHandler_Endpoints(t *testing.T) { t.Run("Slices args", func(t *testing.T) { w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/query?slices=0,1", strings.NewReader("Count(Bitmap(field=f0, row=30))"))) + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/query?slices=0,1", strings.NewReader("Count(Row(f0=30))"))) if w.Code != gohttp.StatusOK { t.Fatalf("unexpected status code: %d %s", w.Code, w.Body.String()) } else if body := w.Body.String(); body != `{"results":[2]}`+"\n" { @@ -144,7 +144,7 @@ func TestHandler_Endpoints(t *testing.T) { t.Run("Slices args protobuf", func(t *testing.T) { // Generate request body. reqBody, err := proto.Marshal(&internal.QueryRequest{ - Query: "Count(Bitmap(field=f0, row=30))", + Query: "Count(Row(f0=30))", Slices: []uint64{0, 1}, }) if err != nil { @@ -168,7 +168,7 @@ func TestHandler_Endpoints(t *testing.T) { t.Run("Query args error", func(t *testing.T) { w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/query?slices=a,b", strings.NewReader("Count(Bitmap(field=f0, row=30))"))) + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/query?slices=a,b", strings.NewReader("Count(Row(f0=30))"))) if w.Code != gohttp.StatusBadRequest { t.Fatalf("unexpected status code: %d", w.Code) } else if body := w.Body.String(); body != `{"error":"invalid slice argument"}`+"\n" { @@ -178,7 +178,7 @@ func TestHandler_Endpoints(t *testing.T) { t.Run("Query params err", func(t *testing.T) { w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/query?slices=0,1&db=sample", strings.NewReader("Count(Bitmap(field=f0, row=30))"))) + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/query?slices=0,1&db=sample", strings.NewReader("Count(Row(f0=30))"))) if w.Code != gohttp.StatusBadRequest { t.Fatalf("unexpected status code: %d", w.Code) } else if body := w.Body.String(); body != `{"error":"db is not a valid argument"}`+"\n" { @@ -188,7 +188,7 @@ func TestHandler_Endpoints(t *testing.T) { t.Run("Uint64 protobuf", func(t *testing.T) { w := httptest.NewRecorder() - r := test.MustNewHTTPRequest("POST", "/index/i0/query", strings.NewReader("Count(Bitmap(field=f0, row=30))")) + r := test.MustNewHTTPRequest("POST", "/index/i0/query", strings.NewReader("Count(Row(f0=30))")) r.Header.Set("Accept", "application/x-protobuf") h.ServeHTTP(w, r) if w.Code != gohttp.StatusOK { @@ -205,9 +205,9 @@ func TestHandler_Endpoints(t *testing.T) { } }) - t.Run("Bitmap JSON", func(t *testing.T) { + t.Run("Row JSON", func(t *testing.T) { w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/query", strings.NewReader("Bitmap(field=f0, row=30)"))) + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/query", strings.NewReader("Row(f0=30)"))) if w.Code != gohttp.StatusOK { t.Fatalf("unexpected status code: %d", w.Code) } else if body := w.Body.String(); body != `{"results":[{"attrs":{},"columns":[1048577,1048578,3145732]}]}`+"\n" { @@ -226,7 +226,7 @@ func TestHandler_Endpoints(t *testing.T) { t.Run("ColumnAttrs_JSON", func(t *testing.T) { w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/query?columnAttrs=true", strings.NewReader("Bitmap(field=f0, row=30)"))) + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/query?columnAttrs=true", strings.NewReader("Row(f0=30)"))) if w.Code != gohttp.StatusOK { t.Fatalf("unexpected status code: %d. body: %s", w.Code, w.Body.String()) } else if body := w.Body.String(); body != `{"results":[{"attrs":{"a":"b","c":1,"d":true},"columns":[1048577,1048578,3145732]}],"columnAttrs":[{"id":1048577,"attrs":{"x":"y"}},{"id":1048578,"attrs":{"y":123,"z":false}}]}`+"\n" { @@ -236,7 +236,7 @@ func TestHandler_Endpoints(t *testing.T) { t.Run("Row pbuf", func(t *testing.T) { w := httptest.NewRecorder() - r := test.MustNewHTTPRequest("POST", "/index/i0/query", strings.NewReader("Bitmap(field=f0, row=30)")) + r := test.MustNewHTTPRequest("POST", "/index/i0/query", strings.NewReader("Row(f0=30)")) r.Header.Set("Accept", "application/x-protobuf") h.ServeHTTP(w, r) if w.Code != gohttp.StatusOK { @@ -264,7 +264,7 @@ func TestHandler_Endpoints(t *testing.T) { t.Run("Row columnattrs protobuf", func(t *testing.T) { // Encode request body. buf, err := proto.Marshal(&internal.QueryRequest{ - Query: "Bitmap(field=f0, row=30)", + Query: "Row(f0=30)", ColumnAttrs: true, }) if err != nil { @@ -311,7 +311,7 @@ func TestHandler_Endpoints(t *testing.T) { t.Run("Query Pairs JSON", func(t *testing.T) { w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/query", strings.NewReader(`TopN(field=f0, n=2)`))) + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/query", strings.NewReader(`TopN(f0, n=2)`))) if w.Code != gohttp.StatusOK { t.Fatalf("unexpected status code: %d", w.Code) } else if body := w.Body.String(); body != `{"results":[[{"id":30,"count":3},{"id":31,"count":1}]]}`+"\n" { @@ -321,7 +321,7 @@ func TestHandler_Endpoints(t *testing.T) { t.Run("Query Pairs protobuf", func(t *testing.T) { w := httptest.NewRecorder() - r := test.MustNewHTTPRequest("POST", "/index/i0/query", strings.NewReader(`TopN(field=f0, n=2)`)) + r := test.MustNewHTTPRequest("POST", "/index/i0/query", strings.NewReader(`TopN(f0, n=2)`)) r.Header.Set("Accept", "application/x-protobuf") h.ServeHTTP(w, r) if w.Code != gohttp.StatusOK { @@ -340,7 +340,7 @@ func TestHandler_Endpoints(t *testing.T) { t.Run("Query err JSON", func(t *testing.T) { w := httptest.NewRecorder() - h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/query", strings.NewReader(`Bitmap(row=30)`))) + h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/i0/query", strings.NewReader(`Row(row=30)`))) if w.Code != gohttp.StatusBadRequest { t.Fatalf("unexpected status code: %d", w.Code) } else if body := w.Body.String(); body != `{"error":"executing: field not found"}`+"\n" { @@ -350,7 +350,7 @@ func TestHandler_Endpoints(t *testing.T) { t.Run("Query err protobuf", func(t *testing.T) { w := httptest.NewRecorder() - r := test.MustNewHTTPRequest("POST", "/index/i0/query", strings.NewReader(`Bitmap(row=30)`)) + r := test.MustNewHTTPRequest("POST", "/index/i0/query", strings.NewReader(`Row(row=30)`)) r.Header.Set("Accept", "application/x-protobuf") h.ServeHTTP(w, r) if w.Code != gohttp.StatusBadRequest { @@ -378,7 +378,7 @@ func TestHandler_Endpoints(t *testing.T) { h.ServeHTTP(w, test.MustNewHTTPRequest("POST", "/index/idx0/query?slices=0,1", strings.NewReader("bad_fn("))) if w.Code != gohttp.StatusBadRequest { t.Fatalf("unexpected status code: %d", w.Code) - } else if body := w.Body.String(); body != `{"error":"parsing: expected comma, right paren, or identifier, found \"\" occurred at line 1, char 8"}`+"\n" { + } else if body := w.Body.String(); body != `{"error":"parsing: parsing: \nparse error near IDENT (line 1 symbol 1 - line 1 symbol 4):\n\"bad\"\n"}`+"\n" { t.Fatalf("unexpected body: %s", body) } }) From 5d28d2dc3103b666e32c7197d4f310351c04bc81 Mon Sep 17 00:00:00 2001 From: Matt Jaffee Date: Fri, 22 Jun 2018 13:00:52 -0500 Subject: [PATCH 13/16] skip new tests which use test.NewServer --- ctl/import_test.go | 2 +- executor_test.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ctl/import_test.go b/ctl/import_test.go index eadbdb47f..3d48418cd 100644 --- a/ctl/import_test.go +++ b/ctl/import_test.go @@ -177,7 +177,7 @@ func GetIO(buf bytes.Buffer) (io.Reader, io.Writer, io.Writer) { } func TestImportCommand_BugOverwriteValue(t *testing.T) { - + t.Skip("test.NewServer broken") buf := bytes.Buffer{} stdin, stdout, stderr := GetIO(buf) cm := NewImportCommand(stdin, stdout, stderr) diff --git a/executor_test.go b/executor_test.go index 71e5b4393..e4500b9a5 100644 --- a/executor_test.go +++ b/executor_test.go @@ -1310,6 +1310,7 @@ func TestExecutor_Execute_Remote_TopN(t *testing.T) { // Ensure a remote query can set RowAttrs func TestExecutor_Execute_Remote_SetRowAttrs(t *testing.T) { + t.Skip("test.NewServer broken") c := pilosa.NewTestCluster(2) // Create secondary server and update second cluster node. From c9e6d36f94fa9a4815e0ec12a1919cf49e2cae83 Mon Sep 17 00:00:00 2001 From: Matt Jaffee Date: Fri, 22 Jun 2018 13:44:50 -0500 Subject: [PATCH 14/16] fix some of the client tests --- http/client_test.go | 51 +++++++++++++-------------------------------- 1 file changed, 14 insertions(+), 37 deletions(-) diff --git a/http/client_test.go b/http/client_test.go index 03b5b8217..5ac29ec2c 100644 --- a/http/client_test.go +++ b/http/client_test.go @@ -219,23 +219,17 @@ func TestClient_MultiNode(t *testing.T) { // Ensure client can bulk import data. func TestClient_Import(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() + cmd := test.MustRunMainWithCluster(t, 1)[0] + host := cmd.Server.Addr().String() + holder := cmd.Server.Holder() + hldr := test.Holder{Holder: holder} // Load bitmap into cache to ensure cache gets updated. hldr.SetBit("i", "f", 1, 0) // set a bit so the view gets created. hldr.Row("i", "f", 0) - s := test.NewServer() - defer s.Close() - s.Handler.API.Cluster = test.NewCluster(1) - s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() - s.Handler.API.Holder = hldr.Holder - // Send import request. - c := MustNewClient(s.Host(), defaultClient) + c := MustNewClient(host, defaultClient) if err := c.Import(context.Background(), "i", "f", 0, []pilosa.Bit{ {RowID: 0, ColumnID: 1}, {RowID: 0, ColumnID: 5}, @@ -255,13 +249,12 @@ func TestClient_Import(t *testing.T) { // Ensure client can bulk import value data. func TestClient_ImportValue(t *testing.T) { - t.Skip() // Until test.NewServer() works - - hldr := test.MustOpenHolder() - defer hldr.Close() + cmd := test.MustRunMainWithCluster(t, 1)[0] + host := cmd.Server.Addr().String() + holder := cmd.Server.Holder() + hldr := test.Holder{Holder: holder} fldName := "f" - fo := pilosa.FieldOptions{ Type: pilosa.FieldTypeInt, Min: -100, @@ -275,14 +268,8 @@ func TestClient_ImportValue(t *testing.T) { t.Fatal(err) } - s := test.NewServer() - defer s.Close() - s.Handler.API.Cluster = test.NewCluster(1) - s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() - s.Handler.API.Holder = hldr.Holder - // Send import request. - c := MustNewClient(s.Host(), defaultClient) + c := MustNewClient(host, defaultClient) if err := c.ImportValue(context.Background(), "i", "f", 0, []pilosa.FieldValue{ {ColumnID: 1, Value: -10}, {ColumnID: 2, Value: 20}, @@ -334,26 +321,16 @@ func TestClient_ImportValue(t *testing.T) { // Ensure client can retrieve a list of all checksums for blocks in a fragment. func TestClient_FragmentBlocks(t *testing.T) { - t.Skip() // Until test.NewServer() works + cmd := test.MustRunMainWithCluster(t, 1)[0] + holder := cmd.Server.Holder() + hldr := test.Holder{Holder: holder} - hldr := test.MustOpenHolder() - defer hldr.Close() - - // Set two bits on blocks 0 & 3. hldr.SetBit("i", "f", 0, 1) hldr.SetBit("i", "f", pilosa.HashBlockSize*3, 100) // Set a bit on a different slice. hldr.SetBit("i", "f", 0, 1) - - s := test.NewServer() - defer s.Close() - s.Handler.API.Cluster = test.NewCluster(1) - s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() - s.Handler.API.Holder = hldr.Holder - - // Retrieve blocks. - c := MustNewClient(s.Host(), defaultClient) + c := MustNewClient(cmd.Server.Addr().String(), defaultClient) blocks, err := c.FragmentBlocks(context.Background(), nil, "i", "f", 0) if err != nil { t.Fatal(err) From 6093064ac0b00f295cf2184873087e7e04be6d92 Mon Sep 17 00:00:00 2001 From: Matt Jaffee Date: Fri, 22 Jun 2018 16:26:45 -0500 Subject: [PATCH 15/16] remove unecessary test and convert import test --- ctl/import_test.go | 17 +++++------------ fragment_internal_test.go | 33 --------------------------------- server/handler_test.go | 2 +- 3 files changed, 6 insertions(+), 46 deletions(-) diff --git a/ctl/import_test.go b/ctl/import_test.go index 3d48418cd..717c40342 100644 --- a/ctl/import_test.go +++ b/ctl/import_test.go @@ -177,7 +177,8 @@ func GetIO(buf bytes.Buffer) (io.Reader, io.Writer, io.Writer) { } func TestImportCommand_BugOverwriteValue(t *testing.T) { - t.Skip("test.NewServer broken") + cmd := test.MustRunMainWithCluster(t, 1)[0] + buf := bytes.Buffer{} stdin, stdout, stderr := GetIO(buf) cm := NewImportCommand(stdin, stdout, stderr) @@ -188,18 +189,10 @@ func TestImportCommand_BugOverwriteValue(t *testing.T) { t.Fatal(err) } - hldr := test.MustOpenHolder() - defer hldr.Close() - s := test.NewServer() - defer s.Close() + cm.Host = cmd.Server.Addr().String() - s.Handler.API.Cluster = test.NewCluster(1) - s.Handler.API.Cluster.Nodes[0].URI = s.HostURI() - s.Handler.API.Holder = hldr.Holder - cm.Host = s.Host() - - http.DefaultClient.Do(MustNewHTTPRequest("POST", s.URL+"/index/i", strings.NewReader(""))) - http.DefaultClient.Do(MustNewHTTPRequest("POST", s.URL+"/index/i/field/f", strings.NewReader(`{"options":{"type": "int", "min": 0, "max":2147483648 }}`))) + http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+cm.Host+"/index/i", strings.NewReader(""))) + http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+cm.Host+"/index/i/field/f", strings.NewReader(`{"options":{"type": "int", "min": 0, "max":2147483648 }}`))) cm.Index = "i" cm.Field = "f" diff --git a/fragment_internal_test.go b/fragment_internal_test.go index 6da3ded7e..6c733ba4c 100644 --- a/fragment_internal_test.go +++ b/fragment_internal_test.go @@ -213,39 +213,6 @@ func TestFragment_SetValue(t *testing.T) { t.Fatal(err) } }) - t.Run("Crash", func(t *testing.T) { - f := mustOpenFragment("i", "f", ViewStandard, 0, "") - defer f.Close() - - // Set value. - if changed, err := f.setValue(0, 32, 17); err != nil { - t.Fatal(err) - } else if !changed { - t.Fatal("expected change") - } - - if changed, err := f.setValue(0, 32, 16); err != nil { - t.Fatal(err) - } else if !changed { - t.Fatal("expected change") - } - - if changed, err := f.setValue(0, 32, 19); err != nil { - t.Fatal(err) - } else if !changed { - t.Fatal("expected change") - } - - // Read value. - if value, exists, err := f.value(0, 32); err != nil { - t.Fatal(err) - } else if value != 19 { - t.Fatalf("unexpected value: %d", value) - } else if !exists { - t.Fatal("expected to exist") - } - }) - } // Ensure a fragment can sum values. diff --git a/server/handler_test.go b/server/handler_test.go index 0320b6f09..070a3176a 100644 --- a/server/handler_test.go +++ b/server/handler_test.go @@ -39,7 +39,7 @@ func TestHandler_Endpoints(t *testing.T) { cmd := test.MustRunMainWithCluster(t, 1)[0] h := cmd.Handler.(*http.Handler).Handler holder := cmd.Server.Holder() - hldr := test.Holder{holder} + hldr := test.Holder{Holder: holder} t.Run("Not Found", func(t *testing.T) { w := httptest.NewRecorder() From 8878b02345d8b74cdadc2077df710d7ad8b18262 Mon Sep 17 00:00:00 2001 From: Matt Jaffee Date: Mon, 25 Jun 2018 11:08:00 -0500 Subject: [PATCH 16/16] cleanup - address review feedback --- api.go | 2 +- ctl/import_test.go | 7 +++---- test/handler.go | 7 ------- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/api.go b/api.go index c02e67b79..28248230c 100644 --- a/api.go +++ b/api.go @@ -50,7 +50,7 @@ type API struct { } // APIOption is a functional option type for pilosa.API -type APIOption func(s *API) error +type APIOption func(*API) error func OptAPIServer(s *Server) APIOption { return func(a *API) error { diff --git a/ctl/import_test.go b/ctl/import_test.go index 717c40342..5500fdadf 100644 --- a/ctl/import_test.go +++ b/ctl/import_test.go @@ -87,11 +87,10 @@ func TestImportCommand_RunValue(t *testing.T) { } cmd := test.MustRunMainWithCluster(t, 1)[0] - hostport := cmd.Server.URI.HostPort() - cm.Host = hostport + cm.Host = cmd.Server.URI.HostPort() - http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+hostport+"/index/i", strings.NewReader(""))) - http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+hostport+"/index/i/field/f", strings.NewReader(`{"options":{"type": "int", "min": 0, "max": 100}}`))) + http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+cm.Host+"/index/i", strings.NewReader(""))) + http.DefaultClient.Do(MustNewHTTPRequest("POST", "http://"+cm.Host+"/index/i/field/f", strings.NewReader(`{"options":{"type": "int", "min": 0, "max": 100}}`))) cm.Index = "i" cm.Field = "f" diff --git a/test/handler.go b/test/handler.go index 8b58d5a6e..048d9b883 100644 --- a/test/handler.go +++ b/test/handler.go @@ -46,13 +46,6 @@ func NewHandler(opts ...http.HandlerOption) (*Handler, error) { Handler: handler, } - //h.API, err = pilosa.NewAPI(OptAPIServer(s)) - if err != nil { - return nil, err - } - h.Handler.API = h.API - h.Handler.API.Executor = &h.Executor - // Handler test messages can no-op. h.API.Broadcaster = pilosa.NopBroadcaster