<commit_before>package core

import (
	"fmt"
	"log"
	"net/http"
	"net/http/pprof"
	"os"
	"runtime"
	"strconv"

	"github.com/mdlayher/wavepipe/api"
	"github.com/mdlayher/wavepipe/api/auth"
	"github.com/mdlayher/wavepipe/config"

	"github.com/go-martini/martini"
	"github.com/martini-contrib/gzip"
	"github.com/martini-contrib/render"
)

// apiRouter sets up the instance of martini
func apiRouter(apiKillChan chan struct{}) {
	log.Println("api: starting...")

	// Initialize martini
	m := martini.New()

	// Set up middleware
	// GZIP all requests to drastically reduce size
	m.Use(gzip.All())
	m.Use(render.Renderer(render.Options{
		// Output human-readable JSON. GZIP will essentially negate the size increase, and this
		// makes the API much more developer-friendly
		IndentJSON: true,
	}))

	// Enable graceful shutdown when triggered by manager
	stopAPI := false
	m.Use(func(req *http.Request, res http.ResponseWriter, r render.Render) {
		// On debug, log everything
		if os.Getenv("WAVEPIPE_DEBUG") == "1" {
			log.Println(req.Header)
			log.Println(req.URL)
		}

		// Send a Server header with all responses
		res.Header().Set("Server", fmt.Sprintf("%s/%s (%s_%s)", App, Version, runtime.GOOS, runtime.GOARCH))

		// If API is stopping, render a HTTP 503
		if stopAPI {
			r.JSON(503, api.Error{
				Code:    503,
				Message: "service is shutting down",
			})
			return
		}
	})

	// Serve static content from web directory
	m.Use(martini.Static("web", martini.StaticOptions{
		// Use the index in the web directory
		IndexFile: "index.html",
		// Skip logging output
		SkipLogging: true,
	}))

	// Authenticate all API calls
	m.Use(func(req *http.Request, res http.ResponseWriter, c martini.Context, r render.Render) {
		// Use factory to determine the proper authentication method for this path
		method := auth.Factory(req.URL.Path)
		if method == nil {
			// If no method returned, path is not authenticated
			return
		}

		// Attempt authentication
		user, session, clientErr, serverErr := method.Authenticate(req)

		// Check for client error
		if clientErr != nil {
			// If debug mode, and no username or password, send a WWW-Authenticate header to prompt request
			// This allows for manual exploration of the API if needed
			if os.Getenv("WAVEPIPE_DEBUG") == "1" && (clientErr == auth.ErrNoUsername || clientErr == auth.ErrNoPassword) {
				res.Header().Set("WWW-Authenticate", "Basic")
			}

			r.JSON(401, api.Error{
				Code:    401,
				Message: "authentication failed: " + clientErr.Error(),
			})
			return
		}

		// Check for server error
		if serverErr != nil {
			log.Println(serverErr)

			r.JSON(500, api.Error{
				Code:    500,
				Message: "server error",
			})
			return
		}

		// Successful login, map session user and session to martini context
		c.Map(user)
		c.Map(session)

		// Print information about this API call
		log.Printf("api: [%s] %s", req.RemoteAddr, req.URL.Path)
	})

	// Set up API routes
	r := martini.NewRouter()

	// Set up API information route
	r.Get("/api", api.APIInfo)

	// Set up API group routes, with API version parameter
	r.Group("/api/:version", func(r martini.Router) {
		// Root API, containing information and help
		r.Get("", api.APIInfo)

		// Albums API
		r.Get("/albums", api.GetAlbums)
		r.Get("/albums/:id", api.GetAlbums)

		// Art API
		r.Get("/art", api.GetArt)
		r.Get("/art/:id", api.GetArt)

		// Artists API
		r.Get("/artists", api.GetArtists)
		r.Get("/artists/:id", api.GetArtists)

		// Folders API
		r.Get("/folders", api.GetFolders)
		r.Get("/folders/:id", api.GetFolders)

		// LastFM API
		r.Get("/lastfm", api.GetLastFM)
		r.Get("/lastfm/:action", api.GetLastFM)
		r.Get("/lastfm/:action/:id", api.GetLastFM)

		// Login API
		r.Get("/login", api.GetLogin)

		// Logout API
		r.Get("/logout", api.GetLogout)

		// Search API
		r.Get("/search", api.GetSearch)
		r.Get("/search/:query", api.GetSearch)

		// Songs API
		r.Get("/songs", api.GetSongs)
		r.Get("/songs/:id", api.GetSongs)

		// Status API
		r.Get("/status", api.GetStatus)

		// Stream API
		r.Get("/stream", api.GetStream)
		r.Get("/stream/:id", api.GetStream)

		// Transcode API
		r.Get("/transcode", api.GetTranscode)
		r.Get("/transcode/:id", api.GetTranscode)
	})

	// On debug mode, enable pprof debug endpoints
	// Thanks: https://github.com/go-martini/martini/issues/228
	if os.Getenv("WAVEPIPE_DEBUG") == "1" {
		r.Group("/debug/pprof", func(r martini.Router) {
			r.Any("/", pprof.Index)
			r.Any("/cmdline", pprof.Cmdline)
			r.Any("/profile", pprof.Profile)
			r.Any("/symbol", pprof.Symbol)
			r.Any("/block", pprof.Handler("block").ServeHTTP)
			r.Any("/heap", pprof.Handler("heap").ServeHTTP)
			r.Any("/goroutine", pprof.Handler("goroutine").ServeHTTP)
			r.Any("/threadcreate", pprof.Handler("threadcreate").ServeHTTP)
		})
	}

	// Add router action, start server
	m.Action(r.Handle)
	go func() {
		// Load config
		conf, err := config.C.Load()
		if err != nil {
			log.Println(err)
			return
		}

		// Start server
		log.Println("api: listening on port", conf.Port)
		if err := http.ListenAndServe(":"+strconv.Itoa(conf.Port), m); err != nil {
			log.Println(err)
		}
	}()

	// Trigger events via channel
	for {
		select {
		// Stop API
		case <-apiKillChan:
			// Stop serving requests
			stopAPI = true

			// Inform manager that shutdown is complete
			log.Println("api: stopped!")
			apiKillChan <- struct{}{}
			return
		}
	}
}
<commit_msg>core/apiRouter: Split out API routes function for testing<commit_after>package core

import (
	"fmt"
	"log"
	"net/http"
	"net/http/pprof"
	"os"
	"runtime"
	"strconv"

	"github.com/mdlayher/wavepipe/api"
	"github.com/mdlayher/wavepipe/api/auth"
	"github.com/mdlayher/wavepipe/config"

	"github.com/go-martini/martini"
	"github.com/martini-contrib/gzip"
	"github.com/martini-contrib/render"
)

// apiRouter sets up the instance of martini
func apiRouter(apiKillChan chan struct{}) {
	log.Println("api: starting...")

	// Initialize martini
	m := martini.New()

	// Set up middleware
	// GZIP all requests to drastically reduce size
	m.Use(gzip.All())
	m.Use(render.Renderer(render.Options{
		// Output human-readable JSON. GZIP will essentially negate the size increase, and this
		// makes the API much more developer-friendly
		IndentJSON: true,
	}))

	// Enable graceful shutdown when triggered by manager
	stopAPI := false
	m.Use(func(req *http.Request, res http.ResponseWriter, r render.Render) {
		// On debug, log everything
		if os.Getenv("WAVEPIPE_DEBUG") == "1" {
			log.Println(req.Header)
			log.Println(req.URL)
		}

		// Send a Server header with all responses
		res.Header().Set("Server", fmt.Sprintf("%s/%s (%s_%s)", App, Version, runtime.GOOS, runtime.GOARCH))

		// If API is stopping, render a HTTP 503
		if stopAPI {
			r.JSON(503, api.Error{
				Code:    503,
				Message: "service is shutting down",
			})
			return
		}
	})

	// Serve static content from web directory
	m.Use(martini.Static("web", martini.StaticOptions{
		// Use the index in the web directory
		IndexFile: "index.html",
		// Skip logging output
		SkipLogging: true,
	}))

	// Authenticate all API calls
	m.Use(func(req *http.Request, res http.ResponseWriter, c martini.Context, r render.Render) {
		// Use factory to determine the proper authentication method for this path
		method := auth.Factory(req.URL.Path)
		if method == nil {
			// If no method returned, path is not authenticated
			return
		}

		// Attempt authentication
		user, session, clientErr, serverErr := method.Authenticate(req)

		// Check for client error
		if clientErr != nil {
			// If debug mode, and no username or password, send a WWW-Authenticate header to prompt request
			// This allows for manual exploration of the API if needed
			if os.Getenv("WAVEPIPE_DEBUG") == "1" && (clientErr == auth.ErrNoUsername || clientErr == auth.ErrNoPassword) {
				res.Header().Set("WWW-Authenticate", "Basic")
			}

			r.JSON(401, api.Error{
				Code:    401,
				Message: "authentication failed: " + clientErr.Error(),
			})
			return
		}

		// Check for server error
		if serverErr != nil {
			log.Println(serverErr)

			r.JSON(500, api.Error{
				Code:    500,
				Message: "server error",
			})
			return
		}

		// Successful login, map session user and session to martini context
		c.Map(user)
		c.Map(session)

		// Print information about this API call
		log.Printf("api: [%s] %s", req.RemoteAddr, req.URL.Path)
	})

	// Set up API routes
	r := martini.NewRouter()

	// Set up API information route
	r.Get("/api", api.APIInfo)

	// Set up API group routes, with API version parameter
	r.Group("/api/:version", apiRoutes)

	// On debug mode, enable pprof debug endpoints
	// Thanks: https://github.com/go-martini/martini/issues/228
	if os.Getenv("WAVEPIPE_DEBUG") == "1" {
		r.Group("/debug/pprof", func(r martini.Router) {
			r.Any("/", pprof.Index)
			r.Any("/cmdline", pprof.Cmdline)
			r.Any("/profile", pprof.Profile)
			r.Any("/symbol", pprof.Symbol)
			r.Any("/block", pprof.Handler("block").ServeHTTP)
			r.Any("/heap", pprof.Handler("heap").ServeHTTP)
			r.Any("/goroutine", pprof.Handler("goroutine").ServeHTTP)
			r.Any("/threadcreate", pprof.Handler("threadcreate").ServeHTTP)
		})
	}

	// Add router action, start server
	m.Action(r.Handle)
	go func() {
		// Load config
		conf, err := config.C.Load()
		if err != nil {
			log.Println(err)
			return
		}

		// Start server
		log.Println("api: listening on port", conf.Port)
		if err := http.ListenAndServe(":"+strconv.Itoa(conf.Port), m); err != nil {
			log.Println(err)
		}
	}()

	// Trigger events via channel
	for {
		select {
		// Stop API
		case <-apiKillChan:
			// Stop serving requests
			stopAPI = true

			// Inform manager that shutdown is complete
			log.Println("api: stopped!")
			apiKillChan <- struct{}{}
			return
		}
	}
}

// apiRoutes sets up the API routes required by wavepipe
func apiRoutes(r martini.Router) {
	// Root API, containing information and help
	r.Get("", api.APIInfo)

	// Albums API
	r.Get("/albums", api.GetAlbums)
	r.Get("/albums/:id", api.GetAlbums)

	// Art API
	r.Get("/art", api.GetArt)
	r.Get("/art/:id", api.GetArt)

	// Artists API
	r.Get("/artists", api.GetArtists)
	r.Get("/artists/:id", api.GetArtists)

	// Folders API
	r.Get("/folders", api.GetFolders)
	r.Get("/folders/:id", api.GetFolders)

	// LastFM API
	r.Get("/lastfm", api.GetLastFM)
	r.Get("/lastfm/:action", api.GetLastFM)
	r.Get("/lastfm/:action/:id", api.GetLastFM)

	// Login API
	r.Get("/login", api.GetLogin)

	// Logout API
	r.Get("/logout", api.GetLogout)

	// Search API
	r.Get("/search", api.GetSearch)
	r.Get("/search/:query", api.GetSearch)

	// Songs API
	r.Get("/songs", api.GetSongs)
	r.Get("/songs/:id", api.GetSongs)

	// Status API
	r.Get("/status", api.GetStatus)

	// Stream API
	r.Get("/stream", api.GetStream)
	r.Get("/stream/:id", api.GetStream)

	// Transcode API
	r.Get("/transcode", api.GetTranscode)
	r.Get("/transcode/:id", api.GetTranscode)
}
<|endoftext|>


<commit_before><commit_msg>storage: shorten test prefix<commit_after><|endoftext|>


<commit_before>// Copyright 2019 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package helper_test

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"net/http"
	"testing"
	"time"

	"github.com/gorilla/mux"
	. "github.com/pingcap/check"
	"github.com/pingcap/log"
	"github.com/pingcap/tidb/store/helper"
	"github.com/pingcap/tidb/store/mockstore"
	"github.com/pingcap/tidb/store/mockstore/mocktikv"
	"github.com/pingcap/tidb/store/tikv"
	"github.com/pingcap/tidb/util/pdapi"
	"go.uber.org/zap"
)

type HelperTestSuite struct {
	store tikv.Storage
}

var _ = Suite(new(HelperTestSuite))

func TestT(t *testing.T) {
	CustomVerboseFlag = true
	TestingT(t)
}

type mockStore struct {
	tikv.Storage
	pdAddrs []string
}

func (s *mockStore) EtcdAddrs() []string {
	return s.pdAddrs
}

func (s *mockStore) StartGCWorker() error {
	panic("not implemented")
}

func (s *mockStore) TLSConfig() *tls.Config {
	panic("not implemented")
}

func (s *HelperTestSuite) SetUpSuite(c *C) {
	go s.mockPDHTTPServer(c)
	time.Sleep(100 * time.Millisecond)
	mvccStore := mocktikv.MustNewMVCCStore()
	mockTikvStore, err := mockstore.NewMockTikvStore(mockstore.WithMVCCStore(mvccStore))
	s.store = &mockStore{
		mockTikvStore.(tikv.Storage),
		[]string{"127.0.0.1:10090/"},
	}
	c.Assert(err, IsNil)
}

func (s *HelperTestSuite) TestHotRegion(c *C) {
	helper := helper.Helper{
		Store:       s.store,
		RegionCache: s.store.GetRegionCache(),
	}
	regionMetric, err := helper.FetchHotRegion(pdapi.HotRead)
	c.Assert(err, IsNil, Commentf("err: %+v", err))
	c.Assert(fmt.Sprintf("%v", regionMetric), Equals, "map[1:{100 1 0}]")
}

func (s *HelperTestSuite) mockPDHTTPServer(c *C) {
	router := mux.NewRouter()
	router.HandleFunc("/pd/api/v1/hotspot/regions/read", s.mockHotRegionResponse)
	serverMux := http.NewServeMux()
	serverMux.Handle("/", router)
	server := &http.Server{Addr: "127.0.0.1:10090", Handler: serverMux}
	err := server.ListenAndServe()
	c.Assert(err, IsNil)
}

func (s *HelperTestSuite) mockHotRegionResponse(w http.ResponseWriter, req *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	regionsStat := helper.HotRegionsStat{
		RegionsStat: []helper.RegionStat{
			{
				FlowBytes: 100,
				RegionID:  1,
				HotDegree: 1,
			},
		},
	}
	resp := helper.StoreHotRegionInfos{
		AsLeader: make(map[uint64]*helper.HotRegionsStat),
	}
	resp.AsLeader[0] = &regionsStat
	data, err := json.MarshalIndent(resp, "", "	")
	if err != nil {
		log.Panic("json marshal failed", zap.Error(err))
	}
	_, err = w.Write(data)
	if err != nil {
		log.Panic("write http response failed", zap.Error(err))
	}

}
<commit_msg>store/helper: make test stable (#10211)<commit_after>// Copyright 2019 PingCAP, Inc.
//
// 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package helper_test

import (
	"crypto/tls"
	"encoding/json"
	"fmt"
	"net/http"
	"testing"
	"time"

	"github.com/gorilla/mux"
	. "github.com/pingcap/check"
	"github.com/pingcap/log"
	"github.com/pingcap/tidb/store/helper"
	"github.com/pingcap/tidb/store/mockstore"
	"github.com/pingcap/tidb/store/mockstore/mocktikv"
	"github.com/pingcap/tidb/store/tikv"
	"github.com/pingcap/tidb/util/pdapi"
	"go.uber.org/zap"
)

type HelperTestSuite struct {
	store tikv.Storage
}

var _ = Suite(new(HelperTestSuite))

func TestT(t *testing.T) {
	CustomVerboseFlag = true
	TestingT(t)
}

type mockStore struct {
	tikv.Storage
	pdAddrs []string
}

func (s *mockStore) EtcdAddrs() []string {
	return s.pdAddrs
}

func (s *mockStore) StartGCWorker() error {
	panic("not implemented")
}

func (s *mockStore) TLSConfig() *tls.Config {
	panic("not implemented")
}

func (s *HelperTestSuite) SetUpSuite(c *C) {
	go s.mockPDHTTPServer(c)
	time.Sleep(100 * time.Millisecond)
	mvccStore := mocktikv.MustNewMVCCStore()
	mockTikvStore, err := mockstore.NewMockTikvStore(mockstore.WithMVCCStore(mvccStore))
	s.store = &mockStore{
		mockTikvStore.(tikv.Storage),
		[]string{"127.0.0.1:10100/"},
	}
	c.Assert(err, IsNil)
}

func (s *HelperTestSuite) TestHotRegion(c *C) {
	helper := helper.Helper{
		Store:       s.store,
		RegionCache: s.store.GetRegionCache(),
	}
	regionMetric, err := helper.FetchHotRegion(pdapi.HotRead)
	c.Assert(err, IsNil, Commentf("err: %+v", err))
	c.Assert(fmt.Sprintf("%v", regionMetric), Equals, "map[1:{100 1 0}]")
}

func (s *HelperTestSuite) mockPDHTTPServer(c *C) {
	router := mux.NewRouter()
	router.HandleFunc("/pd/api/v1/hotspot/regions/read", s.mockHotRegionResponse)
	serverMux := http.NewServeMux()
	serverMux.Handle("/", router)
	server := &http.Server{Addr: "127.0.0.1:10100", Handler: serverMux}
	err := server.ListenAndServe()
	c.Assert(err, IsNil)
}

func (s *HelperTestSuite) mockHotRegionResponse(w http.ResponseWriter, req *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	regionsStat := helper.HotRegionsStat{
		RegionsStat: []helper.RegionStat{
			{
				FlowBytes: 100,
				RegionID:  1,
				HotDegree: 1,
			},
		},
	}
	resp := helper.StoreHotRegionInfos{
		AsLeader: make(map[uint64]*helper.HotRegionsStat),
	}
	resp.AsLeader[0] = &regionsStat
	data, err := json.MarshalIndent(resp, "", "	")
	if err != nil {
		log.Panic("json marshal failed", zap.Error(err))
	}
	_, err = w.Write(data)
	if err != nil {
		log.Panic("write http response failed", zap.Error(err))
	}

}
<|endoftext|>


<commit_before>// Copyright 2020 Google Inc.
//
// 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 tcell

import (
	"github.com/gdamore/tcell"
	"github.com/mum4k/termdash/cell"
	"github.com/mum4k/termdash/terminal/terminalapi"
)

// cellColor converts termdash cell color to the tcell format.
func cellColor(c cell.Color) tcell.Color {
	return tcell.Color(c&0x1ff) - 1
}

// fixColor converts the target color for the current color mode
func fixColor(c tcell.Color, colorMode terminalapi.ColorMode) tcell.Color {
	if colorMode == DefaultColorMode {
		return c
	}
	switch colorMode {
	case terminalapi.ColorModeNormal:
		c %= tcell.Color(16)
	case terminalapi.ColorMode256:
		c %= tcell.Color(256)
	case terminalapi.ColorMode216:
		c %= tcell.Color(216)
		c += tcell.Color(16)
	case terminalapi.ColorModeGrayscale:
		c %= tcell.Color(24)
		c += tcell.Color(232)
	default:
		c = tcell.ColorDefault
	}
	return c
}

// cellOptsToStyle converts termdash cell color to the tcell format.
func cellOptsToStyle(opts *cell.Options, colorMode terminalapi.ColorMode) tcell.Style {
	st := tcell.StyleDefault

	fg := cellColor(opts.FgColor)
	bg := cellColor(opts.BgColor)

	fg = fixColor(fg, colorMode)
	bg = fixColor(bg, colorMode)

	st = st.Foreground(fg).Background(bg)
	return st
}
<commit_msg>Fix a check in fixColor<commit_after>// Copyright 2020 Google Inc.
//
// 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 tcell

import (
	"github.com/gdamore/tcell"
	"github.com/mum4k/termdash/cell"
	"github.com/mum4k/termdash/terminal/terminalapi"
)

// cellColor converts termdash cell color to the tcell format.
func cellColor(c cell.Color) tcell.Color {
	return tcell.Color(c&0x1ff) - 1
}

// fixColor converts the target color for the current color mode
func fixColor(c tcell.Color, colorMode terminalapi.ColorMode) tcell.Color {
	if c == tcell.ColorDefault {
		return c
	}
	switch colorMode {
	case terminalapi.ColorModeNormal:
		c %= tcell.Color(16)
	case terminalapi.ColorMode256:
		c %= tcell.Color(256)
	case terminalapi.ColorMode216:
		c %= tcell.Color(216)
		c += tcell.Color(16)
	case terminalapi.ColorModeGrayscale:
		c %= tcell.Color(24)
		c += tcell.Color(232)
	default:
		c = tcell.ColorDefault
	}
	return c
}

// cellOptsToStyle converts termdash cell color to the tcell format.
func cellOptsToStyle(opts *cell.Options, colorMode terminalapi.ColorMode) tcell.Style {
	st := tcell.StyleDefault

	fg := cellColor(opts.FgColor)
	bg := cellColor(opts.BgColor)

	fg = fixColor(fg, colorMode)
	bg = fixColor(bg, colorMode)

	st = st.Foreground(fg).Background(bg)
	return st
}
<|endoftext|>


<commit_before>package indicators

import (
	"container/list"
	"errors"
	"github.com/thetruetrade/gotrade"
)

// A Money Flow Index Indicator (Mfi), no storage, for use in other indicators
type MfiWithoutStorage struct {
	*baseIndicator
	*baseFloatBounds

	// private variables
	valueAvailableAction ValueAvailableActionFloat
	periodCounter        int
	typicalPrice         *TypPriceWithoutStorage
	positiveMoneyFlow    float64
	negativeMoneyFlow    float64
	positiveHistory      *list.List
	negativeHistory      *list.List
	previousTypPrice     float64
	currentVolume        float64
	timePeriod           int
}

// NewMfiWithoutStorage creates a Money Flow Index Indicator (Mfi) without storage
func NewMfiWithoutStorage(timePeriod int, valueAvailableAction ValueAvailableActionFloat) (indicator *MfiWithoutStorage, err error) {

	// an indicator without storage MUST have a value available action
	if valueAvailableAction == nil {
		return nil, ErrValueAvailableActionIsNil
	}

	// the minimum timeperiod for this indicator is 2
	if timePeriod < 2 {
		return nil, errors.New("timePeriod is less than the minimum (2)")
	}

	// check the maximum timeperiod
	if timePeriod > MaximumLookbackPeriod {
		return nil, errors.New("timePeriod is greater than the maximum (100000)")
	}

	lookback := timePeriod
	ind := MfiWithoutStorage{
		baseIndicator:        newBaseIndicator(lookback),
		baseFloatBounds:      newBaseFloatBounds(),
		periodCounter:        (timePeriod * -1) - 1,
		positiveHistory:      list.New(),
		negativeHistory:      list.New(),
		positiveMoneyFlow:    0.0,
		negativeMoneyFlow:    0.0,
		currentVolume:        0.0,
		previousTypPrice:     0.0,
		valueAvailableAction: valueAvailableAction,
		timePeriod:           timePeriod,
	}

	ind.typicalPrice, err = NewTypPriceWithoutStorage(func(dataItem float64, streamBarIndex int) {
		ind.periodCounter += 1

		if ind.periodCounter > (ind.timePeriod * -1) {
			moneyFlow := dataItem * ind.currentVolume

			if ind.periodCounter <= 0 {
				if dataItem > ind.previousTypPrice {
					ind.positiveMoneyFlow += moneyFlow
					ind.positiveHistory.PushBack(moneyFlow)
					ind.negativeHistory.PushBack(0.0)
				} else if dataItem < ind.previousTypPrice {
					ind.negativeMoneyFlow += moneyFlow
					ind.positiveHistory.PushBack(0.0)
					ind.negativeHistory.PushBack(moneyFlow)
				} else {
					ind.positiveHistory.PushBack(0.0)
					ind.negativeHistory.PushBack(0.0)
				}
			}

			if ind.periodCounter == 0 {

				result := 100.0 * (ind.positiveMoneyFlow / (ind.positiveMoneyFlow + ind.negativeMoneyFlow))

				// increment the number of results this indicator can be expected to return
				ind.dataLength += 1
				if ind.validFromBar == -1 {
					// set the streamBarIndex from which this indicator returns valid results
					ind.validFromBar = streamBarIndex
				}

				// update the maximum result value
				if result > ind.maxValue {
					ind.maxValue = result
				}

				// update the minimum result value
				if result < ind.minValue {
					ind.minValue = result
				}

				// notify of a new result value though the value available action
				ind.valueAvailableAction(result, streamBarIndex)
			}
			if ind.periodCounter > 0 {
				firstPositive := ind.positiveHistory.Front().Value.(float64)
				ind.positiveMoneyFlow -= firstPositive

				firstNegative := ind.negativeHistory.Front().Value.(float64)
				ind.negativeMoneyFlow -= firstNegative

				if dataItem > ind.previousTypPrice {
					ind.positiveMoneyFlow += moneyFlow
					ind.positiveHistory.PushBack(moneyFlow)
					ind.negativeHistory.PushBack(0.0)
				} else if dataItem < ind.previousTypPrice {
					ind.negativeMoneyFlow += moneyFlow
					ind.positiveHistory.PushBack(0.0)
					ind.negativeHistory.PushBack(moneyFlow)
				} else {
					ind.positiveHistory.PushBack(0.0)
					ind.negativeHistory.PushBack(0.0)
				}

				result := 100.0 * (ind.positiveMoneyFlow / (ind.positiveMoneyFlow + ind.negativeMoneyFlow))

				// increment the number of results this indicator can be expected to return
				ind.dataLength += 1

				// update the maximum result value
				if result > ind.maxValue {
					ind.maxValue = result
				}

				// update the minimum result value
				if result < ind.minValue {
					ind.minValue = result
				}

				// notify of a new result value though the value available action
				ind.valueAvailableAction(result, streamBarIndex)
			}

		}
		ind.previousTypPrice = dataItem

		if ind.positiveHistory.Len() > ind.timePeriod {
			first := ind.positiveHistory.Front()
			ind.positiveHistory.Remove(first)
		}

		if ind.negativeHistory.Len() > ind.timePeriod {
			first := ind.negativeHistory.Front()
			ind.negativeHistory.Remove(first)
		}
	})

	return &ind, err
}

// A Money Flow Index Indicator (Mfi)
type Mfi struct {
	*MfiWithoutStorage

	// public variables
	Data []float64
}

// NewMfi creates a Money Flow Index Indicator (Mfi) for online usage
func NewMfi(timePeriod int) (indicator *Mfi, err error) {
	ind := Mfi{}
	ind.MfiWithoutStorage, err = NewMfiWithoutStorage(timePeriod, func(dataItem float64, streamBarIndex int) {
		ind.Data = append(ind.Data, dataItem)
	})

	return &ind, err
}

// NewDefaultMfi creates a Money Flow Index Indicator (Mfi) for online usage with default parameters
//	- timePeriod: 25
func NewDefaultMfi() (indicator *Mfi, err error) {
	timePeriod := 25
	return NewMfi(timePeriod)
}

// NewMfiWithSrcLen creates a Money Flow Index Indicator (Mfi) for offline usage
func NewMfiWithSrcLen(sourceLength uint, timePeriod int) (indicator *Mfi, err error) {
	ind, err := NewMfi(timePeriod)

	// only initialise the storage if there is enough source data to require it
	if sourceLength-uint(ind.GetLookbackPeriod()) > 1 {
		ind.Data = make([]float64, 0, sourceLength-uint(ind.GetLookbackPeriod()))
	}

	return ind, err
}

// NewDefaultMfiWithSrcLen creates a Money Flow Index Indicator (Mfi) for offline usage with default parameters
func NewDefaultMfiWithSrcLen(sourceLength uint) (indicator *Mfi, err error) {
	ind, err := NewDefaultMfi()

	// only initialise the storage if there is enough source data to require it
	if sourceLength-uint(ind.GetLookbackPeriod()) > 1 {
		ind.Data = make([]float64, 0, sourceLength-uint(ind.GetLookbackPeriod()))
	}

	return ind, err
}

// NewMfiForStream creates a Money Flow Index Indicator (Mfi) for online usage with a source data stream
func NewMfiForStream(priceStream gotrade.DOHLCVStreamSubscriber, timePeriod int) (indicator *Mfi, err error) {
	ind, err := NewMfi(timePeriod)
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// NewDefaultMfiForStream creates a Money Flow Index Indicator (Mfi) for online usage with a source data stream
func NewDefaultMfiForStream(priceStream gotrade.DOHLCVStreamSubscriber) (indicator *Mfi, err error) {
	ind, err := NewDefaultMfi()
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// NewMfiForStreamWithSrcLen creates a Money Flow Index Indicator (Mfi) for offline usage with a source data stream
func NewMfiForStreamWithSrcLen(sourceLength uint, priceStream gotrade.DOHLCVStreamSubscriber, timePeriod int) (indicator *Mfi, err error) {
	ind, err := NewMfiWithSrcLen(sourceLength, timePeriod)
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// NewDefaultMfiForStreamWithSrcLen creates a Money Flow Index Indicator (Mfi) for offline usage with a source data stream
func NewDefaultMfiForStreamWithSrcLen(sourceLength uint, priceStream gotrade.DOHLCVStreamSubscriber) (indicator *Mfi, err error) {
	ind, err := NewDefaultMfiWithSrcLen(sourceLength)
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// ReceiveDOHLCVTick consumes a source data DOHLCV price tick
func (ind *MfiWithoutStorage) ReceiveDOHLCVTick(tickData gotrade.DOHLCV, streamBarIndex int) {
	ind.currentVolume = tickData.V()
	ind.typicalPrice.ReceiveDOHLCVTick(tickData, streamBarIndex)
}
<commit_msg>#76 Remove duplication - mfi<commit_after>package indicators

import (
	"container/list"
	"errors"
	"github.com/thetruetrade/gotrade"
)

// A Money Flow Index Indicator (Mfi), no storage, for use in other indicators
type MfiWithoutStorage struct {
	*baseIndicatorWithFloatBounds

	// private variables
	periodCounter     int
	typicalPrice      *TypPriceWithoutStorage
	positiveMoneyFlow float64
	negativeMoneyFlow float64
	positiveHistory   *list.List
	negativeHistory   *list.List
	previousTypPrice  float64
	currentVolume     float64
	timePeriod        int
}

// NewMfiWithoutStorage creates a Money Flow Index Indicator (Mfi) without storage
func NewMfiWithoutStorage(timePeriod int, valueAvailableAction ValueAvailableActionFloat) (indicator *MfiWithoutStorage, err error) {

	// an indicator without storage MUST have a value available action
	if valueAvailableAction == nil {
		return nil, ErrValueAvailableActionIsNil
	}

	// the minimum timeperiod for this indicator is 2
	if timePeriod < 2 {
		return nil, errors.New("timePeriod is less than the minimum (2)")
	}

	// check the maximum timeperiod
	if timePeriod > MaximumLookbackPeriod {
		return nil, errors.New("timePeriod is greater than the maximum (100000)")
	}

	lookback := timePeriod
	ind := MfiWithoutStorage{
		baseIndicatorWithFloatBounds: newBaseIndicatorWithFloatBounds(lookback, valueAvailableAction),
		periodCounter:                (timePeriod * -1) - 1,
		positiveHistory:              list.New(),
		negativeHistory:              list.New(),
		positiveMoneyFlow:            0.0,
		negativeMoneyFlow:            0.0,
		currentVolume:                0.0,
		previousTypPrice:             0.0,
		timePeriod:                   timePeriod,
	}

	ind.typicalPrice, err = NewTypPriceWithoutStorage(func(dataItem float64, streamBarIndex int) {
		ind.periodCounter += 1

		if ind.periodCounter > (ind.timePeriod * -1) {
			moneyFlow := dataItem * ind.currentVolume

			if ind.periodCounter <= 0 {
				if dataItem > ind.previousTypPrice {
					ind.positiveMoneyFlow += moneyFlow
					ind.positiveHistory.PushBack(moneyFlow)
					ind.negativeHistory.PushBack(0.0)
				} else if dataItem < ind.previousTypPrice {
					ind.negativeMoneyFlow += moneyFlow
					ind.positiveHistory.PushBack(0.0)
					ind.negativeHistory.PushBack(moneyFlow)
				} else {
					ind.positiveHistory.PushBack(0.0)
					ind.negativeHistory.PushBack(0.0)
				}
			}

			if ind.periodCounter == 0 {

				result := 100.0 * (ind.positiveMoneyFlow / (ind.positiveMoneyFlow + ind.negativeMoneyFlow))

				ind.UpdateIndicatorWithNewValue(result, streamBarIndex)
			}
			if ind.periodCounter > 0 {
				firstPositive := ind.positiveHistory.Front().Value.(float64)
				ind.positiveMoneyFlow -= firstPositive

				firstNegative := ind.negativeHistory.Front().Value.(float64)
				ind.negativeMoneyFlow -= firstNegative

				if dataItem > ind.previousTypPrice {
					ind.positiveMoneyFlow += moneyFlow
					ind.positiveHistory.PushBack(moneyFlow)
					ind.negativeHistory.PushBack(0.0)
				} else if dataItem < ind.previousTypPrice {
					ind.negativeMoneyFlow += moneyFlow
					ind.positiveHistory.PushBack(0.0)
					ind.negativeHistory.PushBack(moneyFlow)
				} else {
					ind.positiveHistory.PushBack(0.0)
					ind.negativeHistory.PushBack(0.0)
				}

				result := 100.0 * (ind.positiveMoneyFlow / (ind.positiveMoneyFlow + ind.negativeMoneyFlow))

				ind.UpdateIndicatorWithNewValue(result, streamBarIndex)
			}

		}
		ind.previousTypPrice = dataItem

		if ind.positiveHistory.Len() > ind.timePeriod {
			first := ind.positiveHistory.Front()
			ind.positiveHistory.Remove(first)
		}

		if ind.negativeHistory.Len() > ind.timePeriod {
			first := ind.negativeHistory.Front()
			ind.negativeHistory.Remove(first)
		}
	})

	return &ind, err
}

// A Money Flow Index Indicator (Mfi)
type Mfi struct {
	*MfiWithoutStorage

	// public variables
	Data []float64
}

// NewMfi creates a Money Flow Index Indicator (Mfi) for online usage
func NewMfi(timePeriod int) (indicator *Mfi, err error) {
	ind := Mfi{}
	ind.MfiWithoutStorage, err = NewMfiWithoutStorage(timePeriod, func(dataItem float64, streamBarIndex int) {
		ind.Data = append(ind.Data, dataItem)
	})

	return &ind, err
}

// NewDefaultMfi creates a Money Flow Index Indicator (Mfi) for online usage with default parameters
//	- timePeriod: 25
func NewDefaultMfi() (indicator *Mfi, err error) {
	timePeriod := 25
	return NewMfi(timePeriod)
}

// NewMfiWithSrcLen creates a Money Flow Index Indicator (Mfi) for offline usage
func NewMfiWithSrcLen(sourceLength uint, timePeriod int) (indicator *Mfi, err error) {
	ind, err := NewMfi(timePeriod)

	// only initialise the storage if there is enough source data to require it
	if sourceLength-uint(ind.GetLookbackPeriod()) > 1 {
		ind.Data = make([]float64, 0, sourceLength-uint(ind.GetLookbackPeriod()))
	}

	return ind, err
}

// NewDefaultMfiWithSrcLen creates a Money Flow Index Indicator (Mfi) for offline usage with default parameters
func NewDefaultMfiWithSrcLen(sourceLength uint) (indicator *Mfi, err error) {
	ind, err := NewDefaultMfi()

	// only initialise the storage if there is enough source data to require it
	if sourceLength-uint(ind.GetLookbackPeriod()) > 1 {
		ind.Data = make([]float64, 0, sourceLength-uint(ind.GetLookbackPeriod()))
	}

	return ind, err
}

// NewMfiForStream creates a Money Flow Index Indicator (Mfi) for online usage with a source data stream
func NewMfiForStream(priceStream gotrade.DOHLCVStreamSubscriber, timePeriod int) (indicator *Mfi, err error) {
	ind, err := NewMfi(timePeriod)
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// NewDefaultMfiForStream creates a Money Flow Index Indicator (Mfi) for online usage with a source data stream
func NewDefaultMfiForStream(priceStream gotrade.DOHLCVStreamSubscriber) (indicator *Mfi, err error) {
	ind, err := NewDefaultMfi()
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// NewMfiForStreamWithSrcLen creates a Money Flow Index Indicator (Mfi) for offline usage with a source data stream
func NewMfiForStreamWithSrcLen(sourceLength uint, priceStream gotrade.DOHLCVStreamSubscriber, timePeriod int) (indicator *Mfi, err error) {
	ind, err := NewMfiWithSrcLen(sourceLength, timePeriod)
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// NewDefaultMfiForStreamWithSrcLen creates a Money Flow Index Indicator (Mfi) for offline usage with a source data stream
func NewDefaultMfiForStreamWithSrcLen(sourceLength uint, priceStream gotrade.DOHLCVStreamSubscriber) (indicator *Mfi, err error) {
	ind, err := NewDefaultMfiWithSrcLen(sourceLength)
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// ReceiveDOHLCVTick consumes a source data DOHLCV price tick
func (ind *MfiWithoutStorage) ReceiveDOHLCVTick(tickData gotrade.DOHLCV, streamBarIndex int) {
	ind.currentVolume = tickData.V()
	ind.typicalPrice.ReceiveDOHLCVTick(tickData, streamBarIndex)
}
<|endoftext|>


<commit_before>package indicators

import (
	"errors"
	"github.com/thetruetrade/gotrade"
	"math"
)

// A Stop and Reverse Indicator (Sar), no storage, for use in other indicators
type SarWithoutStorage struct {
	*baseIndicator
	*baseFloatBounds

	// private variables
	valueAvailableAction  ValueAvailableActionFloat
	periodCounter         int
	isLong                bool
	extremePoint          float64
	accelerationFactor    float64
	acceleration          float64
	accelerationFactorMax float64
	previousSar           float64
	previousHigh          float64
	previousLow           float64
	minusDM               *MinusDmWithoutStorage
	hasInitialDirection   bool
}

func NewSarWithoutStorage(accelerationFactor float64, accelerationFactorMax float64, valueAvailableAction ValueAvailableActionFloat) (indicator *SarWithoutStorage, err error) {

	// an indicator without storage MUST have a value available action
	if valueAvailableAction == nil {
		return nil, ErrValueAvailableActionIsNil
	}

	// the minimum accelerationFactor for this indicator is 0
	if accelerationFactor < 0 {
		return nil, errors.New("accelerationFactor is less than the minimum (0)")
	}

	// check the maximum accelerationFactor
	if accelerationFactor >= math.MaxFloat64 {
		return nil, errors.New("accelerationFactor is greater than the maximum float64 size")
	}

	// the minimum accelerationFactorMax for this indicator is 0
	if accelerationFactorMax < 0 {
		return nil, errors.New("accelerationFactorMax is less than the minimum (0)")
	}

	// check the maximum accelerationFactorMax
	if accelerationFactorMax >= math.MaxFloat64 {
		return nil, errors.New("accelerationFactorMax is greater than the maximum float64 size")
	}

	lookback := 1
	ind := SarWithoutStorage{
		baseIndicator:         newBaseIndicator(lookback),
		baseFloatBounds:       newBaseFloatBounds(),
		periodCounter:         -2,
		isLong:                false,
		hasInitialDirection:   false,
		accelerationFactor:    accelerationFactor,
		accelerationFactorMax: accelerationFactorMax,
		extremePoint:          0.0,
		previousSar:           0.0,
		previousHigh:          0.0,
		previousLow:           0.0,
		acceleration:          accelerationFactor,
		valueAvailableAction:  valueAvailableAction,
	}

	ind.minusDM, err = NewMinusDmWithoutStorage(1, func(dataItem float64, streamBarIndex int) {
		if dataItem > 0 {
			ind.isLong = false
		} else {
			ind.isLong = true
		}
		ind.hasInitialDirection = true
	})

	return &ind, err
}

// A Stop and Reverse Indicator (Sar)
type Sar struct {
	*SarWithoutStorage

	// public variables
	Data []float64
}

// NewSar creates a Stop and Reverse Indicator (Sar) for online usage
func NewSar(accelerationFactor float64, accelerationFactorMax float64) (indicator *Sar, err error) {
	ind := Sar{}
	ind.SarWithoutStorage, err = NewSarWithoutStorage(accelerationFactor, accelerationFactorMax, func(dataItem float64, streamBarIndex int) {
		ind.Data = append(ind.Data, dataItem)
	})

	return &ind, err
}

// NewDefaultSar creates a Stop and Reverse Indicator (Sar) for online usage with default parameters
//	- accelerationFactor: 0.02
//  - accelerationFactorMax: 0.2
func NewDefaultSar() (indicator *Sar, err error) {
	accelerationFactor := 0.02
	accelerationFactorMax := 0.2
	return NewSar(accelerationFactor, accelerationFactorMax)
}

// NewSarWithSrcLen creates a Stop and Reverse Indicator (Sar) for offline usage
func NewSarWithSrcLen(sourceLength uint, accelerationFactor float64, accelerationFactorMax float64) (indicator *Sar, err error) {
	ind, err := NewSar(accelerationFactor, accelerationFactorMax)

	// only initialise the storage if there is enough source data to require it
	if sourceLength-uint(ind.GetLookbackPeriod()) > 1 {
		ind.Data = make([]float64, 0, sourceLength-uint(ind.GetLookbackPeriod()))
	}

	return ind, err
}

// NewDefaultSarWithSrcLen creates a Stop and Reverse Indicator (Sar) for offline usage with default parameters
func NewDefaultSarWithSrcLen(sourceLength uint) (indicator *Sar, err error) {
	ind, err := NewDefaultSar()

	// only initialise the storage if there is enough source data to require it
	if sourceLength-uint(ind.GetLookbackPeriod()) > 1 {
		ind.Data = make([]float64, 0, sourceLength-uint(ind.GetLookbackPeriod()))
	}

	return ind, err
}

// NewSarForStream creates a Stop and Reverse Indicator (Sar) for online usage with a source data stream
func NewSarForStream(priceStream gotrade.DOHLCVStreamSubscriber, accelerationFactor float64, accelerationFactorMax float64) (indicator *Sar, err error) {
	ind, err := NewSar(accelerationFactor, accelerationFactorMax)
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// NewDefaultSarForStream creates a Stop and Reverse Indicator (Sar) for online usage with a source data stream
func NewDefaultSarForStream(priceStream gotrade.DOHLCVStreamSubscriber) (indicator *Sar, err error) {
	ind, err := NewDefaultSar()
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// NewSarForStreamWithSrcLen creates a Stop and Reverse Indicator (Sar) for offline usage with a source data stream
func NewSarForStreamWithSrcLen(sourceLength uint, priceStream gotrade.DOHLCVStreamSubscriber, accelerationFactor float64, accelerationFactorMax float64) (indicator *Sar, err error) {
	ind, err := NewSarWithSrcLen(sourceLength, accelerationFactor, accelerationFactorMax)
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// NewDefaultSarForStreamWithSrcLen creates a Stop and Reverse Indicator (Sar) for offline usage with a source data stream
func NewDefaultSarForStreamWithSrcLen(sourceLength uint, priceStream gotrade.DOHLCVStreamSubscriber) (indicator *Sar, err error) {
	ind, err := NewDefaultSarWithSrcLen(sourceLength)
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// ReceiveDOHLCVTick consumes a source data DOHLCV price tick
func (ind *SarWithoutStorage) ReceiveDOHLCVTick(tickData gotrade.DOHLCV, streamBarIndex int) {
	ind.periodCounter += 1
	if ind.hasInitialDirection == false {
		ind.minusDM.ReceiveDOHLCVTick(tickData, streamBarIndex)
	}

	if ind.hasInitialDirection == true {
		if ind.periodCounter == 0 {
			if ind.isLong {
				ind.extremePoint = tickData.H()
				ind.previousSar = ind.previousLow
			} else {
				ind.extremePoint = tickData.L()
				ind.previousSar = ind.previousHigh
			}

			// this is a trick for the first iteration only,
			// the high low of the first bar will be used as the sar for the
			// second bar. According tyo TALib this is the closest to Wilders
			// originla idea of having the first entry day use the previous
			// extreme, except now that extreme is solely derived from the first
			// bar, supposedly Meta stock uses the same method.
			ind.previousHigh = tickData.H()
			ind.previousLow = tickData.L()
		}

		if ind.periodCounter >= 0 {
			var result float64 = 0.0
			if ind.isLong {
				if tickData.L() <= ind.previousSar {
					// switch to short if the low penetrates the Sar value
					ind.isLong = false
					ind.previousSar = ind.extremePoint

					// make sure the overridden Sar is within yesterdays and todays range
					if ind.previousSar < ind.previousHigh {
						ind.previousSar = ind.previousHigh
					}
					if ind.previousSar < tickData.H() {
						ind.previousSar = tickData.H()
					}

					result = ind.previousSar

					// increment the number of results this indicator can be expected to return
					ind.dataLength += 1

					if ind.validFromBar == -1 {
						// set the streamBarIndex from which this indicator returns valid results
						ind.validFromBar = streamBarIndex
					}

					// update the maximum result value
					if result > ind.maxValue {
						ind.maxValue = result
					}

					// update the minimum result value
					if result < ind.minValue {
						ind.minValue = result
					}

					// notify of a new result value though the value available action
					ind.valueAvailableAction(result, streamBarIndex)

					// adjust af and extremePoint
					ind.acceleration = ind.accelerationFactor
					ind.extremePoint = tickData.L()

					// calculate the new Sar
					var diff float64 = ind.extremePoint - ind.previousSar
					ind.previousSar = ind.previousSar + ind.acceleration*(diff)

					// make sure the overridden Sar is within yesterdays and todays range
					if ind.previousSar < ind.previousHigh {
						ind.previousSar = ind.previousHigh
					}
					if ind.previousSar < tickData.H() {
						ind.previousSar = tickData.H()
					}

				} else {
					// no switch

					// just output the current Sar
					result = ind.previousSar

					// increment the number of results this indicator can be expected to return
					ind.dataLength += 1

					if ind.validFromBar == -1 {
						// set the streamBarIndex from which this indicator returns valid results
						ind.validFromBar = streamBarIndex
					}

					if result > ind.maxValue {
						// update the maximum result value
						ind.maxValue = result
					}

					// update the minimum result value
					if result < ind.minValue {
						ind.minValue = result
					}

					// notify of a new result value though the value available action
					ind.valueAvailableAction(result, streamBarIndex)

					if tickData.H() > ind.extremePoint {
						// adjust af and extremePoint
						ind.extremePoint = tickData.H()
						ind.acceleration += ind.accelerationFactor
						if ind.acceleration > ind.accelerationFactorMax {
							ind.acceleration = ind.accelerationFactorMax
						}
					}

					// calculate the new Sar
					var diff float64 = ind.extremePoint - ind.previousSar
					ind.previousSar = ind.previousSar + ind.acceleration*(diff)

					// make sure the overridden Sar is within yesterdays and todays range
					if ind.previousSar > ind.previousLow {
						ind.previousSar = ind.previousLow
					}
					if ind.previousSar > tickData.L() {
						ind.previousSar = tickData.L()
					}
				}
			} else {
				// short
				// switch to long if the high penetrates the Sar value
				if tickData.H() >= ind.previousSar {
					ind.isLong = true
					ind.previousSar = ind.extremePoint

					// make sure the overridden Sar is within yesterdays and todays range
					if ind.previousSar > ind.previousLow {
						ind.previousSar = ind.previousLow
					}
					if ind.previousSar > tickData.L() {
						ind.previousSar = tickData.L()
					}

					result = ind.previousSar

					// increment the number of results this indicator can be expected to return
					ind.dataLength += 1

					if ind.validFromBar == -1 {
						// set the streamBarIndex from which this indicator returns valid results
						ind.validFromBar = streamBarIndex
					}

					if result > ind.maxValue {
						// update the maximum result value
						ind.maxValue = result
					}

					// update the minimum result value
					if result < ind.minValue {
						ind.minValue = result
					}

					// notify of a new result value though the value available action
					ind.valueAvailableAction(result, streamBarIndex)

					// adjust af and extremePoint
					ind.acceleration = ind.accelerationFactor
					ind.extremePoint = tickData.H()

					// calculate the new Sar
					var diff float64 = ind.extremePoint - ind.previousSar
					ind.previousSar = ind.previousSar + ind.acceleration*(diff)

					// make sure the overridden Sar is within yesterdays and todays range
					if ind.previousSar > ind.previousLow {
						ind.previousSar = ind.previousLow
					}
					if ind.previousSar > tickData.L() {
						ind.previousSar = tickData.L()
					}
				} else {
					// no switch

					// just output the current Sar
					result = ind.previousSar

					// increment the number of results this indicator can be expected to return
					ind.dataLength += 1

					if ind.validFromBar == -1 {
						// set the streamBarIndex from which this indicator returns valid results
						ind.validFromBar = streamBarIndex
					}

					if result > ind.maxValue {
						// update the maximum result value
						ind.maxValue = result
					}

					// update the minimum result value
					if result < ind.minValue {
						ind.minValue = result
					}

					// notify of a new result value though the value available action
					ind.valueAvailableAction(result, streamBarIndex)

					if tickData.L() < ind.extremePoint {
						// adjust af and extremePoint
						ind.extremePoint = tickData.L()
						ind.acceleration += ind.accelerationFactor
						if ind.acceleration > ind.accelerationFactorMax {
							ind.acceleration = ind.accelerationFactorMax
						}
					}

					// calculate the new Sar
					var diff float64 = ind.extremePoint - ind.previousSar
					ind.previousSar = ind.previousSar + ind.acceleration*(diff)

					// make sure the overridden Sar is within yesterdays and todays range
					if ind.previousSar < ind.previousHigh {
						ind.previousSar = ind.previousHigh
					}
					if ind.previousSar < tickData.H() {
						ind.previousSar = tickData.H()
					}
				}
			}
		}
	}

	ind.previousHigh = tickData.H()
	ind.previousLow = tickData.L()
}
<commit_msg>#76 Remove duplication - sar<commit_after>package indicators

import (
	"errors"
	"github.com/thetruetrade/gotrade"
	"math"
)

// A Stop and Reverse Indicator (Sar), no storage, for use in other indicators
type SarWithoutStorage struct {
	*baseIndicatorWithFloatBounds

	// private variables
	periodCounter         int
	isLong                bool
	extremePoint          float64
	accelerationFactor    float64
	acceleration          float64
	accelerationFactorMax float64
	previousSar           float64
	previousHigh          float64
	previousLow           float64
	minusDM               *MinusDmWithoutStorage
	hasInitialDirection   bool
}

func NewSarWithoutStorage(accelerationFactor float64, accelerationFactorMax float64, valueAvailableAction ValueAvailableActionFloat) (indicator *SarWithoutStorage, err error) {

	// an indicator without storage MUST have a value available action
	if valueAvailableAction == nil {
		return nil, ErrValueAvailableActionIsNil
	}

	// the minimum accelerationFactor for this indicator is 0
	if accelerationFactor < 0 {
		return nil, errors.New("accelerationFactor is less than the minimum (0)")
	}

	// check the maximum accelerationFactor
	if accelerationFactor >= math.MaxFloat64 {
		return nil, errors.New("accelerationFactor is greater than the maximum float64 size")
	}

	// the minimum accelerationFactorMax for this indicator is 0
	if accelerationFactorMax < 0 {
		return nil, errors.New("accelerationFactorMax is less than the minimum (0)")
	}

	// check the maximum accelerationFactorMax
	if accelerationFactorMax >= math.MaxFloat64 {
		return nil, errors.New("accelerationFactorMax is greater than the maximum float64 size")
	}

	lookback := 1
	ind := SarWithoutStorage{
		baseIndicatorWithFloatBounds: newBaseIndicatorWithFloatBounds(lookback, valueAvailableAction),
		periodCounter:                -2,
		isLong:                       false,
		hasInitialDirection:          false,
		accelerationFactor:           accelerationFactor,
		accelerationFactorMax:        accelerationFactorMax,
		extremePoint:                 0.0,
		previousSar:                  0.0,
		previousHigh:                 0.0,
		previousLow:                  0.0,
		acceleration:                 accelerationFactor,
	}

	ind.minusDM, err = NewMinusDmWithoutStorage(1, func(dataItem float64, streamBarIndex int) {
		if dataItem > 0 {
			ind.isLong = false
		} else {
			ind.isLong = true
		}
		ind.hasInitialDirection = true
	})

	return &ind, err
}

// A Stop and Reverse Indicator (Sar)
type Sar struct {
	*SarWithoutStorage

	// public variables
	Data []float64
}

// NewSar creates a Stop and Reverse Indicator (Sar) for online usage
func NewSar(accelerationFactor float64, accelerationFactorMax float64) (indicator *Sar, err error) {
	ind := Sar{}
	ind.SarWithoutStorage, err = NewSarWithoutStorage(accelerationFactor, accelerationFactorMax, func(dataItem float64, streamBarIndex int) {
		ind.Data = append(ind.Data, dataItem)
	})

	return &ind, err
}

// NewDefaultSar creates a Stop and Reverse Indicator (Sar) for online usage with default parameters
//	- accelerationFactor: 0.02
//  - accelerationFactorMax: 0.2
func NewDefaultSar() (indicator *Sar, err error) {
	accelerationFactor := 0.02
	accelerationFactorMax := 0.2
	return NewSar(accelerationFactor, accelerationFactorMax)
}

// NewSarWithSrcLen creates a Stop and Reverse Indicator (Sar) for offline usage
func NewSarWithSrcLen(sourceLength uint, accelerationFactor float64, accelerationFactorMax float64) (indicator *Sar, err error) {
	ind, err := NewSar(accelerationFactor, accelerationFactorMax)

	// only initialise the storage if there is enough source data to require it
	if sourceLength-uint(ind.GetLookbackPeriod()) > 1 {
		ind.Data = make([]float64, 0, sourceLength-uint(ind.GetLookbackPeriod()))
	}

	return ind, err
}

// NewDefaultSarWithSrcLen creates a Stop and Reverse Indicator (Sar) for offline usage with default parameters
func NewDefaultSarWithSrcLen(sourceLength uint) (indicator *Sar, err error) {
	ind, err := NewDefaultSar()

	// only initialise the storage if there is enough source data to require it
	if sourceLength-uint(ind.GetLookbackPeriod()) > 1 {
		ind.Data = make([]float64, 0, sourceLength-uint(ind.GetLookbackPeriod()))
	}

	return ind, err
}

// NewSarForStream creates a Stop and Reverse Indicator (Sar) for online usage with a source data stream
func NewSarForStream(priceStream gotrade.DOHLCVStreamSubscriber, accelerationFactor float64, accelerationFactorMax float64) (indicator *Sar, err error) {
	ind, err := NewSar(accelerationFactor, accelerationFactorMax)
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// NewDefaultSarForStream creates a Stop and Reverse Indicator (Sar) for online usage with a source data stream
func NewDefaultSarForStream(priceStream gotrade.DOHLCVStreamSubscriber) (indicator *Sar, err error) {
	ind, err := NewDefaultSar()
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// NewSarForStreamWithSrcLen creates a Stop and Reverse Indicator (Sar) for offline usage with a source data stream
func NewSarForStreamWithSrcLen(sourceLength uint, priceStream gotrade.DOHLCVStreamSubscriber, accelerationFactor float64, accelerationFactorMax float64) (indicator *Sar, err error) {
	ind, err := NewSarWithSrcLen(sourceLength, accelerationFactor, accelerationFactorMax)
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// NewDefaultSarForStreamWithSrcLen creates a Stop and Reverse Indicator (Sar) for offline usage with a source data stream
func NewDefaultSarForStreamWithSrcLen(sourceLength uint, priceStream gotrade.DOHLCVStreamSubscriber) (indicator *Sar, err error) {
	ind, err := NewDefaultSarWithSrcLen(sourceLength)
	priceStream.AddTickSubscription(ind)
	return ind, err
}

// ReceiveDOHLCVTick consumes a source data DOHLCV price tick
func (ind *SarWithoutStorage) ReceiveDOHLCVTick(tickData gotrade.DOHLCV, streamBarIndex int) {
	ind.periodCounter += 1
	if ind.hasInitialDirection == false {
		ind.minusDM.ReceiveDOHLCVTick(tickData, streamBarIndex)
	}

	if ind.hasInitialDirection == true {
		if ind.periodCounter == 0 {
			if ind.isLong {
				ind.extremePoint = tickData.H()
				ind.previousSar = ind.previousLow
			} else {
				ind.extremePoint = tickData.L()
				ind.previousSar = ind.previousHigh
			}

			// this is a trick for the first iteration only,
			// the high low of the first bar will be used as the sar for the
			// second bar. According tyo TALib this is the closest to Wilders
			// originla idea of having the first entry day use the previous
			// extreme, except now that extreme is solely derived from the first
			// bar, supposedly Meta stock uses the same method.
			ind.previousHigh = tickData.H()
			ind.previousLow = tickData.L()
		}

		if ind.periodCounter >= 0 {
			var result float64 = 0.0
			if ind.isLong {
				if tickData.L() <= ind.previousSar {
					// switch to short if the low penetrates the Sar value
					ind.isLong = false
					ind.previousSar = ind.extremePoint

					// make sure the overridden Sar is within yesterdays and todays range
					if ind.previousSar < ind.previousHigh {
						ind.previousSar = ind.previousHigh
					}
					if ind.previousSar < tickData.H() {
						ind.previousSar = tickData.H()
					}

					result = ind.previousSar

					ind.UpdateIndicatorWithNewValue(result, streamBarIndex)

					// adjust af and extremePoint
					ind.acceleration = ind.accelerationFactor
					ind.extremePoint = tickData.L()

					// calculate the new Sar
					var diff float64 = ind.extremePoint - ind.previousSar
					ind.previousSar = ind.previousSar + ind.acceleration*(diff)

					// make sure the overridden Sar is within yesterdays and todays range
					if ind.previousSar < ind.previousHigh {
						ind.previousSar = ind.previousHigh
					}
					if ind.previousSar < tickData.H() {
						ind.previousSar = tickData.H()
					}

				} else {
					// no switch

					// just output the current Sar
					result = ind.previousSar

					ind.UpdateIndicatorWithNewValue(result, streamBarIndex)

					if tickData.H() > ind.extremePoint {
						// adjust af and extremePoint
						ind.extremePoint = tickData.H()
						ind.acceleration += ind.accelerationFactor
						if ind.acceleration > ind.accelerationFactorMax {
							ind.acceleration = ind.accelerationFactorMax
						}
					}

					// calculate the new Sar
					var diff float64 = ind.extremePoint - ind.previousSar
					ind.previousSar = ind.previousSar + ind.acceleration*(diff)

					// make sure the overridden Sar is within yesterdays and todays range
					if ind.previousSar > ind.previousLow {
						ind.previousSar = ind.previousLow
					}
					if ind.previousSar > tickData.L() {
						ind.previousSar = tickData.L()
					}
				}
			} else {
				// short
				// switch to long if the high penetrates the Sar value
				if tickData.H() >= ind.previousSar {
					ind.isLong = true
					ind.previousSar = ind.extremePoint

					// make sure the overridden Sar is within yesterdays and todays range
					if ind.previousSar > ind.previousLow {
						ind.previousSar = ind.previousLow
					}
					if ind.previousSar > tickData.L() {
						ind.previousSar = tickData.L()
					}

					result = ind.previousSar

					ind.UpdateIndicatorWithNewValue(result, streamBarIndex)

					// adjust af and extremePoint
					ind.acceleration = ind.accelerationFactor
					ind.extremePoint = tickData.H()

					// calculate the new Sar
					var diff float64 = ind.extremePoint - ind.previousSar
					ind.previousSar = ind.previousSar + ind.acceleration*(diff)

					// make sure the overridden Sar is within yesterdays and todays range
					if ind.previousSar > ind.previousLow {
						ind.previousSar = ind.previousLow
					}
					if ind.previousSar > tickData.L() {
						ind.previousSar = tickData.L()
					}
				} else {
					// no switch

					// just output the current Sar
					result = ind.previousSar

					ind.UpdateIndicatorWithNewValue(result, streamBarIndex)

					if tickData.L() < ind.extremePoint {
						// adjust af and extremePoint
						ind.extremePoint = tickData.L()
						ind.acceleration += ind.accelerationFactor
						if ind.acceleration > ind.accelerationFactorMax {
							ind.acceleration = ind.accelerationFactorMax
						}
					}

					// calculate the new Sar
					var diff float64 = ind.extremePoint - ind.previousSar
					ind.previousSar = ind.previousSar + ind.acceleration*(diff)

					// make sure the overridden Sar is within yesterdays and todays range
					if ind.previousSar < ind.previousHigh {
						ind.previousSar = ind.previousHigh
					}
					if ind.previousSar < tickData.H() {
						ind.previousSar = tickData.H()
					}
				}
			}
		}
	}

	ind.previousHigh = tickData.H()
	ind.previousLow = tickData.L()
}
<|endoftext|>


<commit_before>package appdash

import (
	"errors"
	"fmt"
	"net/url"
	"reflect"
	"time"

	influxDBClient "github.com/influxdb/influxdb/client"
	influxDBServer "github.com/influxdb/influxdb/cmd/influxd/run"
	influxDBModels "github.com/influxdb/influxdb/models"
)

const (
	dbName               string = "appdash" // InfluxDB db name.
	spanMeasurementName  string = "spans"   // InfluxDB container name for trace spans.
	defaultTracesPerPage int    = 10        // Default number of traces per page.
)

// Compile-time "implements" check.
var _ interface {
	Store
	Queryer
} = (*InfluxDBStore)(nil)

// zeroID is ID's zero value as string.
var zeroID string = ID(0).String()

// pointFields -> influxDBClient.Point.Fields
type pointFields map[string]interface{}

type InfluxDBStore struct {
	con           *influxDBClient.Client // InfluxDB client connection.
	server        *influxDBServer.Server // InfluxDB API server.
	tracesPerPage int                    // Number of traces per page.
}

func (in *InfluxDBStore) Collect(id SpanID, anns ...Annotation) error {
	p, err := in.findSpanPoint(id)
	if err != nil {
		return err
	}

	// trace_id, span_id & parent_id are set as tags
	// because InfluxDB tags are indexed & those values
	// are used later on queries.
	tags := map[string]string{
		"trace_id":  id.Trace.String(),
		"span_id":   id.Span.String(),
		"parent_id": id.Parent.String(),
	}

	// Saving annotations as InfluxDB measurement spans fields
	// which are not indexed.
	fields := make(map[string]interface{}, len(anns))
	for _, ann := range anns {
		fields[ann.Key] = string(ann.Value)
	}

	if p != nil { // span exists on DB.
		p.Measurement = spanMeasurementName
		p.Tags = tags
		// Using extendFields & withoutEmptyFields in order to have
		// pointFields that only contain:
		// - Fields that are not saved on DB.
		// - Fields that are saved but have empty values.
		p.Fields = extendFields(fields, withoutEmptyFields(p.Fields))
	} else { // new span to be saved on DB.
		p = &influxDBClient.Point{
			Measurement: spanMeasurementName,
			Tags:        tags,   // indexed metadata.
			Fields:      fields, // non-indexed metadata.
			Time:        time.Now().UTC(),
		}
	}

	// InfluxDB point represents a single span.
	pts := []influxDBClient.Point{*p}
	bps := influxDBClient.BatchPoints{
		Points:          pts,
		Database:        dbName,
		RetentionPolicy: "default",
	}
	_, writeErr := in.con.Write(bps)
	if writeErr != nil {
		return writeErr
	}
	return nil
}

func (in *InfluxDBStore) Trace(id ID) (*Trace, error) {
	trace := &Trace{}

	// GROUP BY * -> meaning group by all tags(trace_id, span_id & parent_id)
	// grouping by all tags includes those and it's values on the query response.
	q := fmt.Sprintf("SELECT * FROM spans WHERE trace_id='%s' GROUP BY *", id)
	result, err := in.executeOneQuery(q)
	if err != nil {
		return nil, err
	}

	// result.Series -> A slice containing all the spans.
	if len(result.Series) == 0 {
		return nil, errors.New("trace not found")
	}

	// Iterate over series(spans) to create trace children's & set trace fields.
	var rootSpanSet bool
	for _, s := range result.Series {
		var isRootSpan bool
		span, err := newSpanFromRow(&s)
		if err != nil {
			return nil, err
		}
		annotations, err := annotationsFromRow(&s)
		if err != nil {
			return trace, nil
		}
		span.Annotations = *annotations
		if span.ID.IsRoot() && rootSpanSet {
			return nil, errors.New("unexpected multiple root spans")
		}
		if span.ID.IsRoot() && !rootSpanSet {
			isRootSpan = true
		}
		if isRootSpan { // root span.
			trace.Span = *span
			rootSpanSet = true
		} else { // children span.
			trace.Sub = append(trace.Sub, &Trace{Span: *span})
		}
	}
	return trace, nil
}

func (in *InfluxDBStore) Traces() ([]*Trace, error) {
	traces := make([]*Trace, 0)

	// GROUP BY * -> meaning group by all tags(trace_id, span_id & parent_id)
	// grouping by all tags includes those and it's values on the query response.
	rootSpansQuery := fmt.Sprintf("SELECT * FROM spans WHERE parent_id='%s' GROUP BY * LIMIT %d", zeroID, in.tracesPerPage)
	rootSpansResult, err := in.executeOneQuery(rootSpansQuery)
	if err != nil {
		return nil, err
	}

	// result.Series -> A slice containing all the spans.
	if len(rootSpansResult.Series) == 0 {
		return traces, nil
	}

	// Cache to keep track of traces to be returned.
	tracesCache := make(map[ID]*Trace, 0)

	// Iterate over series(spans) to create traces.
	for _, s := range rootSpansResult.Series {
		span, err := newSpanFromRow(&s)
		if err != nil {
			return nil, err
		}
		annotations, err := annotationsFromRow(&s)
		if err != nil {
			return nil, err
		}
		span.Annotations = *annotations
		_, present := tracesCache[span.ID.Trace]
		if !present {
			tracesCache[span.ID.Trace] = &Trace{Span: *span}
		} else {
			return nil, errors.New("duplicated root span")
		}
	}

	// Using 'OR' since 'IN' not supported yet.
	where := `WHERE `
	var i int = 1
	for _, trace := range tracesCache {
		where += fmt.Sprintf("(trace_id='%s' AND parent_id!='%s')", trace.Span.ID.Trace, zeroID)

		// Adds 'OR' except for last iteration.
		if i != len(tracesCache) && len(tracesCache) > 1 {
			where += " OR "
		}
		i += 1
	}

	// Queries for all children spans of the traces to be returned.
	childrenSpansQuery := fmt.Sprintf("SELECT * FROM spans %s GROUP BY *", where)
	childrenSpansResult, err := in.executeOneQuery(childrenSpansQuery)
	if err != nil {
		return nil, err
	}

	// Iterate over series(children spans) to create sub-traces
	// and associates sub-traces with it's parent trace.
	for _, s := range childrenSpansResult.Series {
		span, err := newSpanFromRow(&s)
		if err != nil {
			return nil, err
		}
		annotations, err := annotationsFromRow(&s)
		if err != nil {
			return nil, err
		}
		span.Annotations = *annotations
		trace, present := tracesCache[span.ID.Trace]
		if !present { // Root trace not added.
			return nil, errors.New("parent not found")
		} else { // Root trace already added so append a sub-trace.
			trace.Sub = append(trace.Sub, &Trace{Span: *span})
		}
	}
	for _, trace := range tracesCache {
		traces = append(traces, trace)
	}
	return traces, nil
}

func (in *InfluxDBStore) Close() error {
	return in.server.Close()
}

func (in *InfluxDBStore) createDBIfNotExists() error {
	// If no errors query execution was successfully - either DB was created or already exists.
	response, err := in.con.Query(influxDBClient.Query{
		Command: fmt.Sprintf("%s %s", "CREATE DATABASE IF NOT EXISTS", dbName),
	})
	if err != nil {
		return err
	}
	if response.Error() != nil {
		return response.Error()
	}
	return nil
}

func (in *InfluxDBStore) executeOneQuery(command string) (*influxDBClient.Result, error) {
	response, err := in.con.Query(influxDBClient.Query{
		Command:  command,
		Database: dbName,
	})
	if err != nil {
		return nil, err
	}
	if response.Error() != nil {
		return nil, response.Error()
	}

	// Expecting one result, since a single query is executed.
	if len(response.Results) != 1 {
		return nil, errors.New("unexpected number of results for an influxdb single query")
	}
	return &response.Results[0], nil
}

func (in *InfluxDBStore) findSpanPoint(ID SpanID) (*influxDBClient.Point, error) {
	q := fmt.Sprintf(`
		SELECT * FROM spans WHERE trace_id='%s' AND span_id='%s' AND parent_id='%s' GROUP BY *
	`, ID.Trace, ID.Span, ID.Parent)
	result, err := in.executeOneQuery(q)
	if err != nil {
		return nil, err
	}
	if len(result.Series) == 0 {
		return nil, nil
	}
	if len(result.Series) > 1 {
		return nil, errors.New("unexpected multiple series")
	}
	r := result.Series[0]
	if len(r.Values) == 0 {
		return nil, errors.New("unexpected empty series")
	}
	p := influxDBClient.Point{
		Fields: make(pointFields, 0),
	}
	fields := r.Values[0]
	for i, field := range fields {
		key := r.Columns[i]
		switch field.(type) {
		case string:
			// time field is set by InfluxDB not related to annotations.
			if key == "time" {
				t, err := time.Parse(time.RFC3339Nano, field.(string))
				if err != nil {
					return nil, err
				}
				p.Time = t
			}
			p.Fields[key] = field.(string)
		case nil:
			continue
		default:
			return nil, fmt.Errorf("unexpected field type: %v", reflect.TypeOf(field))
		}
	}
	return &p, err
}

func (in *InfluxDBStore) init(server *influxDBServer.Server) error {
	in.server = server
	url, err := url.Parse(fmt.Sprintf("http://%s:%d", influxDBClient.DefaultHost, influxDBClient.DefaultPort))
	if err != nil {
		return err
	}
	con, err := influxDBClient.NewClient(influxDBClient.Config{URL: *url})
	if err != nil {
		return err
	}
	in.con = con
	if err := in.createDBIfNotExists(); err != nil {
		return err
	}
	// TODO: support specifying the number of traces per page.
	in.tracesPerPage = defaultTracesPerPage
	return nil
}

func annotationsFromRow(r *influxDBModels.Row) (*Annotations, error) {
	// Actually an influxDBModels.Row represents a single InfluxDB serie.
	// r.Values[n] is a slice containing span's annotation values.
	var fields []interface{}
	if len(r.Values) == 1 {
		fields = r.Values[0]
	}

	// len(r.Values) might be greater than one - meaning there are
	// some spans to drop, see: InfluxDBStore.Collect(...).
	// If so last one is picked.
	if len(r.Values) > 1 {
		fields = r.Values[len(r.Values)-1]
	}
	annotations := make(Annotations, 0)

	// Iterates over fields which represent span's annotation values.
	for i, field := range fields {
		// It is safe to do column[0] (eg. 'Server.Request.Method')
		// matches fields[0] (eg. 'GET')
		key := r.Columns[i]
		var value []byte
		switch field.(type) {
		case string:
			value = []byte(field.(string))
		case nil:
		default:
			return nil, fmt.Errorf("unexpected field type: %v", reflect.TypeOf(field))
		}
		a := Annotation{
			Key:   key,
			Value: value,
		}
		annotations = append(annotations, a)
	}

	return &annotations, nil
}

// extendFields replaces existing items on dst from src.
func extendFields(dst, src pointFields) pointFields {
	for k, v := range src {
		if _, present := dst[k]; present {
			dst[k] = v
		}
	}
	return dst
}

// withoutEmptyFields returns a pointFields without
// those fields that has empty values.
func withoutEmptyFields(pf pointFields) pointFields {
	r := make(pointFields, 0)
	for k, v := range pf {
		switch v.(type) {
		case string:
			if v.(string) == "" {
				continue
			}
			r[k] = v
		case nil:
			continue
		default:
			r[k] = v
		}
	}
	return r
}

func newSpanFromRow(r *influxDBModels.Row) (*Span, error) {
	span := &Span{}
	traceID, err := ParseID(r.Tags["trace_id"])
	if err != nil {
		return nil, err
	}
	spanID, err := ParseID(r.Tags["span_id"])
	if err != nil {
		return nil, err
	}
	parentID, err := ParseID(r.Tags["parent_id"])
	if err != nil {
		return nil, err
	}
	span.ID = SpanID{
		Trace:  ID(traceID),
		Span:   ID(spanID),
		Parent: ID(parentID),
	}
	return span, nil
}

type InfluxDBStoreConfig struct {
	Server    *influxDBServer.Config
	BuildInfo *influxDBServer.BuildInfo
}

func NewInfluxDBStore(config InfluxDBStoreConfig) (*InfluxDBStore, error) {
	//TODO: add Authentication.
	s, err := influxDBServer.NewServer(config.Server, config.BuildInfo)
	if err != nil {
		return nil, err
	}
	if err := s.Open(); err != nil {
		return nil, err
	}
	var in InfluxDBStore
	if err := in.init(s); err != nil {
		return nil, err
	}
	return &in, nil
}
<commit_msg>updates to handle multiple row values & update docs<commit_after>package appdash

import (
	"errors"
	"fmt"
	"net/url"
	"reflect"
	"time"

	influxDBClient "github.com/influxdb/influxdb/client"
	influxDBServer "github.com/influxdb/influxdb/cmd/influxd/run"
	influxDBModels "github.com/influxdb/influxdb/models"
)

const (
	dbName               string = "appdash" // InfluxDB db name.
	spanMeasurementName  string = "spans"   // InfluxDB container name for trace spans.
	defaultTracesPerPage int    = 10        // Default number of traces per page.
)

// Compile-time "implements" check.
var _ interface {
	Store
	Queryer
} = (*InfluxDBStore)(nil)

// zeroID is ID's zero value as string.
var zeroID string = ID(0).String()

// pointFields -> influxDBClient.Point.Fields
type pointFields map[string]interface{}

type InfluxDBStore struct {
	con           *influxDBClient.Client // InfluxDB client connection.
	server        *influxDBServer.Server // InfluxDB API server.
	tracesPerPage int                    // Number of traces per page.
}

func (in *InfluxDBStore) Collect(id SpanID, anns ...Annotation) error {
	p, err := in.findSpanPoint(id)
	if err != nil {
		return err
	}

	// trace_id, span_id & parent_id are set as tags
	// because InfluxDB tags are indexed & those values
	// are used later on queries.
	tags := map[string]string{
		"trace_id":  id.Trace.String(),
		"span_id":   id.Span.String(),
		"parent_id": id.Parent.String(),
	}

	// Saving annotations as InfluxDB measurement spans fields
	// which are not indexed.
	fields := make(map[string]interface{}, len(anns))
	for _, ann := range anns {
		fields[ann.Key] = string(ann.Value)
	}

	if p != nil { // span exists on DB.
		p.Measurement = spanMeasurementName
		p.Tags = tags
		// Using extendFields & withoutEmptyFields in order to have
		// pointFields that only contain:
		// - Fields that are not saved on DB.
		// - Fields that are saved but have empty values.
		p.Fields = extendFields(fields, withoutEmptyFields(p.Fields))
	} else { // new span to be saved on DB.
		p = &influxDBClient.Point{
			Measurement: spanMeasurementName,
			Tags:        tags,   // indexed metadata.
			Fields:      fields, // non-indexed metadata.
			Time:        time.Now().UTC(),
		}
	}

	// InfluxDB point represents a single span.
	pts := []influxDBClient.Point{*p}
	bps := influxDBClient.BatchPoints{
		Points:          pts,
		Database:        dbName,
		RetentionPolicy: "default",
	}
	_, writeErr := in.con.Write(bps)
	if writeErr != nil {
		return writeErr
	}
	return nil
}

func (in *InfluxDBStore) Trace(id ID) (*Trace, error) {
	trace := &Trace{}

	// GROUP BY * -> meaning group by all tags(trace_id, span_id & parent_id)
	// grouping by all tags includes those and it's values on the query response.
	q := fmt.Sprintf("SELECT * FROM spans WHERE trace_id='%s' GROUP BY *", id)
	result, err := in.executeOneQuery(q)
	if err != nil {
		return nil, err
	}

	// result.Series -> A slice containing all the spans.
	if len(result.Series) == 0 {
		return nil, errors.New("trace not found")
	}

	// Iterate over series(spans) to create trace children's & set trace fields.
	var rootSpanSet bool
	for _, s := range result.Series {
		var isRootSpan bool
		span, err := newSpanFromRow(&s)
		if err != nil {
			return nil, err
		}
		annotations, err := annotationsFromRow(&s)
		if err != nil {
			return trace, nil
		}
		span.Annotations = *annotations
		if span.ID.IsRoot() && rootSpanSet {
			return nil, errors.New("unexpected multiple root spans")
		}
		if span.ID.IsRoot() && !rootSpanSet {
			isRootSpan = true
		}
		if isRootSpan { // root span.
			trace.Span = *span
			rootSpanSet = true
		} else { // children span.
			trace.Sub = append(trace.Sub, &Trace{Span: *span})
		}
	}
	return trace, nil
}

func (in *InfluxDBStore) Traces() ([]*Trace, error) {
	traces := make([]*Trace, 0)

	// GROUP BY * -> meaning group by all tags(trace_id, span_id & parent_id)
	// grouping by all tags includes those and it's values on the query response.
	rootSpansQuery := fmt.Sprintf("SELECT * FROM spans WHERE parent_id='%s' GROUP BY * LIMIT %d", zeroID, in.tracesPerPage)
	rootSpansResult, err := in.executeOneQuery(rootSpansQuery)
	if err != nil {
		return nil, err
	}

	// result.Series -> A slice containing all the spans.
	if len(rootSpansResult.Series) == 0 {
		return traces, nil
	}

	// Cache to keep track of traces to be returned.
	tracesCache := make(map[ID]*Trace, 0)

	// Iterate over series(spans) to create traces.
	for _, s := range rootSpansResult.Series {
		span, err := newSpanFromRow(&s)
		if err != nil {
			return nil, err
		}
		annotations, err := annotationsFromRow(&s)
		if err != nil {
			return nil, err
		}
		span.Annotations = *annotations
		_, present := tracesCache[span.ID.Trace]
		if !present {
			tracesCache[span.ID.Trace] = &Trace{Span: *span}
		} else {
			return nil, errors.New("duplicated root span")
		}
	}

	// Using 'OR' since 'IN' not supported yet.
	where := `WHERE `
	var i int = 1
	for _, trace := range tracesCache {
		where += fmt.Sprintf("(trace_id='%s' AND parent_id!='%s')", trace.Span.ID.Trace, zeroID)

		// Adds 'OR' except for last iteration.
		if i != len(tracesCache) && len(tracesCache) > 1 {
			where += " OR "
		}
		i += 1
	}

	// Queries for all children spans of the traces to be returned.
	childrenSpansQuery := fmt.Sprintf("SELECT * FROM spans %s GROUP BY *", where)
	childrenSpansResult, err := in.executeOneQuery(childrenSpansQuery)
	if err != nil {
		return nil, err
	}

	// Iterate over series(children spans) to create sub-traces
	// and associates sub-traces with it's parent trace.
	for _, s := range childrenSpansResult.Series {
		span, err := newSpanFromRow(&s)
		if err != nil {
			return nil, err
		}
		annotations, err := annotationsFromRow(&s)
		if err != nil {
			return nil, err
		}
		span.Annotations = *annotations
		trace, present := tracesCache[span.ID.Trace]
		if !present { // Root trace not added.
			return nil, errors.New("parent not found")
		} else { // Root trace already added so append a sub-trace.
			trace.Sub = append(trace.Sub, &Trace{Span: *span})
		}
	}
	for _, trace := range tracesCache {
		traces = append(traces, trace)
	}
	return traces, nil
}

func (in *InfluxDBStore) Close() error {
	return in.server.Close()
}

func (in *InfluxDBStore) createDBIfNotExists() error {
	// If no errors query execution was successfully - either DB was created or already exists.
	response, err := in.con.Query(influxDBClient.Query{
		Command: fmt.Sprintf("%s %s", "CREATE DATABASE IF NOT EXISTS", dbName),
	})
	if err != nil {
		return err
	}
	if response.Error() != nil {
		return response.Error()
	}
	return nil
}

func (in *InfluxDBStore) executeOneQuery(command string) (*influxDBClient.Result, error) {
	response, err := in.con.Query(influxDBClient.Query{
		Command:  command,
		Database: dbName,
	})
	if err != nil {
		return nil, err
	}
	if response.Error() != nil {
		return nil, response.Error()
	}

	// Expecting one result, since a single query is executed.
	if len(response.Results) != 1 {
		return nil, errors.New("unexpected number of results for an influxdb single query")
	}
	return &response.Results[0], nil
}

func (in *InfluxDBStore) findSpanPoint(ID SpanID) (*influxDBClient.Point, error) {
	q := fmt.Sprintf(`
		SELECT * FROM spans WHERE trace_id='%s' AND span_id='%s' AND parent_id='%s' GROUP BY *
	`, ID.Trace, ID.Span, ID.Parent)
	result, err := in.executeOneQuery(q)
	if err != nil {
		return nil, err
	}
	if len(result.Series) == 0 {
		return nil, nil
	}
	if len(result.Series) > 1 {
		return nil, errors.New("unexpected multiple series")
	}
	r := result.Series[0]
	if len(r.Values) == 0 {
		return nil, errors.New("unexpected empty series")
	}
	p := influxDBClient.Point{
		Fields: make(pointFields, 0),
	}
	fields := r.Values[0]
	for i, field := range fields {
		key := r.Columns[i]
		switch field.(type) {
		case string:
			// time field is set by InfluxDB not related to annotations.
			if key == "time" {
				t, err := time.Parse(time.RFC3339Nano, field.(string))
				if err != nil {
					return nil, err
				}
				p.Time = t
			}
			p.Fields[key] = field.(string)
		case nil:
			continue
		default:
			return nil, fmt.Errorf("unexpected field type: %v", reflect.TypeOf(field))
		}
	}
	return &p, err
}

func (in *InfluxDBStore) init(server *influxDBServer.Server) error {
	in.server = server
	url, err := url.Parse(fmt.Sprintf("http://%s:%d", influxDBClient.DefaultHost, influxDBClient.DefaultPort))
	if err != nil {
		return err
	}
	con, err := influxDBClient.NewClient(influxDBClient.Config{URL: *url})
	if err != nil {
		return err
	}
	in.con = con
	if err := in.createDBIfNotExists(); err != nil {
		return err
	}
	// TODO: support specifying the number of traces per page.
	in.tracesPerPage = defaultTracesPerPage
	return nil
}

func annotationsFromRow(r *influxDBModels.Row) (*Annotations, error) {
	// Actually an influxDBModels.Row represents a single InfluxDB serie.
	// r.Values[n] is a slice containing span's annotation values.
	var fields []interface{}
	if len(r.Values) == 1 {
		fields = r.Values[0]
	}

	// len(r.Values) cannot be greater than 1.
	// Values[0] is the slice containing a span's
	// annotation values.
	if len(r.Values) > 1 {
		return nil, errors.New("unexpected multiple row values")
	}
	annotations := make(Annotations, 0)

	// Iterates over fields which represent span's annotation values.
	for i, field := range fields {
		// It is safe to do column[0] (eg. 'Server.Request.Method')
		// matches fields[0] (eg. 'GET')
		key := r.Columns[i]
		var value []byte
		switch field.(type) {
		case string:
			value = []byte(field.(string))
		case nil:
		default:
			return nil, fmt.Errorf("unexpected field type: %v", reflect.TypeOf(field))
		}
		a := Annotation{
			Key:   key,
			Value: value,
		}
		annotations = append(annotations, a)
	}

	return &annotations, nil
}

// extendFields replaces existing items on dst from src.
func extendFields(dst, src pointFields) pointFields {
	for k, v := range src {
		if _, present := dst[k]; present {
			dst[k] = v
		}
	}
	return dst
}

// withoutEmptyFields returns a pointFields without
// those fields that has empty values.
func withoutEmptyFields(pf pointFields) pointFields {
	r := make(pointFields, 0)
	for k, v := range pf {
		switch v.(type) {
		case string:
			if v.(string) == "" {
				continue
			}
			r[k] = v
		case nil:
			continue
		default:
			r[k] = v
		}
	}
	return r
}

func newSpanFromRow(r *influxDBModels.Row) (*Span, error) {
	span := &Span{}
	traceID, err := ParseID(r.Tags["trace_id"])
	if err != nil {
		return nil, err
	}
	spanID, err := ParseID(r.Tags["span_id"])
	if err != nil {
		return nil, err
	}
	parentID, err := ParseID(r.Tags["parent_id"])
	if err != nil {
		return nil, err
	}
	span.ID = SpanID{
		Trace:  ID(traceID),
		Span:   ID(spanID),
		Parent: ID(parentID),
	}
	return span, nil
}

type InfluxDBStoreConfig struct {
	Server    *influxDBServer.Config
	BuildInfo *influxDBServer.BuildInfo
}

func NewInfluxDBStore(config InfluxDBStoreConfig) (*InfluxDBStore, error) {
	//TODO: add Authentication.
	s, err := influxDBServer.NewServer(config.Server, config.BuildInfo)
	if err != nil {
		return nil, err
	}
	if err := s.Open(); err != nil {
		return nil, err
	}
	var in InfluxDBStore
	if err := in.init(s); err != nil {
		return nil, err
	}
	return &in, nil
}
<|endoftext|>


<commit_before>package reservation

import (
	"fmt"
	"os"
	"sync"
	"testing"
	"time"

	"github.com/garyburd/redigo/redis"
	"github.com/stretchr/testify/assert"
)

var redisTestURL = os.Getenv("REDIS_TEST_URL")

func TestNewManager(t *testing.T) {
	manager, err := NewManager(redisTestURL, "test-worker")
	assert.Nil(t, err)
	// If manager is not nil, NewManager was able to succesfully ping local redis
	assert.NotNil(t, manager)
}

func setUp(t *testing.T) (*Manager, string) {
	conn, err := redis.DialTimeout("tcp", redisTestURL, 15*time.Second, 10*time.Second, 10*time.Second)
	message := fmt.Sprintf("Could not connect to redis at %s. Ensure redis is running at this location (maybe locally)?", redisTestURL)
	assert.Nil(t, err, message)
	defer conn.Close()
	conn.Do("FLUSHALL")
	manager, err := NewManager(redisTestURL, "test-worker")
	assert.Nil(t, err)
	resourceID := "12345"
	return manager, resourceID
}

func TestSourceExposed(t *testing.T) {
	manager, resourceID := setUp(t)

	// Create a reservation
	reservation, err := manager.Lock(resourceID)
	assert.Nil(t, err)

	hostname, err := os.Hostname()
	assert.Nil(t, err)

	expectedKeySubstr := fmt.Sprintf("%s-%s", hostname, manager.owner)
	// Assert we can access the reservation value
	assert.Contains(t, reservation.Source, expectedKeySubstr)
}

func TestManagerLockCreate(t *testing.T) {
	manager, resourceID := setUp(t)

	// Make a new reservation
	reservation, err := manager.Lock(resourceID)
	assert.Nil(t, err)

	// Assert reservation created with correct field values
	assert.NotNil(t, reservation)

	// Make the same reservation again and ensure error returned
	dupeReservation, err := manager.Lock(resourceID)
	assert.Nil(t, dupeReservation)
	assert.EqualError(t, err, fmt.Sprintf("Reservation already exists for resource %s", resourceID))

	// Release the first reservation and ensure we can make a second reservation
	err = reservation.Release()
	assert.Nil(t, err)
	reservation, err = manager.Lock(resourceID)
	assert.NotNil(t, reservation)
}

func TestManagerLockConcurrentRequests(t *testing.T) {
	// Test to ensure no race conditions when making many concurrent lock requests
	manager, resourceID := setUp(t)

	var wg sync.WaitGroup

	hold := make(chan struct{})

	// Make 100 simultaneous requests for locks
	numReservationExistsErrors := 0
	numOtherErrors := 0
	numReservations := 0
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			<-hold // try to read from channel to block the goroutine
			reservation, err := manager.Lock(resourceID)
			expectedErr := fmt.Sprintf("Reservation already exists for resource %s", resourceID)
			if fmt.Sprintf("%s", err) == expectedErr {
				numReservationExistsErrors++
			} else if err != nil {
				fmt.Printf("Other error: %s", err)
				numOtherErrors++
			}
			if reservation != nil {
				numReservations++
			}
		}()
	}
	close(hold) // close channel so all goroutines make requests at once
	wg.Wait()

	assert.Equal(t, numReservations, 1, "Expected only one reservation to be made")
	assert.Equal(t, numOtherErrors+numReservationExistsErrors, 99, "Expected 99 errors")
}

func TestReservationTTL(t *testing.T) {
	manager, resourceID := setUp(t)

	// This setup would never happen in production, but simulates orphaned reservations
	// being left in redis if (for example) the process dies unexpectedly without releasing
	// the reservation
	manager.Heartbeat = 100 * time.Second
	manager.TTL = 1 * time.Second

	// Create a reservation
	reservation, err := manager.Lock(resourceID)
	assert.Nil(t, err)

	// Assert we can make a new reservation after TTL expires
	time.Sleep(2 * time.Second)
	reservation, err = manager.Lock(resourceID)
	assert.NotNil(t, reservation)
	assert.Nil(t, err)
}

func TestReservationWaitUntilLock(t *testing.T) {
	manager, resourceID := setUp(t)
	c := make(chan interface{})

	// Create a reservation and hold it for a little while
	go func() {
		reservation, err := manager.Lock(resourceID)
		assert.Nil(t, err)
		close(c)
		time.Sleep(5 * time.Second)
		reservation.Release()
	}()

	// Assert that the reservation is currently held, and we can't get it
	<-c
	_, err := manager.Lock(resourceID)
	assert.EqualError(t, err, fmt.Sprintf("Reservation already exists for resource %s", resourceID))

	// Now wait for the reservation, and assert that we get it eventually
	waitingReservation, err := manager.WaitUntilLock(resourceID)
	assert.NoError(t, err)
	assert.NotNil(t, waitingReservation)
}

func TestReservationExtend(t *testing.T) {
	manager, resourceID := setUp(t)

	manager.Heartbeat = 500 * time.Millisecond
	manager.TTL = 1 * time.Second

	// Create a reservation
	reservation, err := manager.Lock(resourceID)
	assert.Nil(t, err)

	// Assert we cannot make a new reservation because TTL has been extended
	time.Sleep(2 * time.Second)
	reservation, err = manager.Lock(resourceID)
	assert.Nil(t, reservation)
	assert.EqualError(t, err, fmt.Sprintf("Reservation already exists for resource %s", resourceID))
}
<commit_msg>remove race condition from concurrency test :chompy:<commit_after>package reservation

import (
	"fmt"
	"os"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	"github.com/garyburd/redigo/redis"
	"github.com/stretchr/testify/assert"
)

var redisTestURL = os.Getenv("REDIS_TEST_URL")

func TestNewManager(t *testing.T) {
	manager, err := NewManager(redisTestURL, "test-worker")
	assert.Nil(t, err)
	// If manager is not nil, NewManager was able to succesfully ping local redis
	assert.NotNil(t, manager)
}

func setUp(t *testing.T) (*Manager, string) {
	conn, err := redis.DialTimeout("tcp", redisTestURL, 15*time.Second, 10*time.Second, 10*time.Second)
	message := fmt.Sprintf("Could not connect to redis at %s. Ensure redis is running at this location (maybe locally)?", redisTestURL)
	assert.Nil(t, err, message)
	defer conn.Close()
	conn.Do("FLUSHALL")
	manager, err := NewManager(redisTestURL, "test-worker")
	assert.Nil(t, err)
	resourceID := "12345"
	return manager, resourceID
}

func TestSourceExposed(t *testing.T) {
	manager, resourceID := setUp(t)

	// Create a reservation
	reservation, err := manager.Lock(resourceID)
	assert.Nil(t, err)

	hostname, err := os.Hostname()
	assert.Nil(t, err)

	expectedKeySubstr := fmt.Sprintf("%s-%s", hostname, manager.owner)
	// Assert we can access the reservation value
	assert.Contains(t, reservation.Source, expectedKeySubstr)
}

func TestManagerLockCreate(t *testing.T) {
	manager, resourceID := setUp(t)

	// Make a new reservation
	reservation, err := manager.Lock(resourceID)
	assert.Nil(t, err)

	// Assert reservation created with correct field values
	assert.NotNil(t, reservation)

	// Make the same reservation again and ensure error returned
	dupeReservation, err := manager.Lock(resourceID)
	assert.Nil(t, dupeReservation)
	assert.EqualError(t, err, fmt.Sprintf("Reservation already exists for resource %s", resourceID))

	// Release the first reservation and ensure we can make a second reservation
	err = reservation.Release()
	assert.Nil(t, err)
	reservation, err = manager.Lock(resourceID)
	assert.NotNil(t, reservation)
}

func TestManagerLockConcurrentRequests(t *testing.T) {
	// Test to ensure no race conditions when making many concurrent lock requests
	manager, resourceID := setUp(t)

	var wg sync.WaitGroup

	hold := make(chan struct{})

	// Make 100 simultaneous requests for locks
	var (
		numReservationExistsErrors int32
		numOtherErrors             int32
		numReservations            int32
	)
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			<-hold // try to read from channel to block the goroutine
			reservation, err := manager.Lock(resourceID)
			expectedErr := fmt.Sprintf("Reservation already exists for resource %s", resourceID)
			if fmt.Sprintf("%s", err) == expectedErr {
				atomic.AddInt32(&numReservationExistsErrors, 1)
			} else if err != nil {
				fmt.Printf("Other error: %s", err)
				atomic.AddInt32(&numOtherErrors, 1)
			}
			if reservation != nil {
				atomic.AddInt32(&numReservations, 1)
			}
		}()
	}
	close(hold) // close channel so all goroutines make requests at once
	wg.Wait()

	assert.Equal(t, numReservations, int32(1), "Expected only one reservation to be made")
	assert.Equal(t, numOtherErrors+numReservationExistsErrors, int32(99), "Expected 99 errors")
}

func TestReservationTTL(t *testing.T) {
	manager, resourceID := setUp(t)

	// This setup would never happen in production, but simulates orphaned reservations
	// being left in redis if (for example) the process dies unexpectedly without releasing
	// the reservation
	manager.Heartbeat = 100 * time.Second
	manager.TTL = 1 * time.Second

	// Create a reservation
	reservation, err := manager.Lock(resourceID)
	assert.Nil(t, err)

	// Assert we can make a new reservation after TTL expires
	time.Sleep(2 * time.Second)
	reservation, err = manager.Lock(resourceID)
	assert.NotNil(t, reservation)
	assert.Nil(t, err)
}

func TestReservationWaitUntilLock(t *testing.T) {
	manager, resourceID := setUp(t)
	c := make(chan interface{})

	// Create a reservation and hold it for a little while
	go func() {
		reservation, err := manager.Lock(resourceID)
		assert.Nil(t, err)
		close(c)
		time.Sleep(5 * time.Second)
		reservation.Release()
	}()

	// Assert that the reservation is currently held, and we can't get it
	<-c
	_, err := manager.Lock(resourceID)
	assert.EqualError(t, err, fmt.Sprintf("Reservation already exists for resource %s", resourceID))

	// Now wait for the reservation, and assert that we get it eventually
	waitingReservation, err := manager.WaitUntilLock(resourceID)
	assert.NoError(t, err)
	assert.NotNil(t, waitingReservation)
}

func TestReservationExtend(t *testing.T) {
	manager, resourceID := setUp(t)

	manager.Heartbeat = 500 * time.Millisecond
	manager.TTL = 1 * time.Second

	// Create a reservation
	reservation, err := manager.Lock(resourceID)
	assert.Nil(t, err)

	// Assert we cannot make a new reservation because TTL has been extended
	time.Sleep(2 * time.Second)
	reservation, err = manager.Lock(resourceID)
	assert.Nil(t, reservation)
	assert.EqualError(t, err, fmt.Sprintf("Reservation already exists for resource %s", resourceID))
}
<|endoftext|>


<commit_before>package aws

import (
	"testing"

	"github.com/hashicorp/terraform/helper/resource"
)

func TestAccAWSEBSVolume(t *testing.T) {
	resource.Test(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: testAccAwsEbsVolumeConfig,
			},
		},
	})
}

const testAccAwsEbsVolumeConfig = `
resource "aws_ebs_volume" "test" {
	availability_zone = "us-east-1d"
	size = 1
}
`
<commit_msg>provider/aws: move EBS test into us-west-2<commit_after>package aws

import (
	"testing"

	"github.com/hashicorp/terraform/helper/resource"
)

func TestAccAWSEBSVolume(t *testing.T) {
	resource.Test(t, resource.TestCase{
		PreCheck:  func() { testAccPreCheck(t) },
		Providers: testAccProviders,
		Steps: []resource.TestStep{
			resource.TestStep{
				Config: testAccAwsEbsVolumeConfig,
			},
		},
	})
}

const testAccAwsEbsVolumeConfig = `
resource "aws_ebs_volume" "test" {
	availability_zone = "us-west-2a"
	size = 1
}
`
<|endoftext|>


<commit_before>package comments

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"

	"github.com/google/go-github/github"
)

type CommentHandler func(client *github.Client, comment github.IssueCommentEvent) error

type CommentsHandler struct {
	client               *github.Client
	issueCommentHandlers []CommentHandler
	pullCommentHandlers  []CommentHandler
}

// NewHandler returns an HTTP handler which deprecates repositories
// by closing new issues with a comment directing attention elsewhere.
func NewHandler(client *github.Client, issuesHandlers []CommentHandler, pullRequestsHandlers []CommentHandler) *CommentsHandler {
	return &CommentsHandler{
		client:               client,
		issueCommentHandlers: issuesHandlers,
		pullCommentHandlers:  pullRequestsHandlers,
	}
}

func (h *CommentsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if eventType := r.Header.Get("X-GitHub-Event"); !isComment(eventType) {
		log.Printf("received invalid event of type X-GitHub-Event: %s", eventType)
		http.Error(w, "not an issue_comment event.", 200)
		return
	}

	var event github.IssueCommentEvent
	err := json.NewDecoder(r.Body).Decode(&event)
	if err != nil {
		log.Println("error unmarshalling issue comment stuffs:", err)
		http.Error(w, "bad json", 400)
		return
	}

	var handlers []CommentHandler
	if isPullRequest(event) {
		handlers = h.pullCommentHandlers
	} else {
		handlers = h.issueCommentHandlers
	}

	for _, handler := range handlers {
		go handler(h.client, event)
	}

	fmt.Fprintf(w, "fired %d handlers", len(handlers))
}

func isPullRequest(event github.IssueCommentEvent) bool {
	return event.Issue.PullRequestLinks != nil
}

func isComment(eventType string) bool {
	return eventType == "issue_comment" || eventType == "pull_request"
}
<commit_msg>Ensure isPullRequest() doesn't try to dereference a nil object.<commit_after>package comments

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"

	"github.com/google/go-github/github"
)

type CommentHandler func(client *github.Client, comment github.IssueCommentEvent) error

type CommentsHandler struct {
	client               *github.Client
	issueCommentHandlers []CommentHandler
	pullCommentHandlers  []CommentHandler
}

// NewHandler returns an HTTP handler which deprecates repositories
// by closing new issues with a comment directing attention elsewhere.
func NewHandler(client *github.Client, issuesHandlers []CommentHandler, pullRequestsHandlers []CommentHandler) *CommentsHandler {
	return &CommentsHandler{
		client:               client,
		issueCommentHandlers: issuesHandlers,
		pullCommentHandlers:  pullRequestsHandlers,
	}
}

func (h *CommentsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if eventType := r.Header.Get("X-GitHub-Event"); !isComment(eventType) {
		log.Printf("received invalid event of type X-GitHub-Event: %s", eventType)
		http.Error(w, "not an issue_comment event.", 200)
		return
	}

	var event github.IssueCommentEvent
	err := json.NewDecoder(r.Body).Decode(&event)
	if err != nil {
		log.Println("error unmarshalling issue comment stuffs:", err)
		http.Error(w, "bad json", 400)
		return
	}

	var handlers []CommentHandler
	if isPullRequest(event) {
		handlers = h.pullCommentHandlers
	} else {
		handlers = h.issueCommentHandlers
	}

	for _, handler := range handlers {
		go handler(h.client, event)
	}

	fmt.Fprintf(w, "fired %d handlers", len(handlers))
}

func isPullRequest(event github.IssueCommentEvent) bool {
	return &event != nil && event.Issue != nil && event.Issue.PullRequestLinks != nil
}

func isComment(eventType string) bool {
	return eventType == "issue_comment" || eventType == "pull_request"
}
<|endoftext|>