<commit_before>/*
Copyright 2019 The Kubernetes Authors.

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 metadata

import (
	"strings"
)

// Started holds the started.json values of the build.
type Started struct {
	// Timestamp is UTC epoch seconds when the job started.
	Timestamp int64 `json:"timestamp"` // epoch seconds
	// Node holds the name of the machine that ran the job.
	Node string `json:"node,omitempty"`

	// Consider whether to keep the following:

	// Pull holds the PR number the primary repo is testing
	Pull string `json:"pull,omitempty"`
	// Repos holds the RepoVersion of all commits checked out.
	Repos      map[string]string `json:"repos,omitempty"` // {repo: branch_or_pull} map
	RepoCommit string            `json:"repo-commit,omitempty"`

	// Deprecated fields:

	// Metadata is deprecated, add to finished.json
	Metadata Metadata `json:"metadata,omitempty"` // TODO(fejta): remove

	// Use RepoCommit
	DeprecatedJobVersion  string `json:"job-version,omitempty"`  // TODO(fejta): remove
	DeprecatedRepoVersion string `json:"repo-version,omitempty"` // TODO(fejta): remove

}

const (
	// JobVersion is the metadata key that overrides repo-commit in Started when set.
	JobVersion = "job-version"
)

// Finished holds the finished.json values of the build
type Finished struct {
	// Timestamp is UTC epoch seconds when the job finished.
	// An empty value indicates an incomplete job.
	Timestamp *int64 `json:"timestamp,omitempty"`
	// Passed is true when the job completes successfully.
	Passed *bool `json:"passed"`
	// Metadata holds data computed by the job at runtime.
	// For example, the version of a binary downloaded at runtime
	// The JobVersion key overrides the auto-version set in Started.
	Metadata Metadata `json:"metadata,omitempty"`

	// Consider whether to keep the following:

	// Deprecated fields:

	// Result is deprecated, use Passed.
	Result string `json:"result,omitempty"` // TODO(fejta): remove

	// Use Metadata[JobVersion] or Started.RepoCommit
	DeprecatedJobVersion  string `json:"job-version,omitempty"`  // TODO(fejta): remove
	DeprecatedRevision    string `json:"revision,omitempty"`     // TODO(fejta): remove
	DeprecatedRepoVersion string `json:"repo-version,omitempty"` // TODO(fejta): remove
}

// Metadata holds the finished.json values in the metadata key.
//
// Metadata values can either be string or string map of strings
//
// TODO(fejta): figure out which of these we want and document them
// Special values: infra-commit, repos, repo, repo-commit, links, others
type Metadata map[string]interface{}

// String returns the name key if its value is a string, and true if the key is present.
func (m Metadata) String(name string) (*string, bool) {
	if v, ok := m[name]; !ok {
		return nil, false
	} else if t, good := v.(string); !good {
		return nil, true
	} else {
		return &t, true
	}
}

// Meta returns the name key if its value is a child object, and true if they key is present.
func (m Metadata) Meta(name string) (*Metadata, bool) {
	if v, ok := m[name]; !ok {
		return nil, false
	} else if t, good := v.(Metadata); good {
		return &t, true
	} else if t, good := v.(map[string]interface{}); good {
		child := Metadata(t)
		return &child, true
	}
	return nil, true
}

// Keys returns an array of the keys of all valid Metadata values.
func (m Metadata) Keys() []string {
	ka := make([]string, 0, len(m))
	for k := range m {
		if _, ok := m.Meta(k); ok {
			ka = append(ka, k)
		}
	}
	return ka
}

// Strings returns the submap of values in the map that are strings.
func (m Metadata) Strings() map[string]string {
	bm := map[string]string{}
	for k, v := range m {
		if s, ok := v.(string); ok {
			bm[k] = s
		}
		// TODO(fejta): handle sub items
	}
	return bm
}

/* MultiString get list of strings if exist, and true if they key is present.
If value is list of strings we return it as is.
If value is list of interfaces, we try convert it into list of strings, if fail we return an empty list */
func (m Metadata) MultiString(name string) ([]string, bool) {
	if v, ok := m[name]; !ok {
		return []string{}, false
	} else if lstStr, good := v.([]string); good {
		return lstStr, true
	} else if lstInter, good := v.([]interface{}); good {
		convertedStrings := []string{}
		for _, inter := range lstInter {
			if s, good := inter.(string); !good {
				return []string{}, true
			} else {
				convertedStrings = append(convertedStrings, s)
			}
		}
		return convertedStrings, true
	}
	return []string{}, true
}

// firstFilled returns the first non-empty option or else def.
func firstFilled(def string, options ...string) string {
	for _, o := range options {
		if o != "" {
			return o
		}
	}
	return def
}

// Missing is the key for a missing version.
const Missing = "missing"

// Version extracts the job's custom version or else the checked out repo commit.
func Version(started Started, finished Finished) string {
	// TODO(fejta): started.RepoCommit, finished.Metadata.String(JobVersion)
	meta := func(key string) string {
		if finished.Metadata == nil {
			return ""
		}
		v, ok := finished.Metadata.String(key)
		if !ok {
			return ""
		}
		return *v
	}

	val := firstFilled(
		Missing,
		finished.DeprecatedJobVersion, started.DeprecatedJobVersion,
		started.DeprecatedRepoVersion, finished.DeprecatedRepoVersion,
		meta("revision"), meta("repo-commit"),
		meta(JobVersion), started.RepoCommit, // TODO(fejta): remove others
	)
	parts := strings.SplitN(val, "+", 2)
	val = parts[len(parts)-1]
	if n := len(val); n > 9 {
		return val[:9]
	}
	return val
}

// SetVersion ensures that the repoCommit and jobVersion are set appropriately.
func SetVersion(started *Started, finished *Finished, repoCommit, jobVersion string) {
	if started != nil && repoCommit != "" {
		started.DeprecatedRepoVersion = repoCommit // TODO(fejta): pump this
		started.RepoCommit = repoCommit
	}
	if finished != nil && jobVersion != "" {
		if finished.Metadata == nil {
			finished.Metadata = Metadata{}
		}
		finished.Metadata["job-version"] = jobVersion
		finished.DeprecatedJobVersion = jobVersion
	}
}
<commit_msg>TestGrid updater - on dynamic email list, solve casting []string issue<commit_after>/*
Copyright 2019 The Kubernetes Authors.

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 metadata

import (
	"strings"
)

// Started holds the started.json values of the build.
type Started struct {
	// Timestamp is UTC epoch seconds when the job started.
	Timestamp int64 `json:"timestamp"` // epoch seconds
	// Node holds the name of the machine that ran the job.
	Node string `json:"node,omitempty"`

	// Consider whether to keep the following:

	// Pull holds the PR number the primary repo is testing
	Pull string `json:"pull,omitempty"`
	// Repos holds the RepoVersion of all commits checked out.
	Repos      map[string]string `json:"repos,omitempty"` // {repo: branch_or_pull} map
	RepoCommit string            `json:"repo-commit,omitempty"`

	// Deprecated fields:

	// Metadata is deprecated, add to finished.json
	Metadata Metadata `json:"metadata,omitempty"` // TODO(fejta): remove

	// Use RepoCommit
	DeprecatedJobVersion  string `json:"job-version,omitempty"`  // TODO(fejta): remove
	DeprecatedRepoVersion string `json:"repo-version,omitempty"` // TODO(fejta): remove

}

const (
	// JobVersion is the metadata key that overrides repo-commit in Started when set.
	JobVersion = "job-version"
)

// Finished holds the finished.json values of the build
type Finished struct {
	// Timestamp is UTC epoch seconds when the job finished.
	// An empty value indicates an incomplete job.
	Timestamp *int64 `json:"timestamp,omitempty"`
	// Passed is true when the job completes successfully.
	Passed *bool `json:"passed"`
	// Metadata holds data computed by the job at runtime.
	// For example, the version of a binary downloaded at runtime
	// The JobVersion key overrides the auto-version set in Started.
	Metadata Metadata `json:"metadata,omitempty"`

	// Consider whether to keep the following:

	// Deprecated fields:

	// Result is deprecated, use Passed.
	Result string `json:"result,omitempty"` // TODO(fejta): remove

	// Use Metadata[JobVersion] or Started.RepoCommit
	DeprecatedJobVersion  string `json:"job-version,omitempty"`  // TODO(fejta): remove
	DeprecatedRevision    string `json:"revision,omitempty"`     // TODO(fejta): remove
	DeprecatedRepoVersion string `json:"repo-version,omitempty"` // TODO(fejta): remove
}

// Metadata holds the finished.json values in the metadata key.
//
// Metadata values can either be string or string map of strings
//
// TODO(fejta): figure out which of these we want and document them
// Special values: infra-commit, repos, repo, repo-commit, links, others
type Metadata map[string]interface{}

// String returns the name key if its value is a string, and true if the key is present.
func (m Metadata) String(name string) (*string, bool) {
	if v, ok := m[name]; !ok {
		return nil, false
	} else if t, good := v.(string); !good {
		return nil, true
	} else {
		return &t, true
	}
}

// Meta returns the name key if its value is a child object, and true if they key is present.
func (m Metadata) Meta(name string) (*Metadata, bool) {
	if v, ok := m[name]; !ok {
		return nil, false
	} else if t, good := v.(Metadata); good {
		return &t, true
	} else if t, good := v.(map[string]interface{}); good {
		child := Metadata(t)
		return &child, true
	}
	return nil, true
}

// Keys returns an array of the keys of all valid Metadata values.
func (m Metadata) Keys() []string {
	ka := make([]string, 0, len(m))
	for k := range m {
		if _, ok := m.Meta(k); ok {
			ka = append(ka, k)
		}
	}
	return ka
}

// Strings returns the submap of values in the map that are strings.
func (m Metadata) Strings() map[string]string {
	bm := map[string]string{}
	for k, v := range m {
		if s, ok := v.(string); ok {
			bm[k] = s
		}
		// TODO(fejta): handle sub items
	}
	return bm
}

// MultiString get list of strings if exist, and true if they key is present.
// If value is list of strings we return it as is.
// If value is list of interfaces, we try convert it into list of strings, if fail we return an empty list
func (m Metadata) MultiString(name string) ([]string, bool) {
	if v, ok := m[name]; !ok {
		return []string{}, false
	} else if lstStr, good := v.([]string); good {
		return lstStr, true
	} else if lstInter, good := v.([]interface{}); good {
		convertedStrings := []string{}
		for _, inter := range lstInter {
			s, good := inter.(string)
			if !good {
				return []string{}, true
			}
			convertedStrings = append(convertedStrings, s)
		}
		return convertedStrings, true
	}
	return []string{}, true
}

// firstFilled returns the first non-empty option or else def.
func firstFilled(def string, options ...string) string {
	for _, o := range options {
		if o != "" {
			return o
		}
	}
	return def
}

// Missing is the key for a missing version.
const Missing = "missing"

// Version extracts the job's custom version or else the checked out repo commit.
func Version(started Started, finished Finished) string {
	// TODO(fejta): started.RepoCommit, finished.Metadata.String(JobVersion)
	meta := func(key string) string {
		if finished.Metadata == nil {
			return ""
		}
		v, ok := finished.Metadata.String(key)
		if !ok {
			return ""
		}
		return *v
	}

	val := firstFilled(
		Missing,
		finished.DeprecatedJobVersion, started.DeprecatedJobVersion,
		started.DeprecatedRepoVersion, finished.DeprecatedRepoVersion,
		meta("revision"), meta("repo-commit"),
		meta(JobVersion), started.RepoCommit, // TODO(fejta): remove others
	)
	parts := strings.SplitN(val, "+", 2)
	val = parts[len(parts)-1]
	if n := len(val); n > 9 {
		return val[:9]
	}
	return val
}

// SetVersion ensures that the repoCommit and jobVersion are set appropriately.
func SetVersion(started *Started, finished *Finished, repoCommit, jobVersion string) {
	if started != nil && repoCommit != "" {
		started.DeprecatedRepoVersion = repoCommit // TODO(fejta): pump this
		started.RepoCommit = repoCommit
	}
	if finished != nil && jobVersion != "" {
		if finished.Metadata == nil {
			finished.Metadata = Metadata{}
		}
		finished.Metadata["job-version"] = jobVersion
		finished.DeprecatedJobVersion = jobVersion
	}
}
<|endoftext|>


<commit_before>package gitmedia

import (
	"bytes"
	"io"
)

var MediaWarning = []byte("# This is a placeholder for large media, please install GitHub git-media to retrieve content\n# It is also possible you did not have the media locally, run 'git media sync' to retrieve it\n")

func Encode(writer io.Writer, sha string) (int, error) {
	written, err := writer.Write(MediaWarning)
	if err != nil {
		return written, err
	}

	written2, err := writer.Write([]byte(sha))
	return written + written2, err
}

func Decode(reader io.Reader) (string, error) {
	buf := make([]byte, 1024)
	written, err := reader.Read(buf)
	if err != nil {
		return "", err
	}

	return lastNonEmpty(bytes.Split(buf[0:written], []byte("\n"))), nil
}

func lastNonEmpty(parts [][]byte) string {
	idx := len(parts)
	var part []byte
	for len(part) == 0 {
		idx -= 1
		part = parts[idx]
	}
	return string(part)
}
<commit_msg>add a pseudo shebang header for identification<commit_after>package gitmedia

import (
	"bytes"
	"io"
)

var MediaWarning = []byte("#!/usr/bin/env git media-smudge\n# This is a placeholder for large media, please install git-media to retrieve content\n# It is also possible you did not have the media locally, run 'git media sync' to retrieve it\n")

func Encode(writer io.Writer, sha string) (int, error) {
	written, err := writer.Write(MediaWarning)
	if err != nil {
		return written, err
	}

	written2, err := writer.Write([]byte(sha))
	return written + written2, err
}

func Decode(reader io.Reader) (string, error) {
	buf := make([]byte, 1024)
	written, err := reader.Read(buf)
	if err != nil {
		return "", err
	}

	return lastNonEmpty(bytes.Split(buf[0:written], []byte("\n"))), nil
}

func lastNonEmpty(parts [][]byte) string {
	idx := len(parts)
	var part []byte
	for len(part) == 0 {
		idx -= 1
		part = parts[idx]
	}
	return string(part)
}
<|endoftext|>


<commit_before>package mezvaro

import (
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestCreateEmpty(t *testing.T) {
	// just make sure there is no boom
	m := New()
	if len(m.handlerChain) > 0 {
		t.Fatal("Fresh instance is not empty")
	}
	m.ServeHTTP(httptest.NewRecorder(), nil)
}

func TestCreate(t *testing.T) {
	m := New(HandlerFunc(func(c *Context) {}))
	if len(m.handlerChain) != 1 {
		t.Fatal("Expected 1 handler, found: ", len(m.handlerChain))
	}
}

func TestUse(t *testing.T) {
	m := New()
	m.Use(
		HandlerFunc(func(c *Context) {}),
		HandlerFunc(func(c *Context) {}),
	)
	if len(m.handlerChain) != 2 {
		t.Fatalf("Expected 2 handlers, found: ", len(m.handlerChain))
	}
}

func TestUseFunc(t *testing.T) {
	m := New()
	m.UseFunc(
		func(c *Context) {},
		func(c *Context) {},
	)
	if len(m.handlerChain) != 2 {
		t.Fatal("Expected 2 handlers, found: ", len(m.handlerChain))
	}
}

func TestUseHandler(t *testing.T) {
	m := New()
	m.UseHandler(
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
	)
	if len(m.handlerChain) != 2 {
		t.Fatal("Expected 2 handlers, found: ", len(m.handlerChain))
	}
}

func TestUseHandlerFunc(t *testing.T) {
	m := New()
	m.UseHandlerFunc(
		func(w http.ResponseWriter, r *http.Request) {},
		func(w http.ResponseWriter, r *http.Request) {},
	)
	if len(m.handlerChain) != 2 {
		t.Fatal("Expected 2 handlers, found: ", len(m.handlerChain))
	}
}

func TestUseHandlerMiddleware(t *testing.T) {
	m := New()
	m.UseHandlerMiddleware(
		func(h http.Handler) http.Handler { return nil },
		func(h http.Handler) http.Handler { return nil },
	)
	if len(m.handlerChain) != 2 {
		t.Fatal("Expected 2 handlers, found: ", len(m.handlerChain))
	}
}

func TestForkHandlerCount(t *testing.T) {
	original := HandlerFunc(func(c *Context) {})
	forkHandler := HandlerFunc(func(c *Context) {})
	m := New(original)
	fork := m.Fork(forkHandler)
	if len(fork.handlerChain) != 2 {
		t.Fatal("Expected 2 handlers in fork mezvaro, found: ", len(fork.handlerChain))
	}
}

func TestServeHTTP(t *testing.T) {
	var called bool
	m := New(HandlerFunc(
		func(c *Context) {
			called = true
		}))
	response := httptest.NewRecorder()
	m.ServeHTTP(response, nil)
	if !called {
		t.Fatal("Handler not called.")
	}
}

func TestHandle(t *testing.T) {
	var called bool
	m := New(HandlerFunc(
		func(c *Context) {
			called = true
		}))
	response := httptest.NewRecorder()
	ctx := &Context{Response: response}
	m.Handle(ctx)
	if !called {
		t.Fatal("Handler not called.")
	}
}

func TestWrapHandlerMiddleware(t *testing.T) {
	var called bool
	middleware := func(h http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			called = true
			h.ServeHTTP(w, r)
		})
	}

	handler := WrapHandlerMiddleware(middleware)
	ctx := &Context{Response: httptest.NewRecorder()}
	handler.Handle(ctx)
	if !called {
		t.Fatal("Wrapping middleware not called.")
	}
}

func TestWrapHandlerMiddlewareReplaceResponseRequest(t *testing.T) {
	originalResponse := httptest.NewRecorder()
	originalRequest, _ := http.NewRequest("GET", "", nil)
	replacementResponse := httptest.NewRecorder()
	replacementRequest, _ := http.NewRequest("POST", "", nil)
	middleware := func(h http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			h.ServeHTTP(replacementResponse, replacementRequest)
		})
	}
	handler := WrapHandlerMiddleware(middleware)
	ctx := &Context{Response: originalResponse, Request: originalRequest}
	handler.Handle(ctx)
	if ctx.Response != replacementResponse {
		t.Fatal("Response not replaced by handler middleware.")
	}
	if ctx.Request != replacementRequest {
		t.Fatal("Request not replaced by handler middleware.")
	}
}

func TestWrapHandlerMiddlewareAbort(t *testing.T) {
	response := httptest.NewRecorder()
	request, _ := http.NewRequest("GET", "", nil)
	middleware := func(h http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// do not call "h", next middleware in chain
		})
	}
	handler := WrapHandlerMiddleware(middleware)
	ctx := &Context{Response: response, Request: request}
	handler.Handle(ctx)
	if !ctx.IsAborted() {
		t.Fatal("Response should be aborted.")
	}
}

func TestWrapHandler(t *testing.T) {
	response := httptest.NewRecorder()
	request, _ := http.NewRequest("GET", "", nil)
	var called bool
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
	})
	mezvaroHandler := WrapHandler(handler)
	ctx := &Context{Response: response, Request: request}
	mezvaroHandler.Handle(ctx)
	if !called {
		t.Fatal("Handler not called.")
	}
	if ctx.index == -1 {
		t.Fatal("Next handler not called.")
	}
}

func TestWrapHandlerFunc(t *testing.T) {
	response := httptest.NewRecorder()
	request, _ := http.NewRequest("GET", "", nil)
	var called bool
	handlerFunc := func(w http.ResponseWriter, r *http.Request) {
		called = true
	}
	mezvaroHandler := WrapHandlerFunc(handlerFunc)
	ctx := &Context{Response: response, Request: request}
	mezvaroHandler.Handle(ctx)
	if !called {
		t.Fatal("Handler func not called.")
	}
	if ctx.index == -1 {
		t.Fatal("Next handler in line not called.")
	}
}

func TestDefaultParamsExtractor(t *testing.T) {
	var urlParams map[string]string
	m := New(HandlerFunc(func(c *Context) {
		urlParams = c.urlParams
	}))
	m.ServeHTTP(httptest.NewRecorder(), nil)
	if urlParams != nil {
		t.Fatal("Default URL parameters extractors did not return nil.")
	}
}

func TestCustomParamsExtractor(t *testing.T) {
	var urlParams map[string]string
	m := New(HandlerFunc(func(c *Context) {
		urlParams = c.urlParams
	}))
	extractor := func(r *http.Request) map[string]string {
		return map[string]string{
			"param": "value",
		}
	}
	SetUrlParamsExtractor(extractor)
	m.ServeHTTP(httptest.NewRecorder(), nil)
	if val, ok := urlParams["param"]; !ok {
		t.Fatal("URL parameters key do not match.")
	} else {
		if val != "value" {
			t.Fatal("URL parameter value does not match.")
		}
	}
}
<commit_msg>Fix Fatalf withotu formatting<commit_after>package mezvaro

import (
	"net/http"
	"net/http/httptest"
	"testing"
)

func TestCreateEmpty(t *testing.T) {
	// just make sure there is no boom
	m := New()
	if len(m.handlerChain) > 0 {
		t.Fatal("Fresh instance is not empty")
	}
	m.ServeHTTP(httptest.NewRecorder(), nil)
}

func TestCreate(t *testing.T) {
	m := New(HandlerFunc(func(c *Context) {}))
	if len(m.handlerChain) != 1 {
		t.Fatal("Expected 1 handler, found: ", len(m.handlerChain))
	}
}

func TestUse(t *testing.T) {
	m := New()
	m.Use(
		HandlerFunc(func(c *Context) {}),
		HandlerFunc(func(c *Context) {}),
	)
	if len(m.handlerChain) != 2 {
		t.Fatal("Expected 2 handlers, found: ", len(m.handlerChain))
	}
}

func TestUseFunc(t *testing.T) {
	m := New()
	m.UseFunc(
		func(c *Context) {},
		func(c *Context) {},
	)
	if len(m.handlerChain) != 2 {
		t.Fatal("Expected 2 handlers, found: ", len(m.handlerChain))
	}
}

func TestUseHandler(t *testing.T) {
	m := New()
	m.UseHandler(
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}),
	)
	if len(m.handlerChain) != 2 {
		t.Fatal("Expected 2 handlers, found: ", len(m.handlerChain))
	}
}

func TestUseHandlerFunc(t *testing.T) {
	m := New()
	m.UseHandlerFunc(
		func(w http.ResponseWriter, r *http.Request) {},
		func(w http.ResponseWriter, r *http.Request) {},
	)
	if len(m.handlerChain) != 2 {
		t.Fatal("Expected 2 handlers, found: ", len(m.handlerChain))
	}
}

func TestUseHandlerMiddleware(t *testing.T) {
	m := New()
	m.UseHandlerMiddleware(
		func(h http.Handler) http.Handler { return nil },
		func(h http.Handler) http.Handler { return nil },
	)
	if len(m.handlerChain) != 2 {
		t.Fatal("Expected 2 handlers, found: ", len(m.handlerChain))
	}
}

func TestForkHandlerCount(t *testing.T) {
	original := HandlerFunc(func(c *Context) {})
	forkHandler := HandlerFunc(func(c *Context) {})
	m := New(original)
	fork := m.Fork(forkHandler)
	if len(fork.handlerChain) != 2 {
		t.Fatal("Expected 2 handlers in fork mezvaro, found: ", len(fork.handlerChain))
	}
}

func TestServeHTTP(t *testing.T) {
	var called bool
	m := New(HandlerFunc(
		func(c *Context) {
			called = true
		}))
	response := httptest.NewRecorder()
	m.ServeHTTP(response, nil)
	if !called {
		t.Fatal("Handler not called.")
	}
}

func TestHandle(t *testing.T) {
	var called bool
	m := New(HandlerFunc(
		func(c *Context) {
			called = true
		}))
	response := httptest.NewRecorder()
	ctx := &Context{Response: response}
	m.Handle(ctx)
	if !called {
		t.Fatal("Handler not called.")
	}
}

func TestWrapHandlerMiddleware(t *testing.T) {
	var called bool
	middleware := func(h http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			called = true
			h.ServeHTTP(w, r)
		})
	}

	handler := WrapHandlerMiddleware(middleware)
	ctx := &Context{Response: httptest.NewRecorder()}
	handler.Handle(ctx)
	if !called {
		t.Fatal("Wrapping middleware not called.")
	}
}

func TestWrapHandlerMiddlewareReplaceResponseRequest(t *testing.T) {
	originalResponse := httptest.NewRecorder()
	originalRequest, _ := http.NewRequest("GET", "", nil)
	replacementResponse := httptest.NewRecorder()
	replacementRequest, _ := http.NewRequest("POST", "", nil)
	middleware := func(h http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			h.ServeHTTP(replacementResponse, replacementRequest)
		})
	}
	handler := WrapHandlerMiddleware(middleware)
	ctx := &Context{Response: originalResponse, Request: originalRequest}
	handler.Handle(ctx)
	if ctx.Response != replacementResponse {
		t.Fatal("Response not replaced by handler middleware.")
	}
	if ctx.Request != replacementRequest {
		t.Fatal("Request not replaced by handler middleware.")
	}
}

func TestWrapHandlerMiddlewareAbort(t *testing.T) {
	response := httptest.NewRecorder()
	request, _ := http.NewRequest("GET", "", nil)
	middleware := func(h http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			// do not call "h", next middleware in chain
		})
	}
	handler := WrapHandlerMiddleware(middleware)
	ctx := &Context{Response: response, Request: request}
	handler.Handle(ctx)
	if !ctx.IsAborted() {
		t.Fatal("Response should be aborted.")
	}
}

func TestWrapHandler(t *testing.T) {
	response := httptest.NewRecorder()
	request, _ := http.NewRequest("GET", "", nil)
	var called bool
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
	})
	mezvaroHandler := WrapHandler(handler)
	ctx := &Context{Response: response, Request: request}
	mezvaroHandler.Handle(ctx)
	if !called {
		t.Fatal("Handler not called.")
	}
	if ctx.index == -1 {
		t.Fatal("Next handler not called.")
	}
}

func TestWrapHandlerFunc(t *testing.T) {
	response := httptest.NewRecorder()
	request, _ := http.NewRequest("GET", "", nil)
	var called bool
	handlerFunc := func(w http.ResponseWriter, r *http.Request) {
		called = true
	}
	mezvaroHandler := WrapHandlerFunc(handlerFunc)
	ctx := &Context{Response: response, Request: request}
	mezvaroHandler.Handle(ctx)
	if !called {
		t.Fatal("Handler func not called.")
	}
	if ctx.index == -1 {
		t.Fatal("Next handler in line not called.")
	}
}

func TestDefaultParamsExtractor(t *testing.T) {
	var urlParams map[string]string
	m := New(HandlerFunc(func(c *Context) {
		urlParams = c.urlParams
	}))
	m.ServeHTTP(httptest.NewRecorder(), nil)
	if urlParams != nil {
		t.Fatal("Default URL parameters extractors did not return nil.")
	}
}

func TestCustomParamsExtractor(t *testing.T) {
	var urlParams map[string]string
	m := New(HandlerFunc(func(c *Context) {
		urlParams = c.urlParams
	}))
	extractor := func(r *http.Request) map[string]string {
		return map[string]string{
			"param": "value",
		}
	}
	SetUrlParamsExtractor(extractor)
	m.ServeHTTP(httptest.NewRecorder(), nil)
	if val, ok := urlParams["param"]; !ok {
		t.Fatal("URL parameters key do not match.")
	} else {
		if val != "value" {
			t.Fatal("URL parameter value does not match.")
		}
	}
}
<|endoftext|>


<commit_before>package dev

import (
	"time"

	"golang.org/x/net/context"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
)

type readReply struct {
	nid   uint32
	reply *State
	err   error
}

func (m *Manager) read(c *Configuration, args *ReadRequest) (*ReadReply, error) {
	var (
		replyChan   = make(chan readReply, c.n)
		stopSignal  = make(chan struct{})
		ctx, cancel = context.WithCancel(context.Background())
	)

	for _, n := range c.nodes {
		go func(node *Node) {
			reply := new(State)
			ce := make(chan error, 1)
			start := time.Now()
			go func() {
				select {
				case ce <- grpc.Invoke(
					ctx,
					"/dev.Register/Read",
					args,
					reply,
					node.conn,
				):
				case <-stopSignal:
					return
				}
			}()
			select {
			case err := <-ce:
				switch grpc.Code(err) { // nil -> codes.OK
				case codes.OK, codes.Canceled:
					node.setLatency(time.Since(start))
				default:
					node.setLastErr(err)
				}
				replyChan <- readReply{node.id, reply, err}
			case <-stopSignal:
				return
			}
		}(n)
	}

	var (
		replyValues = make([]*State, 0, c.n)
		reply       = &ReadReply{NodeIDs: make([]uint32, 0, c.n)}
		errCount    int
		quorum      bool
	)

	/*
		Alternative for time.After in select below: stop rpc timeout timer explicitly.

		See
		https://github.com/kubernetes/kubernetes/pull/23210/commits/e4b369e1d74ac8f2d2a20afce92d93c804afa5d2
		and
		https://github.com/golang/go/issues/8898l

		t := time.NewTimer(c.timeout)
		defer t.Stop()

		and change the corresponding select case below:

		case <-t.C:

		Actually gaven an +1% on the local read benchmark, so not implemted yet.
	*/

	for {

		select {
		case r := <-replyChan:
			if r.err != nil {
				errCount++
				goto terminationCheck
			}
			replyValues = append(replyValues, r.reply)
			reply.NodeIDs = append(reply.NodeIDs, r.nid)
			if reply.Reply, quorum = c.qspec.ReadQF(replyValues); quorum {
				close(stopSignal)
				cancel()
				return reply, nil
			}
		case <-time.After(c.timeout):
			close(stopSignal)
			cancel()
			return reply, TimeoutRPCError{c.timeout, errCount, len(replyValues)}
		}

	terminationCheck:
		if errCount+len(replyValues) == c.n {
			close(stopSignal)
			cancel()
			return reply, IncompleteRPCError{errCount, len(replyValues)}
		}

	}
}

type writeReply struct {
	nid   uint32
	reply *WriteResponse
	err   error
}

func (m *Manager) write(c *Configuration, args *State) (*WriteReply, error) {
	var (
		replyChan   = make(chan writeReply, c.n)
		stopSignal  = make(chan struct{})
		ctx, cancel = context.WithCancel(context.Background())
	)

	for _, n := range c.nodes {
		go func(node *Node) {
			reply := new(WriteResponse)
			ce := make(chan error, 1)
			start := time.Now()
			go func() {
				select {
				case ce <- grpc.Invoke(
					ctx,
					"/dev.Register/Write",
					args,
					reply,
					node.conn,
				):
				case <-stopSignal:
					return
				}
			}()
			select {
			case err := <-ce:
				switch grpc.Code(err) { // nil -> codes.OK
				case codes.OK, codes.Canceled:
					node.setLatency(time.Since(start))
				default:
					node.setLastErr(err)
				}
				replyChan <- writeReply{node.id, reply, err}
			case <-stopSignal:
				return
			}
		}(n)
	}

	var (
		replyValues = make([]*WriteResponse, 0, c.n)
		reply       = &WriteReply{NodeIDs: make([]uint32, 0, c.n)}
		errCount    int
		quorum      bool
	)

	for {

		select {
		case r := <-replyChan:
			if r.err != nil {
				errCount++
				goto terminationCheck
			}
			replyValues = append(replyValues, r.reply)
			reply.NodeIDs = append(reply.NodeIDs, r.nid)
			if reply.Reply, quorum = c.qspec.WriteQF(replyValues); quorum {
				close(stopSignal)
				cancel()
				return reply, nil
			}
		case <-time.After(c.timeout):
			close(stopSignal)
			cancel()
			return reply, TimeoutRPCError{c.timeout, errCount, len(replyValues)}
		}

	terminationCheck:
		if errCount+len(replyValues) == c.n {
			close(stopSignal)
			cancel()
			return reply, IncompleteRPCError{errCount, len(replyValues)}
		}
	}
}

func (m *Manager) writeAsync(c *Configuration, args *State) error {
	for _, node := range c.nodes {
		go func(n *Node) {
			err := n.writeAsyncClient.Send(args)
			if err == nil {
				return
			}
			if m.logger != nil {
				m.logger.Printf("%d: writeAsync stream send error: %v", n.id, err)
			}
		}(node)
	}

	return nil
}
<commit_msg>dev: remove stop signal for rpc methods (two channels + one goroutine)<commit_after>package dev

import (
	"time"

	"golang.org/x/net/context"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
)

type readReply struct {
	nid   uint32
	reply *State
	err   error
}

func (m *Manager) read(c *Configuration, args *ReadRequest) (*ReadReply, error) {
	var (
		replyChan   = make(chan readReply, c.n)
		ctx, cancel = context.WithCancel(context.Background())
	)

	for _, n := range c.nodes {
		go func(node *Node) {
			reply := new(State)
			start := time.Now()
			err := grpc.Invoke(
				ctx,
				"/dev.Register/Read",
				args,
				reply,
				node.conn,
			)
			switch grpc.Code(err) { // nil -> codes.OK
			case codes.OK, codes.Canceled:
				node.setLatency(time.Since(start))
			default:
				node.setLastErr(err)
			}
			replyChan <- readReply{node.id, reply, err}
		}(n)
	}

	var (
		replyValues = make([]*State, 0, c.n)
		reply       = &ReadReply{NodeIDs: make([]uint32, 0, c.n)}
		errCount    int
		quorum      bool
	)

	/*
		Alternative for time.After in select below: stop rpc timeout timer explicitly.

		See
		https://github.com/kubernetes/kubernetes/pull/23210/commits/e4b369e1d74ac8f2d2a20afce92d93c804afa5d2
		and
		https://github.com/golang/go/issues/8898l

		t := time.NewTimer(c.timeout)
		defer t.Stop()

		and change the corresponding select case below:

		case <-t.C:

		Actually gaven an +1% on the local read benchmark, so not implemted yet.
	*/

	for {

		select {
		case r := <-replyChan:
			if r.err != nil {
				errCount++
				goto terminationCheck
			}
			replyValues = append(replyValues, r.reply)
			reply.NodeIDs = append(reply.NodeIDs, r.nid)
			if reply.Reply, quorum = c.qspec.ReadQF(replyValues); quorum {
				cancel()
				return reply, nil
			}
		case <-time.After(c.timeout):
			cancel()
			return reply, TimeoutRPCError{c.timeout, errCount, len(replyValues)}
		}

	terminationCheck:
		if errCount+len(replyValues) == c.n {
			cancel()
			return reply, IncompleteRPCError{errCount, len(replyValues)}
		}

	}
}

type writeReply struct {
	nid   uint32
	reply *WriteResponse
	err   error
}

func (m *Manager) write(c *Configuration, args *State) (*WriteReply, error) {
	var (
		replyChan   = make(chan writeReply, c.n)
		ctx, cancel = context.WithCancel(context.Background())
	)

	for _, n := range c.nodes {
		go func(node *Node) {
			reply := new(WriteResponse)
			start := time.Now()
			err := grpc.Invoke(
				ctx,
				"/dev.Register/Write",
				args,
				reply,
				node.conn,
			)
			switch grpc.Code(err) { // nil -> codes.OK
			case codes.OK, codes.Canceled:
				node.setLatency(time.Since(start))
			default:
				node.setLastErr(err)
			}
			replyChan <- writeReply{node.id, reply, err}
		}(n)
	}

	var (
		replyValues = make([]*WriteResponse, 0, c.n)
		reply       = &WriteReply{NodeIDs: make([]uint32, 0, c.n)}
		errCount    int
		quorum      bool
	)

	for {

		select {
		case r := <-replyChan:
			if r.err != nil {
				errCount++
				goto terminationCheck
			}
			replyValues = append(replyValues, r.reply)
			reply.NodeIDs = append(reply.NodeIDs, r.nid)
			if reply.Reply, quorum = c.qspec.WriteQF(replyValues); quorum {
				cancel()
				return reply, nil
			}
		case <-time.After(c.timeout):
			cancel()
			return reply, TimeoutRPCError{c.timeout, errCount, len(replyValues)}
		}

	terminationCheck:
		if errCount+len(replyValues) == c.n {
			cancel()
			return reply, IncompleteRPCError{errCount, len(replyValues)}
		}
	}
}

func (m *Manager) writeAsync(c *Configuration, args *State) error {
	for _, node := range c.nodes {
		go func(n *Node) {
			err := n.writeAsyncClient.Send(args)
			if err == nil {
				return
			}
			if m.logger != nil {
				m.logger.Printf("%d: writeAsync stream send error: %v", n.id, err)
			}
		}(node)
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	log "github.com/Sirupsen/logrus"
	"github.com/lavalamp/client-go-flat/kubernetes"
	"github.com/lavalamp/client-go-flat/rest"
	"github.com/lavalamp/client-go-flat/pkg/apis/extensions/v1beta1"
	"github.com/lavalamp/client-go-flat/apimachinery/pkg/apis/meta/v1"

	"errors"
	"fmt"
	"os"
	"path/filepath"
	"os/user"
	"strings"
)

const (
	hostUrl = "KUBERNETES_HOST"
	caFile = "KUBERNETES_CA_FILE"
	secretToken = "KUBERNETES_TOKEN"
)

type KubeClient struct {
	api *kubernetes.Clientset
}

func NewKubeClient() KubeClient {

	client, err := kubernetes.NewForConfig(getKubeConfig())
	if err != nil {
		log.Fatal("Failed to create k8s client.", err)
	}

	return KubeClient{api: client}
}

func (kube KubeClient) UpdateDeployment(name, namespace, image string) error {

	log.Infof("Loking for deployment: %s, namespace: %s", name, namespace)
	deployment, err := findDeployment(name, namespace)
	if err != nil {
		return err
	}

	log.Infof("Updating deployment: %s image to %s (namespace: %s)", name, image, namespace)
	if _, err := kube.api.Deployments(namespace).Update(prepareKubeDeployment(deployment, image)); err != nil {
		return errors.New(fmt.Sprintf("Failed to update deployment: %s (namespace: %s, image: %s). %v", name, namespace, image, err))
	}
	log.Infof("Deployment %s has been updated to image %s (namespace %s)", name, image, namespace)

	return nil
}

func findDeployment(name, namespace string) (*v1beta1.Deployment, error) {

	deployments, err := kube.api.Deployments(namespace).List(v1.ListOptions{})
	if err != nil {
		return nil, errors.New(fmt.Sprintf("Failed to get deployments. %v", err))
	}
	for _, currDeployment := range deployments.Items {
		if strings.EqualFold(currDeployment.Name, name) {
			return &currDeployment, nil
		}
	}

	return nil, errors.New(fmt.Sprintf("Deployment %s not found (namespace: %s).", name, namespace))
}

func prepareKubeDeployment(deployment *v1beta1.Deployment, image string) *v1beta1.Deployment {

	deployment.Spec.Template.Spec.Containers[0].Image = image
	deployment.ObjectMeta.SetUID("")
	deployment.ObjectMeta.ResourceVersion = ""

	return deployment
}

func getKubeConfig() *rest.Config {

	host := os.Getenv(hostUrl)
	if host == "" {
		host = "https://192.168.99.100:8443"
		log.Infof("%s is not defined, using %s", hostUrl, host)
	}

	token := os.Getenv(secretToken)
	if token == "" {
		log.Fatalf("Empty %s", secretToken)
	}

	return &rest.Config{
		Host:            host,
		BearerToken:     token,
		TLSClientConfig: rest.TLSClientConfig{CAFile: getCaFile()},
	}
}

func getCaFile() string {

	ret := os.Getenv(caFile)
	if ret == "" {
		log.Infof("%s is not defined, looking for ca.crt file in .minikube folder under home directory.", caFile)
		usr, err := user.Current()
		if err != nil {
			log.Fatalln("Failed to get home directory.", err)
		}
		ret = filepath.Join(usr.HomeDir, ".minikube", "ca.crt")
		if _, err := os.Stat(ret); os.IsNotExist(err) {
			log.Fatalf("File %s does not exists.", ret)
		}
	}

	return ret
}<commit_msg>fixed typo<commit_after>package main

import (
	log "github.com/Sirupsen/logrus"
	"github.com/lavalamp/client-go-flat/apimachinery/pkg/apis/meta/v1"
	"github.com/lavalamp/client-go-flat/kubernetes"
	"github.com/lavalamp/client-go-flat/pkg/apis/extensions/v1beta1"
	"github.com/lavalamp/client-go-flat/rest"

	"errors"
	"fmt"
	"os"
	"os/user"
	"path/filepath"
	"strings"
)

const (
	hostUrl     = "KUBERNETES_HOST"
	caFile      = "KUBERNETES_CA_FILE"
	secretToken = "KUBERNETES_TOKEN"
)

type KubeClient struct {
	api *kubernetes.Clientset
}

func NewKubeClient() KubeClient {

	client, err := kubernetes.NewForConfig(getKubeConfig())
	if err != nil {
		log.Fatal("Failed to create k8s client.", err)
	}

	return KubeClient{api: client}
}

func (kube KubeClient) UpdateDeployment(name, namespace, image string) error {

	log.Infof("Looking for deployment: %s, namespace: %s", name, namespace)
	deployment, err := findDeployment(name, namespace)
	if err != nil {
		return err
	}

	log.Infof("Updating deployment: %s image to %s (namespace: %s)", name, image, namespace)
	if _, err := kube.api.Deployments(namespace).Update(prepareKubeDeployment(deployment, image)); err != nil {
		return errors.New(fmt.Sprintf("Failed to update deployment: %s (namespace: %s, image: %s). %v", name, namespace, image, err))
	}
	log.Infof("Deployment %s has been updated to image %s (namespace %s)", name, image, namespace)

	return nil
}

func findDeployment(name, namespace string) (*v1beta1.Deployment, error) {

	deployments, err := kube.api.Deployments(namespace).List(v1.ListOptions{})
	if err != nil {
		return nil, errors.New(fmt.Sprintf("Failed to get deployments. %v", err))
	}
	for _, currDeployment := range deployments.Items {
		if strings.EqualFold(currDeployment.Name, name) {
			return &currDeployment, nil
		}
	}

	return nil, errors.New(fmt.Sprintf("Deployment %s not found (namespace: %s).", name, namespace))
}

func prepareKubeDeployment(deployment *v1beta1.Deployment, image string) *v1beta1.Deployment {

	deployment.Spec.Template.Spec.Containers[0].Image = image
	deployment.ObjectMeta.SetUID("")
	deployment.ObjectMeta.ResourceVersion = ""

	return deployment
}

func getKubeConfig() *rest.Config {

	host := os.Getenv(hostUrl)
	if host == "" {
		host = "https://192.168.99.100:8443"
		log.Infof("%s is not defined, using %s", hostUrl, host)
	}

	token := os.Getenv(secretToken)
	if token == "" {
		log.Fatalf("Empty %s", secretToken)
	}

	return &rest.Config{
		Host:            host,
		BearerToken:     token,
		TLSClientConfig: rest.TLSClientConfig{CAFile: getCaFile()},
	}
}

func getCaFile() string {

	ret := os.Getenv(caFile)
	if ret == "" {
		log.Infof("%s is not defined, looking for ca.crt file in .minikube folder under home directory.", caFile)
		usr, err := user.Current()
		if err != nil {
			log.Fatalln("Failed to get home directory.", err)
		}
		ret = filepath.Join(usr.HomeDir, ".minikube", "ca.crt")
		if _, err := os.Stat(ret); os.IsNotExist(err) {
			log.Fatalf("File %s does not exists.", ret)
		}
	}

	return ret
}
<|endoftext|>


<commit_before>package libkbfs

import (
	"errors"
	"fmt"
	"runtime"
	"sync"
	"time"

	"github.com/keybase/client/go/auth"
	keybase1 "github.com/keybase/client/go/protocol"
	"golang.org/x/net/context"
)

// AuthTokenMinRefreshSeconds is the minimum number of seconds between refreshes.
const AuthTokenMinRefreshSeconds = 60

// AuthToken encapsulates a timed authentication token.
type AuthToken struct {
	config         Config
	tokenType      string
	expireIn       int
	clientName     string
	clientVersion  string
	refreshHandler AuthTokenRefreshHandler
	tickerCancel   context.CancelFunc
	tickerMu       sync.Mutex // protects the ticker cancel function
}

// NewAuthToken creates a new authentication token.
func NewAuthToken(config Config, tokenType string, expireIn int,
	submoduleName string, rh AuthTokenRefreshHandler) *AuthToken {
	clientName := fmt.Sprintf("go %s %s %s", submoduleName, runtime.GOOS, runtime.GOARCH)
	authToken := &AuthToken{
		config:         config,
		tokenType:      tokenType,
		expireIn:       expireIn,
		clientName:     clientName,
		clientVersion:  VersionString(),
		refreshHandler: rh,
	}
	return authToken
}

// Sign is called to create a new signed authentication token.
func (a *AuthToken) Sign(ctx context.Context, challengeInfo keybase1.ChallengeInfo) (string, error) {
	// make sure we're being asked to sign a legit challenge
	if !auth.IsValidChallenge(challengeInfo.Challenge) {
		return "", errors.New("Invalid challenge")
	}

	// get UID, deviceKID and normalized username
	username, uid, err := a.config.KBPKI().GetCurrentUserInfo(ctx)
	if err != nil {
		return "", err
	}
	key, err := a.config.KBPKI().GetCurrentVerifyingKey(ctx)
	if err != nil {
		return "", err
	}

	// create the token
	token := auth.NewToken(uid, username, key.kid, a.tokenType,
		challengeInfo.Challenge, challengeInfo.Now, a.expireIn,
		a.clientName, a.clientVersion)

	// sign the token
	signature, err := a.config.Crypto().SignToString(ctx, token.Bytes())
	if err != nil {
		return "", err
	}

	// reset the ticker
	refreshSeconds := a.expireIn / 2
	if refreshSeconds < AuthTokenMinRefreshSeconds {
		refreshSeconds = AuthTokenMinRefreshSeconds
	}
	a.startTicker(refreshSeconds)

	return signature, nil
}

// Shutdown is called to stop the refresh ticker.
func (a *AuthToken) Shutdown() {
	a.stopTicker()
}

// Helper to start the ticker (if not started.)
func (a *AuthToken) startTicker(intervalSeconds int) {
	a.tickerMu.Lock()
	defer a.tickerMu.Unlock()

	if a.tickerCancel != nil {
		return
	}

	var ctx context.Context
	ctx, a.tickerCancel = context.WithCancel(context.Background())
	go func() {
		ticker := time.NewTicker(time.Duration(intervalSeconds) * time.Second)
		for {
			select {
			case <-ticker.C:
				a.refreshHandler.RefreshAuthToken(ctx)
			case <-ctx.Done():
				ticker.Stop()
				return
			}
		}
	}()
}

// Helper to stop the refresh ticker.
func (a *AuthToken) stopTicker() {
	a.tickerMu.Lock()
	defer a.tickerMu.Unlock()

	if a.tickerCancel != nil {
		a.tickerCancel()
		a.tickerCancel = nil
	}
}
<commit_msg>auth_token: allow userless signed token<commit_after>package libkbfs

import (
	"errors"
	"fmt"
	"runtime"
	"sync"
	"time"

	"github.com/keybase/client/go/auth"
	"github.com/keybase/client/go/libkb"
	keybase1 "github.com/keybase/client/go/protocol"
	"golang.org/x/net/context"
)

// AuthTokenMinRefreshSeconds is the minimum number of seconds between refreshes.
const AuthTokenMinRefreshSeconds = 60

// AuthToken encapsulates a timed authentication token.
type AuthToken struct {
	config         Config
	tokenType      string
	expireIn       int
	clientName     string
	clientVersion  string
	refreshHandler AuthTokenRefreshHandler
	tickerCancel   context.CancelFunc
	tickerMu       sync.Mutex // protects the ticker cancel function
}

// NewAuthToken creates a new authentication token.
func NewAuthToken(config Config, tokenType string, expireIn int,
	submoduleName string, rh AuthTokenRefreshHandler) *AuthToken {
	clientName := fmt.Sprintf("go %s %s %s", submoduleName, runtime.GOOS, runtime.GOARCH)
	authToken := &AuthToken{
		config:         config,
		tokenType:      tokenType,
		expireIn:       expireIn,
		clientName:     clientName,
		clientVersion:  VersionString(),
		refreshHandler: rh,
	}
	return authToken
}

// Sign is called to create a new signed authentication token.
func (a *AuthToken) signWithUserAndKeyInfo(ctx context.Context,
	challengeInfo keybase1.ChallengeInfo, uid keybase1.UID,
	username libkb.NormalizedUsername, key VerifyingKey) (string, error) {
	// create the token
	token := auth.NewToken(uid, username, key.kid, a.tokenType,
		challengeInfo.Challenge, challengeInfo.Now, a.expireIn,
		a.clientName, a.clientVersion)

	// sign the token
	signature, err := a.config.Crypto().SignToString(ctx, token.Bytes())
	if err != nil {
		return "", err
	}

	// reset the ticker
	refreshSeconds := a.expireIn / 2
	if refreshSeconds < AuthTokenMinRefreshSeconds {
		refreshSeconds = AuthTokenMinRefreshSeconds
	}
	a.startTicker(refreshSeconds)

	return signature, nil
}

// Sign is called to create a new signed authentication token,
// including a challenge and username/uid/kid identifiers.
func (a *AuthToken) Sign(ctx context.Context, challengeInfo keybase1.ChallengeInfo) (string, error) {
	// make sure we're being asked to sign a legit challenge
	if !auth.IsValidChallenge(challengeInfo.Challenge) {
		return "", errors.New("Invalid challenge")
	}

	// get UID, deviceKID and normalized username
	username, uid, err := a.config.KBPKI().GetCurrentUserInfo(ctx)
	if err != nil {
		return "", err
	}
	key, err := a.config.KBPKI().GetCurrentVerifyingKey(ctx)
	if err != nil {
		return "", err
	}

	return a.signWithUserAndKeyInfo(ctx, challengeInfo, uid, username, key)
}

// SignUserless signs the token without a username, UID, or challenge.
// This is useful for server-to-server communication where identity is
// established using only the KID.
func (a *AuthToken) SignUserless(ctx context.Context, key VerifyingKey) (
	string, error) {
	// Pass in a reserved, meaningless UID.
	return a.signWithUserAndKeyInfo(ctx, keybase1.ChallengeInfo{},
		keybase1.PublicUID, "", key)
}

// Shutdown is called to stop the refresh ticker.
func (a *AuthToken) Shutdown() {
	a.stopTicker()
}

// Helper to start the ticker (if not started.)
func (a *AuthToken) startTicker(intervalSeconds int) {
	a.tickerMu.Lock()
	defer a.tickerMu.Unlock()

	if a.tickerCancel != nil {
		return
	}

	var ctx context.Context
	ctx, a.tickerCancel = context.WithCancel(context.Background())
	go func() {
		ticker := time.NewTicker(time.Duration(intervalSeconds) * time.Second)
		for {
			select {
			case <-ticker.C:
				a.refreshHandler.RefreshAuthToken(ctx)
			case <-ctx.Done():
				ticker.Stop()
				return
			}
		}
	}()
}

// Helper to stop the refresh ticker.
func (a *AuthToken) stopTicker() {
	a.tickerMu.Lock()
	defer a.tickerMu.Unlock()

	if a.tickerCancel != nil {
		a.tickerCancel()
		a.tickerCancel = nil
	}
}
<|endoftext|>


<commit_before>package downloader

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"

	"github.com/kkdai/youtube/v2"
	"github.com/vbauerster/mpb/v5"
	"github.com/vbauerster/mpb/v5/decor"
)

// Downloader offers high level functions to download videos into files
type Downloader struct {
	youtube.Client
	OutputDir string // optional directory to store the files
}

func (dl *Downloader) getOutputFile(v *youtube.Video, format *youtube.Format, outputFile string) (string, error) {
	if outputFile == "" {
		outputFile = SanitizeFilename(v.Title)
		outputFile += pickIdealFileExtension(format.MimeType)
	}

	if dl.OutputDir != "" {
		if err := os.MkdirAll(dl.OutputDir, 0o755); err != nil {
			return "", err
		}
		outputFile = filepath.Join(dl.OutputDir, outputFile)
	}

	return outputFile, nil
}

// Download : Starting download video by arguments.
func (dl *Downloader) Download(ctx context.Context, v *youtube.Video, format *youtube.Format, outputFile string) error {
	destFile, err := dl.getOutputFile(v, format, outputFile)
	if err != nil {
		return err
	}

	// Create output file
	out, err := os.Create(destFile)
	if err != nil {
		return err
	}
	defer out.Close()

	dl.logf("Download to file=%s", destFile)
	return dl.videoDLWorker(ctx, out, v, format)
}

// DownloadWithHighQuality : Starting downloading video with high quality (>720p).
func (dl *Downloader) DownloadWithHighQuality(ctx context.Context, outputFile string, v *youtube.Video, quality string) error {
	var videoFormat, audioFormat *youtube.Format

	switch quality {
	case "hd1080":
		videoFormat = v.Formats.FindByItag(137)
		audioFormat = v.Formats.FindByItag(140)
	default:
		return fmt.Errorf("unknown quality: %s", quality)
	}

	if videoFormat == nil {
		return fmt.Errorf("no format video/mp4 for %s found", quality)
	}
	if audioFormat == nil {
		return fmt.Errorf("no format audio/mp4 for %s found", quality)
	}

	destFile, err := dl.getOutputFile(v, videoFormat, outputFile)
	if err != nil {
		return err
	}
	outputDir := filepath.Dir(destFile)

	// Create temporary video file
	videoFile, err := ioutil.TempFile(outputDir, "youtube_*.m4v")
	if err != nil {
		return err
	}
	defer os.Remove(videoFile.Name())

	// Create temporary audio file
	audioFile, err := ioutil.TempFile(outputDir, "youtube_*.m4a")
	if err != nil {
		return err
	}
	defer os.Remove(audioFile.Name())

	dl.logf("Downloading video file...")
	err = dl.videoDLWorker(ctx, videoFile, v, videoFormat)
	if err != nil {
		return err
	}

	dl.logf("Downloading audio file...")
	err = dl.videoDLWorker(ctx, audioFile, v, audioFormat)
	if err != nil {
		return err
	}

	ffmpegVersionCmd := exec.Command("ffmpeg", "-y",
		"-i", videoFile.Name(),
		"-i", audioFile.Name(),
		"-strict",
		"-2",
		"-shortest",
		destFile,
		"-loglevel", "warning",
	)
	ffmpegVersionCmd.Stderr = os.Stderr
	ffmpegVersionCmd.Stdout = os.Stdout
	dl.logf("merging video and audio to %s", destFile)

	return ffmpegVersionCmd.Run()
}

func (dl *Downloader) videoDLWorker(ctx context.Context, out *os.File, video *youtube.Video, format *youtube.Format) error {
	resp, err := dl.GetStreamContext(ctx, video, format)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	prog := &progress{
		contentLength: float64(resp.ContentLength),
	}

	// create progress bar
	progress := mpb.New(mpb.WithWidth(64))
	bar := progress.AddBar(
		int64(prog.contentLength),

		mpb.PrependDecorators(
			decor.CountersKibiByte("% .2f / % .2f"),
			decor.Percentage(decor.WCSyncSpace),
		),
		mpb.AppendDecorators(
			decor.EwmaETA(decor.ET_STYLE_GO, 90),
			decor.Name(" ] "),
			decor.EwmaSpeed(decor.UnitKiB, "% .2f", 60),
		),
	)

	reader := bar.ProxyReader(resp.Body)
	mw := io.MultiWriter(out, prog)
	_, err = io.Copy(mw, reader)
	if err != nil {
		return err
	}

	progress.Wait()
	return nil
}

func (dl *Downloader) logf(format string, v ...interface{}) {
	if dl.Debug {
		log.Printf(format, v...)
	}
}
<commit_msg>Optimize ffmpeg flags<commit_after>package downloader

import (
	"context"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"

	"github.com/kkdai/youtube/v2"
	"github.com/vbauerster/mpb/v5"
	"github.com/vbauerster/mpb/v5/decor"
)

// Downloader offers high level functions to download videos into files
type Downloader struct {
	youtube.Client
	OutputDir string // optional directory to store the files
}

func (dl *Downloader) getOutputFile(v *youtube.Video, format *youtube.Format, outputFile string) (string, error) {
	if outputFile == "" {
		outputFile = SanitizeFilename(v.Title)
		outputFile += pickIdealFileExtension(format.MimeType)
	}

	if dl.OutputDir != "" {
		if err := os.MkdirAll(dl.OutputDir, 0o755); err != nil {
			return "", err
		}
		outputFile = filepath.Join(dl.OutputDir, outputFile)
	}

	return outputFile, nil
}

// Download : Starting download video by arguments.
func (dl *Downloader) Download(ctx context.Context, v *youtube.Video, format *youtube.Format, outputFile string) error {
	destFile, err := dl.getOutputFile(v, format, outputFile)
	if err != nil {
		return err
	}

	// Create output file
	out, err := os.Create(destFile)
	if err != nil {
		return err
	}
	defer out.Close()

	dl.logf("Download to file=%s", destFile)
	return dl.videoDLWorker(ctx, out, v, format)
}

// DownloadWithHighQuality : Starting downloading video with high quality (>720p).
func (dl *Downloader) DownloadWithHighQuality(ctx context.Context, outputFile string, v *youtube.Video, quality string) error {
	var videoFormat, audioFormat *youtube.Format

	switch quality {
	case "hd1080":
		videoFormat = v.Formats.FindByItag(137)
		audioFormat = v.Formats.FindByItag(140)
	default:
		return fmt.Errorf("unknown quality: %s", quality)
	}

	if videoFormat == nil {
		return fmt.Errorf("no format video/mp4 for %s found", quality)
	}
	if audioFormat == nil {
		return fmt.Errorf("no format audio/mp4 for %s found", quality)
	}

	destFile, err := dl.getOutputFile(v, videoFormat, outputFile)
	if err != nil {
		return err
	}
	outputDir := filepath.Dir(destFile)

	// Create temporary video file
	videoFile, err := ioutil.TempFile(outputDir, "youtube_*.m4v")
	if err != nil {
		return err
	}
	defer os.Remove(videoFile.Name())

	// Create temporary audio file
	audioFile, err := ioutil.TempFile(outputDir, "youtube_*.m4a")
	if err != nil {
		return err
	}
	defer os.Remove(audioFile.Name())

	dl.logf("Downloading video file...")
	err = dl.videoDLWorker(ctx, videoFile, v, videoFormat)
	if err != nil {
		return err
	}

	dl.logf("Downloading audio file...")
	err = dl.videoDLWorker(ctx, audioFile, v, audioFormat)
	if err != nil {
		return err
	}

	ffmpegVersionCmd := exec.Command("ffmpeg", "-y",
		"-i", videoFile.Name(),
		"-i", audioFile.Name(),
		"-c", "copy", // Just copy without re-encoding
		"-shortest", // Finish encoding when the shortest input stream ends
		destFile,
		"-loglevel", "warning",
	)
	ffmpegVersionCmd.Stderr = os.Stderr
	ffmpegVersionCmd.Stdout = os.Stdout
	dl.logf("merging video and audio to %s", destFile)

	return ffmpegVersionCmd.Run()
}

func (dl *Downloader) videoDLWorker(ctx context.Context, out *os.File, video *youtube.Video, format *youtube.Format) error {
	resp, err := dl.GetStreamContext(ctx, video, format)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	prog := &progress{
		contentLength: float64(resp.ContentLength),
	}

	// create progress bar
	progress := mpb.New(mpb.WithWidth(64))
	bar := progress.AddBar(
		int64(prog.contentLength),

		mpb.PrependDecorators(
			decor.CountersKibiByte("% .2f / % .2f"),
			decor.Percentage(decor.WCSyncSpace),
		),
		mpb.AppendDecorators(
			decor.EwmaETA(decor.ET_STYLE_GO, 90),
			decor.Name(" ] "),
			decor.EwmaSpeed(decor.UnitKiB, "% .2f", 60),
		),
	)

	reader := bar.ProxyReader(resp.Body)
	mw := io.MultiWriter(out, prog)
	_, err = io.Copy(mw, reader)
	if err != nil {
		return err
	}

	progress.Wait()
	return nil
}

func (dl *Downloader) logf(format string, v ...interface{}) {
	if dl.Debug {
		log.Printf(format, v...)
	}
}
<|endoftext|>


<commit_before>package downloader

import (
	"bufio"
	"fmt"
	"io"
	"net/http"
	"os"
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"
)

// Number of simultaneous file fetches that we should perform.
const numWorkers = 5

// File represents a site file that should be fetch from a local URL and
// stored as a local file.
type File struct {
	URL    string
	Target string

	Err error
}

// Fetch performs an HTTP fetch for each given file and stores them to their
// corresponding local targets.
func Fetch(files []*File) error {
	var wg sync.WaitGroup
	wg.Add(len(files))

	filesChan := make(chan *File, len(files))

	// Signal workers to stop looping and shut down.
	defer close(filesChan)

	for i := 0; i < numWorkers; i++ {
		go workFiles(filesChan, &wg)
	}

	for _, file := range files {
		filesChan <- file
	}

	wg.Wait()

	// This is not the greatest possible approach because we have to wait for
	// all files to be processed, but practically problems should be
	// relatively rare. Implement fast timeouts so we can recover in
	// degenerate cases.
	for _, file := range files {
		if file.Err != nil {
			return file.Err
		}
	}

	return nil
}

func fetchFile(client *http.Client, file *File) error {
	log.Debugf("Fetching file: %v", file.URL)

	resp, err := client.Get(file.URL)
	if err != nil {
		return fmt.Errorf("Error fetching %v: %v", file.URL, err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return fmt.Errorf("Unexpected status code fetching %v: %d",
			file.URL, resp.StatusCode)
	}

	f, err := os.Create(file.Target)
	if err != nil {
		return fmt.Errorf("Error creating %v: %v", file.Target, err)
	}
	defer f.Close()

	w := bufio.NewWriter(f)

	// probably not needed
	defer w.Flush()

	_, err = io.Copy(w, resp.Body)
	if err != nil {
		return fmt.Errorf("Error copying to %v from HTTP response: %v", file.Target, err)
	}

	return nil
}

func workFiles(filesChan chan *File, wg *sync.WaitGroup) {
	client := &http.Client{
		Timeout: 5 * time.Second,
	}

	// Note that this loop falls through when the channel is closed.
	for file := range filesChan {
		err := fetchFile(client, file)
		if err != nil {
			file.Err = err
		}
		wg.Done()
	}
}
<commit_msg>Use a less aggressive timeout (these are big images)<commit_after>package downloader

import (
	"bufio"
	"fmt"
	"io"
	"net/http"
	"os"
	"sync"
	"time"

	log "github.com/Sirupsen/logrus"
)

// Number of simultaneous file fetches that we should perform.
const numWorkers = 5

// File represents a site file that should be fetch from a local URL and
// stored as a local file.
type File struct {
	URL    string
	Target string

	Err error
}

// Fetch performs an HTTP fetch for each given file and stores them to their
// corresponding local targets.
func Fetch(files []*File) error {
	var wg sync.WaitGroup
	wg.Add(len(files))

	filesChan := make(chan *File, len(files))

	// Signal workers to stop looping and shut down.
	defer close(filesChan)

	for i := 0; i < numWorkers; i++ {
		go workFiles(filesChan, &wg)
	}

	for _, file := range files {
		filesChan <- file
	}

	wg.Wait()

	// This is not the greatest possible approach because we have to wait for
	// all files to be processed, but practically problems should be
	// relatively rare. Implement fast timeouts so we can recover in
	// degenerate cases.
	for _, file := range files {
		if file.Err != nil {
			return file.Err
		}
	}

	return nil
}

func fetchFile(client *http.Client, file *File) error {
	log.Debugf("Fetching file: %v", file.URL)

	resp, err := client.Get(file.URL)
	if err != nil {
		return fmt.Errorf("Error fetching %v: %v", file.URL, err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != 200 {
		return fmt.Errorf("Unexpected status code fetching %v: %d",
			file.URL, resp.StatusCode)
	}

	f, err := os.Create(file.Target)
	if err != nil {
		return fmt.Errorf("Error creating %v: %v", file.Target, err)
	}
	defer f.Close()

	w := bufio.NewWriter(f)

	// probably not needed
	defer w.Flush()

	_, err = io.Copy(w, resp.Body)
	if err != nil {
		return fmt.Errorf("Error copying to %v from HTTP response: %v", file.Target, err)
	}

	return nil
}

func workFiles(filesChan chan *File, wg *sync.WaitGroup) {
	client := &http.Client{
		Timeout: 30 * time.Second,
	}

	// Note that this loop falls through when the channel is closed.
	for file := range filesChan {
		err := fetchFile(client, file)
		if err != nil {
			file.Err = err
		}
		wg.Done()
	}
}
<|endoftext|>


<commit_before>package downloader

import (
	"fmt"
	"io"
	"net"
	"os"
	"path/filepath"
	"strings"

	pivnet "github.com/pivotal-cf/go-pivnet"
	"github.com/pivotal-cf/go-pivnet/logger"
)

//go:generate counterfeiter --fake-name FakeClient . client
type client interface {
	DownloadProductFile(writer io.Writer, productSlug string, releaseID int, productFileID int) error
}

type Downloader struct {
	client      client
	downloadDir string
	logger      logger.Logger
}

func NewDownloader(
	client client,
	downloadDir string,
	logger logger.Logger,
) *Downloader {
	return &Downloader{
		client:      client,
		downloadDir: downloadDir,
		logger:      logger,
	}
}

func (d Downloader) Download(
	pfs []pivnet.ProductFile,
	productSlug string,
	releaseID int,
) ([]string, error) {
	d.logger.Debug("Ensuring download directory exists")

	err := os.MkdirAll(d.downloadDir, os.ModePerm)
	if err != nil {
		return nil, err
	}

	var fileNames []string
	for _, pf := range pfs {
		parts := strings.Split(pf.AWSObjectKey, "/")
		fileName := parts[len(parts)-1]

		downloadPath := filepath.Join(d.downloadDir, fileName)

		d.logger.Debug(fmt.Sprintf("Creating file: '%s'", downloadPath))
		file, err := os.Create(downloadPath)
		if err != nil {
			return nil, err
		}

		d.logger.Info(fmt.Sprintf(
			"Downloading: '%s' to file: '%s'",
			pf.Name,
			downloadPath,
		))

		err = d.downloadProductFileWithRetries(file, productSlug, releaseID, pf.ID)
		if err != nil {
			return nil, err
		}
		fileNames = append(fileNames, downloadPath)
	}

	return fileNames, nil
}

var maxDownloadAttempts int = 3

func (d Downloader) downloadProductFileWithRetries(
	file io.Writer,
	productSlug string,
	releaseID int,
	productFileID int,
) error {
	var err error

	for i := maxDownloadAttempts; i > 0; i-- {
		err = d.client.DownloadProductFile(file, productSlug, releaseID, productFileID)

		if err != nil {
			if err == io.ErrUnexpectedEOF {
				d.logger.Info(fmt.Sprintf("Unexpected EOF error (%s); retrying download", err.Error()))
				continue
			}

			if netErr, ok := err.(net.Error); ok {
				if netErr.Temporary() {
					d.logger.Info(fmt.Sprintf("Temporary network error (%s); retrying download", err.Error()))
					continue
				}
			}

			d.logger.Debug(fmt.Sprintf("Download failed: %s", err.Error()))
			return err
		}

		return nil
	}

	d.logger.Debug(fmt.Sprintf("Download failed after %d attempts: %s",
		maxDownloadAttempts,
		err.Error(),
	))
	return err
}
<commit_msg>Change download failed message log level from debug to info. [#133499883]<commit_after>package downloader

import (
	"fmt"
	"io"
	"net"
	"os"
	"path/filepath"
	"strings"

	pivnet "github.com/pivotal-cf/go-pivnet"
	"github.com/pivotal-cf/go-pivnet/logger"
)

//go:generate counterfeiter --fake-name FakeClient . client
type client interface {
	DownloadProductFile(writer io.Writer, productSlug string, releaseID int, productFileID int) error
}

type Downloader struct {
	client      client
	downloadDir string
	logger      logger.Logger
}

func NewDownloader(
	client client,
	downloadDir string,
	logger logger.Logger,
) *Downloader {
	return &Downloader{
		client:      client,
		downloadDir: downloadDir,
		logger:      logger,
	}
}

func (d Downloader) Download(
	pfs []pivnet.ProductFile,
	productSlug string,
	releaseID int,
) ([]string, error) {
	d.logger.Debug("Ensuring download directory exists")

	err := os.MkdirAll(d.downloadDir, os.ModePerm)
	if err != nil {
		return nil, err
	}

	var fileNames []string
	for _, pf := range pfs {
		parts := strings.Split(pf.AWSObjectKey, "/")
		fileName := parts[len(parts)-1]

		downloadPath := filepath.Join(d.downloadDir, fileName)

		d.logger.Debug(fmt.Sprintf("Creating file: '%s'", downloadPath))
		file, err := os.Create(downloadPath)
		if err != nil {
			return nil, err
		}

		d.logger.Info(fmt.Sprintf(
			"Downloading: '%s' to file: '%s'",
			pf.Name,
			downloadPath,
		))

		err = d.downloadProductFileWithRetries(file, productSlug, releaseID, pf.ID)
		if err != nil {
			return nil, err
		}
		fileNames = append(fileNames, downloadPath)
	}

	return fileNames, nil
}

var maxDownloadAttempts int = 3

func (d Downloader) downloadProductFileWithRetries(
	file io.Writer,
	productSlug string,
	releaseID int,
	productFileID int,
) error {
	var err error

	for i := maxDownloadAttempts; i > 0; i-- {
		err = d.client.DownloadProductFile(file, productSlug, releaseID, productFileID)

		if err != nil {
			if err == io.ErrUnexpectedEOF {
				d.logger.Info(fmt.Sprintf("Unexpected EOF error (%s); retrying download", err.Error()))
				continue
			}

			if netErr, ok := err.(net.Error); ok {
				if netErr.Temporary() {
					d.logger.Info(fmt.Sprintf("Temporary network error (%s); retrying download", err.Error()))
					continue
				}
			}

			d.logger.Debug(fmt.Sprintf("Download failed: %s", err.Error()))
			return err
		}

		return nil
	}

	d.logger.Info(fmt.Sprintf("Download failed after %d attempts: %s",
		maxDownloadAttempts,
		err.Error(),
	))
	return err
}
<|endoftext|>


<commit_before>// Copyright 2015 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.

package gl

import (
	"image"
	"image/draw"
	"image/png"
	"os"

	"github.com/google/gxui/math"
	fnt "golang.org/x/exp/shiny/font"
	"golang.org/x/image/math/fixed"
)

const (
	dumpGlyphPages     = false
	glyphPageWidth     = 512
	glyphPageHeight    = 512
	glyphSizeAlignment = 8
	glyphPadding       = 1
)

type glyphPage struct {
	image     *image.Alpha
	size      math.Size // in pixels
	offsets   map[rune]math.Point
	rowHeight int
	tex       *texture
	nextPoint math.Point
}

func point26_6toPoint(p fixed.Point26_6) math.Point {
	return math.Point{X: int(p.X) >> 6, Y: int(p.Y) >> 6}
}

func rectangle26_6toRect(p fixed.Rectangle26_6) math.Rect {
	return math.Rect{Min: point26_6toPoint(p.Min), Max: point26_6toPoint(p.Max)}
}

func align(v, pot int) int {
	return (v + pot - 1) & ^(pot - 1)
}

func newGlyphPage(face fnt.Face, r rune) *glyphPage {
	// Start the page big enough to hold the initial rune.
	b, _, _ := face.GlyphBounds(r)
	bounds := rectangle26_6toRect(b)
	size := math.Size{W: glyphPageWidth, H: glyphPageHeight}.Max(bounds.Size())
	size.W = align(size.W, glyphSizeAlignment)
	size.H = align(size.H, glyphSizeAlignment)

	page := &glyphPage{
		image:     image.NewAlpha(image.Rect(0, 0, size.W, size.H)),
		size:      size,
		offsets:   make(map[rune]math.Point),
		rowHeight: 0,
	}
	page.add(face, r)
	return page
}

func (p *glyphPage) commit() {
	if p.tex != nil {
		return
	}
	p.tex = newTexture(p.image, 1.0)
	if dumpGlyphPages {
		f, _ := os.Create("glyph-page.png")
		defer f.Close()
		png.Encode(f, p.image)
	}
}

func (p *glyphPage) add(face fnt.Face, r rune) bool {
	if _, found := p.offsets[r]; found {
		panic("Glyph already added to glyph page")
	}

	b, _, _ := face.GlyphBounds(r)
	bounds := rectangle26_6toRect(b)

	w, h := bounds.Size().WH()
	x, y := p.nextPoint.X, p.nextPoint.Y

	if x+w > p.size.W {
		// Row full, start new line
		x = 0
		y += p.rowHeight + glyphPadding
		p.rowHeight = 0
	}

	if y+h > p.size.H {
		return false // Page full
	}

	_, _, mask, maskp, _ := face.Glyph(fixed.Point26_6{}, r)
	draw.Draw(p.image, image.Rect(x, y, x+w, y+h), mask, maskp, draw.Src)

	p.offsets[r] = math.Point{X: x, Y: y}
	p.nextPoint = math.Point{X: x + w + glyphPadding, Y: y}
	if h > p.rowHeight {
		p.rowHeight = h
	}
	p.tex = nil

	return true
}

func (p *glyphPage) texture() *texture {
	if p.tex == nil {
		p.commit()
	}
	return p.tex
}

func (p *glyphPage) offset(rune rune) math.Point {
	return p.offsets[rune]
}
<commit_msg>Fix for breaking changes in freetype<commit_after>// Copyright 2015 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.

package gl

import (
	"image"
	"image/draw"
	"image/png"
	"os"

	"github.com/google/gxui/math"
	fnt "golang.org/x/exp/shiny/font"
	"golang.org/x/image/math/fixed"
)

const (
	dumpGlyphPages     = false
	glyphPageWidth     = 512
	glyphPageHeight    = 512
	glyphSizeAlignment = 8
	glyphPadding       = 1
)

type glyphPage struct {
	image     *image.Alpha
	size      math.Size // in pixels
	offsets   map[rune]math.Point
	rowHeight int
	tex       *texture
	nextPoint math.Point
}

func point26_6toPoint(p fixed.Point26_6) math.Point {
	return math.Point{X: int(p.X) >> 6, Y: int(p.Y) >> 6}
}

func rectangle26_6toRect(p fixed.Rectangle26_6) math.Rect {
	return math.Rect{Min: point26_6toPoint(p.Min), Max: point26_6toPoint(p.Max)}
}

func align(v, pot int) int {
	return (v + pot - 1) & ^(pot - 1)
}

func newGlyphPage(face fnt.Face, r rune) *glyphPage {
	// Start the page big enough to hold the initial rune.
	b, _, _ := face.GlyphBounds(r)
	bounds := rectangle26_6toRect(b)
	size := math.Size{W: glyphPageWidth, H: glyphPageHeight}.Max(bounds.Size())
	size.W = align(size.W, glyphSizeAlignment)
	size.H = align(size.H, glyphSizeAlignment)

	page := &glyphPage{
		image:     image.NewAlpha(image.Rect(0, 0, size.W, size.H)),
		size:      size,
		offsets:   make(map[rune]math.Point),
		rowHeight: 0,
	}
	page.add(face, r)
	return page
}

func (p *glyphPage) commit() {
	if p.tex != nil {
		return
	}
	p.tex = newTexture(p.image, 1.0)
	if dumpGlyphPages {
		f, _ := os.Create("glyph-page.png")
		defer f.Close()
		png.Encode(f, p.image)
	}
}

func (p *glyphPage) add(face fnt.Face, r rune) bool {
	if _, found := p.offsets[r]; found {
		panic("Glyph already added to glyph page")
	}

	b, _, _ := face.GlyphBounds(r)
	bounds := rectangle26_6toRect(b)

	w, h := bounds.Size().WH()
	x, y := p.nextPoint.X, p.nextPoint.Y

	if x+w > p.size.W {
		// Row full, start new line
		x = 0
		y += p.rowHeight + glyphPadding
		p.rowHeight = 0
	}

	if y+h > p.size.H {
		return false // Page full
	}

	_, mask, maskp, _, _ := face.Glyph(fixed.Point26_6{}, r)
	draw.Draw(p.image, image.Rect(x, y, x+w, y+h), mask, maskp, draw.Src)

	p.offsets[r] = math.Point{X: x, Y: y}
	p.nextPoint = math.Point{X: x + w + glyphPadding, Y: y}
	if h > p.rowHeight {
		p.rowHeight = h
	}
	p.tex = nil

	return true
}

func (p *glyphPage) texture() *texture {
	if p.tex == nil {
		p.commit()
	}
	return p.tex
}

func (p *glyphPage) offset(rune rune) math.Point {
	return p.offsets[rune]
}
<|endoftext|>


<commit_before>package dictionary_test

import (
	"fmt"
	"math"
	"math/rand"
	"testing"

	"github.com/bakins/dictionary"
	"github.com/stretchr/testify/require"
)

func TestSimpleSet(t *testing.T) {
	d := dictionary.New()
	k := dictionary.StringKey("foo")

	d.Set(k, "bar")
	v, ok := d.Get(k)
	require.NotNil(t, v)
	require.Equal(t, true, ok, "should have found key")
	require.Equal(t, "bar", v.(string), "unexpected value")

	v, ok = d.Get(dictionary.StringKey("bar"))
	require.Nil(t, v)
	require.Equal(t, false, ok, "should not have found key")
}

type entry struct {
	key dictionary.StringKey
	val int
}

func TestSet(t *testing.T) {
	d := dictionary.New()

	entries := make([]entry, 0)
	for i, c := range "abcdefghijklmnopqrstuvwxyz" {
		e := entry{
			key: dictionary.StringKey(c),
			val: i,
		}

		entries = append(entries, e)
		d.Set(e.key, &e)
	}

	for i := range entries {
		j := rand.Intn(i + 1)
		entries[i], entries[j] = entries[j], entries[i]
	}

	for _, e := range entries {
		v, ok := d.Get(e.key)
		require.Equal(t, true, ok, "should have found key")
		require.Equal(t, e.val, v.(*entry).val, "unexpected value")
	}
}

func TestDelete(t *testing.T) {
	d := dictionary.New()
	k := dictionary.StringKey("foo")

	d.Set(k, "bar")
	v, ok := d.Get(k)
	require.NotNil(t, v)
	require.Equal(t, true, ok, "should have found key")
	require.Equal(t, "bar", v.(string), "unexpected value")

	v, ok = d.Delete(dictionary.StringKey("foo"))
	require.NotNil(t, v)
	require.Equal(t, true, ok, "should have found key")
	require.Equal(t, "bar", v.(string), "unexpected value")

	v, ok = d.Delete(dictionary.StringKey("bar"))
	require.Nil(t, v)
	require.Equal(t, false, ok, "should not have found key")
}

type intKey int

func (i intKey) Hash() uint32 {
	if i < 0 {
		i = -i
	}
	if i < math.MaxUint32 {
		return uint32(i)
	}

	// hacky but good enough for a test
	return uint32(i - math.MaxUint32)
}

func (i intKey) Equal(v interface{}) bool {
	return int(i) == int(v.(intKey))
}

func TestSimpleIntSet(t *testing.T) {
	d := dictionary.New()
	k := intKey(99)

	d.Set(k, "bar")
	v, ok := d.Get(k)
	require.NotNil(t, v)
	require.Equal(t, true, ok, "should have found key")
	require.Equal(t, "bar", v.(string), "unexpected value")

	v, ok = d.Get(intKey(1))
	require.Nil(t, v)
	require.Equal(t, false, ok, "should not have found key")
}

type intEntry struct {
	key intKey
	val int
}

func TestIntSet(t *testing.T) {
	d := dictionary.New()

	entries := make([]intEntry, 0)
	for i := 0; i < 8192; i++ {
		e := intEntry{
			key: intKey(i),
			val: i,
		}

		entries = append(entries, e)
		d.Set(e.key, &e)
	}

	for i := range entries {
		j := rand.Intn(i + 1)
		entries[i], entries[j] = entries[j], entries[i]
	}

	for _, e := range entries {
		v, ok := d.Get(e.key)
		require.Equal(t, true, ok, "should have found key")
		require.Equal(t, e.val, v.(*intEntry).val, "unexpected value")
	}
}

func TestEach(t *testing.T) {
	d := dictionary.New()

	keys := []string{"a", "b", "c", "d"}
	entries := make(map[string]string, len(keys))
	for _, k := range keys {
		entries[k] = k
		d.Set(dictionary.StringKey(k), k)
	}

	f := func(h dictionary.Hasher, v interface{}) error {
		k := string(h.(dictionary.StringKey))
		val := v.(string)
		e, ok := entries[k]
		if !ok {
			return fmt.Errorf("did not find %s", k)
		}
		if e != val {
			return fmt.Errorf("bad value - %s - for %s", e, val)
		}
		return nil
	}

	err := d.Each(f)
	require.Nil(t, err)

}

// TODO: test keys
// TODO: benchmarks of various bucket sizes
<commit_msg>Add a simple example to tests<commit_after>package dictionary_test

import (
	"fmt"
	"math"
	"math/rand"
	"testing"

	"github.com/bakins/dictionary"
	"github.com/stretchr/testify/require"
)

func TestSimpleSet(t *testing.T) {
	d := dictionary.New()
	k := dictionary.StringKey("foo")

	d.Set(k, "bar")
	v, ok := d.Get(k)
	require.NotNil(t, v)
	require.Equal(t, true, ok, "should have found key")
	require.Equal(t, "bar", v.(string), "unexpected value")

	v, ok = d.Get(dictionary.StringKey("bar"))
	require.Nil(t, v)
	require.Equal(t, false, ok, "should not have found key")
}

type entry struct {
	key dictionary.StringKey
	val int
}

func TestSet(t *testing.T) {
	d := dictionary.New()

	entries := make([]entry, 0)
	for i, c := range "abcdefghijklmnopqrstuvwxyz" {
		e := entry{
			key: dictionary.StringKey(c),
			val: i,
		}

		entries = append(entries, e)
		d.Set(e.key, &e)
	}

	for i := range entries {
		j := rand.Intn(i + 1)
		entries[i], entries[j] = entries[j], entries[i]
	}

	for _, e := range entries {
		v, ok := d.Get(e.key)
		require.Equal(t, true, ok, "should have found key")
		require.Equal(t, e.val, v.(*entry).val, "unexpected value")
	}
}

func TestDelete(t *testing.T) {
	d := dictionary.New()
	k := dictionary.StringKey("foo")

	d.Set(k, "bar")
	v, ok := d.Get(k)
	require.NotNil(t, v)
	require.Equal(t, true, ok, "should have found key")
	require.Equal(t, "bar", v.(string), "unexpected value")

	v, ok = d.Delete(dictionary.StringKey("foo"))
	require.NotNil(t, v)
	require.Equal(t, true, ok, "should have found key")
	require.Equal(t, "bar", v.(string), "unexpected value")

	v, ok = d.Delete(dictionary.StringKey("bar"))
	require.Nil(t, v)
	require.Equal(t, false, ok, "should not have found key")
}

type intKey int

func (i intKey) Hash() uint32 {
	if i < 0 {
		i = -i
	}
	if i < math.MaxUint32 {
		return uint32(i)
	}

	// hacky but good enough for a test
	return uint32(i - math.MaxUint32)
}

func (i intKey) Equal(v interface{}) bool {
	return int(i) == int(v.(intKey))
}

func TestSimpleIntSet(t *testing.T) {
	d := dictionary.New()
	k := intKey(99)

	d.Set(k, "bar")
	v, ok := d.Get(k)
	require.NotNil(t, v)
	require.Equal(t, true, ok, "should have found key")
	require.Equal(t, "bar", v.(string), "unexpected value")

	v, ok = d.Get(intKey(1))
	require.Nil(t, v)
	require.Equal(t, false, ok, "should not have found key")
}

type intEntry struct {
	key intKey
	val int
}

func TestIntSet(t *testing.T) {
	d := dictionary.New()

	entries := make([]intEntry, 0)
	for i := 0; i < 8192; i++ {
		e := intEntry{
			key: intKey(i),
			val: i,
		}

		entries = append(entries, e)
		d.Set(e.key, &e)
	}

	for i := range entries {
		j := rand.Intn(i + 1)
		entries[i], entries[j] = entries[j], entries[i]
	}

	for _, e := range entries {
		v, ok := d.Get(e.key)
		require.Equal(t, true, ok, "should have found key")
		require.Equal(t, e.val, v.(*intEntry).val, "unexpected value")
	}
}

func TestEach(t *testing.T) {
	d := dictionary.New()

	keys := []string{"a", "b", "c", "d"}
	entries := make(map[string]string, len(keys))
	for _, k := range keys {
		entries[k] = k
		d.Set(dictionary.StringKey(k), k)
	}

	f := func(h dictionary.Hasher, v interface{}) error {
		k := string(h.(dictionary.StringKey))
		val := v.(string)
		e, ok := entries[k]
		if !ok {
			return fmt.Errorf("did not find %s", k)
		}
		if e != val {
			return fmt.Errorf("bad value - %s - for %s", e, val)
		}
		return nil
	}

	err := d.Each(f)
	require.Nil(t, err)

}

func ExampleNew() {
	d := dictionary.New()
	k := dictionary.StringKey("foo")

	d.Set(k, "bar")
	v, _ := d.Get(k)

	fmt.Println(v.(string))
	// Output: bar
}

// TODO: test keys
// TODO: benchmarks of various bucket sizes
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
)

// TODO(jwall): Move these to a common flags library.
type stringEnum struct {
	val string
	e   map[string]struct{}
}

func (e *stringEnum) Set(v string) error {
	if _, ok := e.e[v]; ok {
		e.val = v
		return nil
	}
	return fmt.Errorf("Value %q not a valid enum value")
}

func (e *stringEnum) String() string {
	return e.val
}

func StringEnum(name string, e map[string]struct{}, d, doc string) flag.Value {
	val := &stringEnum{
		val: d,
		e:   e,
	}
	flag.Var(val, name, doc)
	return val
}
<commit_msg>Enhanced help text for the enum flags.<commit_after>package main

import (
	"flag"
	"fmt"
)

// TODO(jwall): Move these to a common flags library.
type stringEnum struct {
	val string
	e   map[string]struct{}
}

func (e *stringEnum) Set(v string) error {
	if _, ok := e.e[v]; ok {
		e.val = v
		return nil
	}
	return fmt.Errorf("Value %q not a valid enum value")
}

func (e *stringEnum) String() string {
	var list []string
	for k, _ := range e.e {
		list = append(list, k)
	}
	return e.val + fmt.Sprintf(" from %v", list)
}

func StringEnum(name string, e map[string]struct{}, d, doc string) flag.Value {
	val := &stringEnum{
		val: d,
		e:   e,
	}
	flag.Var(val, name, doc)
	return val
}
<|endoftext|>


<commit_before>package sarah

import (
	"context"
	"errors"
	"github.com/fsnotify/fsnotify"
	"path/filepath"
	"reflect"
	"strings"
	"testing"
	"time"
)

type DummyWatcher struct {
	addFunc    func(string) error
	removeFunc func(string) error
	closeFunc  func() error
}

func (w *DummyWatcher) Add(dir string) error {
	return w.addFunc(dir)
}

func (w *DummyWatcher) Remove(dir string) error {
	return w.removeFunc(dir)
}

func (w *DummyWatcher) Close() error {
	return w.closeFunc()
}

func Test_runConfigWatcher(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	dw, err := runConfigWatcher(ctx)
	if err != nil {
		t.Fatalf("Error is returned: %s.", err.Error())
	}
	if dw == nil {
		t.Fatal("Expected dirWatcher instance is not returned.")
	}
}

func TestDirWatcher_watch(t *testing.T) {
	dir, err := filepath.Abs("dummy")
	if err != nil {
		t.Fatalf("Unexpected error on path string generation: %s.", err.Error())
	}

	watcher := &DummyWatcher{}
	target := make(chan *watchingDir, 1)
	cancelWatch := make(chan BotType, 1)
	dw := &dirWatcher{
		watcher:  watcher,
		watchDir: target,
		cancel:   cancelWatch,
	}

	var botType BotType = "Foo"
	callback := func(_ string) {}
	go func() {
		select {
		case d := <-dw.watchDir:
			if d.botType != botType {
				t.Errorf("Unexpected BotType is given: %s.", d.botType.String())
			}
			if d.dir != dir {
				t.Errorf("Unexpected directory is given: %s.", d.dir)
			}
			if reflect.ValueOf(d.callback).Pointer() != reflect.ValueOf(callback).Pointer() {
				t.Errorf("Unexpected callback function is given: %#v.", d.callback)
			}
			d.initErr <- nil
			return
		}
	}()

	ctx, cancel := context.WithCancel(context.Background())
	err = dw.watch(ctx, botType, dir, callback)
	if err != nil {
		cancel()
		t.Fatalf("Unexpected error is returned: %s.", err.Error())
	}

	cancel()
	select {
	case canceledBotType := <-cancelWatch:
		if canceledBotType != botType {
			t.Errorf("Unexpected BotType is passed: %s.", canceledBotType.String())
		}
	case <-time.NewTimer(10 * time.Second).C:
		t.Error("Context cancellation is not propagated.")
	}
}

func TestDirWatcher_watch_InitError(t *testing.T) {
	dir, err := filepath.Abs("dummy")
	if err != nil {
		t.Fatalf("Unexpected error on path string generation: %s.", err.Error())
	}

	watcher := &DummyWatcher{}
	target := make(chan *watchingDir, 1)
	cancelWatch := make(chan BotType, 1)
	dw := &dirWatcher{
		watcher:  watcher,
		watchDir: target,
		cancel:   cancelWatch,
	}

	initErr := errors.New("")
	go func() {
		select {
		case d := <-dw.watchDir:
			d.initErr <- initErr
			return
		}
	}()

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	err = dw.watch(ctx, "Foo", dir, func(_ string) {})
	if err == nil {
		t.Fatal("Expected error is not returned.")
	}
}

func TestDirWatcher_receiveEvent_watchFailure(t *testing.T) {
	watchErr := errors.New("")
	watcher := &DummyWatcher{
		addFunc: func(_ string) error {
			return watchErr
		},
		closeFunc: func() error {
			return nil
		},
	}
	dw := &dirWatcher{
		watcher:  watcher,
		watchDir: make(chan *watchingDir, 1),
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	go dw.receiveEvent(ctx, make(chan fsnotify.Event, 1), make(chan error, 1))

	target := &watchingDir{
		dir:      "dummy",
		botType:  "DummyBot",
		callback: func(path string) {},
		initErr:  make(chan error, 1),
	}
	dw.watchDir <- target
	select {
	case initErr := <-target.initErr:
		if initErr != watchErr {
			t.Fatalf("Unexpected error is returned: %s.", initErr.Error())
		}
	case <-time.NewTimer(10 * time.Second).C:
		t.Fatal("Directory addition did not complete in time.")
	}
}

func TestDirWatcher_receiveEvent_Events(t *testing.T) {
	watcher := &DummyWatcher{
		addFunc: func(_ string) error {
			return nil
		},
		closeFunc: func() error {
			return nil
		},
	}
	watchTarget := make(chan *watchingDir, 1)
	cancelWatch := make(chan BotType, 1)
	dw := &dirWatcher{
		watcher:  watcher,
		watchDir: watchTarget,
		cancel:   cancelWatch,
	}

	// Start receiving events
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	eventChan := make(chan fsnotify.Event, 1)
	errorChan := make(chan error, 1)
	go dw.receiveEvent(ctx, eventChan, errorChan)

	// Let receiveEvent stash directory information internally.
	var botType BotType = "Foo"
	callbackPath := make(chan string, 1)
	configDir, err := filepath.Abs(filepath.Join("dummy", strings.ToLower(botType.String())))
	if err != nil {
		t.Fatalf("Unexpected error on path string generation: %s.", err.Error())
	}
	watch := &watchingDir{
		dir:     configDir,
		botType: botType,
		callback: func(path string) {
			callbackPath <- path
		},
		initErr: make(chan error, 1),
	}
	watchingDir := watch
	dw.watchDir <- watchingDir
	select {
	case initErr := <-watch.initErr:
		if initErr != nil {
			t.Fatalf("Unexpected error is returned: %s.", initErr.Error())
		}
	case <-time.NewTimer(10 * time.Second).C:
		t.Fatal("Directory addition did not complete in time.")
	}

	// Event is sent for the stashed directory
	createdFile, err := filepath.Abs(filepath.Join(watch.dir, "newFile"))
	if err != nil {
		t.Fatalf("Unexpected error on path string generation: %s.", err.Error())
	}
	event := fsnotify.Event{Name: createdFile}
	event.Op |= fsnotify.Create
	eventChan <- event

	select {
	case path := <-callbackPath:
		if filepath.Dir(path) != watch.dir {
			t.Errorf("Expected %s, but was %s.", watch.dir, path)
		}
	case <-time.NewTimer(10 * time.Second).C:
		t.Fatal("Callback function is not called.")
	}
}

func TestDirWatcher_receiveEvent_cancel(t *testing.T) {
	watcher := &DummyWatcher{
		addFunc: func(_ string) error {
			return nil
		},
		closeFunc: func() error {
			return nil
		},
		removeFunc: func(_ string) error {
			return nil
		},
	}

	// Start receiving events
	dw := &dirWatcher{
		watcher:  watcher,
		watchDir: make(chan *watchingDir, 1),
		cancel:   make(chan BotType, 1),
	}
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	eventChan := make(chan fsnotify.Event, 1)
	errorChan := make(chan error, 1)
	go dw.receiveEvent(ctx, eventChan, errorChan)

	// Let receiveEvent stash directory information internally.
	var botType BotType = "Foo"
	callbackPath := make(chan string, 1)
	configDir, err := filepath.Abs(filepath.Join("dummy", strings.ToLower(botType.String())))
	if err != nil {
		t.Fatalf("Unexpected error on path string generation: %s.", err.Error())
	}
	watch := &watchingDir{
		dir:     configDir,
		botType: botType,
		callback: func(path string) {
			callbackPath <- path
		},
		initErr: make(chan error, 1),
	}
	dw.watchDir <- watch
	select {
	case <-watch.initErr:
		// no-opp
	case <-time.NewTimer(10 * time.Second).C:
		t.Fatal("Directory addition did not complete in time.")
	}

	// Do the cancellation
	dw.cancel <- botType

	// Nothing bad happens
}
<commit_msg>fix import<commit_after>package sarah

import (
	"errors"
	"github.com/fsnotify/fsnotify"
	"golang.org/x/net/context"
	"path/filepath"
	"reflect"
	"strings"
	"testing"
	"time"
)

type DummyWatcher struct {
	addFunc    func(string) error
	removeFunc func(string) error
	closeFunc  func() error
}

func (w *DummyWatcher) Add(dir string) error {
	return w.addFunc(dir)
}

func (w *DummyWatcher) Remove(dir string) error {
	return w.removeFunc(dir)
}

func (w *DummyWatcher) Close() error {
	return w.closeFunc()
}

func Test_runConfigWatcher(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	dw, err := runConfigWatcher(ctx)
	if err != nil {
		t.Fatalf("Error is returned: %s.", err.Error())
	}
	if dw == nil {
		t.Fatal("Expected dirWatcher instance is not returned.")
	}
}

func TestDirWatcher_watch(t *testing.T) {
	dir, err := filepath.Abs("dummy")
	if err != nil {
		t.Fatalf("Unexpected error on path string generation: %s.", err.Error())
	}

	watcher := &DummyWatcher{}
	target := make(chan *watchingDir, 1)
	cancelWatch := make(chan BotType, 1)
	dw := &dirWatcher{
		watcher:  watcher,
		watchDir: target,
		cancel:   cancelWatch,
	}

	var botType BotType = "Foo"
	callback := func(_ string) {}
	go func() {
		select {
		case d := <-dw.watchDir:
			if d.botType != botType {
				t.Errorf("Unexpected BotType is given: %s.", d.botType.String())
			}
			if d.dir != dir {
				t.Errorf("Unexpected directory is given: %s.", d.dir)
			}
			if reflect.ValueOf(d.callback).Pointer() != reflect.ValueOf(callback).Pointer() {
				t.Errorf("Unexpected callback function is given: %#v.", d.callback)
			}
			d.initErr <- nil
			return
		}
	}()

	ctx, cancel := context.WithCancel(context.Background())
	err = dw.watch(ctx, botType, dir, callback)
	if err != nil {
		cancel()
		t.Fatalf("Unexpected error is returned: %s.", err.Error())
	}

	cancel()
	select {
	case canceledBotType := <-cancelWatch:
		if canceledBotType != botType {
			t.Errorf("Unexpected BotType is passed: %s.", canceledBotType.String())
		}
	case <-time.NewTimer(10 * time.Second).C:
		t.Error("Context cancellation is not propagated.")
	}
}

func TestDirWatcher_watch_InitError(t *testing.T) {
	dir, err := filepath.Abs("dummy")
	if err != nil {
		t.Fatalf("Unexpected error on path string generation: %s.", err.Error())
	}

	watcher := &DummyWatcher{}
	target := make(chan *watchingDir, 1)
	cancelWatch := make(chan BotType, 1)
	dw := &dirWatcher{
		watcher:  watcher,
		watchDir: target,
		cancel:   cancelWatch,
	}

	initErr := errors.New("")
	go func() {
		select {
		case d := <-dw.watchDir:
			d.initErr <- initErr
			return
		}
	}()

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	err = dw.watch(ctx, "Foo", dir, func(_ string) {})
	if err == nil {
		t.Fatal("Expected error is not returned.")
	}
}

func TestDirWatcher_receiveEvent_watchFailure(t *testing.T) {
	watchErr := errors.New("")
	watcher := &DummyWatcher{
		addFunc: func(_ string) error {
			return watchErr
		},
		closeFunc: func() error {
			return nil
		},
	}
	dw := &dirWatcher{
		watcher:  watcher,
		watchDir: make(chan *watchingDir, 1),
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	go dw.receiveEvent(ctx, make(chan fsnotify.Event, 1), make(chan error, 1))

	target := &watchingDir{
		dir:      "dummy",
		botType:  "DummyBot",
		callback: func(path string) {},
		initErr:  make(chan error, 1),
	}
	dw.watchDir <- target
	select {
	case initErr := <-target.initErr:
		if initErr != watchErr {
			t.Fatalf("Unexpected error is returned: %s.", initErr.Error())
		}
	case <-time.NewTimer(10 * time.Second).C:
		t.Fatal("Directory addition did not complete in time.")
	}
}

func TestDirWatcher_receiveEvent_Events(t *testing.T) {
	watcher := &DummyWatcher{
		addFunc: func(_ string) error {
			return nil
		},
		closeFunc: func() error {
			return nil
		},
	}
	watchTarget := make(chan *watchingDir, 1)
	cancelWatch := make(chan BotType, 1)
	dw := &dirWatcher{
		watcher:  watcher,
		watchDir: watchTarget,
		cancel:   cancelWatch,
	}

	// Start receiving events
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	eventChan := make(chan fsnotify.Event, 1)
	errorChan := make(chan error, 1)
	go dw.receiveEvent(ctx, eventChan, errorChan)

	// Let receiveEvent stash directory information internally.
	var botType BotType = "Foo"
	callbackPath := make(chan string, 1)
	configDir, err := filepath.Abs(filepath.Join("dummy", strings.ToLower(botType.String())))
	if err != nil {
		t.Fatalf("Unexpected error on path string generation: %s.", err.Error())
	}
	watch := &watchingDir{
		dir:     configDir,
		botType: botType,
		callback: func(path string) {
			callbackPath <- path
		},
		initErr: make(chan error, 1),
	}
	watchingDir := watch
	dw.watchDir <- watchingDir
	select {
	case initErr := <-watch.initErr:
		if initErr != nil {
			t.Fatalf("Unexpected error is returned: %s.", initErr.Error())
		}
	case <-time.NewTimer(10 * time.Second).C:
		t.Fatal("Directory addition did not complete in time.")
	}

	// Event is sent for the stashed directory
	createdFile, err := filepath.Abs(filepath.Join(watch.dir, "newFile"))
	if err != nil {
		t.Fatalf("Unexpected error on path string generation: %s.", err.Error())
	}
	event := fsnotify.Event{
		Name: createdFile,
		Op:   fsnotify.Create,
	}
	eventChan <- event

	select {
	case path := <-callbackPath:
		if filepath.Dir(path) != watch.dir {
			t.Errorf("Expected %s, but was %s.", watch.dir, path)
		}
	case <-time.NewTimer(10 * time.Second).C:
		t.Fatal("Callback function is not called.")
	}
}

func TestDirWatcher_receiveEvent_cancel(t *testing.T) {
	watcher := &DummyWatcher{
		addFunc: func(_ string) error {
			return nil
		},
		closeFunc: func() error {
			return nil
		},
		removeFunc: func(_ string) error {
			return nil
		},
	}

	// Start receiving events
	dw := &dirWatcher{
		watcher:  watcher,
		watchDir: make(chan *watchingDir, 1),
		cancel:   make(chan BotType, 1),
	}
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	eventChan := make(chan fsnotify.Event, 1)
	errorChan := make(chan error, 1)
	go dw.receiveEvent(ctx, eventChan, errorChan)

	// Let receiveEvent stash directory information internally.
	var botType BotType = "Foo"
	callbackPath := make(chan string, 1)
	configDir, err := filepath.Abs(filepath.Join("dummy", strings.ToLower(botType.String())))
	if err != nil {
		t.Fatalf("Unexpected error on path string generation: %s.", err.Error())
	}
	watch := &watchingDir{
		dir:     configDir,
		botType: botType,
		callback: func(path string) {
			callbackPath <- path
		},
		initErr: make(chan error, 1),
	}
	dw.watchDir <- watch
	select {
	case <-watch.initErr:
		// no-opp
	case <-time.NewTimer(10 * time.Second).C:
		t.Fatal("Directory addition did not complete in time.")
	}

	// Do the cancellation
	dw.cancel <- botType

	// Nothing bad happens
}
<|endoftext|>