<commit_before>/*
Copyright 2020 The cert-manager 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 trigger

import (
	"context"
	"time"

	"github.com/go-logr/logr"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/informers"
	corelisters "k8s.io/client-go/listers/core/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/record"
	"k8s.io/client-go/util/workqueue"
	"k8s.io/utils/clock"

	apiutil "github.com/jetstack/cert-manager/pkg/api/util"
	cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
	cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
	cmclient "github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
	cminformers "github.com/jetstack/cert-manager/pkg/client/informers/externalversions"
	cmlisters "github.com/jetstack/cert-manager/pkg/client/listers/certmanager/v1"
	controllerpkg "github.com/jetstack/cert-manager/pkg/controller"
	"github.com/jetstack/cert-manager/pkg/controller/certificates"
	"github.com/jetstack/cert-manager/pkg/controller/certificates/trigger/policies"
	logf "github.com/jetstack/cert-manager/pkg/logs"
	"github.com/jetstack/cert-manager/pkg/scheduler"
	"github.com/jetstack/cert-manager/pkg/util/predicate"
)

const (
	ControllerName = "certificates-trigger"

	// the amount of time after the LastFailureTime of a Certificate
	// before the request should be retried.
	// In future this should be replaced with a more dynamic exponential
	// back-off algorithm.
	retryAfterLastFailure = time.Hour
)

// This controller observes the state of the certificate's currently
// issued `spec.secretName` and the rest of the `certificate.spec` fields to
// determine whether a re-issuance is required.
// It triggers re-issuance by adding the `Issuing` status condition when a new
// certificate is required.
type controller struct {
	certificateLister        cmlisters.CertificateLister
	certificateRequestLister cmlisters.CertificateRequestLister
	secretLister             corelisters.SecretLister
	client                   cmclient.Interface
	recorder                 record.EventRecorder
	scheduledWorkQueue       scheduler.ScheduledWorkQueue

	// The following are used for testing purposes.
	clock              clock.Clock
	shouldReissue      policies.Func
	dataForCertificate func(context.Context, *cmapi.Certificate) (policies.Input, error)
}

func NewController(
	log logr.Logger,
	client cmclient.Interface,
	factory informers.SharedInformerFactory,
	cmFactory cminformers.SharedInformerFactory,
	recorder record.EventRecorder,
	clock clock.Clock,
	shouldReissue policies.Func,
) (*controller, workqueue.RateLimitingInterface, []cache.InformerSynced) {
	// create a queue used to queue up items to be processed
	queue := workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(time.Second*1, time.Second*30), ControllerName)

	// obtain references to all the informers used by this controller
	certificateInformer := cmFactory.Certmanager().V1().Certificates()
	certificateRequestInformer := cmFactory.Certmanager().V1().CertificateRequests()
	secretsInformer := factory.Core().V1().Secrets()

	certificateInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: queue})

	// When a CertificateRequest resource changes, enqueue the Certificate resource that owns it.
	certificateRequestInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{
		WorkFunc: certificates.EnqueueCertificatesForResourceUsingPredicates(log, queue, certificateInformer.Lister(), labels.Everything(), predicate.ResourceOwnerOf),
	})
	// When a Secret resource changes, enqueue any Certificate resources that name it as spec.secretName.
	secretsInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{
		// Trigger reconciles on changes to the Secret named `spec.secretName`
		WorkFunc: certificates.EnqueueCertificatesForResourceUsingPredicates(log, queue, certificateInformer.Lister(), labels.Everything(),
			predicate.ExtractResourceName(predicate.CertificateSecretName)),
	})

	// build a list of InformerSynced functions that will be returned by the Register method.
	// the controller will only begin processing items once all of these informers have synced.
	mustSync := []cache.InformerSynced{
		certificateRequestInformer.Informer().HasSynced,
		secretsInformer.Informer().HasSynced,
		certificateInformer.Informer().HasSynced,
	}

	return &controller{
		certificateLister:        certificateInformer.Lister(),
		certificateRequestLister: certificateRequestInformer.Lister(),
		secretLister:             secretsInformer.Lister(),
		client:                   client,
		recorder:                 recorder,
		scheduledWorkQueue:       scheduler.NewScheduledWorkQueue(clock, queue.Add),

		// The following are used for testing purposes.
		clock:         clock,
		shouldReissue: shouldReissue,
		dataForCertificate: (&policies.Gatherer{
			CertificateRequestLister: certificateRequestInformer.Lister(),
			SecretLister:             secretsInformer.Lister(),
		}).DataForCertificate,
	}, queue, mustSync
}

func (c *controller) ProcessItem(ctx context.Context, key string) error {
	log := logf.FromContext(ctx).WithValues("key", key)
	ctx = logf.NewContext(ctx, log)
	namespace, name, err := cache.SplitMetaNamespaceKey(key)
	if err != nil {
		log.Error(err, "invalid resource key passed to ProcessItem")
		return nil
	}

	crt, err := c.certificateLister.Certificates(namespace).Get(name)
	if apierrors.IsNotFound(err) {
		log.Error(err, "certificate not found for key")
		return nil
	}
	if err != nil {
		return err
	}
	if apiutil.CertificateHasCondition(crt, cmapi.CertificateCondition{
		Type:   cmapi.CertificateConditionIssuing,
		Status: cmmeta.ConditionTrue,
	}) {
		// Do nothing if an issuance is already in progress.
		return nil
	}

	input, err := c.dataForCertificate(ctx, crt)
	if err != nil {
		return err
	}

	// Back off from re-issuing immediately when the certificate has been
	// in failing mode for less than 1 hour.
	backoff, delay := shouldBackoffReissuingOnFailure(log, c.clock, input.Certificate, input.NextRevisionRequest)
	if backoff {
		log.V(logf.InfoLevel).Info("Not re-issuing certificate as an attempt has been made in the last hour", "retry_delay", delay)
		c.scheduleRecheckOfCertificateIfRequired(log, key, delay)
		return nil
	}

	if crt.Status.RenewalTime != nil {
		// ensure a resync is scheduled in the future so that we re-check
		// Certificate resources and trigger them near expiry time
		c.scheduleRecheckOfCertificateIfRequired(log, key, crt.Status.RenewalTime.Time.Sub(c.clock.Now()))
	}

	reason, message, reissue := c.shouldReissue(input)
	if !reissue {
		// no re-issuance required, return early
		return nil
	}

	// Although the below recorder.Event already logs the event, the log
	// line is quite unreadable (very long). Since this information is very
	// important for the user and the operator, we log the following
	// message.
	log.V(logf.InfoLevel).Info("Certificate must be re-issued", "reason", reason, "message", message)

	crt = crt.DeepCopy()
	apiutil.SetCertificateCondition(crt, crt.Generation, cmapi.CertificateConditionIssuing, cmmeta.ConditionTrue, reason, message)
	_, err = c.client.CertmanagerV1().Certificates(crt.Namespace).UpdateStatus(ctx, crt, metav1.UpdateOptions{})
	if err != nil {
		return err
	}
	c.recorder.Event(crt, corev1.EventTypeNormal, "Issuing", message)

	return nil
}

// shouldBackoffReissuingOnFailure tells us if we should back off
// re-issuing for this certificate and for how much time.
//
// The request can be left nil, in which case no back off is required.
// Additionally, if the certificate doesn't match the request, no back
// off is required because it means the request will have to be re-issued.
func shouldBackoffReissuingOnFailure(log logr.Logger, c clock.Clock, crt *cmapi.Certificate, nextCR *cmapi.CertificateRequest) (backoff bool, delay time.Duration) {
	if crt.Status.LastFailureTime == nil {
		return false, 0
	}

	// We want to immediately trigger a re-issuance when the certificate
	// changes, even when it is failing. In order to detect a "change", we
	// compare the "next" CR with the certificate spec and reissue and there is
	// a mismatch.
	//
	// Note that the "next" CR is the only CR that matters when looking at
	// whether the certificate still matches its CR. The "current" CR matches
	// the previous spec of the certificate, so we don't want to be looking at
	// the current CR.
	if nextCR != nil {
		mismatches, err := certificates.RequestMatchesSpec(nextCR, crt.Spec)
		if err != nil {
			log.V(logf.InfoLevel).Info("next CertificateRequest cannot be decoded, skipping checking if Certificate matches the CertificateRequest")
			return false, 0
		}
		if len(mismatches) > 0 {
			log.V(logf.ExtendedInfoLevel).WithValues("mismatches", mismatches).Info("Certificate is failing but the Certificate differs from CertificateRequest, backoff is not required")
			return false, 0
		}
	} else {
		log.V(logf.InfoLevel).Info("next CertificateRequest not available, skipping checking if Certificate matches the CertificateRequest")
	}

	now := c.Now()
	durationSinceFailure := now.Sub(crt.Status.LastFailureTime.Time)
	if durationSinceFailure >= retryAfterLastFailure {
		log.V(logf.ExtendedInfoLevel).WithValues("since_failure", durationSinceFailure).Info("Certificate has been in failure mode long enough, no need to back off")
		return false, 0
	}
	return true, retryAfterLastFailure - durationSinceFailure
}

// scheduleRecheckOfCertificateIfRequired will schedule the resource with the
// given key to be re-queued for processing after the given amount of time
// has elapsed.
// If the 'durationUntilRenewalTime' is less than zero, it will not be
// queued again.
func (c *controller) scheduleRecheckOfCertificateIfRequired(log logr.Logger, key string, durationUntilRenewalTime time.Duration) {
	// don't schedule a re-queue if the time is in the past.
	// if it is in the past, the resource will be triggered during the
	// current call to the ProcessItem method. If we added the item to the
	// queue with a duration of <=0, we would otherwise continually re-queue
	// in a tight loop whilst we wait for the caching listers to observe
	// the 'Triggered' status condition changing to 'True'.
	if durationUntilRenewalTime < 0 {
		return
	}

	log.V(logf.DebugLevel).Info("scheduling renewal", "duration_until_renewal", durationUntilRenewalTime.String())

	c.scheduledWorkQueue.Add(key, durationUntilRenewalTime)
}

// controllerWrapper wraps the `controller` structure to make it implement
// the controllerpkg.queueingController interface
type controllerWrapper struct {
	*controller
}

func (c *controllerWrapper) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) {
	// construct a new named logger to be reused throughout the controller
	log := logf.FromContext(ctx.RootContext, ControllerName)

	ctrl, queue, mustSync := NewController(log,
		ctx.CMClient,
		ctx.KubeSharedInformerFactory,
		ctx.SharedInformerFactory,
		ctx.Recorder,
		ctx.Clock,
		policies.NewTriggerPolicyChain(ctx.Clock, cmapi.DefaultRenewBefore).Evaluate,
	)
	c.controller = ctrl

	return queue, mustSync, nil
}

func init() {
	controllerpkg.Register(ControllerName, func(ctx *controllerpkg.Context) (controllerpkg.Interface, error) {
		return controllerpkg.NewBuilder(ctx, ControllerName).
			For(&controllerWrapper{}).
			Complete()
	})
}
<commit_msg>trigger-controller: further comments on shouldBackoffReissuingOnFailure<commit_after>/*
Copyright 2020 The cert-manager 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 trigger

import (
	"context"
	"time"

	"github.com/go-logr/logr"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/informers"
	corelisters "k8s.io/client-go/listers/core/v1"
	"k8s.io/client-go/tools/cache"
	"k8s.io/client-go/tools/record"
	"k8s.io/client-go/util/workqueue"
	"k8s.io/utils/clock"

	apiutil "github.com/jetstack/cert-manager/pkg/api/util"
	cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
	cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
	cmclient "github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
	cminformers "github.com/jetstack/cert-manager/pkg/client/informers/externalversions"
	cmlisters "github.com/jetstack/cert-manager/pkg/client/listers/certmanager/v1"
	controllerpkg "github.com/jetstack/cert-manager/pkg/controller"
	"github.com/jetstack/cert-manager/pkg/controller/certificates"
	"github.com/jetstack/cert-manager/pkg/controller/certificates/trigger/policies"
	logf "github.com/jetstack/cert-manager/pkg/logs"
	"github.com/jetstack/cert-manager/pkg/scheduler"
	"github.com/jetstack/cert-manager/pkg/util/predicate"
)

const (
	ControllerName = "certificates-trigger"

	// the amount of time after the LastFailureTime of a Certificate
	// before the request should be retried.
	// In future this should be replaced with a more dynamic exponential
	// back-off algorithm.
	retryAfterLastFailure = time.Hour
)

// This controller observes the state of the certificate's currently
// issued `spec.secretName` and the rest of the `certificate.spec` fields to
// determine whether a re-issuance is required.
// It triggers re-issuance by adding the `Issuing` status condition when a new
// certificate is required.
type controller struct {
	certificateLister        cmlisters.CertificateLister
	certificateRequestLister cmlisters.CertificateRequestLister
	secretLister             corelisters.SecretLister
	client                   cmclient.Interface
	recorder                 record.EventRecorder
	scheduledWorkQueue       scheduler.ScheduledWorkQueue

	// The following are used for testing purposes.
	clock              clock.Clock
	shouldReissue      policies.Func
	dataForCertificate func(context.Context, *cmapi.Certificate) (policies.Input, error)
}

func NewController(
	log logr.Logger,
	client cmclient.Interface,
	factory informers.SharedInformerFactory,
	cmFactory cminformers.SharedInformerFactory,
	recorder record.EventRecorder,
	clock clock.Clock,
	shouldReissue policies.Func,
) (*controller, workqueue.RateLimitingInterface, []cache.InformerSynced) {
	// create a queue used to queue up items to be processed
	queue := workqueue.NewNamedRateLimitingQueue(workqueue.NewItemExponentialFailureRateLimiter(time.Second*1, time.Second*30), ControllerName)

	// obtain references to all the informers used by this controller
	certificateInformer := cmFactory.Certmanager().V1().Certificates()
	certificateRequestInformer := cmFactory.Certmanager().V1().CertificateRequests()
	secretsInformer := factory.Core().V1().Secrets()

	certificateInformer.Informer().AddEventHandler(&controllerpkg.QueuingEventHandler{Queue: queue})

	// When a CertificateRequest resource changes, enqueue the Certificate resource that owns it.
	certificateRequestInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{
		WorkFunc: certificates.EnqueueCertificatesForResourceUsingPredicates(log, queue, certificateInformer.Lister(), labels.Everything(), predicate.ResourceOwnerOf),
	})
	// When a Secret resource changes, enqueue any Certificate resources that name it as spec.secretName.
	secretsInformer.Informer().AddEventHandler(&controllerpkg.BlockingEventHandler{
		// Trigger reconciles on changes to the Secret named `spec.secretName`
		WorkFunc: certificates.EnqueueCertificatesForResourceUsingPredicates(log, queue, certificateInformer.Lister(), labels.Everything(),
			predicate.ExtractResourceName(predicate.CertificateSecretName)),
	})

	// build a list of InformerSynced functions that will be returned by the Register method.
	// the controller will only begin processing items once all of these informers have synced.
	mustSync := []cache.InformerSynced{
		certificateRequestInformer.Informer().HasSynced,
		secretsInformer.Informer().HasSynced,
		certificateInformer.Informer().HasSynced,
	}

	return &controller{
		certificateLister:        certificateInformer.Lister(),
		certificateRequestLister: certificateRequestInformer.Lister(),
		secretLister:             secretsInformer.Lister(),
		client:                   client,
		recorder:                 recorder,
		scheduledWorkQueue:       scheduler.NewScheduledWorkQueue(clock, queue.Add),

		// The following are used for testing purposes.
		clock:         clock,
		shouldReissue: shouldReissue,
		dataForCertificate: (&policies.Gatherer{
			CertificateRequestLister: certificateRequestInformer.Lister(),
			SecretLister:             secretsInformer.Lister(),
		}).DataForCertificate,
	}, queue, mustSync
}

func (c *controller) ProcessItem(ctx context.Context, key string) error {
	log := logf.FromContext(ctx).WithValues("key", key)
	ctx = logf.NewContext(ctx, log)
	namespace, name, err := cache.SplitMetaNamespaceKey(key)
	if err != nil {
		log.Error(err, "invalid resource key passed to ProcessItem")
		return nil
	}

	crt, err := c.certificateLister.Certificates(namespace).Get(name)
	if apierrors.IsNotFound(err) {
		log.Error(err, "certificate not found for key")
		return nil
	}
	if err != nil {
		return err
	}
	if apiutil.CertificateHasCondition(crt, cmapi.CertificateCondition{
		Type:   cmapi.CertificateConditionIssuing,
		Status: cmmeta.ConditionTrue,
	}) {
		// Do nothing if an issuance is already in progress.
		return nil
	}

	input, err := c.dataForCertificate(ctx, crt)
	if err != nil {
		return err
	}

	// Back off from re-issuing immediately when the certificate has been
	// in failing mode for less than 1 hour.
	backoff, delay := shouldBackoffReissuingOnFailure(log, c.clock, input.Certificate, input.NextRevisionRequest)
	if backoff {
		log.V(logf.InfoLevel).Info("Not re-issuing certificate as an attempt has been made in the last hour", "retry_delay", delay)
		c.scheduleRecheckOfCertificateIfRequired(log, key, delay)
		return nil
	}

	if crt.Status.RenewalTime != nil {
		// ensure a resync is scheduled in the future so that we re-check
		// Certificate resources and trigger them near expiry time
		c.scheduleRecheckOfCertificateIfRequired(log, key, crt.Status.RenewalTime.Time.Sub(c.clock.Now()))
	}

	reason, message, reissue := c.shouldReissue(input)
	if !reissue {
		// no re-issuance required, return early
		return nil
	}

	// Although the below recorder.Event already logs the event, the log
	// line is quite unreadable (very long). Since this information is very
	// important for the user and the operator, we log the following
	// message.
	log.V(logf.InfoLevel).Info("Certificate must be re-issued", "reason", reason, "message", message)

	crt = crt.DeepCopy()
	apiutil.SetCertificateCondition(crt, crt.Generation, cmapi.CertificateConditionIssuing, cmmeta.ConditionTrue, reason, message)
	_, err = c.client.CertmanagerV1().Certificates(crt.Namespace).UpdateStatus(ctx, crt, metav1.UpdateOptions{})
	if err != nil {
		return err
	}
	c.recorder.Event(crt, corev1.EventTypeNormal, "Issuing", message)

	return nil
}

// shouldBackoffReissuingOnFailure tells us if we should back-off re-issuing for
// an hour or not. Notably, it returns no back-off when the certificate doesn't
// match the "next" certificate (since a mismatch means that this certificate
// gets re-issued immediately).
//
// Note that the request can be left nil: in that case, the returned back-off
// will be 0 since it means the CR must be created immediately.
func shouldBackoffReissuingOnFailure(log logr.Logger, c clock.Clock, crt *cmapi.Certificate, nextCR *cmapi.CertificateRequest) (backoff bool, delay time.Duration) {
	if crt.Status.LastFailureTime == nil {
		return false, 0
	}

	// We want to immediately trigger a re-issuance when the certificate
	// changes. In order to detect a "change", we compare the "next" CR with the
	// certificate spec and reissue and there is a mismatch.
	//
	// Note that the "next" CR is the only CR that matters when looking at
	// whether the certificate still matches its CR. The "current" CR matches
	// the previous spec of the certificate, so we don't want to be looking at
	// the current CR.
	if nextCR == nil {
		log.V(logf.InfoLevel).Info("next CertificateRequest not available, skipping checking if Certificate matches the CertificateRequest")
	} else {
		mismatches, err := certificates.RequestMatchesSpec(nextCR, crt.Spec)
		if err != nil {
			log.V(logf.InfoLevel).Info("next CertificateRequest cannot be decoded, skipping checking if Certificate matches the CertificateRequest")
			return false, 0
		}
		if len(mismatches) > 0 {
			log.V(logf.ExtendedInfoLevel).WithValues("mismatches", mismatches).Info("Certificate is failing but the Certificate differs from CertificateRequest, backoff is not required")
			return false, 0
		}
	}

	now := c.Now()
	durationSinceFailure := now.Sub(crt.Status.LastFailureTime.Time)
	if durationSinceFailure >= retryAfterLastFailure {
		log.V(logf.ExtendedInfoLevel).WithValues("since_failure", durationSinceFailure).Info("Certificate has been in failure mode long enough, no need to back off")
		return false, 0
	}
	return true, retryAfterLastFailure - durationSinceFailure
}

// scheduleRecheckOfCertificateIfRequired will schedule the resource with the
// given key to be re-queued for processing after the given amount of time
// has elapsed.
// If the 'durationUntilRenewalTime' is less than zero, it will not be
// queued again.
func (c *controller) scheduleRecheckOfCertificateIfRequired(log logr.Logger, key string, durationUntilRenewalTime time.Duration) {
	// don't schedule a re-queue if the time is in the past.
	// if it is in the past, the resource will be triggered during the
	// current call to the ProcessItem method. If we added the item to the
	// queue with a duration of <=0, we would otherwise continually re-queue
	// in a tight loop whilst we wait for the caching listers to observe
	// the 'Triggered' status condition changing to 'True'.
	if durationUntilRenewalTime < 0 {
		return
	}

	log.V(logf.DebugLevel).Info("scheduling renewal", "duration_until_renewal", durationUntilRenewalTime.String())

	c.scheduledWorkQueue.Add(key, durationUntilRenewalTime)
}

// controllerWrapper wraps the `controller` structure to make it implement
// the controllerpkg.queueingController interface
type controllerWrapper struct {
	*controller
}

func (c *controllerWrapper) Register(ctx *controllerpkg.Context) (workqueue.RateLimitingInterface, []cache.InformerSynced, error) {
	// construct a new named logger to be reused throughout the controller
	log := logf.FromContext(ctx.RootContext, ControllerName)

	ctrl, queue, mustSync := NewController(log,
		ctx.CMClient,
		ctx.KubeSharedInformerFactory,
		ctx.SharedInformerFactory,
		ctx.Recorder,
		ctx.Clock,
		policies.NewTriggerPolicyChain(ctx.Clock, cmapi.DefaultRenewBefore).Evaluate,
	)
	c.controller = ctrl

	return queue, mustSync, nil
}

func init() {
	controllerpkg.Register(ControllerName, func(ctx *controllerpkg.Context) (controllerpkg.Interface, error) {
		return controllerpkg.NewBuilder(ctx, ControllerName).
			For(&controllerWrapper{}).
			Complete()
	})
}
<|endoftext|>


<commit_before>// Copyright ©2014 The gonum 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 dist

import (
	"math"
	"math/rand"
)

// Uniform represents a continuous uniform distribution (https://en.wikipedia.org/wiki/Uniform_distribution_%28continuous%29).
type Uniform struct {
	Min    float64
	Max    float64
	Source *rand.Rand
}

// CDF computes the value of the cumulative density function at x.
func (u Uniform) CDF(x float64) float64 {
	if x < u.Min {
		return 0
	}
	if x > u.Max {
		return 1
	}
	return (x - u.Min) / (u.Max - u.Min)
}

// Uniform doesn't have any of the DLogProbD? because the derivative is 0 everywhere
// except where it's undefined

// Entropy returns the entropy of the distribution.
func (u Uniform) Entropy() float64 {
	return math.Log(u.Max - u.Min)
}

// ExKurtosis returns the excess kurtosis of the distribution.
func (Uniform) ExKurtosis() float64 {
	return -6.0 / 5.0
}

// Uniform doesn't have Fit because it's a bad idea to fit a uniform from data.

// LogProb computes the natural logarithm of the value of the probability density function at x.
func (u Uniform) LogProb(x float64) float64 {
	return -math.Log(u.Max - u.Min)
}

// MarshalParameters implements the ParameterMarshaler interface
func (u Uniform) MarshalParameters(p []Parameter) {
	if len(p) != u.NumParameters() {
		panic("uniform: improper parameter length")
	}
	p[0].Name = "Min"
	p[0].Value = u.Min
	p[1].Name = "Max"
	p[1].Value = u.Max
	return
}

// Mean returns the mean of the probability distribution.
func (u Uniform) Mean() float64 {
	return (u.Max + u.Min) / 2
}

// Median returns the median of the probability distribution.
func (u Uniform) Median() float64 {
	return (u.Max + u.Min) / 2
}

// Uniform doesn't have a mode because it's any value in the distribution

// NumParameters returns the number of parameters in the distribution.
func (Uniform) NumParameters() int {
	return 2
}

// Prob computes the value of the probability density function at x.
func (u Uniform) Prob(x float64) float64 {
	return 1 / (u.Max - u.Min)
}

// Quantile returns the inverse of the cumulative probability distribution.
func (u Uniform) Quantile(p float64) float64 {
	if p < 0 || p > 1 {
		panic("dist: percentile out of bounds")
	}
	return p*(u.Max-u.Min) + u.Min
}

// Rand returns a random sample drawn from the distribution.
func (u Uniform) Rand() float64 {
	var rnd float64
	if u.Source == nil {
		rnd = rand.Float64()
	} else {
		rnd = u.Source.Float64()
	}
	return rnd*(u.Max-u.Min) + u.Min
}

// Skewness returns the skewness of the distribution.
func (Uniform) Skewness() float64 {
	return 0
}

// StdDev returns the standard deviation of the probability distribution.
func (u Uniform) StdDev() float64 {
	return math.Sqrt(u.Variance())
}

// Survival returns the survival function (complementary CDF) at x.
func (u Uniform) Survival(x float64) float64 {
	if x < u.Min {
		return 1
	}
	if x > u.Max {
		return 0
	}
	return (u.Max - x) / (u.Max - u.Min)
}

// UnmarshalParameters implements the ParameterMarshaler interface
func (u *Uniform) UnmarshalParameters(p []Parameter) {
	if len(p) != u.NumParameters() {
		panic("uniform: incorrect number of parameters to set")
	}
	if p[0].Name != "Min" {
		panic("uniform: " + panicNameMismatch)
	}
	if p[1].Name != "Max" {
		panic("uniform: " + panicNameMismatch)
	}

	u.Min = p[0].Value
	u.Max = p[1].Value
}

// Variance returns the variance of the probability distribution.
func (u Uniform) Variance() float64 {
	return 1.0 / 12.0 * (u.Max - u.Min) * (u.Max - u.Min)
}
<commit_msg>Fix probability for Uniform to return 0 when out of bounds<commit_after>// Copyright ©2014 The gonum 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 dist

import (
	"math"
	"math/rand"
)

// Uniform represents a continuous uniform distribution (https://en.wikipedia.org/wiki/Uniform_distribution_%28continuous%29).
type Uniform struct {
	Min    float64
	Max    float64
	Source *rand.Rand
}

// CDF computes the value of the cumulative density function at x.
func (u Uniform) CDF(x float64) float64 {
	if x < u.Min {
		return 0
	}
	if x > u.Max {
		return 1
	}
	return (x - u.Min) / (u.Max - u.Min)
}

// Uniform doesn't have any of the DLogProbD? because the derivative is 0 everywhere
// except where it's undefined

// Entropy returns the entropy of the distribution.
func (u Uniform) Entropy() float64 {
	return math.Log(u.Max - u.Min)
}

// ExKurtosis returns the excess kurtosis of the distribution.
func (Uniform) ExKurtosis() float64 {
	return -6.0 / 5.0
}

// Uniform doesn't have Fit because it's a bad idea to fit a uniform from data.

// LogProb computes the natural logarithm of the value of the probability density function at x.
func (u Uniform) LogProb(x float64) float64 {
	if x < u.Min {
		return math.Inf(-1)
	}
	if x > u.Max {
		return math.Inf(-1)
	}
	return -math.Log(u.Max - u.Min)
}

// MarshalParameters implements the ParameterMarshaler interface
func (u Uniform) MarshalParameters(p []Parameter) {
	if len(p) != u.NumParameters() {
		panic("uniform: improper parameter length")
	}
	p[0].Name = "Min"
	p[0].Value = u.Min
	p[1].Name = "Max"
	p[1].Value = u.Max
	return
}

// Mean returns the mean of the probability distribution.
func (u Uniform) Mean() float64 {
	return (u.Max + u.Min) / 2
}

// Median returns the median of the probability distribution.
func (u Uniform) Median() float64 {
	return (u.Max + u.Min) / 2
}

// Uniform doesn't have a mode because it's any value in the distribution

// NumParameters returns the number of parameters in the distribution.
func (Uniform) NumParameters() int {
	return 2
}

// Prob computes the value of the probability density function at x.
func (u Uniform) Prob(x float64) float64 {
	if x < u.Min {
		return 0
	}
	if x > u.Max {
		return 0
	}
	return 1 / (u.Max - u.Min)
}

// Quantile returns the inverse of the cumulative probability distribution.
func (u Uniform) Quantile(p float64) float64 {
	if p < 0 || p > 1 {
		panic("dist: percentile out of bounds")
	}
	return p*(u.Max-u.Min) + u.Min
}

// Rand returns a random sample drawn from the distribution.
func (u Uniform) Rand() float64 {
	var rnd float64
	if u.Source == nil {
		rnd = rand.Float64()
	} else {
		rnd = u.Source.Float64()
	}
	return rnd*(u.Max-u.Min) + u.Min
}

// Skewness returns the skewness of the distribution.
func (Uniform) Skewness() float64 {
	return 0
}

// StdDev returns the standard deviation of the probability distribution.
func (u Uniform) StdDev() float64 {
	return math.Sqrt(u.Variance())
}

// Survival returns the survival function (complementary CDF) at x.
func (u Uniform) Survival(x float64) float64 {
	if x < u.Min {
		return 1
	}
	if x > u.Max {
		return 0
	}
	return (u.Max - x) / (u.Max - u.Min)
}

// UnmarshalParameters implements the ParameterMarshaler interface
func (u *Uniform) UnmarshalParameters(p []Parameter) {
	if len(p) != u.NumParameters() {
		panic("uniform: incorrect number of parameters to set")
	}
	if p[0].Name != "Min" {
		panic("uniform: " + panicNameMismatch)
	}
	if p[1].Name != "Max" {
		panic("uniform: " + panicNameMismatch)
	}

	u.Min = p[0].Value
	u.Max = p[1].Value
}

// Variance returns the variance of the probability distribution.
func (u Uniform) Variance() float64 {
	return 1.0 / 12.0 * (u.Max - u.Min) * (u.Max - u.Min)
}
<|endoftext|>


<commit_before><commit_msg>Only get the attributes if the flags are set<commit_after><|endoftext|>


<commit_before>package html

import (
	"html/template"
	"time"

	"github.com/chrisolsen/ae"
	"github.com/russross/blackfriday"
	"google.golang.org/appengine/datastore"
)

// CSS prevents any custom embedded styles from being encoded to html safe values
func CSS(s string) template.CSS {
	return template.CSS(s)
}

// Preview returns a preview of the string
func Preview(size int, val string) string {
	if len(val) <= size {
		return val
	}
	return val[:size-1] + "..."
}

// Markdown converts text in the markdown syntax to html
func Markdown(input string) interface{} {
	out := string(blackfriday.MarkdownCommon([]byte(input)))
	return template.HTML(out)
}

// Add adds the numbers
func Add(a, b int) int {
	return a + b
}

// EncodeKey encodes a datastore key
func EncodeKey(data interface{}) string {
	switch data.(type) {
	case ae.Model:
		return data.(ae.Model).Key.Encode()
	case *datastore.Key:
		return data.(*datastore.Key).Encode()
	default:
		return ""
	}
}

// Checked returns the checked attribute for positive values.
// 	<input type="checkbox" {{IsAdmin | checked}}> => <input type="checkbox" checked="checked">
func Checked(checked bool) template.HTMLAttr {
	if checked {
		return template.HTMLAttr(`checked="checked"`)
	}
	return ""
}

// Disabled returns the checked attribute for positive values.
// 	<button type="submit" {{HasError | disabled}}>Save</button> => <button type="submit" disabled="">Save</button>
// 	or
// 	<button type="submit" {{ValidationError | disabled}}>Save</button> => <button type="submit" disabled="">Save</button>
func Disabled(err interface{}) template.HTMLAttr {
	d := template.HTMLAttr(`disabled="disabled"`)
	switch err.(type) {
	case string:
		if len(err.(string)) == 0 {
			return ""
		}
		return d
	case error:
		return d
	default:
		return ""
	}
}

// KeyEqual allow for *datastore.Key comparison
func KeyEqual(a, b *datastore.Key) bool {
	return a.Equal(b)
}

// Timestamp formats the time to the RFC3339 layout
func Timestamp(d time.Time) string {
	return d.Format(time.RFC3339)
}
<commit_msg>add common html helpers<commit_after>package html

import (
	"html/template"
	"time"

	"github.com/chrisolsen/ae"
	"github.com/russross/blackfriday"
	"google.golang.org/appengine/datastore"
)

// CSS prevents any custom embedded styles from being encoded to html safe values
func CSS(s string) template.CSS {
	return template.CSS(s)
}

// Preview returns a preview of the string
func Preview(size int, val string) string {
	if len(val) <= size {
		return val
	}
	return val[:size-1] + "..."
}

// Markdown converts text in the markdown syntax to html
func Markdown(input string) interface{} {
	out := string(blackfriday.MarkdownCommon([]byte(input)))
	return template.HTML(out)
}

// Add adds the numbers
func Add(a, b int) int {
	return a + b
}

// EncodeKey encodes a datastore key
func EncodeKey(data interface{}) string {
	switch data.(type) {
	case ae.Model:
		return data.(ae.Model).Key.Encode()
	case *datastore.Key:
		return data.(*datastore.Key).Encode()
	default:
		return ""
	}
}

// Checked returns the checked attribute for positive values.
// 	<input type="checkbox" {{IsAdmin | checked}}> => <input type="checkbox" checked="checked">
func Checked(checked bool) template.HTMLAttr {
	if checked {
		return template.HTMLAttr("checked")
	}
	return ""
}

func Selected(selected bool) template.HTMLAttr {
	if selected {
		return template.HTMLAttr("selected")
	}
	return ""
}

// Disabled returns the checked attribute for positive values.
// 	<button type="submit" {{HasError | disabled}}>Save</button> => <button type="submit" disabled="">Save</button>
// 	or
// 	<button type="submit" {{ValidationError | disabled}}>Save</button> => <button type="submit" disabled="">Save</button>
func Disabled(err interface{}) template.HTMLAttr {
	d := template.HTMLAttr("disabled")
	switch err.(type) {
	case string:
		if len(err.(string)) == 0 {
			return ""
		}
		return d
	case error:
		return d
	default:
		return ""
	}
}

// Show returns an empty style attr or display:none
//
 =>
some hidden text

func Show(display bool) template.CSS {
	if display {
		return template.CSS("")
	}
	return template.CSS("display:none;")
}

func Hide(display bool) template.CSS {
	return Show(!display)
}

// KeyEqual allow for *datastore.Key comparison
func KeyEqual(a, b *datastore.Key) bool {
	return a.Equal(b)
}

// Timestamp formats the time to the RFC3339 layout
func Timestamp(d time.Time) string {
	return d.Format(time.RFC3339)
}
<|endoftext|>


<commit_before>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 gcsproxy

import (
	"fmt"

	"github.com/googlecloudplatform/gcsfuse/lease"
	"github.com/jacobsa/gcloud/gcs"
	"golang.org/x/net/context"
)

// Given an object record and content that was originally derived from that
// object's contents (and potentially modified):
//
// *   If the content has not been modified, return a nil read lease and a nil
//     new object.
//
// *   Otherwise, write out a new generation in the bucket (failing with
//     *gcs.PreconditionError if the source generation is no longer current)
//     and return a read lease for that object's contents.
//
// In the second case, the MutableContent is destroyed. Otherwise, including
// when this function fails, it is guaranteed to still be valid.
func Sync(
	ctx context.Context,
	srcObject *gcs.Object,
	content MutableContent,
	bucket gcs.Bucket) (rl lease.ReadLease, o *gcs.Object, err error) {
	// Stat the content.
	sr, err := content.Stat(ctx)
	if err != nil {
		err = fmt.Errorf("Stat: %v", err)
		return
	}

	// Make sure the dirty threshold makes sense.
	if sr.DirtyThreshold > int64(srcObject.Size) {
		err = fmt.Errorf(
			"Stat returned weird DirtyThreshold field: %d vs. %d",
			sr.DirtyThreshold,
			srcObject.Size)

		return
	}

	// If the content hasn't been dirtied (i.e. it is the same size as the source
	// object, and no bytes within the source object have been dirtied), we're
	// done.
	if sr.Size == int64(srcObject.Size) &&
		sr.DirtyThreshold == sr.Size {
		return
	}

	// Otherwise, we need to create a new generation.
	o, err = bucket.CreateObject(
		ctx,
		&gcs.CreateObjectRequest{
			Name: srcObject.Name,
			Contents: &mutableContentReader{
				Ctx:     ctx,
				Content: content,
			},
			GenerationPrecondition: &srcObject.Generation,
		})

	if err != nil {
		// Special case: don't mess with precondition errors.
		if _, ok := err.(*gcs.PreconditionError); ok {
			return
		}

		err = fmt.Errorf("CreateObject: %v", err)
		return
	}

	// Yank out the contents.
	rl = content.Release().Downgrade()

	return
}

////////////////////////////////////////////////////////////////////////
// mutableContentReader
////////////////////////////////////////////////////////////////////////

// An io.Reader that wraps a MutableContent object, reading starting from a
// base offset.
type mutableContentReader struct {
	Ctx     context.Context
	Content MutableContent
	Offset  int64
}

func (mcr *mutableContentReader) Read(p []byte) (n int, err error) {
	n, err = mcr.Content.ReadAt(mcr.Ctx, p, mcr.Offset)
	mcr.Offset += int64(n)
	return
}
<commit_msg>Updated gcsproxy.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// 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 gcsproxy

import (
	"fmt"

	"github.com/googlecloudplatform/gcsfuse/lease"
	"github.com/googlecloudplatform/gcsfuse/mutable"
	"github.com/jacobsa/gcloud/gcs"
	"golang.org/x/net/context"
)

// Given an object record and content that was originally derived from that
// object's contents (and potentially modified):
//
// *   If the content has not been modified, return a nil read lease and a nil
//     new object.
//
// *   Otherwise, write out a new generation in the bucket (failing with
//     *gcs.PreconditionError if the source generation is no longer current)
//     and return a read lease for that object's contents.
//
// In the second case, the mutable.Content is destroyed. Otherwise, including
// when this function fails, it is guaranteed to still be valid.
func Sync(
	ctx context.Context,
	srcObject *gcs.Object,
	content mutable.Content,
	bucket gcs.Bucket) (rl lease.ReadLease, o *gcs.Object, err error) {
	// Stat the content.
	sr, err := content.Stat(ctx)
	if err != nil {
		err = fmt.Errorf("Stat: %v", err)
		return
	}

	// Make sure the dirty threshold makes sense.
	if sr.DirtyThreshold > int64(srcObject.Size) {
		err = fmt.Errorf(
			"Stat returned weird DirtyThreshold field: %d vs. %d",
			sr.DirtyThreshold,
			srcObject.Size)

		return
	}

	// If the content hasn't been dirtied (i.e. it is the same size as the source
	// object, and no bytes within the source object have been dirtied), we're
	// done.
	if sr.Size == int64(srcObject.Size) &&
		sr.DirtyThreshold == sr.Size {
		return
	}

	// Otherwise, we need to create a new generation.
	o, err = bucket.CreateObject(
		ctx,
		&gcs.CreateObjectRequest{
			Name: srcObject.Name,
			Contents: &mutableContentReader{
				Ctx:     ctx,
				Content: content,
			},
			GenerationPrecondition: &srcObject.Generation,
		})

	if err != nil {
		// Special case: don't mess with precondition errors.
		if _, ok := err.(*gcs.PreconditionError); ok {
			return
		}

		err = fmt.Errorf("CreateObject: %v", err)
		return
	}

	// Yank out the contents.
	rl = content.Release().Downgrade()

	return
}

////////////////////////////////////////////////////////////////////////
// mutableContentReader
////////////////////////////////////////////////////////////////////////

// An io.Reader that wraps a mutable.Content object, reading starting from a
// base offset.
type mutableContentReader struct {
	Ctx     context.Context
	Content mutable.Content
	Offset  int64
}

func (mcr *mutableContentReader) Read(p []byte) (n int, err error) {
	n, err = mcr.Content.ReadAt(mcr.Ctx, p, mcr.Offset)
	mcr.Offset += int64(n)
	return
}
<|endoftext|>


<commit_before>// Copyright 2014 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 main

import (
	"bufio"
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"testing"
)

func loadSyms(t *testing.T) map[string]string {
	if runtime.GOOS == "nacl" {
		t.Skip("skipping on nacl")
	}

	cmd := exec.Command("go", "tool", "nm", os.Args[0])
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("go tool nm %v: %v\n%s", os.Args[0], err, string(out))
	}
	syms := make(map[string]string)
	scanner := bufio.NewScanner(bytes.NewReader(out))
	for scanner.Scan() {
		f := strings.Fields(scanner.Text())
		if len(f) < 3 {
			continue
		}
		syms[f[2]] = f[0]
	}
	if err := scanner.Err(); err != nil {
		t.Fatalf("error reading symbols: %v", err)
	}
	return syms
}

func runObjDump(t *testing.T, exe, startaddr, endaddr string) (path, lineno string) {
	if runtime.GOOS == "nacl" {
		t.Skip("skipping on nacl")
	}

	cmd := exec.Command(exe, os.Args[0], startaddr, endaddr)
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("go tool objdump %v: %v\n%s", os.Args[0], err, string(out))
	}
	f := strings.Split(string(out), "\n")
	if len(f) < 1 {
		t.Fatal("objdump output must have at least one line")
	}
	pathAndLineNo := f[0]
	f = strings.Split(pathAndLineNo, ":")
	if runtime.GOOS == "windows" {
		switch len(f) {
		case 2:
			return f[0], f[1]
		case 3:
			return f[0] + ":" + f[1], f[2]
		default:
			t.Fatalf("no line number found in %q", pathAndLineNo)
		}
	}
	if len(f) != 2 {
		t.Fatalf("no line number found in %q", pathAndLineNo)
	}
	return f[0], f[1]
}

func testObjDump(t *testing.T, exe, startaddr, endaddr string, line int) {
	srcPath, srcLineNo := runObjDump(t, exe, startaddr, endaddr)
	fi1, err := os.Stat("objdump_test.go")
	if err != nil {
		t.Fatalf("Stat failed: %v", err)
	}
	fi2, err := os.Stat(srcPath)
	if err != nil {
		t.Fatalf("Stat failed: %v", err)
	}
	if !os.SameFile(fi1, fi2) {
		t.Fatalf("objdump_test.go and %s are not same file", srcPath)
	}
	if srcLineNo != fmt.Sprint(line) {
		t.Fatalf("line number = %v; want %d", srcLineNo, line)
	}
}

func TestObjDump(t *testing.T) {
	_, _, line, _ := runtime.Caller(0)
	syms := loadSyms(t)

	tmp, exe := buildObjdump(t)
	defer os.RemoveAll(tmp)

	startaddr := syms["cmd/objdump.TestObjDump"]
	addr, err := strconv.ParseUint(startaddr, 16, 64)
	if err != nil {
		t.Fatalf("invalid start address %v: %v", startaddr, err)
	}
	endaddr := fmt.Sprintf("%x", addr+10)
	testObjDump(t, exe, startaddr, endaddr, line-1)
	testObjDump(t, exe, "0x"+startaddr, "0x"+endaddr, line-1)
}

func buildObjdump(t *testing.T) (tmp, exe string) {
	if runtime.GOOS == "nacl" {
		t.Skip("skipping on nacl")
	}

	tmp, err := ioutil.TempDir("", "TestObjDump")
	if err != nil {
		t.Fatal("TempDir failed: ", err)
	}

	exe = filepath.Join(tmp, "testobjdump.exe")
	out, err := exec.Command("go", "build", "-o", exe, "cmd/objdump").CombinedOutput()
	if err != nil {
		os.RemoveAll(tmp)
		t.Fatalf("go build -o %v cmd/objdump: %v\n%s", exe, err, string(out))
	}
	return
}

var x86Need = []string{
	"fmthello.go:6",
	"TEXT main.main(SB)",
	"JMP main.main(SB)",
	"CALL fmt.Println(SB)",
	"RET",
}

var armNeed = []string{
	"fmthello.go:6",
	"TEXT main.main(SB)",
	"B.LS main.main(SB)",
	"BL fmt.Println(SB)",
	"RET",
}

// objdump is fully cross platform: it can handle binaries
// from any known operating system and architecture.
// We could in principle add binaries to testdata and check
// all the supported systems during this test. However, the
// binaries would be about 1 MB each, and we don't want to
// add that much junk to the hg repository. Instead, build a
// binary for the current system (only) and test that objdump
// can handle that one.

func TestDisasm(t *testing.T) {
	tmp, exe := buildObjdump(t)
	defer os.RemoveAll(tmp)

	hello := filepath.Join(tmp, "hello.exe")
	out, err := exec.Command("go", "build", "-o", hello, "testdata/fmthello.go").CombinedOutput()
	if err != nil {
		t.Fatalf("go build fmthello.go: %v\n%s", err, out)
	}
	need := []string{
		"fmthello.go:6",
		"TEXT main.main(SB)",
	}
	switch runtime.GOARCH {
	case "amd64", "386":
		need = append(need, x86Need...)
	case "arm":
		need = append(need, armNeed...)
	}

	out, err = exec.Command(exe, "-s", "main.main", hello).CombinedOutput()
	if err != nil {
		t.Fatalf("objdump fmthello.exe: %v\n%s", err, out)
	}

	text := string(out)
	ok := true
	for _, s := range need {
		if !strings.Contains(text, s) {
			t.Errorf("disassembly missing '%s'", s)
			ok = false
		}
	}
	if !ok {
		t.Logf("full disassembly:\n%s", text)
	}
}
<commit_msg>cmd/objdump: skip test on android (no Go tool)<commit_after>// Copyright 2014 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 main

import (
	"bufio"
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"testing"
)

func loadSyms(t *testing.T) map[string]string {
	switch runtime.GOOS {
	case "android", "nacl":
		t.Skipf("skipping on %s", runtime.GOOS)
	}

	cmd := exec.Command("go", "tool", "nm", os.Args[0])
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("go tool nm %v: %v\n%s", os.Args[0], err, string(out))
	}
	syms := make(map[string]string)
	scanner := bufio.NewScanner(bytes.NewReader(out))
	for scanner.Scan() {
		f := strings.Fields(scanner.Text())
		if len(f) < 3 {
			continue
		}
		syms[f[2]] = f[0]
	}
	if err := scanner.Err(); err != nil {
		t.Fatalf("error reading symbols: %v", err)
	}
	return syms
}

func runObjDump(t *testing.T, exe, startaddr, endaddr string) (path, lineno string) {
	switch runtime.GOOS {
	case "android", "nacl":
		t.Skipf("skipping on %s", runtime.GOOS)
	}

	cmd := exec.Command(exe, os.Args[0], startaddr, endaddr)
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("go tool objdump %v: %v\n%s", os.Args[0], err, string(out))
	}
	f := strings.Split(string(out), "\n")
	if len(f) < 1 {
		t.Fatal("objdump output must have at least one line")
	}
	pathAndLineNo := f[0]
	f = strings.Split(pathAndLineNo, ":")
	if runtime.GOOS == "windows" {
		switch len(f) {
		case 2:
			return f[0], f[1]
		case 3:
			return f[0] + ":" + f[1], f[2]
		default:
			t.Fatalf("no line number found in %q", pathAndLineNo)
		}
	}
	if len(f) != 2 {
		t.Fatalf("no line number found in %q", pathAndLineNo)
	}
	return f[0], f[1]
}

func testObjDump(t *testing.T, exe, startaddr, endaddr string, line int) {
	srcPath, srcLineNo := runObjDump(t, exe, startaddr, endaddr)
	fi1, err := os.Stat("objdump_test.go")
	if err != nil {
		t.Fatalf("Stat failed: %v", err)
	}
	fi2, err := os.Stat(srcPath)
	if err != nil {
		t.Fatalf("Stat failed: %v", err)
	}
	if !os.SameFile(fi1, fi2) {
		t.Fatalf("objdump_test.go and %s are not same file", srcPath)
	}
	if srcLineNo != fmt.Sprint(line) {
		t.Fatalf("line number = %v; want %d", srcLineNo, line)
	}
}

func TestObjDump(t *testing.T) {
	_, _, line, _ := runtime.Caller(0)
	syms := loadSyms(t)

	tmp, exe := buildObjdump(t)
	defer os.RemoveAll(tmp)

	startaddr := syms["cmd/objdump.TestObjDump"]
	addr, err := strconv.ParseUint(startaddr, 16, 64)
	if err != nil {
		t.Fatalf("invalid start address %v: %v", startaddr, err)
	}
	endaddr := fmt.Sprintf("%x", addr+10)
	testObjDump(t, exe, startaddr, endaddr, line-1)
	testObjDump(t, exe, "0x"+startaddr, "0x"+endaddr, line-1)
}

func buildObjdump(t *testing.T) (tmp, exe string) {
	switch runtime.GOOS {
	case "android", "nacl":
		t.Skipf("skipping on %s", runtime.GOOS)
	}

	tmp, err := ioutil.TempDir("", "TestObjDump")
	if err != nil {
		t.Fatal("TempDir failed: ", err)
	}

	exe = filepath.Join(tmp, "testobjdump.exe")
	out, err := exec.Command("go", "build", "-o", exe, "cmd/objdump").CombinedOutput()
	if err != nil {
		os.RemoveAll(tmp)
		t.Fatalf("go build -o %v cmd/objdump: %v\n%s", exe, err, string(out))
	}
	return
}

var x86Need = []string{
	"fmthello.go:6",
	"TEXT main.main(SB)",
	"JMP main.main(SB)",
	"CALL fmt.Println(SB)",
	"RET",
}

var armNeed = []string{
	"fmthello.go:6",
	"TEXT main.main(SB)",
	"B.LS main.main(SB)",
	"BL fmt.Println(SB)",
	"RET",
}

// objdump is fully cross platform: it can handle binaries
// from any known operating system and architecture.
// We could in principle add binaries to testdata and check
// all the supported systems during this test. However, the
// binaries would be about 1 MB each, and we don't want to
// add that much junk to the hg repository. Instead, build a
// binary for the current system (only) and test that objdump
// can handle that one.

func TestDisasm(t *testing.T) {
	tmp, exe := buildObjdump(t)
	defer os.RemoveAll(tmp)

	hello := filepath.Join(tmp, "hello.exe")
	out, err := exec.Command("go", "build", "-o", hello, "testdata/fmthello.go").CombinedOutput()
	if err != nil {
		t.Fatalf("go build fmthello.go: %v\n%s", err, out)
	}
	need := []string{
		"fmthello.go:6",
		"TEXT main.main(SB)",
	}
	switch runtime.GOARCH {
	case "amd64", "386":
		need = append(need, x86Need...)
	case "arm":
		need = append(need, armNeed...)
	}

	out, err = exec.Command(exe, "-s", "main.main", hello).CombinedOutput()
	if err != nil {
		t.Fatalf("objdump fmthello.exe: %v\n%s", err, out)
	}

	text := string(out)
	ok := true
	for _, s := range need {
		if !strings.Contains(text, s) {
			t.Errorf("disassembly missing '%s'", s)
			ok = false
		}
	}
	if !ok {
		t.Logf("full disassembly:\n%s", text)
	}
}
<|endoftext|>


<commit_before>package http3

import (
	"bytes"
	"context"
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"runtime"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/lucas-clemente/quic-go"
	"github.com/lucas-clemente/quic-go/internal/protocol"
	"github.com/lucas-clemente/quic-go/internal/utils"
	"github.com/marten-seemann/qpack"
)

// allows mocking of quic.Listen and quic.ListenAddr
var (
	quicListen     = quic.Listen
	quicListenAddr = quic.ListenAddr
)

const nextProtoH3 = "h3-22"

// Server is a HTTP2 server listening for QUIC connections.
type Server struct {
	*http.Server

	// By providing a quic.Config, it is possible to set parameters of the QUIC connection.
	// If nil, it uses reasonable default values.
	QuicConfig *quic.Config

	port uint32 // used atomically

	listenerMutex sync.Mutex
	listener      quic.Listener
	closed        bool

	supportedVersionsAsString string

	logger utils.Logger
}

// ListenAndServe listens on the UDP address s.Addr and calls s.Handler to handle HTTP/3 requests on incoming connections.
func (s *Server) ListenAndServe() error {
	if s.Server == nil {
		return errors.New("use of http3.Server without http.Server")
	}
	return s.serveImpl(s.TLSConfig, nil)
}

// ListenAndServeTLS listens on the UDP address s.Addr and calls s.Handler to handle HTTP/3 requests on incoming connections.
func (s *Server) ListenAndServeTLS(certFile, keyFile string) error {
	var err error
	certs := make([]tls.Certificate, 1)
	certs[0], err = tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return err
	}
	// We currently only use the cert-related stuff from tls.Config,
	// so we don't need to make a full copy.
	config := &tls.Config{
		Certificates: certs,
	}
	return s.serveImpl(config, nil)
}

// Serve an existing UDP connection.
func (s *Server) Serve(conn net.PacketConn) error {
	return s.serveImpl(s.TLSConfig, conn)
}

func (s *Server) serveImpl(tlsConf *tls.Config, conn net.PacketConn) error {
	if s.Server == nil {
		return errors.New("use of http3.Server without http.Server")
	}
	s.logger = utils.DefaultLogger.WithPrefix("server")
	s.listenerMutex.Lock()
	if s.closed {
		s.listenerMutex.Unlock()
		return errors.New("Server is already closed")
	}
	if s.listener != nil {
		s.listenerMutex.Unlock()
		return errors.New("ListenAndServe may only be called once")
	}

	if tlsConf == nil {
		tlsConf = &tls.Config{}
	} else {
		tlsConf = tlsConf.Clone()
	}
	// Replace existing ALPNs by H3
	tlsConf.NextProtos = []string{nextProtoH3}
	if tlsConf.GetConfigForClient != nil {
		getConfigForClient := tlsConf.GetConfigForClient
		tlsConf.GetConfigForClient = func(ch *tls.ClientHelloInfo) (*tls.Config, error) {
			conf, err := getConfigForClient(ch)
			if err != nil || conf == nil {
				return conf, err
			}
			conf.NextProtos = []string{nextProtoH3}
			return conf, nil
		}
	}

	var ln quic.Listener
	var err error
	if conn == nil {
		ln, err = quicListenAddr(s.Addr, tlsConf, s.QuicConfig)
	} else {
		ln, err = quicListen(conn, tlsConf, s.QuicConfig)
	}
	if err != nil {
		s.listenerMutex.Unlock()
		return err
	}
	s.listener = ln
	s.listenerMutex.Unlock()

	for {
		sess, err := ln.Accept(context.Background())
		if err != nil {
			return err
		}
		go s.handleConn(sess)
	}
}

func (s *Server) handleConn(sess quic.Session) {
	// TODO: accept control streams
	decoder := qpack.NewDecoder(nil)

	// send a SETTINGS frame
	str, err := sess.OpenUniStream()
	if err != nil {
		s.logger.Debugf("Opening the control stream failed.")
		return
	}
	buf := bytes.NewBuffer([]byte{0})
	(&settingsFrame{}).Write(buf)
	str.Write(buf.Bytes())

	for {
		str, err := sess.AcceptStream(context.Background())
		if err != nil {
			s.logger.Debugf("Accepting stream failed: %s", err)
			return
		}
		// TODO: handle error
		go func() {
			if err := s.handleRequest(str, decoder); err != nil {
				s.logger.Debugf("Handling request failed: %s", err)
				str.CancelWrite(quic.ErrorCode(errorGeneralProtocolError))
				return
			}
			str.Close()
		}()
	}
}

func (s *Server) maxHeaderBytes() uint64 {
	if s.Server.MaxHeaderBytes <= 0 {
		return http.DefaultMaxHeaderBytes
	}
	return uint64(s.Server.MaxHeaderBytes)
}

// TODO: improve error handling.
// Most (but not all) of the errors occurring here are connection-level erros.
func (s *Server) handleRequest(str quic.Stream, decoder *qpack.Decoder) error {
	frame, err := parseNextFrame(str)
	if err != nil {
		str.CancelWrite(quic.ErrorCode(errorRequestCanceled))
		return err
	}
	hf, ok := frame.(*headersFrame)
	if !ok {
		str.CancelWrite(quic.ErrorCode(errorUnexpectedFrame))
		return errors.New("expected first frame to be a headers frame")
	}
	if hf.Length > s.maxHeaderBytes() {
		str.CancelWrite(quic.ErrorCode(errorLimitExceeded))
		return fmt.Errorf("Headers frame too large: %d bytes (max: %d)", hf.Length, s.maxHeaderBytes())
	}
	headerBlock := make([]byte, hf.Length)
	if _, err := io.ReadFull(str, headerBlock); err != nil {
		str.CancelWrite(quic.ErrorCode(errorIncompleteRequest))
		return err
	}
	hfs, err := decoder.DecodeFull(headerBlock)
	if err != nil {
		// TODO: use the right error code
		str.CancelWrite(quic.ErrorCode(errorGeneralProtocolError))
		return err
	}
	req, err := requestFromHeaders(hfs)
	if err != nil {
		return err
	}
	req.Body = newRequestBody(str)

	if s.logger.Debug() {
		s.logger.Infof("%s %s%s, on stream %d", req.Method, req.Host, req.RequestURI, str.StreamID())
	} else {
		s.logger.Infof("%s %s%s", req.Method, req.Host, req.RequestURI)
	}

	req = req.WithContext(str.Context())
	responseWriter := newResponseWriter(str, s.logger)
	handler := s.Handler
	if handler == nil {
		handler = http.DefaultServeMux
	}

	var panicked, readEOF bool
	func() {
		defer func() {
			if p := recover(); p != nil {
				// Copied from net/http/server.go
				const size = 64 << 10
				buf := make([]byte, size)
				buf = buf[:runtime.Stack(buf, false)]
				s.logger.Errorf("http: panic serving: %v\n%s", p, buf)
				panicked = true
			}
		}()
		handler.ServeHTTP(responseWriter, req)
		// read the eof
		if _, err = str.Read([]byte{0}); err == io.EOF {
			readEOF = true
		}
	}()

	if panicked {
		responseWriter.WriteHeader(500)
	} else {
		responseWriter.WriteHeader(200)
	}

	if !readEOF {
		str.CancelRead(quic.ErrorCode(errorEarlyResponse))
	}
	return nil
}

// Close the server immediately, aborting requests and sending CONNECTION_CLOSE frames to connected clients.
// Close in combination with ListenAndServe() (instead of Serve()) may race if it is called before a UDP socket is established.
func (s *Server) Close() error {
	s.listenerMutex.Lock()
	defer s.listenerMutex.Unlock()
	s.closed = true
	if s.listener != nil {
		err := s.listener.Close()
		s.listener = nil
		return err
	}
	return nil
}

// CloseGracefully shuts down the server gracefully. The server sends a GOAWAY frame first, then waits for either timeout to trigger, or for all running requests to complete.
// CloseGracefully in combination with ListenAndServe() (instead of Serve()) may race if it is called before a UDP socket is established.
func (s *Server) CloseGracefully(timeout time.Duration) error {
	// TODO: implement
	return nil
}

// SetQuicHeaders can be used to set the proper headers that announce that this server supports QUIC.
// The values that are set depend on the port information from s.Server.Addr, and currently look like this (if Addr has port 443):
//  Alt-Svc: quic=":443"; ma=2592000; v="33,32,31,30"
func (s *Server) SetQuicHeaders(hdr http.Header) error {
	port := atomic.LoadUint32(&s.port)

	if port == 0 {
		// Extract port from s.Server.Addr
		_, portStr, err := net.SplitHostPort(s.Server.Addr)
		if err != nil {
			return err
		}
		portInt, err := net.LookupPort("tcp", portStr)
		if err != nil {
			return err
		}
		port = uint32(portInt)
		atomic.StoreUint32(&s.port, port)
	}

	if s.supportedVersionsAsString == "" {
		var versions []string
		for _, v := range protocol.SupportedVersions {
			versions = append(versions, v.ToAltSvc())
		}
		s.supportedVersionsAsString = strings.Join(versions, ",")
	}

	hdr.Add("Alt-Svc", fmt.Sprintf(`quic=":%d"; ma=2592000; v="%s"`, port, s.supportedVersionsAsString))

	return nil
}

// ListenAndServeQUIC listens on the UDP network address addr and calls the
// handler for HTTP/3 requests on incoming connections. http.DefaultServeMux is
// used when handler is nil.
func ListenAndServeQUIC(addr, certFile, keyFile string, handler http.Handler) error {
	server := &Server{
		Server: &http.Server{
			Addr:    addr,
			Handler: handler,
		},
	}
	return server.ListenAndServeTLS(certFile, keyFile)
}

// ListenAndServe listens on the given network address for both, TLS and QUIC
// connetions in parallel. It returns if one of the two returns an error.
// http.DefaultServeMux is used when handler is nil.
// The correct Alt-Svc headers for QUIC are set.
func ListenAndServe(addr, certFile, keyFile string, handler http.Handler) error {
	// Load certs
	var err error
	certs := make([]tls.Certificate, 1)
	certs[0], err = tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return err
	}
	// We currently only use the cert-related stuff from tls.Config,
	// so we don't need to make a full copy.
	config := &tls.Config{
		Certificates: certs,
	}

	// Open the listeners
	udpAddr, err := net.ResolveUDPAddr("udp", addr)
	if err != nil {
		return err
	}
	udpConn, err := net.ListenUDP("udp", udpAddr)
	if err != nil {
		return err
	}
	defer udpConn.Close()

	tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
	if err != nil {
		return err
	}
	tcpConn, err := net.ListenTCP("tcp", tcpAddr)
	if err != nil {
		return err
	}
	defer tcpConn.Close()

	tlsConn := tls.NewListener(tcpConn, config)
	defer tlsConn.Close()

	// Start the servers
	httpServer := &http.Server{
		Addr:      addr,
		TLSConfig: config,
	}

	quicServer := &Server{
		Server: httpServer,
	}

	if handler == nil {
		handler = http.DefaultServeMux
	}
	httpServer.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		quicServer.SetQuicHeaders(w.Header())
		handler.ServeHTTP(w, r)
	})

	hErr := make(chan error)
	qErr := make(chan error)
	go func() {
		hErr <- httpServer.Serve(tlsConn)
	}()
	go func() {
		qErr <- quicServer.Serve(udpConn)
	}()

	select {
	case err := <-hErr:
		quicServer.Close()
		return err
	case err := <-qErr:
		// Cannot close the HTTP server or wait for requests to complete properly :/
		return err
	}
}
<commit_msg>document that closing a http3.Server created by Serve doesn't close conn<commit_after>package http3

import (
	"bytes"
	"context"
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"runtime"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/lucas-clemente/quic-go"
	"github.com/lucas-clemente/quic-go/internal/protocol"
	"github.com/lucas-clemente/quic-go/internal/utils"
	"github.com/marten-seemann/qpack"
)

// allows mocking of quic.Listen and quic.ListenAddr
var (
	quicListen     = quic.Listen
	quicListenAddr = quic.ListenAddr
)

const nextProtoH3 = "h3-22"

// Server is a HTTP2 server listening for QUIC connections.
type Server struct {
	*http.Server

	// By providing a quic.Config, it is possible to set parameters of the QUIC connection.
	// If nil, it uses reasonable default values.
	QuicConfig *quic.Config

	port uint32 // used atomically

	listenerMutex sync.Mutex
	listener      quic.Listener
	closed        bool

	supportedVersionsAsString string

	logger utils.Logger
}

// ListenAndServe listens on the UDP address s.Addr and calls s.Handler to handle HTTP/3 requests on incoming connections.
func (s *Server) ListenAndServe() error {
	if s.Server == nil {
		return errors.New("use of http3.Server without http.Server")
	}
	return s.serveImpl(s.TLSConfig, nil)
}

// ListenAndServeTLS listens on the UDP address s.Addr and calls s.Handler to handle HTTP/3 requests on incoming connections.
func (s *Server) ListenAndServeTLS(certFile, keyFile string) error {
	var err error
	certs := make([]tls.Certificate, 1)
	certs[0], err = tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return err
	}
	// We currently only use the cert-related stuff from tls.Config,
	// so we don't need to make a full copy.
	config := &tls.Config{
		Certificates: certs,
	}
	return s.serveImpl(config, nil)
}

// Serve an existing UDP connection.
// It is possible to reuse the same connection for outgoing connections.
// Closing the server does not close the packet conn.
func (s *Server) Serve(conn net.PacketConn) error {
	return s.serveImpl(s.TLSConfig, conn)
}

func (s *Server) serveImpl(tlsConf *tls.Config, conn net.PacketConn) error {
	if s.Server == nil {
		return errors.New("use of http3.Server without http.Server")
	}
	s.logger = utils.DefaultLogger.WithPrefix("server")
	s.listenerMutex.Lock()
	if s.closed {
		s.listenerMutex.Unlock()
		return errors.New("Server is already closed")
	}
	if s.listener != nil {
		s.listenerMutex.Unlock()
		return errors.New("ListenAndServe may only be called once")
	}

	if tlsConf == nil {
		tlsConf = &tls.Config{}
	} else {
		tlsConf = tlsConf.Clone()
	}
	// Replace existing ALPNs by H3
	tlsConf.NextProtos = []string{nextProtoH3}
	if tlsConf.GetConfigForClient != nil {
		getConfigForClient := tlsConf.GetConfigForClient
		tlsConf.GetConfigForClient = func(ch *tls.ClientHelloInfo) (*tls.Config, error) {
			conf, err := getConfigForClient(ch)
			if err != nil || conf == nil {
				return conf, err
			}
			conf.NextProtos = []string{nextProtoH3}
			return conf, nil
		}
	}

	var ln quic.Listener
	var err error
	if conn == nil {
		ln, err = quicListenAddr(s.Addr, tlsConf, s.QuicConfig)
	} else {
		ln, err = quicListen(conn, tlsConf, s.QuicConfig)
	}
	if err != nil {
		s.listenerMutex.Unlock()
		return err
	}
	s.listener = ln
	s.listenerMutex.Unlock()

	for {
		sess, err := ln.Accept(context.Background())
		if err != nil {
			return err
		}
		go s.handleConn(sess)
	}
}

func (s *Server) handleConn(sess quic.Session) {
	// TODO: accept control streams
	decoder := qpack.NewDecoder(nil)

	// send a SETTINGS frame
	str, err := sess.OpenUniStream()
	if err != nil {
		s.logger.Debugf("Opening the control stream failed.")
		return
	}
	buf := bytes.NewBuffer([]byte{0})
	(&settingsFrame{}).Write(buf)
	str.Write(buf.Bytes())

	for {
		str, err := sess.AcceptStream(context.Background())
		if err != nil {
			s.logger.Debugf("Accepting stream failed: %s", err)
			return
		}
		// TODO: handle error
		go func() {
			if err := s.handleRequest(str, decoder); err != nil {
				s.logger.Debugf("Handling request failed: %s", err)
				str.CancelWrite(quic.ErrorCode(errorGeneralProtocolError))
				return
			}
			str.Close()
		}()
	}
}

func (s *Server) maxHeaderBytes() uint64 {
	if s.Server.MaxHeaderBytes <= 0 {
		return http.DefaultMaxHeaderBytes
	}
	return uint64(s.Server.MaxHeaderBytes)
}

// TODO: improve error handling.
// Most (but not all) of the errors occurring here are connection-level erros.
func (s *Server) handleRequest(str quic.Stream, decoder *qpack.Decoder) error {
	frame, err := parseNextFrame(str)
	if err != nil {
		str.CancelWrite(quic.ErrorCode(errorRequestCanceled))
		return err
	}
	hf, ok := frame.(*headersFrame)
	if !ok {
		str.CancelWrite(quic.ErrorCode(errorUnexpectedFrame))
		return errors.New("expected first frame to be a headers frame")
	}
	if hf.Length > s.maxHeaderBytes() {
		str.CancelWrite(quic.ErrorCode(errorLimitExceeded))
		return fmt.Errorf("Headers frame too large: %d bytes (max: %d)", hf.Length, s.maxHeaderBytes())
	}
	headerBlock := make([]byte, hf.Length)
	if _, err := io.ReadFull(str, headerBlock); err != nil {
		str.CancelWrite(quic.ErrorCode(errorIncompleteRequest))
		return err
	}
	hfs, err := decoder.DecodeFull(headerBlock)
	if err != nil {
		// TODO: use the right error code
		str.CancelWrite(quic.ErrorCode(errorGeneralProtocolError))
		return err
	}
	req, err := requestFromHeaders(hfs)
	if err != nil {
		return err
	}
	req.Body = newRequestBody(str)

	if s.logger.Debug() {
		s.logger.Infof("%s %s%s, on stream %d", req.Method, req.Host, req.RequestURI, str.StreamID())
	} else {
		s.logger.Infof("%s %s%s", req.Method, req.Host, req.RequestURI)
	}

	req = req.WithContext(str.Context())
	responseWriter := newResponseWriter(str, s.logger)
	handler := s.Handler
	if handler == nil {
		handler = http.DefaultServeMux
	}

	var panicked, readEOF bool
	func() {
		defer func() {
			if p := recover(); p != nil {
				// Copied from net/http/server.go
				const size = 64 << 10
				buf := make([]byte, size)
				buf = buf[:runtime.Stack(buf, false)]
				s.logger.Errorf("http: panic serving: %v\n%s", p, buf)
				panicked = true
			}
		}()
		handler.ServeHTTP(responseWriter, req)
		// read the eof
		if _, err = str.Read([]byte{0}); err == io.EOF {
			readEOF = true
		}
	}()

	if panicked {
		responseWriter.WriteHeader(500)
	} else {
		responseWriter.WriteHeader(200)
	}

	if !readEOF {
		str.CancelRead(quic.ErrorCode(errorEarlyResponse))
	}
	return nil
}

// Close the server immediately, aborting requests and sending CONNECTION_CLOSE frames to connected clients.
// Close in combination with ListenAndServe() (instead of Serve()) may race if it is called before a UDP socket is established.
func (s *Server) Close() error {
	s.listenerMutex.Lock()
	defer s.listenerMutex.Unlock()
	s.closed = true
	if s.listener != nil {
		err := s.listener.Close()
		s.listener = nil
		return err
	}
	return nil
}

// CloseGracefully shuts down the server gracefully. The server sends a GOAWAY frame first, then waits for either timeout to trigger, or for all running requests to complete.
// CloseGracefully in combination with ListenAndServe() (instead of Serve()) may race if it is called before a UDP socket is established.
func (s *Server) CloseGracefully(timeout time.Duration) error {
	// TODO: implement
	return nil
}

// SetQuicHeaders can be used to set the proper headers that announce that this server supports QUIC.
// The values that are set depend on the port information from s.Server.Addr, and currently look like this (if Addr has port 443):
//  Alt-Svc: quic=":443"; ma=2592000; v="33,32,31,30"
func (s *Server) SetQuicHeaders(hdr http.Header) error {
	port := atomic.LoadUint32(&s.port)

	if port == 0 {
		// Extract port from s.Server.Addr
		_, portStr, err := net.SplitHostPort(s.Server.Addr)
		if err != nil {
			return err
		}
		portInt, err := net.LookupPort("tcp", portStr)
		if err != nil {
			return err
		}
		port = uint32(portInt)
		atomic.StoreUint32(&s.port, port)
	}

	if s.supportedVersionsAsString == "" {
		var versions []string
		for _, v := range protocol.SupportedVersions {
			versions = append(versions, v.ToAltSvc())
		}
		s.supportedVersionsAsString = strings.Join(versions, ",")
	}

	hdr.Add("Alt-Svc", fmt.Sprintf(`quic=":%d"; ma=2592000; v="%s"`, port, s.supportedVersionsAsString))

	return nil
}

// ListenAndServeQUIC listens on the UDP network address addr and calls the
// handler for HTTP/3 requests on incoming connections. http.DefaultServeMux is
// used when handler is nil.
func ListenAndServeQUIC(addr, certFile, keyFile string, handler http.Handler) error {
	server := &Server{
		Server: &http.Server{
			Addr:    addr,
			Handler: handler,
		},
	}
	return server.ListenAndServeTLS(certFile, keyFile)
}

// ListenAndServe listens on the given network address for both, TLS and QUIC
// connetions in parallel. It returns if one of the two returns an error.
// http.DefaultServeMux is used when handler is nil.
// The correct Alt-Svc headers for QUIC are set.
func ListenAndServe(addr, certFile, keyFile string, handler http.Handler) error {
	// Load certs
	var err error
	certs := make([]tls.Certificate, 1)
	certs[0], err = tls.LoadX509KeyPair(certFile, keyFile)
	if err != nil {
		return err
	}
	// We currently only use the cert-related stuff from tls.Config,
	// so we don't need to make a full copy.
	config := &tls.Config{
		Certificates: certs,
	}

	// Open the listeners
	udpAddr, err := net.ResolveUDPAddr("udp", addr)
	if err != nil {
		return err
	}
	udpConn, err := net.ListenUDP("udp", udpAddr)
	if err != nil {
		return err
	}
	defer udpConn.Close()

	tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
	if err != nil {
		return err
	}
	tcpConn, err := net.ListenTCP("tcp", tcpAddr)
	if err != nil {
		return err
	}
	defer tcpConn.Close()

	tlsConn := tls.NewListener(tcpConn, config)
	defer tlsConn.Close()

	// Start the servers
	httpServer := &http.Server{
		Addr:      addr,
		TLSConfig: config,
	}

	quicServer := &Server{
		Server: httpServer,
	}

	if handler == nil {
		handler = http.DefaultServeMux
	}
	httpServer.Handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		quicServer.SetQuicHeaders(w.Header())
		handler.ServeHTTP(w, r)
	})

	hErr := make(chan error)
	qErr := make(chan error)
	go func() {
		hErr <- httpServer.Serve(tlsConn)
	}()
	go func() {
		qErr <- quicServer.Serve(udpConn)
	}()

	select {
	case err := <-hErr:
		quicServer.Close()
		return err
	case err := <-qErr:
		// Cannot close the HTTP server or wait for requests to complete properly :/
		return err
	}
}
<|endoftext|>


<commit_before>package main

import (
	"rtos"
	"sync/atomic"
)

var (
	waterSig = make(chan struct{}, 1)
	waterCnt int32
)

func waterIRQ() {
	atomic.AddInt32(&waterCnt, 1)
	select {
	case waterSig <- struct{}{}:
	default:
	}
}

const (
	period = 500 // ms
	scale  = 40
)

func waterTask() {
	r0, r1, r2 := ssr0, ssr1, ssr2
	for {
		select {
		case <-waterSig:
		default:
			// Shuffle relays when iddle to evenly use all heaters.
			r0, r1, r2 = r2, r0, r1
			<-waterSig
		}
		ht := int(atomic.SwapInt32(&waterCnt, 0))
		if ht == 0 {
			continue
		}

		start := rtos.Uptime()
		ht *= scale // Heat time (if ht>period more than one heater is need).
		if ht > 3*period {
			// Only 3 heaters are connected.
			ht = 3 * period
			blue.On()
		}

		switch {
		case ht <= period:
			ssrPort.SetBit(r0)
			rtos.SleepUntil(start + uint64(ht)*1e6)
			ssrPort.ClearBit(r0)
		case ht <= 2*period:
			ssrPort.SetBits(1<<r0 | 1<<r1)
			rtos.SleepUntil(start + uint64(ht-period)*1e6)
			ssrPort.ClearBit(r1)
		default:
			ssrPort.SetBits(1<<r0 | 1<<r1 | 1<<r2)
			rtos.SleepUntil(start + uint64(ht-2*period)*1e6)
			ssrPort.ClearBit(r2)
		}

		blue.Off()
		rtos.SleepUntil(start + period*1e6)
		ssrPort.ClearBits(1<<r0 | 1<<r1 | 1<<r2)
	}
}
<commit_msg>power: Add comment to waterIRQ function.<commit_after>package main

import (
	"rtos"
	"sync/atomic"
)

var (
	waterSig = make(chan struct{}, 1)
	waterCnt int32
)

// waterIRQ is called for every pulse from water flow sensor.
func waterIRQ() {
	atomic.AddInt32(&waterCnt, 1)
	select {
	case waterSig <- struct{}{}:
	default:
	}
}

const (
	period = 500 // ms
	scale  = 40
)

func waterTask() {
	r0, r1, r2 := ssr0, ssr1, ssr2
	for {
		select {
		case <-waterSig:
		default:
			// Shuffle relays when iddle to evenly use all heaters.
			r0, r1, r2 = r2, r0, r1
			<-waterSig
		}
		ht := int(atomic.SwapInt32(&waterCnt, 0))
		if ht == 0 {
			continue
		}

		start := rtos.Uptime()
		ht *= scale // Heat time (if ht>period more than one heater is need).
		if ht > 3*period {
			// Only 3 heaters are connected.
			ht = 3 * period
			blue.On()
		}

		switch {
		case ht <= period:
			ssrPort.SetBit(r0)
			rtos.SleepUntil(start + uint64(ht)*1e6)
			ssrPort.ClearBit(r0)
		case ht <= 2*period:
			ssrPort.SetBits(1<<r0 | 1<<r1)
			rtos.SleepUntil(start + uint64(ht-period)*1e6)
			ssrPort.ClearBit(r1)
		default:
			ssrPort.SetBits(1<<r0 | 1<<r1 | 1<<r2)
			rtos.SleepUntil(start + uint64(ht-2*period)*1e6)
			ssrPort.ClearBit(r2)
		}

		blue.Off()
		rtos.SleepUntil(start + period*1e6)
		ssrPort.ClearBits(1<<r0 | 1<<r1 | 1<<r2)
	}
}
<|endoftext|>


<commit_before>package air

import (
	"io"
	"net/http"
	"net/http/httptest"
	"sync"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestStringSliceContains(t *testing.T) {
	assert.True(t, stringSliceContains([]string{"foo"}, "foo"))
	assert.False(t, stringSliceContains([]string{"foo"}, "bar"))
}

func TestStringSliceContainsCIly(t *testing.T) {
	assert.True(t, stringSliceContainsCIly([]string{"foo"}, "FOO"))
	assert.False(t, stringSliceContainsCIly([]string{"foo"}, "BAR"))
}

func TestSplitPathQuery(t *testing.T) {
	p, q := splitPathQuery("/foobar")
	assert.Equal(t, "/foobar", p)
	assert.Empty(t, q)

	p, q = splitPathQuery("/foobar?")
	assert.Equal(t, "/foobar", p)
	assert.Empty(t, q)

	p, q = splitPathQuery("/foobar?foo=bar")
	assert.Equal(t, "/foobar", p)
	assert.Equal(t, "foo=bar", q)
}

func fakeRRCycle(
	a *Air,
	method string,
	target string,
	body io.Reader,
) (*Request, *Response, *httptest.ResponseRecorder) {
	req := &Request{
		Air: a,

		parseRouteParamsOnce: &sync.Once{},
		parseOtherParamsOnce: &sync.Once{},
	}
	req.SetHTTPRequest(httptest.NewRequest(method, target, body))

	rec := httptest.NewRecorder()
	res := &Response{
		Air:    a,
		Status: http.StatusOK,

		req:  req,
		ohrw: rec,
	}
	res.SetHTTPResponseWriter(&responseWriter{
		r: res,
		w: rec,
	})

	req.res = res

	return req, res, rec
}
<commit_msg>test: update air_test.go<commit_after>package air

import (
	"bytes"
	"compress/gzip"
	"context"
	"errors"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httptest"
	"os"
	"path"
	"path/filepath"
	"sync"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

func TestNew(t *testing.T) {
	a := New()

	assert.Equal(t, "air", a.AppName)
	assert.Empty(t, a.MaintainerEmail)
	assert.False(t, a.DebugMode)
	assert.Equal(t, ":8080", a.Address)
	assert.Nil(t, a.HostWhitelist)
	assert.Zero(t, a.ReadTimeout)
	assert.Zero(t, a.ReadHeaderTimeout)
	assert.Zero(t, a.WriteTimeout)
	assert.Zero(t, a.IdleTimeout)
	assert.Equal(t, 1048576, a.MaxHeaderBytes)
	assert.Empty(t, a.TLSCertFile)
	assert.Empty(t, a.TLSKeyFile)
	assert.False(t, a.ACMEEnabled)
	assert.Equal(
		t,
		"https://acme-v01.api.letsencrypt.org/directory",
		a.ACMEDirectoryURL,
	)
	assert.Equal(t, "acme-certs", a.ACMECertRoot)
	assert.False(t, a.HTTPSEnforced)
	assert.Zero(t, a.WebSocketHandshakeTimeout)
	assert.Nil(t, a.WebSocketSubprotocols)
	assert.Nil(t, a.Pregases)
	assert.Nil(t, a.Gases)
	assert.IsType(t, DefaultNotFoundHandler, a.NotFoundHandler)
	assert.IsType(
		t,
		DefaultMethodNotAllowedHandler,
		a.MethodNotAllowedHandler,
	)
	assert.IsType(t, DefaultErrorHandler, a.ErrorHandler)
	assert.Nil(t, a.ErrorLogger)
	assert.False(t, a.AutoPushEnabled)
	assert.False(t, a.MinifierEnabled)
	assert.ElementsMatch(t, a.MinifierMIMETypes, []string{
		"text/html",
		"text/css",
		"application/javascript",
		"application/json",
		"application/xml",
		"image/svg+xml",
	})
	assert.False(t, a.GzipEnabled)
	assert.Equal(t, int64(1024), a.GzipMinContentLength)
	assert.ElementsMatch(t, a.GzipMIMETypes, []string{
		"text/plain",
		"text/html",
		"text/css",
		"application/javascript",
		"application/json",
		"application/xml",
		"application/toml",
		"application/yaml",
		"image/svg+xml",
	})
	assert.Equal(t, gzip.DefaultCompression, a.GzipCompressionLevel)
	assert.Equal(t, 8192, a.GzipFlushThreshold)
	assert.Equal(t, "templates", a.RendererTemplateRoot)
	assert.ElementsMatch(t, a.RendererTemplateExts, []string{".html"})
	assert.Equal(t, "{{", a.RendererTemplateLeftDelim)
	assert.Equal(t, "}}", a.RendererTemplateRightDelim)
	assert.Nil(t, a.RendererTemplateFuncMap)
	assert.False(t, a.CofferEnabled)
	assert.Equal(t, 33554432, a.CofferMaxMemoryBytes)
	assert.Equal(t, "assets", a.CofferAssetRoot)
	assert.ElementsMatch(t, a.CofferAssetExts, []string{
		".html",
		".css",
		".js",
		".json",
		".xml",
		".toml",
		".yaml",
		".yml",
		".svg",
		".jpg",
		".jpeg",
		".png",
		".gif",
	})
	assert.False(t, a.I18nEnabled)
	assert.Equal(t, "locales", a.I18nLocaleRoot)
	assert.Equal(t, "en-US", a.I18nLocaleBase)
	assert.Empty(t, a.ConfigFile)

	assert.NotNil(t, a.errorLogger)
	assert.IsType(t, &errorLogWriter{}, a.errorLogger.Writer())
	assert.Empty(t, a.errorLogger.Prefix())
	assert.Zero(t, a.errorLogger.Flags())

	assert.NotNil(t, a.server)
	assert.NotNil(t, a.router)
	assert.NotNil(t, a.binder)
	assert.NotNil(t, a.minifier)
	assert.NotNil(t, a.renderer)
	assert.NotNil(t, a.coffer)
	assert.NotNil(t, a.i18n)

	assert.NotNil(t, a.contentTypeSnifferBufferPool)
	assert.IsType(t, []byte{}, a.contentTypeSnifferBufferPool.Get())
	assert.Len(t, a.contentTypeSnifferBufferPool.Get(), 512)

	assert.NotNil(t, a.gzipWriterPool)
	assert.IsType(t, &gzip.Writer{}, a.gzipWriterPool.Get())

	assert.NotNil(t, a.reverseProxyTransport)
	assert.NotNil(t, a.reverseProxyBufferPool)
}

func TestAirGET(t *testing.T) {
	a := New()

	a.GET("/foobar", func(req *Request, res *Response) error {
		return res.WriteString("Matched [GET /foobar]")
	})

	req := httptest.NewRequest(http.MethodGet, "/foobar", nil)
	rec := httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Equal(t, "Matched [GET /foobar]", rec.Body.String())
}

func TestAirHEAD(t *testing.T) {
	a := New()

	a.HEAD("/foobar", func(req *Request, res *Response) error {
		return res.Write(nil)
	})

	req := httptest.NewRequest(http.MethodHead, "/foobar", nil)
	rec := httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Empty(t, rec.Body.String())
}

func TestAirPOST(t *testing.T) {
	a := New()

	a.POST("/foobar", func(req *Request, res *Response) error {
		return res.WriteString("Matched [POST /foobar]")
	})

	req := httptest.NewRequest(http.MethodPost, "/foobar", nil)
	rec := httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Equal(t, "Matched [POST /foobar]", rec.Body.String())
}

func TestAirPUT(t *testing.T) {
	a := New()

	a.PUT("/foobar", func(req *Request, res *Response) error {
		return res.WriteString("Matched [PUT /foobar]")
	})

	req := httptest.NewRequest(http.MethodPut, "/foobar", nil)
	rec := httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Equal(t, "Matched [PUT /foobar]", rec.Body.String())
}

func TestAirPATCH(t *testing.T) {
	a := New()

	a.PATCH("/foobar", func(req *Request, res *Response) error {
		return res.WriteString("Matched [PATCH /foobar]")
	})

	req := httptest.NewRequest(http.MethodPatch, "/foobar", nil)
	rec := httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Equal(t, "Matched [PATCH /foobar]", rec.Body.String())
}

func TestAirDELETE(t *testing.T) {
	a := New()

	a.DELETE("/foobar", func(req *Request, res *Response) error {
		return res.WriteString("Matched [DELETE /foobar]")
	})

	req := httptest.NewRequest(http.MethodDelete, "/foobar", nil)
	rec := httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Equal(t, "Matched [DELETE /foobar]", rec.Body.String())
}

func TestAirCONNECT(t *testing.T) {
	a := New()

	a.CONNECT("/foobar", func(req *Request, res *Response) error {
		return res.WriteString("Matched [CONNECT /foobar]")
	})

	req := httptest.NewRequest(http.MethodConnect, "/foobar", nil)
	rec := httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Equal(t, "Matched [CONNECT /foobar]", rec.Body.String())
}

func TestAirOPTIONS(t *testing.T) {
	a := New()

	a.OPTIONS("/foobar", func(req *Request, res *Response) error {
		return res.WriteString("Matched [OPTIONS /foobar]")
	})

	req := httptest.NewRequest(http.MethodOptions, "/foobar", nil)
	rec := httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Equal(t, "Matched [OPTIONS /foobar]", rec.Body.String())
}

func TestAirTRACE(t *testing.T) {
	a := New()

	a.TRACE("/foobar", func(req *Request, res *Response) error {
		return res.WriteString("Matched [TRACE /foobar]")
	})

	req := httptest.NewRequest(http.MethodTrace, "/foobar", nil)
	rec := httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Equal(t, "Matched [TRACE /foobar]", rec.Body.String())
}

func TestAirBATCH(t *testing.T) {
	a := New()

	a.BATCH(nil, "/foobar", func(req *Request, res *Response) error {
		return res.WriteString("Matched [* /foobar]")
	})

	req := httptest.NewRequest(http.MethodGet, "/foobar", nil)
	rec := httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Equal(t, "Matched [* /foobar]", rec.Body.String())

	req = httptest.NewRequest(http.MethodHead, "/foobar", nil)
	rec = httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Empty(t, rec.Body.String())

	req = httptest.NewRequest(http.MethodPost, "/foobar", nil)
	rec = httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Equal(t, "Matched [* /foobar]", rec.Body.String())

	req = httptest.NewRequest(http.MethodPut, "/foobar", nil)
	rec = httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Equal(t, "Matched [* /foobar]", rec.Body.String())

	req = httptest.NewRequest(http.MethodPatch, "/foobar", nil)
	rec = httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Equal(t, "Matched [* /foobar]", rec.Body.String())

	req = httptest.NewRequest(http.MethodDelete, "/foobar", nil)
	rec = httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Equal(t, "Matched [* /foobar]", rec.Body.String())

	req = httptest.NewRequest(http.MethodConnect, "/foobar", nil)
	rec = httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Equal(t, "Matched [* /foobar]", rec.Body.String())

	req = httptest.NewRequest(http.MethodOptions, "/foobar", nil)
	rec = httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Equal(t, "Matched [* /foobar]", rec.Body.String())

	req = httptest.NewRequest(http.MethodTrace, "/foobar", nil)
	rec = httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Equal(t, "Matched [* /foobar]", rec.Body.String())
}

func TestAirFILE(t *testing.T) {
	a := New()

	f, err := ioutil.TempFile("", "air.TestAirFILE")
	assert.NoError(t, err)
	assert.NotNil(t, f)
	defer os.Remove(f.Name())

	_, err = f.Write([]byte("Foobar"))
	assert.NoError(t, err)
	assert.NoError(t, f.Close())

	a.FILE("/foobar", f.Name())

	req := httptest.NewRequest(http.MethodGet, "/foobar", nil)
	rec := httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Equal(t, "Foobar", rec.Body.String())

	req = httptest.NewRequest(http.MethodHead, "/foobar", nil)
	rec = httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Empty(t, rec.Body.String())

	a.FILE("/foobar2", "nowhere")

	req = httptest.NewRequest(http.MethodGet, "/foobar2", nil)
	rec = httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusNotFound, rec.Code)
	assert.Equal(t, http.StatusText(rec.Code), rec.Body.String())

	req = httptest.NewRequest(http.MethodHead, "/foobar2", nil)
	rec = httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusNotFound, rec.Code)
	assert.Empty(t, rec.Body.String())
}

func TestAirFILES(t *testing.T) {
	a := New()

	dir, err := ioutil.TempDir("", "air.TestAirFILES")
	assert.NoError(t, err)
	assert.NotEmpty(t, dir)
	defer os.RemoveAll(dir)

	f, err := ioutil.TempFile(dir, "")
	assert.NoError(t, err)
	assert.NotNil(t, f)

	_, err = f.Write([]byte("Foobar"))
	assert.NoError(t, err)
	assert.NoError(t, f.Close())

	f2, err := ioutil.TempFile(dir, "")
	assert.NoError(t, err)
	assert.NotNil(t, f2)

	_, err = f2.Write([]byte("Foobar2"))
	assert.NoError(t, err)
	assert.NoError(t, f2.Close())

	a.FILES("/foobar", dir)

	req := httptest.NewRequest(
		http.MethodGet,
		path.Join("/foobar", filepath.Base(f.Name())),
		nil,
	)
	rec := httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Equal(t, "Foobar", rec.Body.String())

	req = httptest.NewRequest(
		http.MethodHead,
		path.Join("/foobar", filepath.Base(f.Name())),
		nil,
	)
	rec = httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Empty(t, rec.Body.String())

	req = httptest.NewRequest(http.MethodGet, "/foobar/nowhere", nil)
	rec = httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusNotFound, rec.Code)
	assert.Equal(t, http.StatusText(rec.Code), rec.Body.String())

	req = httptest.NewRequest(http.MethodHead, "/foobar/nowhere", nil)
	rec = httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusNotFound, rec.Code)
	assert.Empty(t, rec.Body.String())

	a.FILES("/foobar2/", "")

	req = httptest.NewRequest(http.MethodGet, "/foobar2/air.go", nil)
	rec = httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.NotEmpty(t, rec.Body.String())

	req = httptest.NewRequest(http.MethodHead, "/foobar2/air.go", nil)
	rec = httptest.NewRecorder()
	a.server.ServeHTTP(rec, req)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Empty(t, rec.Body.String())
}

func TestAirGroup(t *testing.T) {
	a := New()

	g := a.Group("/foobar")
	assert.NotNil(t, g)
	assert.Equal(t, a, g.Air)
	assert.Equal(t, "/foobar", g.Prefix)
	assert.Nil(t, g.Gases)
}

func TestAirServe(t *testing.T) {
	a := New()
	a.Address = "localhost:8080"

	go func() {
		time.Sleep(time.Millisecond)
		assert.NoError(t, a.Close())
	}()

	assert.Equal(t, http.ErrServerClosed, a.Serve())

	dir, err := ioutil.TempDir("", "air.TestAirServe")
	assert.NoError(t, err)
	assert.NotEmpty(t, dir)
	defer os.RemoveAll(dir)

	a = New()
	a.Address = "localhost:8080"
	a.ConfigFile = filepath.Join(dir, "config.json")

	assert.NoError(t, ioutil.WriteFile(
		a.ConfigFile,
		[]byte(`{"app_name":"foobar"}`),
		os.ModePerm,
	))

	go func() {
		time.Sleep(time.Millisecond)
		assert.NoError(t, a.Close())
	}()

	assert.Equal(t, http.ErrServerClosed, a.Serve())
	assert.Equal(t, "foobar", a.AppName)

	a = New()
	a.Address = "localhost:8080"
	a.ConfigFile = filepath.Join(dir, "config.toml")

	assert.NoError(t, ioutil.WriteFile(
		a.ConfigFile,
		[]byte(`app_name = "foobar"`),
		os.ModePerm,
	))

	go func() {
		time.Sleep(time.Millisecond)
		assert.NoError(t, a.Close())
	}()

	assert.Equal(t, http.ErrServerClosed, a.Serve())
	assert.Equal(t, "foobar", a.AppName)

	a = New()
	a.Address = "localhost:8080"
	a.ConfigFile = filepath.Join(dir, "config.yaml")

	assert.NoError(t, ioutil.WriteFile(
		a.ConfigFile,
		[]byte(`app_name: "foobar"`),
		os.ModePerm,
	))

	go func() {
		time.Sleep(time.Millisecond)
		assert.NoError(t, a.Close())
	}()

	assert.Equal(t, http.ErrServerClosed, a.Serve())
	assert.Equal(t, "foobar", a.AppName)

	a = New()
	a.Address = "localhost:8080"
	a.ConfigFile = filepath.Join(dir, "config.yml")

	assert.NoError(t, ioutil.WriteFile(
		a.ConfigFile,
		[]byte(`app_name: "foobar"`),
		os.ModePerm,
	))

	go func() {
		time.Sleep(time.Millisecond)
		assert.NoError(t, a.Close())
	}()

	assert.Equal(t, http.ErrServerClosed, a.Serve())
	assert.Equal(t, "foobar", a.AppName)

	a = New()
	a.ConfigFile = "nowhere"
	assert.True(t, os.IsNotExist(a.Serve()))

	a = New()
	a.ConfigFile = filepath.Join(dir, "config.ext")

	assert.NoError(t, ioutil.WriteFile(a.ConfigFile, nil, os.ModePerm))

	go func() {
		time.Sleep(time.Millisecond)
		assert.NoError(t, a.Close())
	}()

	assert.Equal(
		t,
		"air: unsupported configuration file extension: .ext",
		a.Serve().Error(),
	)

	a = New()
	a.ConfigFile = filepath.Join(dir, "config.json")

	assert.NoError(t, ioutil.WriteFile(
		a.ConfigFile,
		[]byte(`{"app_name":0}`),
		os.ModePerm,
	))

	go func() {
		time.Sleep(time.Millisecond)
		assert.NoError(t, a.Close())
	}()

	assert.Error(t, a.Serve())
}

func TestAirClose(t *testing.T) {
	a := New()
	a.Address = "localhost:8080"

	go func() {
		time.Sleep(time.Millisecond)
		assert.NoError(t, a.Close())
	}()

	assert.Equal(t, http.ErrServerClosed, a.Serve())
}

func TestAirShutdown(t *testing.T) {
	a := New()
	a.Address = "localhost:8080"

	go func() {
		time.Sleep(time.Millisecond)
		assert.NoError(t, a.Shutdown(context.Background()))
	}()

	assert.Equal(t, http.ErrServerClosed, a.Serve())
}

func TestWrapHTTPHandler(t *testing.T) {
	a := New()

	req, res, rec := fakeRRCycle(a, http.MethodGet, "/", nil)
	assert.NoError(t, WrapHTTPHandler(http.HandlerFunc(func(
		rw http.ResponseWriter,
		r *http.Request,
	) {
		rw.Write([]byte("Foobar"))
	}))(req, res))
	assert.Equal(t, "Foobar", rec.Body.String())
}

func TestDefaultNotFoundHandler(t *testing.T) {
	a := New()

	req, res, _ := fakeRRCycle(a, http.MethodGet, "/", nil)
	err := DefaultNotFoundHandler(req, res)
	assert.NotNil(t, err)
	assert.Equal(t, http.StatusNotFound, res.Status)
	assert.Equal(t, http.StatusText(res.Status), err.Error())
}

func TestDefaultMethodNotAllowedHandler(t *testing.T) {
	a := New()

	req, res, _ := fakeRRCycle(a, http.MethodGet, "/", nil)
	err := DefaultMethodNotAllowedHandler(req, res)
	assert.NotNil(t, err)
	assert.Equal(t, http.StatusMethodNotAllowed, res.Status)
	assert.Equal(t, http.StatusText(res.Status), err.Error())
}

func TestDefaultErrorHandler(t *testing.T) {
	a := New()

	req, res, rec := fakeRRCycle(a, http.MethodGet, "/", nil)
	res.Status = http.StatusBadRequest
	DefaultErrorHandler(errors.New("foobar"), req, res)
	assert.Equal(t, "foobar", rec.Body.String())

	req, res, rec = fakeRRCycle(a, http.MethodGet, "/", nil)
	res.Status = http.StatusInternalServerError
	DefaultErrorHandler(errors.New("foobar"), req, res)
	assert.Equal(t, http.StatusText(res.Status), rec.Body.String())

	req, res, rec = fakeRRCycle(a, http.MethodGet, "/", nil)
	assert.NoError(t, res.WriteString("everything is fine"))
	DefaultErrorHandler(errors.New("foobar"), req, res)
	assert.Equal(t, "everything is fine", rec.Body.String())
}

func TestWrapHTTPMiddleWare(t *testing.T) {
	a := New()

	req, res, rec := fakeRRCycle(a, http.MethodHead, "/", nil)
	assert.NoError(t, WrapHTTPMiddleware(func(
		next http.Handler,
	) http.Handler {
		return http.HandlerFunc(func(
			rw http.ResponseWriter,
			r *http.Request,
		) {
			r.Method = http.MethodGet
			next.ServeHTTP(rw, r)
		})
	})(func(req *Request, res *Response) error {
		return res.WriteString("Foobar")
	})(req, res))
	assert.Equal(t, http.MethodGet, req.Method)
	assert.Equal(t, http.StatusOK, rec.Code)
	assert.Equal(t, "Foobar", rec.Body.String())
}

func TestNewErrorLogWriter(t *testing.T) {
	a := New()
	elw := newErrorLogWriter(a)

	assert.NotNil(t, elw)
	assert.Equal(t, a, elw.a)
}

func TestErrorLogWriterWrite(t *testing.T) {
	a := New()
	elw := newErrorLogWriter(a)

	buf := bytes.Buffer{}

	log.SetOutput(&buf)
	n, err := elw.Write([]byte("foobar"))
	assert.Equal(t, len("air: foobar"), n)
	assert.NoError(t, err)
	assert.Contains(t, buf.String(), "air: foobar")
	log.SetOutput(os.Stderr)

	buf.Reset()

	a.ErrorLogger = log.New(&buf, "", 0)
	n, err = elw.Write([]byte("air: foobar"))
	assert.Equal(t, len("air: foobar"), n)
	assert.NoError(t, err)
	assert.Contains(t, buf.String(), "air: foobar")
}

func TestStringSliceContains(t *testing.T) {
	assert.True(t, stringSliceContains([]string{"foo"}, "foo"))
	assert.False(t, stringSliceContains([]string{"foo"}, "bar"))
}

func TestStringSliceContainsCIly(t *testing.T) {
	assert.True(t, stringSliceContainsCIly([]string{"foo"}, "FOO"))
	assert.False(t, stringSliceContainsCIly([]string{"foo"}, "BAR"))
}

func TestSplitPathQuery(t *testing.T) {
	p, q := splitPathQuery("/foobar")
	assert.Equal(t, "/foobar", p)
	assert.Empty(t, q)

	p, q = splitPathQuery("/foobar?")
	assert.Equal(t, "/foobar", p)
	assert.Empty(t, q)

	p, q = splitPathQuery("/foobar?foo=bar")
	assert.Equal(t, "/foobar", p)
	assert.Equal(t, "foo=bar", q)
}

func fakeRRCycle(
	a *Air,
	method string,
	target string,
	body io.Reader,
) (*Request, *Response, *httptest.ResponseRecorder) {
	req := &Request{
		Air: a,

		parseRouteParamsOnce: &sync.Once{},
		parseOtherParamsOnce: &sync.Once{},
	}
	req.SetHTTPRequest(httptest.NewRequest(method, target, body))

	rec := httptest.NewRecorder()
	res := &Response{
		Air:    a,
		Status: http.StatusOK,

		req:  req,
		ohrw: rec,
	}
	res.SetHTTPResponseWriter(&responseWriter{
		r: res,
		w: rec,
	})

	req.res = res

	return req, res, rec
}
<|endoftext|>


<commit_before>package logrus

import (
	"github.com/tobi/airbrake-go"
)

// AirbrakeHook to send exceptions to an exception-tracking service compatible
// with the Airbrake API. You must set:
// * airbrake.Endpoint
// * airbrake.ApiKey
// * airbrake.Environment (only sends exceptions when set to "production")
//
// Before using this hook, to send exceptions. Entries that trigger an Error,
// Fatal or Panic should now include an "Error" field to send to Airbrake.
type AirbrakeHook struct{}

func (hook *AirbrakeHook) Fire(entry *Entry) error {
	if entry.Data["error"] == nil {
		entry.Logger.WithFields(Fields{
			"source":   "airbrake",
			"endpoint": airbrake.Endpoint,
		}).Warn("Exceptions sent to Airbrake must have an 'error' key with the error")
		return nil
	}

	err := airbrake.Notify(entry.Data["error"].(error))
	if err != nil {
		entry.Logger.WithFields(Fields{
			"source":   "airbrake",
			"endpoint": airbrake.Endpoint,
		}).Warn("Failed to send error to Airbrake")
	}

	return nil
}

func (hook *AirbrakeHook) Levels() []Level {
	return []Level{
		Error,
		Fatal,
		Panic,
	}
}
<commit_msg>airbrake: remove from root<commit_after><|endoftext|>