<commit_before>//  Copyright (c) 2014 Couchbase, 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 bleve

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/blevesearch/bleve/analysis"
	"github.com/blevesearch/bleve/search"
)

type numericRange struct {
	Name string   `json:"name,omitempty"`
	Min  *float64 `json:"min,omitempty"`
	Max  *float64 `json:"max,omitempty"`
}

type dateTimeRange struct {
	Name        string    `json:"name,omitempty"`
	Start       time.Time `json:"start,omitempty"`
	End         time.Time `json:"end,omitempty"`
	startString *string
	endString   *string
}

func (dr *dateTimeRange) ParseDates(dateTimeParser analysis.DateTimeParser) {
	if dr.Start.IsZero() && dr.startString != nil {
		start, err := dateTimeParser.ParseDateTime(*dr.startString)
		if err == nil {
			dr.Start = start
		}
	}
	if dr.End.IsZero() && dr.endString != nil {
		end, err := dateTimeParser.ParseDateTime(*dr.endString)
		if err == nil {
			dr.End = end
		}
	}
}

func (dr *dateTimeRange) UnmarshalJSON(input []byte) error {
	var temp struct {
		Name  string  `json:"name,omitempty"`
		Start *string `json:"start,omitempty"`
		End   *string `json:"end,omitempty"`
	}

	err := json.Unmarshal(input, &temp)
	if err != nil {
		return err
	}

	dr.Name = temp.Name
	if temp.Start != nil {
		dr.startString = temp.Start
	}
	if temp.End != nil {
		dr.endString = temp.End
	}

	return nil
}

// A FacetRequest describes an facet or aggregation
// of the result document set you would like to be
// built.
type FacetRequest struct {
	Size           int
	Field          string
	NumericRanges  []*numericRange  `json:"numeric_ranges,omitempty"`
	DateTimeRanges []*dateTimeRange `json:"date_ranges,omitempty"`
}

// NewFacetRequest creates a facet on the specified
// field that limits the number of entries to the
// specified size.
func NewFacetRequest(field string, size int) *FacetRequest {
	return &FacetRequest{
		Field: field,
		Size:  size,
	}
}

// AddDateTimeRange adds a bucket to a field
// containing date values.  Documents with a
// date value falling into this range are tabulated
// as part of this bucket/range.
func (fr *FacetRequest) AddDateTimeRange(name string, start, end time.Time) {
	if fr.DateTimeRanges == nil {
		fr.DateTimeRanges = make([]*dateTimeRange, 0, 1)
	}
	fr.DateTimeRanges = append(fr.DateTimeRanges, &dateTimeRange{Name: name, Start: start, End: end})
}

// AddNumericRange adds a bucket to a field
// containing numeric values.  Documents with a
// numeric value falling into this range are
// tabulated as part of this bucket/range.
func (fr *FacetRequest) AddNumericRange(name string, min, max *float64) {
	if fr.NumericRanges == nil {
		fr.NumericRanges = make([]*numericRange, 0, 1)
	}
	fr.NumericRanges = append(fr.NumericRanges, &numericRange{Name: name, Min: min, Max: max})
}

// FacetsRequest groups together all the
// FacetRequest objects for a single query.
type FacetsRequest map[string]*FacetRequest

// HighlightRequest describes how field matches
// should be highlighted.
type HighlightRequest struct {
	Style  *string  `json:"style"`
	Fields []string `json:"fields"`
}

// NewHighlight creates a default
// HighlightRequest.
func NewHighlight() *HighlightRequest {
	return &HighlightRequest{}
}

// NewHighlightWithStyle creates a HighlightRequest
// with an alternate style.
func NewHighlightWithStyle(style string) *HighlightRequest {
	return &HighlightRequest{
		Style: &style,
	}
}

func (h *HighlightRequest) AddField(field string) {
	if h.Fields == nil {
		h.Fields = make([]string, 0, 1)
	}
	h.Fields = append(h.Fields, field)
}

// A SearchRequest describes all the parameters
// needed to search the index.
// Query is required.
// Size/From describe how much and which part of the
// result set to return.
// Highlight describes optional search result
// highlighting.
// Fields desribed a list of field values whcih
// should be retrieved for result documents.
// Facets describe the set of facets to be computed.
// Explain triggers inclusion of additional search
// result score explanations.
type SearchRequest struct {
	Query     Query             `json:"query"`
	Size      int               `json:"size"`
	From      int               `json:"from"`
	Highlight *HighlightRequest `json:"highlight"`
	Fields    []string          `json:"fields"`
	Facets    FacetsRequest     `json:"facets"`
	Explain   bool              `json:"explain"`
}

// AddFacet adds a FacetRequest to this SearchRequest
func (r *SearchRequest) AddFacet(facetName string, f *FacetRequest) {
	if r.Facets == nil {
		r.Facets = make(FacetsRequest, 1)
	}
	r.Facets[facetName] = f
}

// UnmarshalJSON deserializes a JSON representation of
// a SearchRequest
func (r *SearchRequest) UnmarshalJSON(input []byte) error {
	var temp struct {
		Q         json.RawMessage   `json:"query"`
		Size      int               `json:"size"`
		From      int               `json:"from"`
		Highlight *HighlightRequest `json:"highlight"`
		Fields    []string          `json:"fields"`
		Facets    FacetsRequest     `json:"facets"`
		Explain   bool              `json:"explain"`
	}

	err := json.Unmarshal(input, &temp)
	if err != nil {
		return err
	}

	r.Size = temp.Size
	r.From = temp.From
	r.Explain = temp.Explain
	r.Highlight = temp.Highlight
	r.Fields = temp.Fields
	r.Facets = temp.Facets
	r.Query, err = ParseQuery(temp.Q)
	if err != nil {
		return err
	}

	if r.Size <= 0 {
		r.Size = 10
	}
	if r.From <= 0 {
		r.From = 0
	}

	return nil

}

// NewSearchRequest creates a new SearchRequest
// for the Query, using default values for all
// other search parameters.
func NewSearchRequest(q Query) *SearchRequest {
	return NewSearchRequestOptions(q, 10, 0, false)
}

// NewSearchRequestOptions creates a new SearchRequest
// for the Query, with the requested size, from
// and explanation search parameters.
func NewSearchRequestOptions(q Query, size, from int, explain bool) *SearchRequest {
	return &SearchRequest{
		Query:   q,
		Size:    size,
		From:    from,
		Explain: explain,
	}
}

// A SearchResult describes the results of executing
// a SearchRequest.
type SearchResult struct {
	Request  *SearchRequest                 `json:"request"`
	Hits     search.DocumentMatchCollection `json:"hits"`
	Total    uint64                         `json:"total_hits"`
	MaxScore float64                        `json:"max_score"`
	Took     time.Duration                  `json:"took"`
	Facets   search.FacetResults            `json:"facets"`
}

func (sr *SearchResult) String() string {
	rv := ""
	if sr.Total > 0 {
		if sr.Request.Size > 0 {
			rv = fmt.Sprintf("%d matches, showing %d through %d, took %s\n", sr.Total, sr.Request.From+1, sr.Request.From+len(sr.Hits), sr.Took)
			for i, hit := range sr.Hits {
				rv += fmt.Sprintf("%5d. %s (%f)\n", i+sr.Request.From+1, hit.ID, hit.Score)
				for fragmentField, fragments := range hit.Fragments {
					rv += fmt.Sprintf("\t%s\n", fragmentField)
					for _, fragment := range fragments {
						rv += fmt.Sprintf("\t\t%s\n", fragment)
					}
				}
			}
		} else {
			rv = fmt.Sprintf("%d matches, took %s\n", sr.Total, sr.Took)
		}
	} else {
		rv = "No matches"
	}
	return rv
}

func (sr *SearchResult) Merge(other *SearchResult) {
	sr.Hits = append(sr.Hits, other.Hits...)
	sr.Total += other.Total
	sr.Took += other.Took
	if other.MaxScore > sr.MaxScore {
		sr.MaxScore = other.MaxScore
	}
	sr.Facets.Merge(other.Facets)
}
<commit_msg>fix bug which prevented size=0 via JSON<commit_after>//  Copyright (c) 2014 Couchbase, 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 bleve

import (
	"encoding/json"
	"fmt"
	"time"

	"github.com/blevesearch/bleve/analysis"
	"github.com/blevesearch/bleve/search"
)

type numericRange struct {
	Name string   `json:"name,omitempty"`
	Min  *float64 `json:"min,omitempty"`
	Max  *float64 `json:"max,omitempty"`
}

type dateTimeRange struct {
	Name        string    `json:"name,omitempty"`
	Start       time.Time `json:"start,omitempty"`
	End         time.Time `json:"end,omitempty"`
	startString *string
	endString   *string
}

func (dr *dateTimeRange) ParseDates(dateTimeParser analysis.DateTimeParser) {
	if dr.Start.IsZero() && dr.startString != nil {
		start, err := dateTimeParser.ParseDateTime(*dr.startString)
		if err == nil {
			dr.Start = start
		}
	}
	if dr.End.IsZero() && dr.endString != nil {
		end, err := dateTimeParser.ParseDateTime(*dr.endString)
		if err == nil {
			dr.End = end
		}
	}
}

func (dr *dateTimeRange) UnmarshalJSON(input []byte) error {
	var temp struct {
		Name  string  `json:"name,omitempty"`
		Start *string `json:"start,omitempty"`
		End   *string `json:"end,omitempty"`
	}

	err := json.Unmarshal(input, &temp)
	if err != nil {
		return err
	}

	dr.Name = temp.Name
	if temp.Start != nil {
		dr.startString = temp.Start
	}
	if temp.End != nil {
		dr.endString = temp.End
	}

	return nil
}

// A FacetRequest describes an facet or aggregation
// of the result document set you would like to be
// built.
type FacetRequest struct {
	Size           int
	Field          string
	NumericRanges  []*numericRange  `json:"numeric_ranges,omitempty"`
	DateTimeRanges []*dateTimeRange `json:"date_ranges,omitempty"`
}

// NewFacetRequest creates a facet on the specified
// field that limits the number of entries to the
// specified size.
func NewFacetRequest(field string, size int) *FacetRequest {
	return &FacetRequest{
		Field: field,
		Size:  size,
	}
}

// AddDateTimeRange adds a bucket to a field
// containing date values.  Documents with a
// date value falling into this range are tabulated
// as part of this bucket/range.
func (fr *FacetRequest) AddDateTimeRange(name string, start, end time.Time) {
	if fr.DateTimeRanges == nil {
		fr.DateTimeRanges = make([]*dateTimeRange, 0, 1)
	}
	fr.DateTimeRanges = append(fr.DateTimeRanges, &dateTimeRange{Name: name, Start: start, End: end})
}

// AddNumericRange adds a bucket to a field
// containing numeric values.  Documents with a
// numeric value falling into this range are
// tabulated as part of this bucket/range.
func (fr *FacetRequest) AddNumericRange(name string, min, max *float64) {
	if fr.NumericRanges == nil {
		fr.NumericRanges = make([]*numericRange, 0, 1)
	}
	fr.NumericRanges = append(fr.NumericRanges, &numericRange{Name: name, Min: min, Max: max})
}

// FacetsRequest groups together all the
// FacetRequest objects for a single query.
type FacetsRequest map[string]*FacetRequest

// HighlightRequest describes how field matches
// should be highlighted.
type HighlightRequest struct {
	Style  *string  `json:"style"`
	Fields []string `json:"fields"`
}

// NewHighlight creates a default
// HighlightRequest.
func NewHighlight() *HighlightRequest {
	return &HighlightRequest{}
}

// NewHighlightWithStyle creates a HighlightRequest
// with an alternate style.
func NewHighlightWithStyle(style string) *HighlightRequest {
	return &HighlightRequest{
		Style: &style,
	}
}

func (h *HighlightRequest) AddField(field string) {
	if h.Fields == nil {
		h.Fields = make([]string, 0, 1)
	}
	h.Fields = append(h.Fields, field)
}

// A SearchRequest describes all the parameters
// needed to search the index.
// Query is required.
// Size/From describe how much and which part of the
// result set to return.
// Highlight describes optional search result
// highlighting.
// Fields desribed a list of field values whcih
// should be retrieved for result documents.
// Facets describe the set of facets to be computed.
// Explain triggers inclusion of additional search
// result score explanations.
type SearchRequest struct {
	Query     Query             `json:"query"`
	Size      int               `json:"size"`
	From      int               `json:"from"`
	Highlight *HighlightRequest `json:"highlight"`
	Fields    []string          `json:"fields"`
	Facets    FacetsRequest     `json:"facets"`
	Explain   bool              `json:"explain"`
}

// AddFacet adds a FacetRequest to this SearchRequest
func (r *SearchRequest) AddFacet(facetName string, f *FacetRequest) {
	if r.Facets == nil {
		r.Facets = make(FacetsRequest, 1)
	}
	r.Facets[facetName] = f
}

// UnmarshalJSON deserializes a JSON representation of
// a SearchRequest
func (r *SearchRequest) UnmarshalJSON(input []byte) error {
	var temp struct {
		Q         json.RawMessage   `json:"query"`
		Size      int               `json:"size"`
		From      int               `json:"from"`
		Highlight *HighlightRequest `json:"highlight"`
		Fields    []string          `json:"fields"`
		Facets    FacetsRequest     `json:"facets"`
		Explain   bool              `json:"explain"`
	}

	err := json.Unmarshal(input, &temp)
	if err != nil {
		return err
	}

	r.Size = temp.Size
	r.From = temp.From
	r.Explain = temp.Explain
	r.Highlight = temp.Highlight
	r.Fields = temp.Fields
	r.Facets = temp.Facets
	r.Query, err = ParseQuery(temp.Q)
	if err != nil {
		return err
	}

	if r.Size < 0 {
		r.Size = 10
	}
	if r.From < 0 {
		r.From = 0
	}

	return nil

}

// NewSearchRequest creates a new SearchRequest
// for the Query, using default values for all
// other search parameters.
func NewSearchRequest(q Query) *SearchRequest {
	return NewSearchRequestOptions(q, 10, 0, false)
}

// NewSearchRequestOptions creates a new SearchRequest
// for the Query, with the requested size, from
// and explanation search parameters.
func NewSearchRequestOptions(q Query, size, from int, explain bool) *SearchRequest {
	return &SearchRequest{
		Query:   q,
		Size:    size,
		From:    from,
		Explain: explain,
	}
}

// A SearchResult describes the results of executing
// a SearchRequest.
type SearchResult struct {
	Request  *SearchRequest                 `json:"request"`
	Hits     search.DocumentMatchCollection `json:"hits"`
	Total    uint64                         `json:"total_hits"`
	MaxScore float64                        `json:"max_score"`
	Took     time.Duration                  `json:"took"`
	Facets   search.FacetResults            `json:"facets"`
}

func (sr *SearchResult) String() string {
	rv := ""
	if sr.Total > 0 {
		if sr.Request.Size > 0 {
			rv = fmt.Sprintf("%d matches, showing %d through %d, took %s\n", sr.Total, sr.Request.From+1, sr.Request.From+len(sr.Hits), sr.Took)
			for i, hit := range sr.Hits {
				rv += fmt.Sprintf("%5d. %s (%f)\n", i+sr.Request.From+1, hit.ID, hit.Score)
				for fragmentField, fragments := range hit.Fragments {
					rv += fmt.Sprintf("\t%s\n", fragmentField)
					for _, fragment := range fragments {
						rv += fmt.Sprintf("\t\t%s\n", fragment)
					}
				}
			}
		} else {
			rv = fmt.Sprintf("%d matches, took %s\n", sr.Total, sr.Took)
		}
	} else {
		rv = "No matches"
	}
	return rv
}

func (sr *SearchResult) Merge(other *SearchResult) {
	sr.Hits = append(sr.Hits, other.Hits...)
	sr.Total += other.Total
	sr.Took += other.Took
	if other.MaxScore > sr.MaxScore {
		sr.MaxScore = other.MaxScore
	}
	sr.Facets.Merge(other.Facets)
}
<|endoftext|>


<commit_before>package helpers

import (
	"sync"
	"time"

	"github.com/Seklfreak/Robyul2/cache"
	"github.com/Seklfreak/Robyul2/models"
	raven "github.com/getsentry/raven-go"
	"github.com/globalsign/mgo/bson"
)

var (
	guildSettingsCache = make(map[string]models.Config)
	cacheMutex         sync.RWMutex
)

// GuildSettingsSet writes all $config into the db
func GuildSettingsSet(guild string, config models.Config) error {
	// Check if an config object exists
	var settings models.Config

	err := MdbOneWithoutLogging(
		MdbCollection(models.GuildConfigTable).Find(bson.M{"guildid": guild}),
		&settings,
	)

	if IsMdbNotFound(err) {
		_, err = MDbInsert(
			models.GuildConfigTable,
			config,
		)
	} else if err != nil {
		return err
	} else {
		err = MDbUpdate(models.GuildConfigTable, config.ID, config)
	}
	if err != nil {
		return err
	}

	// Update cache
	cacheMutex.Lock()
	guildSettingsCache[guild] = config
	cacheMutex.Unlock()

	return err
}

// GuildSettingsGet returns all config values for the guild or a default object
func GuildSettingsGet(guild string) (models.Config, error) {
	var settings models.Config
	var err error

	err = MdbOneWithoutLogging(
		MdbCollection(models.GuildConfigTable).Find(bson.M{"guildid": guild}),
		&settings,
	)

	if IsMdbNotFound(err) {
		settings = models.Config{}.Default(guild)
		return settings, nil
	}

	return settings, err
}

func GuildSettingsGetCached(id string) models.Config {
	cacheMutex.RLock()
	defer cacheMutex.RUnlock()

	settings := guildSettingsCache[id]
	return settings
}

// GetPrefixForServer gets the prefix for $guild
func GetPrefixForServer(guildID string) string {
	return GuildSettingsGetCached(guildID).Prefix
}

// SetPrefixForServer sets the prefix for $guild to $prefix
func SetPrefixForServer(guild string, prefix string) error {
	settings := GuildSettingsGetCached(guild)

	settings.Prefix = prefix

	return GuildSettingsSet(guild, settings)
}

func GuildSettingsUpdater() {
	for {
		for _, shard := range cache.GetSession().Sessions {
			for _, guild := range shard.State.Guilds {
				settings, e := GuildSettingsGet(guild.ID)
				if e != nil {
					raven.CaptureError(e, map[string]string{})
					continue
				}

				cacheMutex.Lock()
				guildSettingsCache[guild.ID] = settings
				cacheMutex.Unlock()
			}
		}

		time.Sleep(15 * time.Second)
	}
}
<commit_msg>[helpers] improve guild settings retrieval<commit_after>package helpers

import (
	"sync"
	"time"

	"github.com/Seklfreak/Robyul2/cache"
	"github.com/Seklfreak/Robyul2/models"
	raven "github.com/getsentry/raven-go"
	"github.com/globalsign/mgo/bson"
)

var (
	guildSettingsCache = make(map[string]models.Config)
	cacheMutex         sync.RWMutex
)

// GuildSettingsSet writes all $config into the db
func GuildSettingsSet(guild string, config models.Config) error {
	// Check if an config object exists
	var settings models.Config

	err := MdbOneWithoutLogging(
		MdbCollection(models.GuildConfigTable).Find(bson.M{"guildid": guild}),
		&settings,
	)

	if IsMdbNotFound(err) {
		_, err = MDbInsert(
			models.GuildConfigTable,
			config,
		)
	} else if err != nil {
		return err
	} else {
		err = MDbUpdate(models.GuildConfigTable, config.ID, config)
	}
	if err != nil {
		return err
	}

	// Update cache
	cacheMutex.Lock()
	guildSettingsCache[guild] = config
	cacheMutex.Unlock()

	return err
}

// GuildSettingsGet returns all config values for the guild or a default object
func GuildSettingsGet(guild string) (models.Config, error) {
	cacheMutex.Lock()
	defer cacheMutex.Unlock()

	var settings models.Config
	var err error

	err = MdbOneWithoutLogging(
		MdbCollection(models.GuildConfigTable).Find(bson.M{"guildid": guild}),
		&settings,
	)

	if IsMdbNotFound(err) {
		settings = models.Config{}.Default(guild)
		settings.GuildID = guild
		guildSettingsCache[guild] = settings
		return settings, nil
	}

	guildSettingsCache[guild] = settings
	return settings, err
}

func GuildSettingsGetCached(id string) models.Config {
	cacheMutex.RLock()
	settings := guildSettingsCache[id]
	cacheMutex.RUnlock()

	if settings.GuildID == "" {
		settings, _ = GuildSettingsGet(id)
	}
	return settings
}

// GetPrefixForServer gets the prefix for $guild
func GetPrefixForServer(guildID string) string {
	return GuildSettingsGetCached(guildID).Prefix
}

// SetPrefixForServer sets the prefix for $guild to $prefix
func SetPrefixForServer(guild string, prefix string) error {
	settings := GuildSettingsGetCached(guild)

	settings.Prefix = prefix

	return GuildSettingsSet(guild, settings)
}

func GuildSettingsUpdater() {
	for {
		for _, shard := range cache.GetSession().Sessions {
			for _, guild := range shard.State.Guilds {
				settings, e := GuildSettingsGet(guild.ID)
				if e != nil {
					raven.CaptureError(e, map[string]string{})
					continue
				}

				cacheMutex.Lock()
				guildSettingsCache[guild.ID] = settings
				cacheMutex.Unlock()
			}
		}

		time.Sleep(15 * time.Second)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
)

type buildver struct {
	// sha1 records the previous version compared to build.sha1.
	sha1  string
	build *build
}

type mergereq struct {
	notif *notif
	build *build
}

func newMergereq(notif *notif, build *build) *mergereq {
	return &mergereq{
		notif: notif,
		build: build,
	}
}

type checkout struct {
	stage string
	dir   string
	ver   buildver
}

func newCheckout(stage, dir string, ver buildver) *checkout {
	return &checkout{
		dir:   dir,
		stage: stage,
		ver:   ver,
	}
}

type mergebot struct {
	conf      *config
	project   string
	checkouts map[string]*checkout // stage : checkout
	vers      map[string]*buildver // stage : version
	reqs      chan *mergereq
}

func newMergebot(project string, cf *config) *mergebot {
	b := &mergebot{
		project:   project,
		conf:      cf,
		checkouts: make(map[string]*checkout),
		vers:      make(map[string]*buildver),
		reqs:      make(chan *mergereq),
	}
	return b
}

func (b *mergebot) addCheckout(dir string, notif *notif, build *build) {
	bv := buildver{
		sha1:  notif.sha1,
		build: build,
	}
	b.checkouts[build.stage] = newCheckout(build.stage, dir, bv)
	log.Printf("[mergebot] %s: init %s to %s using stage %s", b.project, notif.branch, notif.sha1, build.stage)
}

func (b *mergebot) registerBuild(req *mergereq) {
	bv := b.vers[req.build.stage]
	if bv == nil {
		bv = &buildver{
			build: req.build,
		}
	}
	bv.sha1 = req.notif.sha1
	b.vers[req.build.stage] = bv
	log.Printf("[mergebot] %s: set latest revision to %s stage %s", b.project, req.notif.sha1, req.build.stage)
}

func (b *mergebot) checkMerged(req *mergereq, co *checkout, pjs *projects) error {
	ver := co.ver
	log.Printf("[mergebot] %s: checking that %s from %s has been merged to %s", b.project, ver.sha1, ver.build.stage, co.stage)
	commits := newGitcommits()
	if ver.sha1 == "" {
		return fmt.Errorf("cannot fetch commits since last build, last SHA1 is empty", b.project)
	}
	if err := commits.since(ver.sha1, co.dir); err != nil {
		return fmt.Errorf("%s: can't fetch commits since %s: %s", co.dir, ver.sha1, err)
	}
	merged := make([]string, 0) // merged stages to remove
	for k, bv := range b.vers {
		if commits.contains(githash(bv.sha1)) {
			log.Printf("[mergebot] %s: can remove env %s, it was merged", b.project, bv.build.stage)
			b.conf.urls.del(bv.build.stage)
			// As we have been called by pjs, to make a request we need to wait for the current one to finish.
			// To avoid a deadlock, we must notify of the merge in the background.
			go pjs.merge(bv.build, req.notif)
			merged = append(merged, k)
		}
	}
	for _, k := range merged {
		delete(b.vers, k)
	}
	return nil
}

func (b *mergebot) send(req *mergereq) {
	b.reqs <- req
}

func (b *mergebot) run(pjs *projects) {
	for req := range b.reqs {
		b.doReq(req, pjs)
	}
}

func (b *mergebot) doReq(req *mergereq, pjs *projects) {
	co, hasCheckout := b.checkouts[req.build.stage]
	if !hasCheckout {
		// normally update some tracked version
		b.registerBuild(req)
		return
	}
	// It's a push to a checked out stage, trigger the delete etc
	if err := b.checkMerged(req, co, pjs); err != nil {
		log.Printf("[mergebot] %s: failed merge check: %s", b.project, err)
	}
	co.ver.sha1 = req.notif.sha1
	log.Printf("[mergebot] %s: merge check done, set latest revision to %s stage %s", b.project, req.notif.sha1, co.ver.build.stage)
}

type mergebots map[string]*mergebot // project : mergebots

func makeMergebots() mergebots {
	return mergebots(make(map[string]*mergebot))
}

func (m mergebots) get(project string) *mergebot {
	return m[project]
}

func (m mergebots) create(project string, cf *config) *mergebot {
	b := newMergebot(project, cf)
	m[project] = b
	return b
}
<commit_msg>Don't check merges onto itself<commit_after>package main

import (
	"fmt"
	"log"
)

type buildver struct {
	// sha1 records the previous version compared to build.sha1.
	sha1  string
	build *build
}

type mergereq struct {
	notif *notif
	build *build
}

func newMergereq(notif *notif, build *build) *mergereq {
	return &mergereq{
		notif: notif,
		build: build,
	}
}

type checkout struct {
	stage string
	dir   string
	ver   buildver
}

func newCheckout(stage, dir string, ver buildver) *checkout {
	return &checkout{
		dir:   dir,
		stage: stage,
		ver:   ver,
	}
}

type mergebot struct {
	conf      *config
	project   string
	checkouts map[string]*checkout // stage : checkout
	vers      map[string]*buildver // stage : version
	reqs      chan *mergereq
}

func newMergebot(project string, cf *config) *mergebot {
	b := &mergebot{
		project:   project,
		conf:      cf,
		checkouts: make(map[string]*checkout),
		vers:      make(map[string]*buildver),
		reqs:      make(chan *mergereq),
	}
	return b
}

func (b *mergebot) addCheckout(dir string, notif *notif, build *build) {
	bv := buildver{
		sha1:  notif.sha1,
		build: build,
	}
	b.checkouts[build.stage] = newCheckout(build.stage, dir, bv)
	log.Printf("[mergebot] %s: init %s to %s using stage %s", b.project, notif.branch, notif.sha1, build.stage)
}

func (b *mergebot) registerBuild(req *mergereq) {
	bv := b.vers[req.build.stage]
	if bv == nil {
		bv = &buildver{
			build: req.build,
		}
	}
	bv.sha1 = req.notif.sha1
	b.vers[req.build.stage] = bv
	log.Printf("[mergebot] %s: set latest revision to %s stage %s", b.project, req.notif.sha1, req.build.stage)
}

func (b *mergebot) checkMerged(req *mergereq, co *checkout, pjs *projects) error {
	ver := co.ver
	// Not a merge, just a commit to a mergeable branch.
	if ver.build.stage == co.stage {
		return nil
	}
	log.Printf("[mergebot] %s: checking that %s from %s has been merged to %s", b.project, ver.sha1, ver.build.stage, co.stage)
	commits := newGitcommits()
	if ver.sha1 == "" {
		return fmt.Errorf("cannot fetch commits since last build, last SHA1 is empty", b.project)
	}
	if err := commits.since(ver.sha1, co.dir); err != nil {
		return fmt.Errorf("%s: can't fetch commits since %s: %s", co.dir, ver.sha1, err)
	}
	merged := make([]string, 0) // merged stages to remove
	for k, bv := range b.vers {
		if commits.contains(githash(bv.sha1)) {
			log.Printf("[mergebot] %s: can remove env %s, it was merged", b.project, bv.build.stage)
			b.conf.urls.del(bv.build.stage)
			// As we have been called by pjs, to make a request we need to wait for the current one to finish.
			// To avoid a deadlock, we must notify of the merge in the background.
			go pjs.merge(bv.build, req.notif)
			merged = append(merged, k)
		}
	}
	for _, k := range merged {
		delete(b.vers, k)
	}
	return nil
}

func (b *mergebot) send(req *mergereq) {
	b.reqs <- req
}

func (b *mergebot) run(pjs *projects) {
	for req := range b.reqs {
		b.doReq(req, pjs)
	}
}

func (b *mergebot) doReq(req *mergereq, pjs *projects) {
	co, hasCheckout := b.checkouts[req.build.stage]
	if !hasCheckout {
		// normally update some tracked version
		b.registerBuild(req)
		return
	}
	// It's a push to a checked out stage, trigger the delete etc
	if err := b.checkMerged(req, co, pjs); err != nil {
		log.Printf("[mergebot] %s: failed merge check: %s", b.project, err)
	}
	co.ver.sha1 = req.notif.sha1
	log.Printf("[mergebot] %s: merge check done, set latest revision to %s stage %s", b.project, req.notif.sha1, co.ver.build.stage)
}

type mergebots map[string]*mergebot // project : mergebots

func makeMergebots() mergebots {
	return mergebots(make(map[string]*mergebot))
}

func (m mergebots) get(project string) *mergebot {
	return m[project]
}

func (m mergebots) create(project string, cf *config) *mergebot {
	b := newMergebot(project, cf)
	m[project] = b
	return b
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"io/ioutil"
	"log"
	"sync"
	"time"

	"github.com/jhump/protoreflect/desc"
	"github.com/jhump/protoreflect/dynamic"
	"github.com/m3db/m3/src/dbnode/client"
	"github.com/m3db/m3/src/dbnode/encoding/proto"
	"github.com/m3db/m3/src/x/ident"
	xtime "github.com/m3db/m3/src/x/time"
	yaml "gopkg.in/yaml.v2"
)

const (
	namespace = "default"
)

var (
	namespaceID = ident.StringID(namespace)
)

type config struct {
	Client client.Configuration `yaml:"m3db_client"`
}

var configFile = flag.String("f", "", "configuration file")

func main() {
	flag.Parse()
	if *configFile == "" {
		flag.Usage()
		return
	}

	cfgBytes, err := ioutil.ReadFile(*configFile)
	if err != nil {
		log.Fatalf("unable to read config file: %s, err: %v", *configFile, err)
	}

	cfg := &config{}
	if err := yaml.UnmarshalStrict(cfgBytes, cfg); err != nil {
		log.Fatalf("unable to parse YAML: %v", err)
	}

	// TODO(rartoul): Provide guidelines on reducing memory usage by tuning pooling options.
	client, err := cfg.Client.NewClient(client.ConfigurationParameters{})
	if err != nil {
		log.Fatalf("unable to create new M3DB client: %v", err)
	}

	session, err := client.DefaultSession()
	if err != nil {
		log.Fatalf("unable to create new M3DB session: %v", err)
	}

	schemaConfig, ok := cfg.Client.Proto.SchemaRegistry[namespace]
	if !ok {
		log.Fatalf("schema path for namespace: %s not found", namespace)
	}

	// NB(rartoul): Using dynamic / reflection based library for marshaling and unmarshaling protobuf
	// messages for simplicity, use generated message-specific bindings in production.
	schema, err := proto.ParseProtoSchema(schemaConfig.SchemaFilePath, schemaConfig.MessageName)
	if err != nil {
		log.Fatalf("could not parse proto schema: %v", err)
	}

	runUntaggedExample(session, schema)
	runTaggedExample(session, schema)
	// TODO(rartoul): Add an aggregations query example.
}

// runUntaggedExample demonstrates how to write "untagged" (unindexed) data into M3DB with a given
// protobuf schema and then read it back out again.
func runUntaggedExample(session client.Session, schema *desc.MessageDescriptor) {
	log.Printf("------ run untagged example ------")
	var (
		untaggedSeriesID = ident.StringID("untagged_seriesID")
		m                = newTestValue(schema)
	)
	marshaled, err := m.Marshal()
	if err != nil {
		log.Fatalf("error marshaling protobuf message: %v", err)
	}

	// Write an untagged series ID. Pass 0 for value since it is ignored.
	if err := session.Write(namespaceID, untaggedSeriesID, time.Now(), 0, xtime.Nanosecond, marshaled); err != nil {
		log.Fatalf("unable to write untagged series: %v", err)
	}

	// Fetch data for the untagged seriesID written within the last minute.
	seriesIter, err := session.Fetch(namespaceID, untaggedSeriesID, time.Now().Add(-time.Minute), time.Now())
	if err != nil {
		log.Fatalf("error fetching data for untagged series: %v", err)
	}
	for seriesIter.Next() {
		m = dynamic.NewMessage(schema)
		dp, _, marshaledProto := seriesIter.Current()
		if err := m.Unmarshal(marshaledProto); err != nil {
			log.Fatalf("error unamrshaling protobuf message: %v", err)
		}
		log.Printf("%s: %s", dp.Timestamp.String(), m.String())
	}
}

// runTaggedExample demonstrates how to write "tagged" (indexed) data into M3DB with a given protobuf
// schema and then read it back out again by either:
//
//   1. Querying for a specific time series by its ID directly
//   2. TODO(rartoul): Querying for a set of time series using an inverted index query
func runTaggedExample(session client.Session, schema *desc.MessageDescriptor) {
	log.Printf("------ run tagged example ------")
	var (
		seriesID = ident.StringID("vehicle_id_1")
		tags     = []ident.Tag{
			{Name: ident.StringID("type"), Value: ident.StringID("sedan")},
			{Name: ident.StringID("city"), Value: ident.StringID("san_francisco")},
		}
		tagsIter = ident.NewTagsIterator(ident.NewTags(tags...))
		m        = newTestValue(schema)
	)
	marshaled, err := m.Marshal()
	if err != nil {
		log.Fatalf("error marshaling protobuf message: %v", err)
	}

	// Write a tagged series ID. Pass 0 for value since it is ignored.
	if err := session.WriteTagged(namespaceID, seriesID, tagsIter, time.Now(), 0, xtime.Nanosecond, marshaled); err != nil {
		log.Fatalf("error writing series %s, err: %v", seriesID.String(), err)
	}

	// Fetch data for the tagged seriesID using a direct ID lookup (only data written within the last minute).
	seriesIter, err := session.Fetch(namespaceID, seriesID, time.Now().Add(-time.Minute), time.Now())
	if err != nil {
		log.Fatalf("error fetching data for untagged series: %v", err)
	}
	for seriesIter.Next() {
		m = dynamic.NewMessage(schema)
		dp, _, marshaledProto := seriesIter.Current()
		if err := m.Unmarshal(marshaledProto); err != nil {
			log.Fatalf("error unamrshaling protobuf message: %v", err)
		}
		log.Printf("%s: %s", dp.Timestamp.String(), m.String())
	}

	// TODO(rartoul): Show an example of how to execute a FetchTagged() call with an index query.
}

var (
	testValueLock  sync.Mutex
	testValueCount = 1
)

func newTestValue(schema *desc.MessageDescriptor) *dynamic.Message {
	testValueLock.Lock()
	defer testValueLock.Unlock()

	m := dynamic.NewMessage(schema)
	m.SetFieldByName("latitude", float64(testValueCount))
	m.SetFieldByName("longitude", float64(testValueCount))
	m.SetFieldByName("fuel_percent", 0.75)
	m.SetFieldByName("status", "active")

	testValueCount++

	return m
}
<commit_msg>Update protobuf example code (#1914)<commit_after>package main

import (
	"flag"
	"io/ioutil"
	"log"
	"sync"
	"time"

	"github.com/jhump/protoreflect/desc"
	"github.com/jhump/protoreflect/dynamic"
	"github.com/m3db/m3/src/dbnode/client"
	"github.com/m3db/m3/src/dbnode/encoding/proto"
	"github.com/m3db/m3/src/x/ident"
	xtime "github.com/m3db/m3/src/x/time"
	yaml "gopkg.in/yaml.v2"
)

const (
	namespace = "default"
)

var (
	namespaceID = ident.StringID(namespace)
)

type config struct {
	Client client.Configuration `yaml:"m3db_client"`
}

var configFile = flag.String("f", "", "configuration file")

func main() {
	flag.Parse()
	if *configFile == "" {
		flag.Usage()
		return
	}

	cfgBytes, err := ioutil.ReadFile(*configFile)
	if err != nil {
		log.Fatalf("unable to read config file: %s, err: %v", *configFile, err)
	}

	cfg := &config{}
	if err := yaml.UnmarshalStrict(cfgBytes, cfg); err != nil {
		log.Fatalf("unable to parse YAML: %v", err)
	}

	// TODO(rartoul): Provide guidelines on reducing memory usage by tuning pooling options.
	client, err := cfg.Client.NewClient(client.ConfigurationParameters{})
	if err != nil {
		log.Fatalf("unable to create new M3DB client: %v", err)
	}

	session, err := client.DefaultSession()
	if err != nil {
		log.Fatalf("unable to create new M3DB session: %v", err)
	}
	defer session.Close()

	schemaConfig, ok := cfg.Client.Proto.SchemaRegistry[namespace]
	if !ok {
		log.Fatalf("schema path for namespace: %s not found", namespace)
	}

	// NB(rartoul): Using dynamic / reflection based library for marshaling and unmarshaling protobuf
	// messages for simplicity, use generated message-specific bindings in production.
	schema, err := proto.ParseProtoSchema(schemaConfig.SchemaFilePath, schemaConfig.MessageName)
	if err != nil {
		log.Fatalf("could not parse proto schema: %v", err)
	}

	runUntaggedExample(session, schema)
	runTaggedExample(session, schema)
	// TODO(rartoul): Add an aggregations query example.
}

// runUntaggedExample demonstrates how to write "untagged" (unindexed) data into M3DB with a given
// protobuf schema and then read it back out again.
func runUntaggedExample(session client.Session, schema *desc.MessageDescriptor) {
	log.Printf("------ run untagged example ------")
	var (
		untaggedSeriesID = ident.StringID("untagged_seriesID")
		m                = newTestValue(schema)
	)
	marshaled, err := m.Marshal()
	if err != nil {
		log.Fatalf("error marshaling protobuf message: %v", err)
	}

	// Write an untagged series ID. Pass 0 for value since it is ignored.
	if err := session.Write(namespaceID, untaggedSeriesID, time.Now(), 0, xtime.Nanosecond, marshaled); err != nil {
		log.Fatalf("unable to write untagged series: %v", err)
	}

	// Fetch data for the untagged seriesID written within the last minute.
	seriesIter, err := session.Fetch(namespaceID, untaggedSeriesID, time.Now().Add(-time.Minute), time.Now())
	if err != nil {
		log.Fatalf("error fetching data for untagged series: %v", err)
	}
	for seriesIter.Next() {
		m = dynamic.NewMessage(schema)
		dp, _, marshaledProto := seriesIter.Current()
		if err := m.Unmarshal(marshaledProto); err != nil {
			log.Fatalf("error unmarshaling protobuf message: %v", err)
		}
		log.Printf("%s: %s", dp.Timestamp.String(), m.String())
	}
	if err := seriesIter.Err(); err != nil {
		log.Fatalf("error in series iterator: %v, err")
	}
}

// runTaggedExample demonstrates how to write "tagged" (indexed) data into M3DB with a given protobuf
// schema and then read it back out again by either:
//
//   1. Querying for a specific time series by its ID directly
//   2. TODO(rartoul): Querying for a set of time series using an inverted index query
func runTaggedExample(session client.Session, schema *desc.MessageDescriptor) {
	log.Printf("------ run tagged example ------")
	var (
		seriesID = ident.StringID("vehicle_id_1")
		tags     = []ident.Tag{
			{Name: ident.StringID("type"), Value: ident.StringID("sedan")},
			{Name: ident.StringID("city"), Value: ident.StringID("san_francisco")},
		}
		tagsIter = ident.NewTagsIterator(ident.NewTags(tags...))
		m        = newTestValue(schema)
	)
	marshaled, err := m.Marshal()
	if err != nil {
		log.Fatalf("error marshaling protobuf message: %v", err)
	}

	// Write a tagged series ID. Pass 0 for value since it is ignored.
	if err := session.WriteTagged(namespaceID, seriesID, tagsIter, time.Now(), 0, xtime.Nanosecond, marshaled); err != nil {
		log.Fatalf("error writing series %s, err: %v", seriesID.String(), err)
	}

	// Fetch data for the tagged seriesID using a direct ID lookup (only data written within the last minute).
	seriesIter, err := session.Fetch(namespaceID, seriesID, time.Now().Add(-time.Minute), time.Now())
	if err != nil {
		log.Fatalf("error fetching data for untagged series: %v", err)
	}
	for seriesIter.Next() {
		m = dynamic.NewMessage(schema)
		dp, _, marshaledProto := seriesIter.Current()
		if err := m.Unmarshal(marshaledProto); err != nil {
			log.Fatalf("error unamrshaling protobuf message: %v", err)
		}
		log.Printf("%s: %s", dp.Timestamp.String(), m.String())
	}
	if err := seriesIter.Err(); err != nil {
		log.Fatalf("error in series iterator: %v, err")
	}

	// TODO(rartoul): Show an example of how to execute a FetchTagged() call with an index query.
}

var (
	testValueLock  sync.Mutex
	testValueCount = 1
)

func newTestValue(schema *desc.MessageDescriptor) *dynamic.Message {
	testValueLock.Lock()
	defer testValueLock.Unlock()

	m := dynamic.NewMessage(schema)
	m.SetFieldByName("latitude", float64(testValueCount))
	m.SetFieldByName("longitude", float64(testValueCount))
	m.SetFieldByName("fuel_percent", 0.75)
	m.SetFieldByName("status", "active")

	testValueCount++

	return m
}
<|endoftext|>


<commit_before>package mackerel

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"reflect"
	"sort"
	"testing"
)

func TestGetRoleFullnames(t *testing.T) {
	host := &Host{
		Roles: Roles{
			"My-Service":  []string{"db-master", "db-slave"},
			"My-Service2": []string{"proxy"},
		},
	}

	fullnames := host.GetRoleFullnames()
	sort.Strings(fullnames)

	if !reflect.DeepEqual(fullnames, []string{"My-Service2:proxy", "My-Service:db-master", "My-Service:db-slave"}) {
		t.Error("RoleFullnames shoud be ['My-Service2:proxy', My-Service:db-master', 'My-Service:db-slave'] but: ", fullnames)
	}
}

func TestFindHost(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		if req.URL.Path != "/api/v0/hosts/9rxGOHfVF8F" {
			t.Error("request URL should be /api/v0/hosts/9rxGOHfVF8F but :", req.URL.Path)
		}

		if req.Method != "GET" {
			t.Error("request method should be GET but :", req.Method)
		}

		respJSON, _ := json.Marshal(map[string]map[string]interface{}{
			"host": {
				"id":     "9rxGOHfVF8F",
				"name":   "mydb001",
				"status": "working",
				"memo":   "hello",
				"roles":  map[string][]string{"My-Service": {"db-master", "db-slave"}},
			},
		})

		res.Header()["Content-Type"] = []string{"application/json"}
		fmt.Fprint(res, string(respJSON))
	}))
	defer ts.Close()

	client, _ := NewClientWithOptions("dummy-key", ts.URL, false)
	host, err := client.FindHost("9rxGOHfVF8F")

	if err != nil {
		t.Error("err shoud be nil but: ", err)
	}

	if host.Memo != "hello" {
		t.Error("request sends json including memo but: ", host)
	}

	if reflect.DeepEqual(host.Roles["My-Service"], []string{"db-master", "db-slave"}) != true {
		t.Errorf("Wrong data for roles: %v", host.Roles)
	}

}

func TestFindHosts(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		if req.URL.Path != "/api/v0/hosts" {
			t.Error("request URL should be /api/v0/hosts but :", req.URL.Path)
		}

		query := req.URL.Query()
		if query.Get("service") != "My-Service" {
			t.Error("request query 'service' param should be My-Service but :", query.Get("service"))
		}
		if !reflect.DeepEqual(query["role"], []string{"db-master"}) {
			t.Error("request query 'role' param should be db-master but :", query.Get("role"))
		}
		if query.Get("name") != "mydb001" {
			t.Error("request query 'name' param should be mydb001 but :", query.Get("name"))
		}
		if !reflect.DeepEqual(query["status"], []string{"working", "standby"}) {
			t.Error("request query 'statuses' param should be ['working','standby'] but :", query["status"])
		}

		if req.Method != "GET" {
			t.Error("request method should be GET but :", req.Method)
		}

		respJSON, _ := json.Marshal(map[string][]map[string]interface{}{
			"hosts": {
				{
					"id":     "9rxGOHfVF8F",
					"name":   "mydb001",
					"status": "working",
					"memo":   "hello",
					"roles":  map[string][]string{"My-Service": {"db-master", "db-slave"}},
				},
			},
		})

		res.Header()["Content-Type"] = []string{"application/json"}
		fmt.Fprint(res, string(respJSON))
	}))
	defer ts.Close()

	client, _ := NewClientWithOptions("dummy-key", ts.URL, false)
	hosts, err := client.FindHosts(&FindHostsParam{
		Service:  "My-Service",
		Roles:    []string{"db-master"},
		Statuses: []string{"working", "standby"},
		Name:     "mydb001",
	})

	if err != nil {
		t.Error("err shoud be nil but: ", err)
	}

	if hosts[0].Memo != "hello" {
		t.Error("request sends json including memo but: ", hosts[0])
	}

	if reflect.DeepEqual(hosts[0].Roles["My-Service"], []string{"db-master", "db-slave"}) != true {
		t.Errorf("Wrong data for roles: %v", hosts[0].Roles)
	}

}

func TestCreateHost(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		if req.URL.Path != "/api/v0/hosts" {
			t.Error("request URL should be /api/v0/hosts but: ", req.URL.Path)
		}

		if req.Method != "POST" {
			t.Error("request method should be POST but: ", req.Method)
		}

		body, _ := ioutil.ReadAll(req.Body)

		var data struct {
			Name          string      `json:"name"`
			Meta          HostMeta    `json:"meta"`
			Interfaces    []Interface `json:"interfaces"`
			RoleFullnames []string    `json:"roleFullnames"`
		}

		err := json.Unmarshal(body, &data)
		if err != nil {
			t.Fatal("request body should be decoded as json", string(body))
		}

		if data.Name != "mydb002" {
			t.Error("request sends json including name but: ", data.Name)
		}
		if !reflect.DeepEqual(data.RoleFullnames, []string{"My-Service:db-master", "My-Service:db-slave"}) {
			t.Error("request sends json including roleFullnames but: ", data.RoleFullnames)
		}

		respJSON, _ := json.Marshal(map[string]string{
			"id": "123456ABCD",
		})

		res.Header()["Content-Type"] = []string{"application/json"}
		fmt.Fprint(res, string(respJSON))
	}))
	defer ts.Close()

	client, _ := NewClientWithOptions("dummy-key", ts.URL, false)
	hostID, err := client.CreateHost(&CreateHostParam{
		Name:          "mydb002",
		RoleFullnames: []string{"My-Service:db-master", "My-Service:db-slave"},
	})

	if err != nil {
		t.Error("err shoud be nil but: ", err)
	}

	if hostID != "123456ABCD" {
		t.Error("hostID shoud be empty but: ", hostID)
	}
}

func TestUpdateHost(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		if req.URL.Path != "/api/v0/hosts/123456ABCD" {
			t.Error("request URL should be /api/v0/hosts/123456ABCD but: ", req.URL.Path)
		}

		if req.Method != "PUT" {
			t.Error("request method should be PUT but: ", req.Method)
		}

		body, _ := ioutil.ReadAll(req.Body)

		var data struct {
			Name          string      `json:"name"`
			Meta          HostMeta    `json:"meta"`
			Interfaces    []Interface `json:"interfaces"`
			RoleFullnames []string    `json:"roleFullnames"`
		}

		err := json.Unmarshal(body, &data)
		if err != nil {
			t.Fatal("request body should be decoded as json", string(body))
		}

		if data.Name != "mydb002" {
			t.Error("request sends json including name but: ", data.Name)
		}
		if !reflect.DeepEqual(data.RoleFullnames, []string{"My-Service:db-master", "My-Service:db-slave"}) {
			t.Error("request sends json including roleFullnames but: ", data.RoleFullnames)
		}

		respJSON, _ := json.Marshal(map[string]string{
			"id": "123456ABCD",
		})

		res.Header()["Content-Type"] = []string{"application/json"}
		fmt.Fprint(res, string(respJSON))
	}))
	defer ts.Close()

	client, _ := NewClientWithOptions("dummy-key", ts.URL, false)
	hostID, err := client.UpdateHost("123456ABCD", &UpdateHostParam{
		Name:          "mydb002",
		RoleFullnames: []string{"My-Service:db-master", "My-Service:db-slave"},
	})

	if err != nil {
		t.Error("err shoud be nil but: ", err)
	}

	if hostID != "123456ABCD" {
		t.Error("hostID shoud be empty but: ", hostID)
	}
}

func TestUpdateHostStatus(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		if req.URL.Path != "/api/v0/hosts/9rxGOHfVF8F/status" {
			t.Error("request URL should be /api/v0/hosts/9rxGOHfVF8F/status but :", req.URL.Path)
		}

		if req.Method != "POST" {
			t.Error("request method should be POST but: ", req.Method)
		}

		body, _ := ioutil.ReadAll(req.Body)

		var data struct {
			Status string `json:"status"`
		}

		err := json.Unmarshal(body, &data)
		if err != nil {
			t.Fatal("request body should be decoded as json", string(body))
		}

		if data.Status != "maintenance" {
			t.Error("request sends json including status but: ", data.Status)
		}

		respJSON, _ := json.Marshal(map[string]bool{
			"success": true,
		})

		res.Header()["Content-Type"] = []string{"application/json"}
		fmt.Fprint(res, string(respJSON))
	}))
	defer ts.Close()

	client, _ := NewClientWithOptions("dummy-key", ts.URL, false)
	err := client.UpdateHostStatus("9rxGOHfVF8F", "maintenance")

	if err != nil {
		t.Error("err shoud be nil but: ", err)
	}
}

func TestUpdateHostRoleFullnames(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		if req.URL.Path != "/api/v0/hosts/9rxGOHfVF8F/role-fullnames" {
			t.Error("request URL should be /api/v0/hosts/9rxGOHfVF8F/role-fullnames but :", req.URL.Path)
		}

		if req.Method != "PUT" {
			t.Error("request method should be PUT but: ", req.Method)
		}

		body, _ := ioutil.ReadAll(req.Body)

		var data struct {
			RoleFullnames []string `json:"roleFullnames"`
		}

		err := json.Unmarshal(body, &data)
		if err != nil {
			t.Fatal("request body should be decoded as json", string(body))
		}

		respJSON, _ := json.Marshal(map[string]bool{
			"success": true,
		})

		res.Header()["Content-Type"] = []string{"application/json"}
		fmt.Fprint(res, string(respJSON))
	}))
	defer ts.Close()

	client, _ := NewClientWithOptions("dummy-key", ts.URL, false)
	err := client.UpdateHostRoleFullnames("9rxGOHfVF8F", []string{"testservice:testrole", "testservice:testrole2"})

	if err != nil {
		t.Error("err shoud be nil but: ", err)
	}
}

func TestRetireHost(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		if req.URL.Path != "/api/v0/hosts/123456ABCD/retire" {
			t.Error("request URL should be /api/v0/hosts/123456ABCD/retire but: ", req.URL.Path)
		}

		if req.Method != "POST" {
			t.Error("request method should be PUT but: ", req.Method)
		}

		body, _ := ioutil.ReadAll(req.Body)

		var data interface{}
		err := json.Unmarshal(body, &data)
		if err != nil {
			t.Fatal("request body should be decoded as json", string(body))
		}

		respJSON, _ := json.Marshal(map[string]bool{
			"success": true,
		})

		res.Header()["Content-Type"] = []string{"application/json"}
		fmt.Fprint(res, string(respJSON))
	}))
	defer ts.Close()

	client, _ := NewClientWithOptions("dummy-key", ts.URL, false)
	err := client.RetireHost("123456ABCD")

	if err != nil {
		t.Error("err shoud be nil but: ", err)
	}
}
<commit_msg>Add tests for Interfaces.IPv[46]Addresses<commit_after>package mackerel

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"reflect"
	"sort"
	"testing"
)

func TestGetRoleFullnames(t *testing.T) {
	host := &Host{
		Roles: Roles{
			"My-Service":  []string{"db-master", "db-slave"},
			"My-Service2": []string{"proxy"},
		},
	}

	fullnames := host.GetRoleFullnames()
	sort.Strings(fullnames)

	if !reflect.DeepEqual(fullnames, []string{"My-Service2:proxy", "My-Service:db-master", "My-Service:db-slave"}) {
		t.Error("RoleFullnames shoud be ['My-Service2:proxy', My-Service:db-master', 'My-Service:db-slave'] but: ", fullnames)
	}
}

func TestFindHost(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		if req.URL.Path != "/api/v0/hosts/9rxGOHfVF8F" {
			t.Error("request URL should be /api/v0/hosts/9rxGOHfVF8F but :", req.URL.Path)
		}

		if req.Method != "GET" {
			t.Error("request method should be GET but :", req.Method)
		}

		respJSON, _ := json.Marshal(map[string]map[string]interface{}{
			"host": {
				"id":     "9rxGOHfVF8F",
				"name":   "mydb001",
				"status": "working",
				"memo":   "hello",
				"roles":  map[string][]string{"My-Service": {"db-master", "db-slave"}},
				"interfaces": []map[string]interface{}{
					{
						"name":          "lo0",
						"ipAddress":     "127.0.0.1",
						"ipv4Addresses": []string{"127.0.0.1"},
						"ipv6Addresses": []string{"fe80::1"},
						"macAddress":    "02:02:02:02:02:02",
					},
				},
			},
		})

		res.Header()["Content-Type"] = []string{"application/json"}
		fmt.Fprint(res, string(respJSON))
	}))
	defer ts.Close()

	client, _ := NewClientWithOptions("dummy-key", ts.URL, false)
	host, err := client.FindHost("9rxGOHfVF8F")

	if err != nil {
		t.Error("err shoud be nil but: ", err)
	}

	if host.Memo != "hello" {
		t.Error("request sends json including memo but: ", host)
	}

	if reflect.DeepEqual(host.Roles["My-Service"], []string{"db-master", "db-slave"}) != true {
		t.Errorf("Wrong data for roles: %v", host.Roles)
	}

	if len(host.Interfaces) == 1 && reflect.DeepEqual(host.Interfaces[0], Interface{
		Name:          "lo0",
		IPAddress:     "127.0.0.1",
		IPv4Addresses: []string{"127.0.0.1"},
		IPv6Addresses: []string{"fe80::1"},
		MacAddress:    "02:02:02:02:02:02",
	}) != true {
		t.Errorf("Wrong data for interfaces: %v", host.Interfaces)
	}
}

func TestFindHosts(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		if req.URL.Path != "/api/v0/hosts" {
			t.Error("request URL should be /api/v0/hosts but :", req.URL.Path)
		}

		query := req.URL.Query()
		if query.Get("service") != "My-Service" {
			t.Error("request query 'service' param should be My-Service but :", query.Get("service"))
		}
		if !reflect.DeepEqual(query["role"], []string{"db-master"}) {
			t.Error("request query 'role' param should be db-master but :", query.Get("role"))
		}
		if query.Get("name") != "mydb001" {
			t.Error("request query 'name' param should be mydb001 but :", query.Get("name"))
		}
		if !reflect.DeepEqual(query["status"], []string{"working", "standby"}) {
			t.Error("request query 'statuses' param should be ['working','standby'] but :", query["status"])
		}

		if req.Method != "GET" {
			t.Error("request method should be GET but :", req.Method)
		}

		respJSON, _ := json.Marshal(map[string][]map[string]interface{}{
			"hosts": {
				{
					"id":     "9rxGOHfVF8F",
					"name":   "mydb001",
					"status": "working",
					"memo":   "hello",
					"roles":  map[string][]string{"My-Service": {"db-master", "db-slave"}},
				},
			},
		})

		res.Header()["Content-Type"] = []string{"application/json"}
		fmt.Fprint(res, string(respJSON))
	}))
	defer ts.Close()

	client, _ := NewClientWithOptions("dummy-key", ts.URL, false)
	hosts, err := client.FindHosts(&FindHostsParam{
		Service:  "My-Service",
		Roles:    []string{"db-master"},
		Statuses: []string{"working", "standby"},
		Name:     "mydb001",
	})

	if err != nil {
		t.Error("err shoud be nil but: ", err)
	}

	if hosts[0].Memo != "hello" {
		t.Error("request sends json including memo but: ", hosts[0])
	}

	if reflect.DeepEqual(hosts[0].Roles["My-Service"], []string{"db-master", "db-slave"}) != true {
		t.Errorf("Wrong data for roles: %v", hosts[0].Roles)
	}

}

func TestCreateHost(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		if req.URL.Path != "/api/v0/hosts" {
			t.Error("request URL should be /api/v0/hosts but: ", req.URL.Path)
		}

		if req.Method != "POST" {
			t.Error("request method should be POST but: ", req.Method)
		}

		body, _ := ioutil.ReadAll(req.Body)

		var data struct {
			Name          string      `json:"name"`
			Meta          HostMeta    `json:"meta"`
			Interfaces    []Interface `json:"interfaces"`
			RoleFullnames []string    `json:"roleFullnames"`
		}

		err := json.Unmarshal(body, &data)
		if err != nil {
			t.Fatal("request body should be decoded as json", string(body))
		}

		if data.Name != "mydb002" {
			t.Error("request sends json including name but: ", data.Name)
		}
		if !reflect.DeepEqual(data.RoleFullnames, []string{"My-Service:db-master", "My-Service:db-slave"}) {
			t.Error("request sends json including roleFullnames but: ", data.RoleFullnames)
		}

		respJSON, _ := json.Marshal(map[string]string{
			"id": "123456ABCD",
		})

		res.Header()["Content-Type"] = []string{"application/json"}
		fmt.Fprint(res, string(respJSON))
	}))
	defer ts.Close()

	client, _ := NewClientWithOptions("dummy-key", ts.URL, false)
	hostID, err := client.CreateHost(&CreateHostParam{
		Name:          "mydb002",
		RoleFullnames: []string{"My-Service:db-master", "My-Service:db-slave"},
	})

	if err != nil {
		t.Error("err shoud be nil but: ", err)
	}

	if hostID != "123456ABCD" {
		t.Error("hostID shoud be empty but: ", hostID)
	}
}

func TestUpdateHost(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		if req.URL.Path != "/api/v0/hosts/123456ABCD" {
			t.Error("request URL should be /api/v0/hosts/123456ABCD but: ", req.URL.Path)
		}

		if req.Method != "PUT" {
			t.Error("request method should be PUT but: ", req.Method)
		}

		body, _ := ioutil.ReadAll(req.Body)

		var data struct {
			Name          string      `json:"name"`
			Meta          HostMeta    `json:"meta"`
			Interfaces    []Interface `json:"interfaces"`
			RoleFullnames []string    `json:"roleFullnames"`
		}

		err := json.Unmarshal(body, &data)
		if err != nil {
			t.Fatal("request body should be decoded as json", string(body))
		}

		if data.Name != "mydb002" {
			t.Error("request sends json including name but: ", data.Name)
		}
		if !reflect.DeepEqual(data.RoleFullnames, []string{"My-Service:db-master", "My-Service:db-slave"}) {
			t.Error("request sends json including roleFullnames but: ", data.RoleFullnames)
		}

		respJSON, _ := json.Marshal(map[string]string{
			"id": "123456ABCD",
		})

		res.Header()["Content-Type"] = []string{"application/json"}
		fmt.Fprint(res, string(respJSON))
	}))
	defer ts.Close()

	client, _ := NewClientWithOptions("dummy-key", ts.URL, false)
	hostID, err := client.UpdateHost("123456ABCD", &UpdateHostParam{
		Name:          "mydb002",
		RoleFullnames: []string{"My-Service:db-master", "My-Service:db-slave"},
	})

	if err != nil {
		t.Error("err shoud be nil but: ", err)
	}

	if hostID != "123456ABCD" {
		t.Error("hostID shoud be empty but: ", hostID)
	}
}

func TestUpdateHostStatus(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		if req.URL.Path != "/api/v0/hosts/9rxGOHfVF8F/status" {
			t.Error("request URL should be /api/v0/hosts/9rxGOHfVF8F/status but :", req.URL.Path)
		}

		if req.Method != "POST" {
			t.Error("request method should be POST but: ", req.Method)
		}

		body, _ := ioutil.ReadAll(req.Body)

		var data struct {
			Status string `json:"status"`
		}

		err := json.Unmarshal(body, &data)
		if err != nil {
			t.Fatal("request body should be decoded as json", string(body))
		}

		if data.Status != "maintenance" {
			t.Error("request sends json including status but: ", data.Status)
		}

		respJSON, _ := json.Marshal(map[string]bool{
			"success": true,
		})

		res.Header()["Content-Type"] = []string{"application/json"}
		fmt.Fprint(res, string(respJSON))
	}))
	defer ts.Close()

	client, _ := NewClientWithOptions("dummy-key", ts.URL, false)
	err := client.UpdateHostStatus("9rxGOHfVF8F", "maintenance")

	if err != nil {
		t.Error("err shoud be nil but: ", err)
	}
}

func TestUpdateHostRoleFullnames(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		if req.URL.Path != "/api/v0/hosts/9rxGOHfVF8F/role-fullnames" {
			t.Error("request URL should be /api/v0/hosts/9rxGOHfVF8F/role-fullnames but :", req.URL.Path)
		}

		if req.Method != "PUT" {
			t.Error("request method should be PUT but: ", req.Method)
		}

		body, _ := ioutil.ReadAll(req.Body)

		var data struct {
			RoleFullnames []string `json:"roleFullnames"`
		}

		err := json.Unmarshal(body, &data)
		if err != nil {
			t.Fatal("request body should be decoded as json", string(body))
		}

		respJSON, _ := json.Marshal(map[string]bool{
			"success": true,
		})

		res.Header()["Content-Type"] = []string{"application/json"}
		fmt.Fprint(res, string(respJSON))
	}))
	defer ts.Close()

	client, _ := NewClientWithOptions("dummy-key", ts.URL, false)
	err := client.UpdateHostRoleFullnames("9rxGOHfVF8F", []string{"testservice:testrole", "testservice:testrole2"})

	if err != nil {
		t.Error("err shoud be nil but: ", err)
	}
}

func TestRetireHost(t *testing.T) {
	ts := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
		if req.URL.Path != "/api/v0/hosts/123456ABCD/retire" {
			t.Error("request URL should be /api/v0/hosts/123456ABCD/retire but: ", req.URL.Path)
		}

		if req.Method != "POST" {
			t.Error("request method should be PUT but: ", req.Method)
		}

		body, _ := ioutil.ReadAll(req.Body)

		var data interface{}
		err := json.Unmarshal(body, &data)
		if err != nil {
			t.Fatal("request body should be decoded as json", string(body))
		}

		respJSON, _ := json.Marshal(map[string]bool{
			"success": true,
		})

		res.Header()["Content-Type"] = []string{"application/json"}
		fmt.Fprint(res, string(respJSON))
	}))
	defer ts.Close()

	client, _ := NewClientWithOptions("dummy-key", ts.URL, false)
	err := client.RetireHost("123456ABCD")

	if err != nil {
		t.Error("err shoud be nil but: ", err)
	}
}
<|endoftext|>


<commit_before>package httpclient

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"strings"
	"time"
)

var XmlHeaderBytes []byte = []byte(xml.Header)

type HTTPClient struct {
	BaseURL          *url.URL
	Headers          http.Header
	Client           *http.Client
	PostHooks        map[int]func(*http.Request, *http.Response) error
	rateLimited      bool
	rateLimitChan    chan struct{}
	rateLimitTimeout time.Duration
}

func New() (httpClient *HTTPClient) {
	return &HTTPClient{
		Client:    HttpClient,
		Headers:   make(http.Header),
		PostHooks: make(map[int]func(*http.Request, *http.Response) error),
	}
}

func Insecure() (httpClient *HTTPClient) {
	httpClient = New()
	httpClient.Client = InsecureHttpClient
	return httpClient
}

var DefaultClient = New()

func (c *HTTPClient) SetPostHook(onStatus int, hook func(*http.Request, *http.Response) error) {
	c.PostHooks[onStatus] = hook
}

func (c *HTTPClient) SetRateLimit(limit int, timeout time.Duration) {
	c.rateLimited = true
	c.rateLimitChan = make(chan struct{}, limit)

	for i := 0; i < limit; i++ {
		c.rateLimitChan <- struct{}{}
	}

	c.rateLimitTimeout = timeout
}

func (c *HTTPClient) buildURL(req *RequestData) *url.URL {
	bu := c.BaseURL

	rpath := req.Path

	if strings.HasSuffix(bu.Path, "/") && strings.HasPrefix(rpath, "/") {
		rpath = rpath[1:]
	}

	opaque := EscapePath(bu.Path + rpath)

	u := &url.URL{
		Scheme: bu.Scheme,
		Host:   bu.Host,
		Opaque: opaque,
	}

	if req.Params != nil {
		u.RawQuery = req.Params.Encode()
	}

	return u
}

func (c *HTTPClient) setHeaders(req *RequestData, httpReq *http.Request) {
	switch req.RespEncoding {
	case EncodingJSON:
		httpReq.Header.Set("Accept", "application/json")
	case EncodingXML:
		httpReq.Header.Set("Accept", "application/xml")
	}

	if c.Headers != nil {
		for key, values := range c.Headers {
			for _, value := range values {
				httpReq.Header.Set(key, value)
			}
		}
	}

	if req.Headers != nil {
		for key, values := range req.Headers {
			for _, value := range values {
				httpReq.Header.Set(key, value)
			}
		}
	}
}

func (c *HTTPClient) checkStatus(req *RequestData, response *http.Response) (err error) {
	if req.ExpectedStatus != nil {
		statusOk := false

		for _, status := range req.ExpectedStatus {
			if response.StatusCode == status {
				statusOk = true
			}
		}

		if !statusOk {
			lr := io.LimitReader(response.Body, 10*1024)
			contentBytes, _ := ioutil.ReadAll(lr)
			content := string(contentBytes)

			err = InvalidStatusError{
				Expected: req.ExpectedStatus,
				Got:      response.StatusCode,
				Headers:  response.Header,
				Content:  content,
			}

			return err
		}
	}

	return nil
}

func (c *HTTPClient) unmarshalResponse(req *RequestData, response *http.Response) (err error) {
	var buf []byte

	switch req.RespEncoding {
	case EncodingJSON:
		defer response.Body.Close()

		if buf, err = ioutil.ReadAll(response.Body); err != nil {
			return err
		}

		err = json.Unmarshal(buf, req.RespValue)

		if err != nil {
			return err
		}

		return nil

	case EncodingXML:
		defer response.Body.Close()

		if buf, err = ioutil.ReadAll(response.Body); err != nil {
			return err
		}

		err = xml.Unmarshal(buf, req.RespValue)

		if err != nil {
			return err
		}

		return nil
	}

	switch req.RespValue.(type) {
	case *[]byte:
		defer response.Body.Close()

		if buf, err = ioutil.ReadAll(response.Body); err != nil {
			return err
		}

		respVal := req.RespValue.(*[]byte)
		*respVal = buf

		return nil
	}

	if req.RespConsume {
		defer response.Body.Close()
		ioutil.ReadAll(response.Body)
	}

	return nil
}

func (c *HTTPClient) marshalRequest(req *RequestData) (err error) {
	if req.ReqReader != nil || req.ReqValue == nil {
		return nil
	}

	if req.Headers == nil {
		req.Headers = make(http.Header)
	}

	var buf []byte

	switch req.ReqEncoding {
	case EncodingJSON:
		buf, err = json.Marshal(req.ReqValue)

		if err != nil {
			return err
		}

		req.ReqReader = bytes.NewReader(buf)
		req.Headers.Set("Content-Type", "application/json")
		req.Headers.Set("Content-Length", fmt.Sprintf("%d", len(buf)))

		req.ReqContentLength = int64(len(buf))

		return nil

	case EncodingXML:
		buf, err = xml.Marshal(req.ReqValue)

		if err != nil {
			return err
		}

		buf = append(XmlHeaderBytes, buf...)

		req.ReqReader = bytes.NewReader(buf)
		req.Headers.Set("Content-Type", "application/xml")
		req.Headers.Set("Content-Length", fmt.Sprintf("%d", len(buf)))

		req.ReqContentLength = int64(len(buf))

		return nil

	case EncodingForm:
		if data, ok := req.ReqValue.(url.Values); ok {
			formStr := data.Encode()
			req.ReqReader = strings.NewReader(formStr)
			req.Headers.Set("Content-Type", "application/x-www-form-urlencoded")

			req.Headers.Set("Content-Length", fmt.Sprintf("%d", len(formStr)))

			req.ReqContentLength = int64(len(formStr))

			return nil
		} else {
			return fmt.Errorf("HTTPClient: invalid ReqValue type %T", req.ReqValue)
		}
	}

	return fmt.Errorf("HTTPClient: invalid ReqEncoding: %s", req.ReqEncoding)
}

func (c *HTTPClient) runPostHook(req *http.Request, response *http.Response) (err error) {
	hook, ok := c.PostHooks[response.StatusCode]

	if ok {
		err = hook(req, response)
	}

	return err
}

func (c *HTTPClient) Request(req *RequestData) (response *http.Response, err error) {
	err = c.marshalRequest(req)

	if err != nil {
		return nil, err
	}

	r, err := http.NewRequest(req.Method, req.FullURL, req.ReqReader)

	if err != nil {
		return nil, err
	}

	r.ContentLength = req.ReqContentLength

	if req.FullURL == "" {
		r.URL = c.buildURL(req)
		r.Host = r.URL.Host
	}

	c.setHeaders(req, r)

	if c.rateLimited {
		if c.rateLimitTimeout > 0 {
			select {
			case t := <-c.rateLimitChan:
				defer func() {
					c.rateLimitChan <- t
				}()
			case <-time.After(c.rateLimitTimeout):
				return nil, RateLimitTimeoutError
			}
		} else {
			t := <-c.rateLimitChan
			defer func() {
				c.rateLimitChan <- t
			}()
		}
	}

	isTraceEnabled := os.Getenv("HTTPCLIENT_TRACE") != ""

	if isTraceEnabled {
		requestBytes, _ := httputil.DumpRequestOut(r, true)
		fmt.Println(string(requestBytes))
	}

	if req.IgnoreRedirects {
		transport := c.Client.Transport

		if transport == nil {
			transport = http.DefaultTransport
		}

		response, err = transport.RoundTrip(r)
	} else {
		response, err = c.Client.Do(r)
	}

	if isTraceEnabled {
		responseBytes, _ := httputil.DumpResponse(response, true)
		fmt.Println(string(responseBytes))
	}

	if err != nil {
		return response, err
	}

	if err = c.runPostHook(r, response); err != nil {
		return response, err
	}

	if err = c.checkStatus(req, response); err != nil {
		defer response.Body.Close()
		return response, err
	}

	if err = c.unmarshalResponse(req, response); err != nil {
		return response, err
	}

	return response, nil
}
<commit_msg>Error Handler Hook<commit_after>package httpclient

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"net/url"
	"os"
	"strings"
	"time"
)

var XmlHeaderBytes []byte = []byte(xml.Header)

type ErrorHandlerFunc func(*http.Response, error)
type PostHookFunc func(*http.Request, *http.Response) error

type HTTPClient struct {
	BaseURL          *url.URL
	Headers          http.Header
	Client           *http.Client
	PostHooks        map[int]PostHookFunc
	errorHandler     ErrorHandlerFunc
	rateLimited      bool
	rateLimitChan    chan struct{}
	rateLimitTimeout time.Duration
}

func New() (httpClient *HTTPClient) {
	return &HTTPClient{
		Client:    HttpClient,
		Headers:   make(http.Header),
		PostHooks: make(map[int]PostHookFunc),
	}
}

func Insecure() (httpClient *HTTPClient) {
	httpClient = New()
	httpClient.Client = InsecureHttpClient
	return httpClient
}

var DefaultClient = New()

func (c *HTTPClient) SetPostHook(onStatus int, hook PostHookFunc) {
	c.PostHooks[onStatus] = hook
}

func (c *HTTPClient) SetErrorHandler(handler ErrorHandlerFunc) {
	c.errorHandler = handler
}

func (c *HTTPClient) SetRateLimit(limit int, timeout time.Duration) {
	c.rateLimited = true
	c.rateLimitChan = make(chan struct{}, limit)

	for i := 0; i < limit; i++ {
		c.rateLimitChan <- struct{}{}
	}

	c.rateLimitTimeout = timeout
}

func (c *HTTPClient) buildURL(req *RequestData) *url.URL {
	bu := c.BaseURL

	rpath := req.Path

	if strings.HasSuffix(bu.Path, "/") && strings.HasPrefix(rpath, "/") {
		rpath = rpath[1:]
	}

	opaque := EscapePath(bu.Path + rpath)

	u := &url.URL{
		Scheme: bu.Scheme,
		Host:   bu.Host,
		Opaque: opaque,
	}

	if req.Params != nil {
		u.RawQuery = req.Params.Encode()
	}

	return u
}

func (c *HTTPClient) setHeaders(req *RequestData, httpReq *http.Request) {
	switch req.RespEncoding {
	case EncodingJSON:
		httpReq.Header.Set("Accept", "application/json")
	case EncodingXML:
		httpReq.Header.Set("Accept", "application/xml")
	}

	if c.Headers != nil {
		for key, values := range c.Headers {
			for _, value := range values {
				httpReq.Header.Set(key, value)
			}
		}
	}

	if req.Headers != nil {
		for key, values := range req.Headers {
			for _, value := range values {
				httpReq.Header.Set(key, value)
			}
		}
	}
}

func (c *HTTPClient) checkStatus(req *RequestData, response *http.Response) (err error) {
	if req.ExpectedStatus != nil {
		statusOk := false

		for _, status := range req.ExpectedStatus {
			if response.StatusCode == status {
				statusOk = true
			}
		}

		if !statusOk {
			lr := io.LimitReader(response.Body, 10*1024)
			contentBytes, _ := ioutil.ReadAll(lr)
			content := string(contentBytes)

			err = InvalidStatusError{
				Expected: req.ExpectedStatus,
				Got:      response.StatusCode,
				Headers:  response.Header,
				Content:  content,
			}

			return err
		}
	}

	return nil
}

func (c *HTTPClient) unmarshalResponse(req *RequestData, response *http.Response) (err error) {
	var buf []byte

	switch req.RespEncoding {
	case EncodingJSON:
		defer response.Body.Close()

		if buf, err = ioutil.ReadAll(response.Body); err != nil {
			return err
		}

		err = json.Unmarshal(buf, req.RespValue)

		if err != nil {
			return err
		}

		return nil

	case EncodingXML:
		defer response.Body.Close()

		if buf, err = ioutil.ReadAll(response.Body); err != nil {
			return err
		}

		err = xml.Unmarshal(buf, req.RespValue)

		if err != nil {
			return err
		}

		return nil
	}

	switch req.RespValue.(type) {
	case *[]byte:
		defer response.Body.Close()

		if buf, err = ioutil.ReadAll(response.Body); err != nil {
			return err
		}

		respVal := req.RespValue.(*[]byte)
		*respVal = buf

		return nil
	}

	if req.RespConsume {
		defer response.Body.Close()
		ioutil.ReadAll(response.Body)
	}

	return nil
}

func (c *HTTPClient) marshalRequest(req *RequestData) (err error) {
	if req.ReqReader != nil || req.ReqValue == nil {
		return nil
	}

	if req.Headers == nil {
		req.Headers = make(http.Header)
	}

	var buf []byte

	switch req.ReqEncoding {
	case EncodingJSON:
		buf, err = json.Marshal(req.ReqValue)

		if err != nil {
			return err
		}

		req.ReqReader = bytes.NewReader(buf)
		req.Headers.Set("Content-Type", "application/json")
		req.Headers.Set("Content-Length", fmt.Sprintf("%d", len(buf)))

		req.ReqContentLength = int64(len(buf))

		return nil

	case EncodingXML:
		buf, err = xml.Marshal(req.ReqValue)

		if err != nil {
			return err
		}

		buf = append(XmlHeaderBytes, buf...)

		req.ReqReader = bytes.NewReader(buf)
		req.Headers.Set("Content-Type", "application/xml")
		req.Headers.Set("Content-Length", fmt.Sprintf("%d", len(buf)))

		req.ReqContentLength = int64(len(buf))

		return nil

	case EncodingForm:
		if data, ok := req.ReqValue.(url.Values); ok {
			formStr := data.Encode()
			req.ReqReader = strings.NewReader(formStr)
			req.Headers.Set("Content-Type", "application/x-www-form-urlencoded")

			req.Headers.Set("Content-Length", fmt.Sprintf("%d", len(formStr)))

			req.ReqContentLength = int64(len(formStr))

			return nil
		} else {
			return fmt.Errorf("HTTPClient: invalid ReqValue type %T", req.ReqValue)
		}
	}

	return fmt.Errorf("HTTPClient: invalid ReqEncoding: %s", req.ReqEncoding)
}

func (c *HTTPClient) runPostHook(req *http.Request, response *http.Response) (err error) {
	hook, ok := c.PostHooks[response.StatusCode]

	if ok {
		err = hook(req, response)
	}

	return err
}

func (c *HTTPClient) Request(req *RequestData) (response *http.Response, err error) {
	err = c.marshalRequest(req)

	if err != nil {
		return nil, err
	}

	r, err := http.NewRequest(req.Method, req.FullURL, req.ReqReader)

	if err != nil {
		return nil, err
	}

	r.ContentLength = req.ReqContentLength

	if req.FullURL == "" {
		r.URL = c.buildURL(req)
		r.Host = r.URL.Host
	}

	c.setHeaders(req, r)

	if c.rateLimited {
		if c.rateLimitTimeout > 0 {
			select {
			case t := <-c.rateLimitChan:
				defer func() {
					c.rateLimitChan <- t
				}()
			case <-time.After(c.rateLimitTimeout):
				return nil, RateLimitTimeoutError
			}
		} else {
			t := <-c.rateLimitChan
			defer func() {
				c.rateLimitChan <- t
			}()
		}
	}

	isTraceEnabled := os.Getenv("HTTPCLIENT_TRACE") != ""

	if isTraceEnabled {
		requestBytes, _ := httputil.DumpRequestOut(r, true)
		fmt.Println(string(requestBytes))
	}

	if req.IgnoreRedirects {
		transport := c.Client.Transport

		if transport == nil {
			transport = http.DefaultTransport
		}

		response, err = transport.RoundTrip(r)
	} else {
		response, err = c.Client.Do(r)
	}

	if isTraceEnabled {
		responseBytes, _ := httputil.DumpResponse(response, true)
		fmt.Println(string(responseBytes))
	}

	if err != nil {
		if c.errorHandler != nil {
			c.errorHandler(response, err)
		}
		return response, err
	}

	if err = c.runPostHook(r, response); err != nil {
		return response, err
	}

	if err = c.checkStatus(req, response); err != nil {
		defer response.Body.Close()
		return response, err
	}

	if err = c.unmarshalResponse(req, response); err != nil {
		return response, err
	}

	return response, nil
}
<|endoftext|>


<commit_before>package hap

import (
	"github.com/brutella/hc/characteristic"
	"github.com/brutella/hc/crypto"
	"net"
)

// Session contains objects (encrypter, decrypter, pairing handler,...) used to handle the data communication.
type Session interface {
	// Decrypter returns decrypter for incoming data, may be nil
	Decrypter() crypto.Decrypter

	// Encrypter returns encrypter for outgoing data, may be nil
	Encrypter() crypto.Encrypter

	// SetCryptographer sets the new cryptographer used for en-/decryption
	SetCryptographer(c crypto.Cryptographer)

	// PairSetupHandler returns the pairing setup handler
	PairSetupHandler() ContainerHandler

	// SetPairSetupHandler sets the handler for pairing setup
	SetPairSetupHandler(c ContainerHandler)

	// PairVerifyHandler returns the pairing verify handler
	PairVerifyHandler() PairVerifyHandler

	// SetPairVerifyHandler sets the handler for pairing verify
	SetPairVerifyHandler(c PairVerifyHandler)

	// Connection returns the associated connection
	Connection() net.Conn

	Subscribe(*characteristic.Characteristic)
	Unsubscribe(*characteristic.Characteristic)
	IsSubscribedTo(*characteristic.Characteristic) bool
}

type session struct {
	cryptographer     crypto.Cryptographer
	pairStartHandler  ContainerHandler
	pairVerifyHandler PairVerifyHandler
	connection        net.Conn
	subs              map[*characteristic.Characteristic]bool

	// Temporary variable to reference next cryptographer
	nextCryptographer crypto.Cryptographer
}

// NewSession returns a session for a connection.
func NewSession(connection net.Conn) Session {
	s := session{
		connection: connection,
		subs:       map[*characteristic.Characteristic]bool{},
	}

	return &s
}

func (s *session) Connection() net.Conn {
	return s.connection
}

func (s *session) Decrypter() crypto.Decrypter {
	// Return the next cryptographer when possible
	// This allows sessions to switch encryption
	if s.nextCryptographer != nil {
		s.cryptographer = s.nextCryptographer
		s.nextCryptographer = nil
	}

	return s.cryptographer
}

func (s *session) Encrypter() crypto.Encrypter {
	return s.cryptographer
}

func (s *session) PairSetupHandler() ContainerHandler {
	return s.pairStartHandler
}

func (s *session) PairVerifyHandler() PairVerifyHandler {
	return s.pairVerifyHandler
}

func (s *session) SetCryptographer(c crypto.Cryptographer) {
	// Temporarily set the cryptographer as the nextCryptographer
	// The nextCryptographer is used the next time Decrypter() is called.
	// Otherwise the Encrypter() encrypts differently than the previous Decrypter()
	s.nextCryptographer = c
}
func (s *session) SetPairSetupHandler(c ContainerHandler) {
	s.pairStartHandler = c
}

func (s *session) SetPairVerifyHandler(c PairVerifyHandler) {
	s.pairVerifyHandler = c
}

func (s *session) IsSubscribedTo(ch *characteristic.Characteristic) bool {
	return s.subs[ch] == true
}

func (s *session) Subscribe(ch *characteristic.Characteristic) {
	s.subs[ch] = true
}

func (s *session) Unsubscribe(ch *characteristic.Characteristic) {
	s.subs[ch] = false
}
<commit_msg>Fix race condition<commit_after>package hap

import (
	"github.com/brutella/hc/characteristic"
	"github.com/brutella/hc/crypto"
	"net"
	"sync"
)

// Session contains objects (encrypter, decrypter, pairing handler,...) used to handle the data communication.
type Session interface {
	// Decrypter returns decrypter for incoming data, may be nil
	Decrypter() crypto.Decrypter

	// Encrypter returns encrypter for outgoing data, may be nil
	Encrypter() crypto.Encrypter

	// SetCryptographer sets the new cryptographer used for en-/decryption
	SetCryptographer(c crypto.Cryptographer)

	// PairSetupHandler returns the pairing setup handler
	PairSetupHandler() ContainerHandler

	// SetPairSetupHandler sets the handler for pairing setup
	SetPairSetupHandler(c ContainerHandler)

	// PairVerifyHandler returns the pairing verify handler
	PairVerifyHandler() PairVerifyHandler

	// SetPairVerifyHandler sets the handler for pairing verify
	SetPairVerifyHandler(c PairVerifyHandler)

	// Connection returns the associated connection
	Connection() net.Conn

	Subscribe(*characteristic.Characteristic)
	Unsubscribe(*characteristic.Characteristic)
	IsSubscribedTo(*characteristic.Characteristic) bool
}

type session struct {
	cryptographer     crypto.Cryptographer
	pairStartHandler  ContainerHandler
	pairVerifyHandler PairVerifyHandler
	connection        net.Conn
	mu                *sync.Mutex
	subs              map[*characteristic.Characteristic]bool

	// Temporary variable to reference next cryptographer
	nextCryptographer crypto.Cryptographer
}

// NewSession returns a session for a connection.
func NewSession(connection net.Conn) Session {
	s := session{
		connection: connection,
		mu:         &sync.Mutex{},
		subs:       map[*characteristic.Characteristic]bool{},
	}

	return &s
}

func (s *session) Connection() net.Conn {
	return s.connection
}

func (s *session) Decrypter() crypto.Decrypter {
	// Return the next cryptographer when possible
	// This allows sessions to switch encryption
	if s.nextCryptographer != nil {
		s.cryptographer = s.nextCryptographer
		s.nextCryptographer = nil
	}

	return s.cryptographer
}

func (s *session) Encrypter() crypto.Encrypter {
	return s.cryptographer
}

func (s *session) PairSetupHandler() ContainerHandler {
	return s.pairStartHandler
}

func (s *session) PairVerifyHandler() PairVerifyHandler {
	return s.pairVerifyHandler
}

func (s *session) SetCryptographer(c crypto.Cryptographer) {
	// Temporarily set the cryptographer as the nextCryptographer
	// The nextCryptographer is used the next time Decrypter() is called.
	// Otherwise the Encrypter() encrypts differently than the previous Decrypter()
	s.nextCryptographer = c
}
func (s *session) SetPairSetupHandler(c ContainerHandler) {
	s.pairStartHandler = c
}

func (s *session) SetPairVerifyHandler(c PairVerifyHandler) {
	s.pairVerifyHandler = c
}

func (s *session) IsSubscribedTo(ch *characteristic.Characteristic) bool {
	s.mu.Lock()
	defer s.mu.Unlock()
	return s.subs[ch] == true
}

func (s *session) Subscribe(ch *characteristic.Characteristic) {
	s.mu.Lock()
	s.subs[ch] = true
	s.mu.Unlock()
}

func (s *session) Unsubscribe(ch *characteristic.Characteristic) {
	s.mu.Lock()
	s.subs[ch] = false
	s.mu.Unlock()
}
<|endoftext|>


<commit_before>// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin,go1.12,!go1.13

package unix

import (
	"unsafe"
)

func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
	// To implement this using libSystem we'd need syscall_syscallPtr for
	// fdopendir. However, syscallPtr was only added in Go 1.13, so we fall
	// back to raw syscalls for this func on Go 1.12.
	var p unsafe.Pointer
	if len(buf) > 0 {
		p = unsafe.Pointer(&buf[0])
	} else {
		p = unsafe.Pointer(&_zero)
	}
	r0, _, e1 := Syscall6(SYS_GETDIRENTRIES64, uintptr(fd), uintptr(p), uintptr(len(buf)), uintptr(unsafe.Pointer(basep)), 0, 0)
	n = int(r0)
	if e1 != 0 {
		return n, errnoErr(e1)
	}
	return n, nil
}
<commit_msg>unix: fix build with Go 1.12 on darwin<commit_after>// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build darwin,go1.12,!go1.13

package unix

import (
	"unsafe"
)

const _SYS_GETDIRENTRIES64 = 344

func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
	// To implement this using libSystem we'd need syscall_syscallPtr for
	// fdopendir. However, syscallPtr was only added in Go 1.13, so we fall
	// back to raw syscalls for this func on Go 1.12.
	var p unsafe.Pointer
	if len(buf) > 0 {
		p = unsafe.Pointer(&buf[0])
	} else {
		p = unsafe.Pointer(&_zero)
	}
	r0, _, e1 := Syscall6(_SYS_GETDIRENTRIES64, uintptr(fd), uintptr(p), uintptr(len(buf)), uintptr(unsafe.Pointer(basep)), 0, 0)
	n = int(r0)
	if e1 != 0 {
		return n, errnoErr(e1)
	}
	return n, nil
}
<|endoftext|>


<commit_before>// Stress test around the build step stuff, specifically trying to
// identify concurrent map read / writes.

package build

import (
	"fmt"
	"sync"
	"testing"

	"github.com/stretchr/testify/assert"

	"core"
)

const size = 1000
const numWorkers = 10

func TestBuildLotsOfTargets(t *testing.T) {
	config, _ := core.ReadConfigFiles(nil, "")
	state := core.NewBuildState(numWorkers, nil, 4, config)
	state.Parser = &fakeParser{
		PostBuildFunctions: buildFunctionMap{},
	}
	pkg := core.NewPackage("pkg")
	state.Graph.AddPackage(pkg)
	for i := 1; i <= size; i++ {
		addTarget(state, i)
	}
	var wg sync.WaitGroup
	wg.Add(numWorkers)
	for i := 0; i < numWorkers; i++ {
		go func() {
			please(i, state)
			wg.Done()
		}()
	}
	// Consume and discard any results
	go func() {
		for result := range state.Results {
			assert.NotEqual(t, core.TargetBuildFailed, result.Status)
			log.Info("%s", result.Description)
		}
	}()
	state.TaskDone() // Initial target adding counts as one.
	wg.Wait()
}

func addTarget(state *core.BuildState, i int) *core.BuildTarget {
	// Create and add a new target, with a parent and a dependency.
	target := core.NewBuildTarget(label(i))
	target.IsFilegroup = true // Will mean it doesn't have to shell out to anything.
	target.SetState(core.Active)
	state.Graph.AddTarget(target)
	if i <= size {
		if i > 10 {
			target.Flakiness = i // Stash this here, will be useful later.
			state.Parser.(*fakeParser).PostBuildFunctions[target] = postBuild
		}
		if i < size/10 {
			for j := 0; j < 10; j++ {
				dep := label(i*10 + j)
				log.Info("Adding dependency %s -> %s", target.Label, dep)
				target.AddDependency(dep)
				state.Graph.AddDependency(target.Label, dep)
			}
		} else {
			// These are buildable now
			state.AddPendingBuild(target.Label, false)
		}
	}
	state.AddActiveTarget()
	return target
}

func label(i int) core.BuildLabel {
	return core.ParseBuildLabel(fmt.Sprintf("//pkg:target%d", i), "")
}

// please mimics the core build 'loop' from src/please.go.
func please(tid int, state *core.BuildState) {
	for {
		label, _, t := state.NextTask()
		switch t {
		case core.Stop, core.Kill:
			return
		case core.Build:
			Build(tid, state, label)
		default:
			panic(fmt.Sprintf("unexpected task type: %d", t))
		}
		state.TaskDone()
	}
}

// Post-build function that adds new targets & ties in dependencies.
func postBuild(target *core.BuildTarget, out string) error {
	// Add a target corresponding to this one to its 'parent'
	if target.Flakiness == 0 {
		return fmt.Errorf("shouldn't be calling a post-build function on %s", target.Label)
	}
	parent := label(target.Flakiness / 10)
	newTarget := addTarget(core.State, target.Flakiness+size)

	// This mimics what interpreter.go does.
	core.State.Graph.TargetOrDie(parent).AddMaybeExportedDependency(newTarget.Label, false, false)
	core.State.Graph.AddDependency(parent, newTarget.Label)
	return nil
}

type buildFunctionMap map[*core.BuildTarget]func(*core.BuildTarget, string) error

type fakeParser struct {
	PostBuildFunctions buildFunctionMap
}

func (fake *fakeParser) ParseFile(state *core.BuildState, pkg *core.Package, filename string) error {
	return nil
}

func (fake *fakeParser) RunPreBuildFunction(threadID int, state *core.BuildState, target *core.BuildTarget) error {
	return nil
}

func (fake *fakeParser) RunPostBuildFunction(threadID int, state *core.BuildState, target *core.BuildTarget, output string) error {
	if f, present := fake.PostBuildFunctions[target]; present {
		return f(target, output)
	}
	return nil
}

func (fake *fakeParser) UndeferAnyParses(state *core.BuildState, target *core.BuildTarget) {
}
<commit_msg>Fix capture of loop variable. (#281)<commit_after>// Stress test around the build step stuff, specifically trying to
// identify concurrent map read / writes.

package build

import (
	"fmt"
	"sync"
	"testing"

	"github.com/stretchr/testify/assert"

	"core"
)

const size = 1000
const numWorkers = 10

func TestBuildLotsOfTargets(t *testing.T) {
	config, _ := core.ReadConfigFiles(nil, "")
	state := core.NewBuildState(numWorkers, nil, 4, config)
	state.Parser = &fakeParser{
		PostBuildFunctions: buildFunctionMap{},
	}
	pkg := core.NewPackage("pkg")
	state.Graph.AddPackage(pkg)
	for i := 1; i <= size; i++ {
		addTarget(state, i)
	}
	var wg sync.WaitGroup
	wg.Add(numWorkers)
	for i := 0; i < numWorkers; i++ {
		go func(i int) {
			please(i, state)
			wg.Done()
		}(i)
	}
	// Consume and discard any results
	go func() {
		for result := range state.Results {
			assert.NotEqual(t, core.TargetBuildFailed, result.Status)
			log.Info("%s", result.Description)
		}
	}()
	state.TaskDone() // Initial target adding counts as one.
	wg.Wait()
}

func addTarget(state *core.BuildState, i int) *core.BuildTarget {
	// Create and add a new target, with a parent and a dependency.
	target := core.NewBuildTarget(label(i))
	target.IsFilegroup = true // Will mean it doesn't have to shell out to anything.
	target.SetState(core.Active)
	state.Graph.AddTarget(target)
	if i <= size {
		if i > 10 {
			target.Flakiness = i // Stash this here, will be useful later.
			state.Parser.(*fakeParser).PostBuildFunctions[target] = postBuild
		}
		if i < size/10 {
			for j := 0; j < 10; j++ {
				dep := label(i*10 + j)
				log.Info("Adding dependency %s -> %s", target.Label, dep)
				target.AddDependency(dep)
				state.Graph.AddDependency(target.Label, dep)
			}
		} else {
			// These are buildable now
			state.AddPendingBuild(target.Label, false)
		}
	}
	state.AddActiveTarget()
	return target
}

func label(i int) core.BuildLabel {
	return core.ParseBuildLabel(fmt.Sprintf("//pkg:target%d", i), "")
}

// please mimics the core build 'loop' from src/please.go.
func please(tid int, state *core.BuildState) {
	for {
		label, _, t := state.NextTask()
		switch t {
		case core.Stop, core.Kill:
			return
		case core.Build:
			Build(tid, state, label)
		default:
			panic(fmt.Sprintf("unexpected task type: %d", t))
		}
		state.TaskDone()
	}
}

// Post-build function that adds new targets & ties in dependencies.
func postBuild(target *core.BuildTarget, out string) error {
	// Add a target corresponding to this one to its 'parent'
	if target.Flakiness == 0 {
		return fmt.Errorf("shouldn't be calling a post-build function on %s", target.Label)
	}
	parent := label(target.Flakiness / 10)
	newTarget := addTarget(core.State, target.Flakiness+size)

	// This mimics what interpreter.go does.
	core.State.Graph.TargetOrDie(parent).AddMaybeExportedDependency(newTarget.Label, false, false)
	core.State.Graph.AddDependency(parent, newTarget.Label)
	return nil
}

type buildFunctionMap map[*core.BuildTarget]func(*core.BuildTarget, string) error

type fakeParser struct {
	PostBuildFunctions buildFunctionMap
}

func (fake *fakeParser) ParseFile(state *core.BuildState, pkg *core.Package, filename string) error {
	return nil
}

func (fake *fakeParser) RunPreBuildFunction(threadID int, state *core.BuildState, target *core.BuildTarget) error {
	return nil
}

func (fake *fakeParser) RunPostBuildFunction(threadID int, state *core.BuildState, target *core.BuildTarget, output string) error {
	if f, present := fake.PostBuildFunctions[target]; present {
		return f(target, output)
	}
	return nil
}

func (fake *fakeParser) UndeferAnyParses(state *core.BuildState, target *core.BuildTarget) {
}
<|endoftext|>