<commit_before>// +build k8srequired

package integration

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/cenkalti/backoff"
	"github.com/giantswarm/awstpr"
	"github.com/giantswarm/certificatetpr"
	"github.com/giantswarm/microerror"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	v1 "k8s.io/client-go/pkg/api/v1"
)

const (
	defaultTimeout         = 300
	awsOperatorValuesFile  = "/tmp/aws-operator-values.yaml"
	awsOperatorChartValues = `Installation:
  V1:
    Name: gauss
    Provider:
      AWS:
        Region: ${AWS_REGION}
    Secret:
      AWSOperator:
        IDRSAPub: ${IDRSA_PUB}
        SecretYaml: |
          service:
            aws:
              accesskey:
                id: ${AWS_ACCESS_KEY_ID}
                secret: ${AWS_SECRET_ACCESS_KEY}
                token: ${AWS_SESSION_TOKEN}
              hostaccesskey:
                id: ""
                secret: ""
      Registry:
        PullSecret:
          DockerConfigJSON: "{\"auths\":{\"quay.io\":{\"auth\":\"${REGISTRY_PULL_SECRET}\"}}}"
`
	awsResourceValuesFile  = "/tmp/aws-operator-values.yaml"
	awsResourceChartValues = `commonDomain: ${COMMON_DOMAIN}
clusterName: ${CLUSTER_NAME}
clusterVersion: v_0_1_0
sshPublicKey: ${IDRSA_PUB}
versionBundleVersion: ${VERSION_BUNDLE_VERSION}
aws:
  networkCIDR: "10.1.173.0/24"
  privateSubnetCIDR: "10.1.173.0/25"
  publicSubnetCIDR: "10.1.173.128/25"
  region: ${AWS_REGION}
  apiHostedZone: ${AWS_API_HOSTED_ZONE}
  ingressHostedZone: ${AWS_INGRESS_HOSTED_ZONE}
  routeTable0: ${AWS_ROUTE_TABLE_0}
  routeTable1: ${AWS_ROUTE_TABLE_1}
  vpcPeerId: ${AWS_VPC_PEER_ID}
`
	certOperatorValuesFile = "/tmp/cert-operator-values.yaml"
	// operatorChartValues values required by aws-operator-chart, the environment
	// variables will be expanded before writing the contents to a file.
	certOperatorChartValues = `commonDomain: ${COMMON_DOMAIN}
clusterName: ${CLUSTER_NAME}
Installation:
  V1:
    Auth:
      Vault:
        Address: http://vault.default.svc.cluster.local:8200
        CA:
          TTL: 1440h
    Guest:
      Kubernetes:
        API:
          EndpointBase: ${COMMON_DOMAIN}
    Secret:
      CertOperator:
        SecretYaml: |
          service:
            vault:
              config:
                token: ${VAULT_TOKEN}
      Registry:
        PullSecret:
          DockerConfigJSON: "{\"auths\":{\"quay.io\":{\"auth\":\"$REGISTRY_PULL_SECRET\"}}}"
`
)

var cs kubernetes.Interface

// TestMain allows us to have common setup and teardown steps that are run
// once for all the tests https://golang.org/pkg/testing/#hdr-Main.
func TestMain(m *testing.M) {
	var v int
	var err error
	cs, err = getK8sClient()
	if err != nil {
		v = 1
		log.Printf("unexpected error: %v\n", err)
	}

	if err := setUp(cs); err != nil {
		v = 1
		log.Printf("unexpected error: %v\n", err)
	}

	if v == 0 {
		v = m.Run()
	}

	tearDown(cs)

	os.Exit(v)
}

func TestGuestClusterIsCreated(t *testing.T) {
	awsResourceChartValuesEnv := os.ExpandEnv(awsResourceChartValues)
	if err := ioutil.WriteFile(awsResourceValuesFile, []byte(awsResourceChartValuesEnv), os.ModePerm); err != nil {
		t.Errorf("unexpected error writing aws-resource-lab values file: %v", err)
	}

	if err := runCmd("helm registry install quay.io/giantswarm/aws-resource-lab-chart:stable -- -n aws-resource-lab --values " + awsOperatorValuesFile); err != nil {
		t.Errorf("unexpected error installing aws-resource-lab chart: %v", err)
	}

	operatorPodName, err := podName(cs, "giantswarm", "app=aws-operator")
	if err != nil {
		t.Errorf("unexpected error getting operator pod name: %v", err)
	}

	logEntry := "cluster '${CLUSTER_NAME}' processed"
	if os.Getenv("VERSION_BUNDLE_VERSION") == "0.2.0" {
		logEntry = "creating AWS cloudformation stack: created"
	}

	if err := waitForPodLog(cs, "giantswarm", logEntry, operatorPodName); err != nil {
		t.Errorf("unexpected error waiting for guest cluster installed: %v", err)
	}
}

func setUp(cs kubernetes.Interface) error {
	if err := createGSNamespace(cs); err != nil {
		return microerror.Mask(err)
	}

	if err := installVault(cs); err != nil {
		return microerror.Mask(err)
	}

	if err := installCertOperator(cs); err != nil {
		return microerror.Mask(err)
	}

	if err := installCertResource(cs); err != nil {
		return microerror.Mask(err)
	}

	if err := installAwsOperator(cs); err != nil {
		return microerror.Mask(err)
	}

	return nil
}

func tearDown(cs kubernetes.Interface) {
	runCmd("helm delete vault --purge")
	runCmd("helm delete cert-resource-lab --purge")
	runCmd("helm delete cert-operator --purge")
	deleteGuestCluster(cs)
	runCmd("helm delete aws-resource-lab --purge")
	runCmd("helm delete aws-operator --purge")
	cs.CoreV1().
		Namespaces().
		Delete("giantswarm", &metav1.DeleteOptions{})
	cs.ExtensionsV1beta1().
		ThirdPartyResources().
		Delete(certificatetpr.Name, &metav1.DeleteOptions{})
	cs.ExtensionsV1beta1().
		ThirdPartyResources().
		Delete(awstpr.Name, &metav1.DeleteOptions{})
}

func createGSNamespace(cs kubernetes.Interface) error {
	// check if the namespace already exists
	_, err := cs.CoreV1().
		Namespaces().
		Get("giantswarm", metav1.GetOptions{})
	if err == nil {
		return nil
	}

	namespace := &v1.Namespace{
		ObjectMeta: metav1.ObjectMeta{
			Name: "giantswarm",
		},
	}
	_, err = cs.CoreV1().
		Namespaces().
		Create(namespace)
	if err != nil {
		return microerror.Mask(err)
	}

	return waitFor(activeNamespaceFunc(cs, "giantswarm"))
}

func installVault(cs kubernetes.Interface) error {
	if err := runCmd("helm registry install quay.io/giantswarm/vaultlab-chart:stable -- --set vaultToken=${VAULT_TOKEN} -n vault"); err != nil {
		return microerror.Mask(err)
	}

	return waitFor(runningPodFunc(cs, "default", "app=vault"))
}

func installCertOperator(cs kubernetes.Interface) error {
	certOperatorChartValuesEnv := os.ExpandEnv(certOperatorChartValues)
	if err := ioutil.WriteFile(certOperatorValuesFile, []byte(certOperatorChartValuesEnv), os.ModePerm); err != nil {
		return microerror.Mask(err)
	}
	if err := runCmd("helm registry install quay.io/giantswarm/cert-operator-chart:stable -- -n cert-operator --values " + certOperatorValuesFile); err != nil {
		return microerror.Mask(err)
	}

	return waitFor(tprFunc(cs, "certificate"))
}

func installCertResource(cs kubernetes.Interface) error {
	err := runCmd("helm registry install quay.io/giantswarm/cert-resource-lab-chart:stable -- -n cert-resource-lab --set commonDomain=${COMMON_DOMAIN} --set clusterName=${CLUSTER_NAME}")
	if err != nil {
		return microerror.Mask(err)
	}

	secretName := fmt.Sprintf("%s-api", os.Getenv("CLUSTER_NAME"))
	log.Printf("waiting for secret %v\n", secretName)
	return waitFor(secretFunc(cs, "default", secretName))
}

func installAwsOperator(cs kubernetes.Interface) error {
	awsOperatorChartValuesEnv := os.ExpandEnv(awsOperatorChartValues)
	if err := ioutil.WriteFile(awsOperatorValuesFile, []byte(awsOperatorChartValuesEnv), os.ModePerm); err != nil {
		return microerror.Mask(err)
	}
	if err := runCmd("helm registry install quay.io/giantswarm/aws-operator-chart@1.0.0-${CIRCLE_SHA1} -- -n aws-operator --values " + awsOperatorValuesFile); err != nil {
		return microerror.Mask(err)
	}

	return waitFor(tprFunc(cs, "aws"))
}

func deleteGuestCluster(cs kubernetes.Interface) error {
	if err := runCmd("kubectl delete aws ${CLUSTER_NAME}"); err != nil {
		return microerror.Mask(err)
	}

	operatorPodName, err := podName(cs, "giantswarm", "app=aws-operator")
	if err != nil {
		return microerror.Mask(err)
	}

	logEntry := "cluster '${CLUSTER_NAME}' deleted"
	if os.Getenv("VERSION_BUNDLE_VERSION") == "0.2.0" {
		logEntry = "deleting AWS cloudformation stack: deleted"
	}
	return waitForPodLog(cs, "giantswarm", logEntry, operatorPodName)
}

func runCmd(cmdStr string) error {
	log.Printf("Running command %v\n", cmdStr)
	cmdEnv := os.ExpandEnv(cmdStr)
	fields := strings.Fields(cmdEnv)
	cmd := exec.Command(fields[0], fields[1:]...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stdout

	return cmd.Run()
}

func waitFor(f func() error) error {
	timeout := time.After(defaultTimeout * time.Second)
	ticker := backoff.NewTicker(backoff.NewExponentialBackOff())

	for {
		select {
		case <-timeout:
			ticker.Stop()
			return microerror.Mask(waitTimeoutError)
		case <-ticker.C:
			if err := f(); err == nil {
				return nil
			}
		}
	}
}

func runningPodFunc(cs kubernetes.Interface, namespace, labelSelector string) func() error {
	return func() error {
		pods, err := cs.CoreV1().
			Pods(namespace).
			List(metav1.ListOptions{
				LabelSelector: labelSelector,
			})
		if err != nil {
			return microerror.Mask(err)
		}
		if len(pods.Items) > 1 {
			return microerror.Mask(tooManyResultsError)
		}
		pod := pods.Items[0]
		phase := pod.Status.Phase
		if phase != v1.PodRunning {
			return microerror.Maskf(unexpectedStatusPhaseError, "current status: %s", string(phase))
		}
		return nil
	}
}

func activeNamespaceFunc(cs kubernetes.Interface, name string) func() error {
	return func() error {
		ns, err := cs.CoreV1().
			Namespaces().
			Get(name, metav1.GetOptions{})

		if err != nil {
			return microerror.Mask(err)
		}

		phase := ns.Status.Phase
		if phase != v1.NamespaceActive {
			return microerror.Maskf(unexpectedStatusPhaseError, "current status: %s", string(phase))
		}

		return nil
	}
}

func secretFunc(cs kubernetes.Interface, namespace, secretName string) func() error {
	return func() error {
		_, err := cs.CoreV1().
			Secrets(namespace).
			Get(secretName, metav1.GetOptions{})
		return microerror.Mask(err)
	}
}

func tprFunc(cs kubernetes.Interface, tprName string) func() error {
	return func() error {
		// FIXME: use proper clientset call when apiextensions are in place,
		// `cs.ExtensionsV1beta1().ThirdPartyResources().Get(tprName, metav1.GetOptions{})` finding
		// the tpr is not enough for being able to create a tpo.
		return runCmd("kubectl get " + tprName)
	}
}

func waitForPodLog(cs kubernetes.Interface, namespace, needle, podName string) error {
	needle = os.ExpandEnv(needle)

	timeout := time.After(defaultTimeout * time.Second)

	req := cs.CoreV1().
		RESTClient().
		Get().
		Namespace(namespace).
		Name(podName).
		Resource("pods").
		SubResource("log").
		Param("follow", strconv.FormatBool(true))

	readCloser, err := req.Stream()
	if err != nil {
		return microerror.Mask(err)
	}
	defer readCloser.Close()

	scanner := bufio.NewScanner(readCloser)
	var lastLine string
	for scanner.Scan() {
		select {
		case <-timeout:
			return microerror.Mask(waitTimeoutError)
		default:
		}
		lastLine = scanner.Text()
		log.Print(lastLine)
		if strings.Contains(lastLine, needle) {
			return nil
		}
	}
	if err := scanner.Err(); err != nil {
		return microerror.Mask(err)
	}

	return microerror.Mask(notFoundError)
}

func podName(cs kubernetes.Interface, namespace, labelSelector string) (string, error) {
	pods, err := cs.CoreV1().
		Pods(namespace).
		List(metav1.ListOptions{
			LabelSelector: labelSelector,
		})
	if err != nil {
		return "", microerror.Mask(err)
	}
	if len(pods.Items) > 1 {
		return "", microerror.Mask(tooManyResultsError)
	}
	pod := pods.Items[0]
	return pod.Name, nil
}
<commit_msg>update e2e test to CRD (#578)<commit_after>// +build k8srequired

package integration

import (
	"bufio"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/cenkalti/backoff"
	"github.com/giantswarm/awstpr"
	"github.com/giantswarm/certificatetpr"
	"github.com/giantswarm/microerror"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	v1 "k8s.io/client-go/pkg/api/v1"
)

const (
	defaultTimeout         = 300
	awsOperatorValuesFile  = "/tmp/aws-operator-values.yaml"
	awsOperatorChartValues = `Installation:
  V1:
    Name: gauss
    Provider:
      AWS:
        Region: ${AWS_REGION}
    Secret:
      AWSOperator:
        IDRSAPub: ${IDRSA_PUB}
        SecretYaml: |
          service:
            aws:
              accesskey:
                id: ${AWS_ACCESS_KEY_ID}
                secret: ${AWS_SECRET_ACCESS_KEY}
                token: ${AWS_SESSION_TOKEN}
              hostaccesskey:
                id: ""
                secret: ""
      Registry:
        PullSecret:
          DockerConfigJSON: "{\"auths\":{\"quay.io\":{\"auth\":\"${REGISTRY_PULL_SECRET}\"}}}"
`
	awsResourceValuesFile  = "/tmp/aws-operator-values.yaml"
	awsResourceChartValues = `commonDomain: ${COMMON_DOMAIN}
clusterName: ${CLUSTER_NAME}
clusterVersion: v_0_1_0
sshPublicKey: ${IDRSA_PUB}
versionBundleVersion: ${VERSION_BUNDLE_VERSION}
aws:
  networkCIDR: "10.1.173.0/24"
  privateSubnetCIDR: "10.1.173.0/25"
  publicSubnetCIDR: "10.1.173.128/25"
  region: ${AWS_REGION}
  apiHostedZone: ${AWS_API_HOSTED_ZONE}
  ingressHostedZone: ${AWS_INGRESS_HOSTED_ZONE}
  routeTable0: ${AWS_ROUTE_TABLE_0}
  routeTable1: ${AWS_ROUTE_TABLE_1}
  vpcPeerId: ${AWS_VPC_PEER_ID}
`
	certOperatorValuesFile = "/tmp/cert-operator-values.yaml"
	// operatorChartValues values required by aws-operator-chart, the environment
	// variables will be expanded before writing the contents to a file.
	certOperatorChartValues = `commonDomain: ${COMMON_DOMAIN}
clusterName: ${CLUSTER_NAME}
Installation:
  V1:
    Auth:
      Vault:
        Address: http://vault.default.svc.cluster.local:8200
        CA:
          TTL: 1440h
    Guest:
      Kubernetes:
        API:
          EndpointBase: ${COMMON_DOMAIN}
    Secret:
      CertOperator:
        SecretYaml: |
          service:
            vault:
              config:
                token: ${VAULT_TOKEN}
      Registry:
        PullSecret:
          DockerConfigJSON: "{\"auths\":{\"quay.io\":{\"auth\":\"$REGISTRY_PULL_SECRET\"}}}"
`
)

var cs kubernetes.Interface

// TestMain allows us to have common setup and teardown steps that are run
// once for all the tests https://golang.org/pkg/testing/#hdr-Main.
func TestMain(m *testing.M) {
	var v int
	var err error
	cs, err = getK8sClient()
	if err != nil {
		v = 1
		log.Printf("unexpected error: %v\n", err)
	}

	if err := setUp(cs); err != nil {
		v = 1
		log.Printf("unexpected error: %v\n", err)
	}

	if v == 0 {
		v = m.Run()
	}

	tearDown(cs)

	os.Exit(v)
}

func TestGuestClusterIsCreated(t *testing.T) {
	awsResourceChartValuesEnv := os.ExpandEnv(awsResourceChartValues)
	if err := ioutil.WriteFile(awsResourceValuesFile, []byte(awsResourceChartValuesEnv), os.ModePerm); err != nil {
		t.Errorf("unexpected error writing aws-resource-lab values file: %v", err)
	}

	if err := runCmd("helm registry install quay.io/giantswarm/aws-resource-lab-chart:stable -- -n aws-resource-lab --values " + awsOperatorValuesFile); err != nil {
		t.Errorf("unexpected error installing aws-resource-lab chart: %v", err)
	}

	operatorPodName, err := podName(cs, "giantswarm", "app=aws-operator")
	if err != nil {
		t.Errorf("unexpected error getting operator pod name: %v", err)
	}

	logEntry := "cluster '${CLUSTER_NAME}' processed"
	if os.Getenv("VERSION_BUNDLE_VERSION") == "0.2.0" {
		logEntry = "creating AWS cloudformation stack: created"
	}

	if err := waitForPodLog(cs, "giantswarm", logEntry, operatorPodName); err != nil {
		t.Errorf("unexpected error waiting for guest cluster installed: %v", err)
	}
}

func setUp(cs kubernetes.Interface) error {
	if err := createGSNamespace(cs); err != nil {
		return microerror.Mask(err)
	}

	if err := installVault(cs); err != nil {
		return microerror.Mask(err)
	}

	if err := installCertOperator(cs); err != nil {
		return microerror.Mask(err)
	}

	if err := installCertResource(cs); err != nil {
		return microerror.Mask(err)
	}

	if err := installAwsOperator(cs); err != nil {
		return microerror.Mask(err)
	}

	return nil
}

func tearDown(cs kubernetes.Interface) {
	runCmd("helm delete vault --purge")
	runCmd("helm delete cert-resource-lab --purge")
	runCmd("helm delete cert-operator --purge")
	deleteGuestCluster(cs)
	runCmd("helm delete aws-resource-lab --purge")
	runCmd("helm delete aws-operator --purge")
	cs.CoreV1().
		Namespaces().
		Delete("giantswarm", &metav1.DeleteOptions{})
	cs.ExtensionsV1beta1().
		ThirdPartyResources().
		Delete(certificatetpr.Name, &metav1.DeleteOptions{})
	cs.ExtensionsV1beta1().
		ThirdPartyResources().
		Delete(awstpr.Name, &metav1.DeleteOptions{})
}

func createGSNamespace(cs kubernetes.Interface) error {
	// check if the namespace already exists
	_, err := cs.CoreV1().
		Namespaces().
		Get("giantswarm", metav1.GetOptions{})
	if err == nil {
		return nil
	}

	namespace := &v1.Namespace{
		ObjectMeta: metav1.ObjectMeta{
			Name: "giantswarm",
		},
	}
	_, err = cs.CoreV1().
		Namespaces().
		Create(namespace)
	if err != nil {
		return microerror.Mask(err)
	}

	return waitFor(activeNamespaceFunc(cs, "giantswarm"))
}

func installVault(cs kubernetes.Interface) error {
	if err := runCmd("helm registry install quay.io/giantswarm/vaultlab-chart:stable -- --set vaultToken=${VAULT_TOKEN} -n vault"); err != nil {
		return microerror.Mask(err)
	}

	return waitFor(runningPodFunc(cs, "default", "app=vault"))
}

func installCertOperator(cs kubernetes.Interface) error {
	certOperatorChartValuesEnv := os.ExpandEnv(certOperatorChartValues)
	if err := ioutil.WriteFile(certOperatorValuesFile, []byte(certOperatorChartValuesEnv), os.ModePerm); err != nil {
		return microerror.Mask(err)
	}
	if err := runCmd("helm registry install quay.io/giantswarm/cert-operator-chart:stable -- -n cert-operator --values " + certOperatorValuesFile); err != nil {
		return microerror.Mask(err)
	}

	return waitFor(tprFunc(cs, "certconfig"))
}

func installCertResource(cs kubernetes.Interface) error {
	err := runCmd("helm registry install quay.io/giantswarm/cert-resource-lab-chart:stable -- -n cert-resource-lab --set commonDomain=${COMMON_DOMAIN} --set clusterName=${CLUSTER_NAME}")
	if err != nil {
		return microerror.Mask(err)
	}

	secretName := fmt.Sprintf("%s-api", os.Getenv("CLUSTER_NAME"))
	log.Printf("waiting for secret %v\n", secretName)
	return waitFor(secretFunc(cs, "default", secretName))
}

func installAwsOperator(cs kubernetes.Interface) error {
	awsOperatorChartValuesEnv := os.ExpandEnv(awsOperatorChartValues)
	if err := ioutil.WriteFile(awsOperatorValuesFile, []byte(awsOperatorChartValuesEnv), os.ModePerm); err != nil {
		return microerror.Mask(err)
	}
	if err := runCmd("helm registry install quay.io/giantswarm/aws-operator-chart@1.0.0-${CIRCLE_SHA1} -- -n aws-operator --values " + awsOperatorValuesFile); err != nil {
		return microerror.Mask(err)
	}

	return waitFor(tprFunc(cs, "awsconfig"))
}

func deleteGuestCluster(cs kubernetes.Interface) error {
	if err := runCmd("kubectl delete awsconfig ${CLUSTER_NAME}"); err != nil {
		return microerror.Mask(err)
	}

	operatorPodName, err := podName(cs, "giantswarm", "app=aws-operator")
	if err != nil {
		return microerror.Mask(err)
	}

	logEntry := "cluster '${CLUSTER_NAME}' deleted"
	if os.Getenv("VERSION_BUNDLE_VERSION") == "0.2.0" {
		logEntry = "deleting AWS cloudformation stack: deleted"
	}
	return waitForPodLog(cs, "giantswarm", logEntry, operatorPodName)
}

func runCmd(cmdStr string) error {
	log.Printf("Running command %v\n", cmdStr)
	cmdEnv := os.ExpandEnv(cmdStr)
	fields := strings.Fields(cmdEnv)
	cmd := exec.Command(fields[0], fields[1:]...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stdout

	return cmd.Run()
}

func waitFor(f func() error) error {
	timeout := time.After(defaultTimeout * time.Second)
	ticker := backoff.NewTicker(backoff.NewExponentialBackOff())

	for {
		select {
		case <-timeout:
			ticker.Stop()
			return microerror.Mask(waitTimeoutError)
		case <-ticker.C:
			if err := f(); err == nil {
				return nil
			}
		}
	}
}

func runningPodFunc(cs kubernetes.Interface, namespace, labelSelector string) func() error {
	return func() error {
		pods, err := cs.CoreV1().
			Pods(namespace).
			List(metav1.ListOptions{
				LabelSelector: labelSelector,
			})
		if err != nil {
			return microerror.Mask(err)
		}
		if len(pods.Items) > 1 {
			return microerror.Mask(tooManyResultsError)
		}
		pod := pods.Items[0]
		phase := pod.Status.Phase
		if phase != v1.PodRunning {
			return microerror.Maskf(unexpectedStatusPhaseError, "current status: %s", string(phase))
		}
		return nil
	}
}

func activeNamespaceFunc(cs kubernetes.Interface, name string) func() error {
	return func() error {
		ns, err := cs.CoreV1().
			Namespaces().
			Get(name, metav1.GetOptions{})

		if err != nil {
			return microerror.Mask(err)
		}

		phase := ns.Status.Phase
		if phase != v1.NamespaceActive {
			return microerror.Maskf(unexpectedStatusPhaseError, "current status: %s", string(phase))
		}

		return nil
	}
}

func secretFunc(cs kubernetes.Interface, namespace, secretName string) func() error {
	return func() error {
		_, err := cs.CoreV1().
			Secrets(namespace).
			Get(secretName, metav1.GetOptions{})
		return microerror.Mask(err)
	}
}

func tprFunc(cs kubernetes.Interface, tprName string) func() error {
	return func() error {
		// FIXME: use proper clientset call when apiextensions are in place,
		// `cs.ExtensionsV1beta1().ThirdPartyResources().Get(tprName, metav1.GetOptions{})` finding
		// the tpr is not enough for being able to create a tpo.
		return runCmd("kubectl get " + tprName)
	}
}

func waitForPodLog(cs kubernetes.Interface, namespace, needle, podName string) error {
	needle = os.ExpandEnv(needle)

	timeout := time.After(defaultTimeout * time.Second)

	req := cs.CoreV1().
		RESTClient().
		Get().
		Namespace(namespace).
		Name(podName).
		Resource("pods").
		SubResource("log").
		Param("follow", strconv.FormatBool(true))

	readCloser, err := req.Stream()
	if err != nil {
		return microerror.Mask(err)
	}
	defer readCloser.Close()

	scanner := bufio.NewScanner(readCloser)
	var lastLine string
	for scanner.Scan() {
		select {
		case <-timeout:
			return microerror.Mask(waitTimeoutError)
		default:
		}
		lastLine = scanner.Text()
		log.Print(lastLine)
		if strings.Contains(lastLine, needle) {
			return nil
		}
	}
	if err := scanner.Err(); err != nil {
		return microerror.Mask(err)
	}

	return microerror.Mask(notFoundError)
}

func podName(cs kubernetes.Interface, namespace, labelSelector string) (string, error) {
	pods, err := cs.CoreV1().
		Pods(namespace).
		List(metav1.ListOptions{
			LabelSelector: labelSelector,
		})
	if err != nil {
		return "", microerror.Mask(err)
	}
	if len(pods.Items) > 1 {
		return "", microerror.Mask(tooManyResultsError)
	}
	pod := pods.Items[0]
	return pod.Name, nil
}
<|endoftext|>


<commit_before>// Copyright 2017 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package entry

import (
	"net/http"
	"testing"

	"github.com/issue9/assert"
)

// BenchmarkBasic_Match-4    	200000000	         6.75 ns/op		go1.8
func BenchmarkBasic_Match(b *testing.B) {
	a := assert.New(b)
	hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	})
	e, err := New("/blog/post/1", hf)
	a.NotError(err)

	for i := 0; i < b.N; i++ {
		if 0 != e.Match("/blog/post/1") {
			b.Error("BenchmarkBasic_Match:error")
		}
	}
}

// BenchmarkStatic_Match-4    	200000000	         7.85 ns/op     go1.8
func BenchmarkStatic_Match(b *testing.B) {
	a := assert.New(b)
	hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	})
	e, err := New("/blog/post/", hf)
	a.NotError(err)

	for i := 0; i < b.N; i++ {
		if e.Match("/blog/post/1") > 1 {
			b.Error("BenchmarkStatic_Match:error")
		}
	}
}

// BenchmarkRegexpr_Match-4   	 5000000	       337 ns/op		go1.8
func BenchmarkRegexpr_Match(b *testing.B) {
	a := assert.New(b)
	hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
	})
	e, err := New("/blog/post/{id:\\d+}", hf)
	a.NotError(err)

	for i := 0; i < b.N; i++ {
		if 0 != e.Match("/blog/post/1") {
			b.Error("BenchmarkRegexp_Match:error")
		}
	}
}
<commit_msg>[internal/entry] 调整性能测试代码<commit_after>// Copyright 2017 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package entry

import (
	"net/http"
	"testing"

	"github.com/issue9/assert"
)

var benchHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
})

// BenchmarkBasic_Match-4    	200000000	         6.75 ns/op		go1.8
func BenchmarkBasic_Match(b *testing.B) {
	a := assert.New(b)
	e, err := New("/blog/post/1", benchHandler)
	a.NotError(err)

	for i := 0; i < b.N; i++ {
		if 0 != e.Match("/blog/post/1") {
			b.Error("BenchmarkBasic_Match:error")
		}
	}
}

// BenchmarkStatic_Match-4    	200000000	         7.85 ns/op     go1.8
func BenchmarkStatic_Match(b *testing.B) {
	a := assert.New(b)
	e, err := New("/blog/post/", benchHandler)
	a.NotError(err)

	for i := 0; i < b.N; i++ {
		if e.Match("/blog/post/1") > 1 {
			b.Error("BenchmarkStatic_Match:error")
		}
	}
}

// BenchmarkRegexpr_Match-4   	 5000000	       337 ns/op		go1.8
func BenchmarkRegexpr_Match(b *testing.B) {
	a := assert.New(b)
	e, err := New("/blog/post/{id:\\d+}", benchHandler)
	a.NotError(err)

	for i := 0; i < b.N; i++ {
		if 0 != e.Match("/blog/post/1") {
			b.Error("BenchmarkRegexp_Match:error")
		}
	}
}
<|endoftext|>


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

package frontend

import (
	"context"
	"fmt"
	"html/template"
	"net/http"
	"strings"

	"golang.org/x/discovery/internal"
	"golang.org/x/discovery/internal/derrors"
	"golang.org/x/discovery/internal/license"
	"golang.org/x/discovery/internal/log"
	"golang.org/x/discovery/internal/stdlib"
	"golang.org/x/discovery/internal/thirdparty/module"
	"golang.org/x/discovery/internal/thirdparty/semver"
	"golang.org/x/xerrors"
)

// DetailsPage contains data for a package of module details template.
type DetailsPage struct {
	basePage
	CanShowDetails bool
	Settings       TabSettings
	Details        interface{}
	Header         interface{}
	BreadcrumbPath template.HTML
	Tabs           []TabSettings
	Namespace      string
}

func (s *Server) handleDetails(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/" {
		s.staticPageHandler("index.tmpl", "go.dev")(w, r)
		return
	}
	parts := strings.SplitN(strings.TrimPrefix(r.URL.Path, "/"), "@", 2)
	if stdlib.Contains(parts[0]) {
		s.handleStdLib(w, r)
		return
	}
	s.handlePackageDetails(w, r)
}

func (s *Server) handlePackageDetails(w http.ResponseWriter, r *http.Request) {
	pkgPath, modulePath, version, err := parseDetailsURLPath(r.URL.Path)
	if err != nil {
		log.Errorf("handlePackageDetails: %v", err)
		s.serveErrorPage(w, r, http.StatusBadRequest, nil)
		return
	}
	s.servePackagePage(w, r, pkgPath, modulePath, version)
}

// handlePackageDetailsRedirect redirects all redirects to "/pkg" to "/".
func (s *Server) handlePackageDetailsRedirect(w http.ResponseWriter, r *http.Request) {
	urlPath := strings.TrimPrefix(r.URL.Path, "/pkg")
	http.Redirect(w, r, urlPath, http.StatusMovedPermanently)
}

// handleModuleDetails applies database data to the appropriate template.
// Handles all endpoints that match "/mod/<module-path>[@<version>?tab=<tab>]".
func (s *Server) handleModuleDetails(w http.ResponseWriter, r *http.Request) {
	urlPath := strings.TrimPrefix(r.URL.Path, "/mod")
	path, _, version, err := parseDetailsURLPath(urlPath)
	if err != nil {
		log.Infof("handleModuleDetails: %v", err)
		s.serveErrorPage(w, r, http.StatusBadRequest, nil)
		return
	}
	s.serveModulePage(w, r, path, version)
}

// servePackagePage applies database data to the appropriate template.
// Handles all endpoints that match "/<import-path>[@<version>?tab=<tab>]".
func (s *Server) servePackagePage(w http.ResponseWriter, r *http.Request, pkgPath, modulePath, version string) {
	if version != internal.LatestVersion && !semver.IsValid(version) {
		epage := &errorPage{Message: fmt.Sprintf("%q is not a valid semantic version.", version)}
		epage.SecondaryMessage = suggestedSearch(pkgPath)
		s.serveErrorPage(w, r, http.StatusBadRequest, epage)
		return
	}

	var pkg *internal.VersionedPackage
	code, epage := fetchPackageOrModule(r.Context(), s.ds, "pkg", pkgPath, version, func(ver string) (string, error) {
		var err error
		pkg, err = s.ds.GetPackage(r.Context(), pkgPath, modulePath, ver)
		return modulePath, err
	})
	if code != http.StatusOK {
		if code == http.StatusNotFound {
			s.serveDirectoryPage(w, r, pkgPath, modulePath, version)
			return
		}
		s.serveErrorPage(w, r, code, epage)
		return
	}

	pkgHeader, err := createPackage(&pkg.Package, &pkg.VersionInfo, version == internal.LatestVersion)
	if err != nil {
		log.Errorf("error creating package header for %s@%s: %v", pkg.Path, pkg.Version, err)
		s.serveErrorPage(w, r, http.StatusInternalServerError, nil)
		return
	}

	tab := r.FormValue("tab")
	settings, ok := packageTabLookup[tab]
	if !ok {
		var tab string
		if pkg.IsRedistributable() {
			tab = "doc"
		} else {
			tab = "overview"
		}
		http.Redirect(w, r, fmt.Sprintf(r.URL.Path+"?tab=%s", tab), http.StatusFound)
		return
	}
	canShowDetails := pkg.IsRedistributable() || settings.AlwaysShowDetails

	var details interface{}
	if canShowDetails {
		var err error
		details, err = fetchDetailsForPackage(r.Context(), r, tab, s.ds, pkg)
		if err != nil {
			log.Errorf("error fetching page for %q: %v", tab, err)
			s.serveErrorPage(w, r, http.StatusInternalServerError, nil)
			return
		}
	}
	page := &DetailsPage{
		basePage:       newBasePage(r, packageTitle(&pkg.Package)),
		Settings:       settings,
		Header:         pkgHeader,
		BreadcrumbPath: breadcrumbPath(pkgHeader.Path, pkgHeader.Module.Path, pkgHeader.Module.Version),
		Details:        details,
		CanShowDetails: canShowDetails,
		Tabs:           packageTabSettings,
		Namespace:      "pkg",
	}
	s.servePage(w, settings.TemplateName, page)
}

// serveModulePage applies database data to the appropriate template.
func (s *Server) serveModulePage(w http.ResponseWriter, r *http.Request, modulePath, version string) {
	if version != internal.LatestVersion && !semver.IsValid(version) {
		epage := &errorPage{Message: fmt.Sprintf("%q is not a valid semantic version.", version)}
		s.serveErrorPage(w, r, http.StatusBadRequest, epage)
		return
	}

	ctx := r.Context()
	var moduleVersion *internal.VersionInfo
	code, epage := fetchPackageOrModule(ctx, s.ds, "mod", modulePath, version, func(ver string) (string, error) {
		var err error
		moduleVersion, err = s.ds.GetVersionInfo(ctx, modulePath, ver)
		return modulePath, err
	})
	if code != http.StatusOK {
		s.serveErrorPage(w, r, code, epage)
		return
	}

	// Here, moduleVersion is a valid *VersionInfo.
	licenses, err := s.ds.GetModuleLicenses(ctx, moduleVersion.ModulePath, moduleVersion.Version)
	if err != nil {
		log.Errorf("error getting module licenses for %s@%s: %v", moduleVersion.ModulePath, moduleVersion.Version, err)
		s.serveErrorPage(w, r, http.StatusInternalServerError, nil)
		return
	}

	modHeader := createModule(moduleVersion, license.ToMetadatas(licenses), version == internal.LatestVersion)
	tab := r.FormValue("tab")
	settings, ok := moduleTabLookup[tab]
	if !ok {
		tab = "overview"
		settings = moduleTabLookup["overview"]
	}
	canShowDetails := modHeader.IsRedistributable || settings.AlwaysShowDetails
	var details interface{}
	if canShowDetails {
		var err error
		details, err = fetchDetailsForModule(ctx, r, tab, s.ds, moduleVersion, licenses)
		if err != nil {
			log.Errorf("error fetching page for %q: %v", tab, err)
			s.serveErrorPage(w, r, http.StatusInternalServerError, nil)
			return
		}
	}
	page := &DetailsPage{
		basePage:       newBasePage(r, moduleTitle(moduleVersion.ModulePath)),
		Settings:       settings,
		Header:         modHeader,
		BreadcrumbPath: breadcrumbPath(moduleVersion.ModulePath, moduleVersion.ModulePath, moduleVersion.Version),
		Details:        details,
		CanShowDetails: canShowDetails,
		Tabs:           moduleTabSettings,
		Namespace:      "mod",
	}
	s.servePage(w, settings.TemplateName, page)
}

// fetchPackageOrModule handles logic common to the initial phase of
// handling both packages and modules: fetching information about the package
// or module.
//
// The get argument is a function that should retrieve a package or module at a
// given version. It returns the error from doing so, as well as the module
// path.
//
// fetchPackageOrModule parses urlPath into an import path and version, then
// calls the get function with those values. If get fails because the version
// cannot be found, fetchPackageOrModule calls get again with the latest
// version, to see if any versions of the package/module exist, in order to
// provide a more helpful error message.
//
// fetchPackageOrModule returns the import path and version requested, an
// HTTP status code, and possibly an error page to display.
func fetchPackageOrModule(ctx context.Context, ds DataSource, namespace, path, version string, get func(v string) (string, error)) (code int, _ *errorPage) {
	excluded, err := ds.IsExcluded(ctx, path)
	if err != nil {
		log.Errorf("error checking excluded path: %v", err)
		return http.StatusInternalServerError, nil
	}
	if excluded {
		// Return NotFound; don't let the user know that the package was excluded.
		return http.StatusNotFound, nil
	}

	// Fetch the package or module from the database.
	_, err = get(version)
	if err == nil {
		// A package or module was found for this path and version.
		return http.StatusOK, nil
	}
	if !xerrors.Is(err, derrors.NotFound) {
		// Something went wrong in executing the get function.
		log.Errorf("fetchPackageOrModule %s@%s: %v", path, version, err)
		return http.StatusInternalServerError, nil
	}
	if version == internal.LatestVersion {
		// We were not able to find a module or package at any version.
		return http.StatusNotFound, nil
	}

	// We did not find the given version, but maybe there is another version
	// available for this package or module.
	modulePath, err := get(internal.LatestVersion)
	if err != nil {
		log.Errorf("error: get(%s, Latest) for %s: %v", path, namespace, err)
		// Couldn't get the latest version, for whatever reason. Treat
		// this like not finding the original version.
		return http.StatusNotFound, nil
	}

	// There is a later version of this package/module.
	word := "package"
	urlPath := "/" + path
	if namespace == "mod" {
		word = "module"
		urlPath = "/mod/" + path
	}
	epage := &errorPage{
		Message: fmt.Sprintf("%s %s@%s is not available.",
			strings.Title(word), path, formattedVersion(version, modulePath)),
		SecondaryMessage: template.HTML(
			fmt.Sprintf(`There are other versions of this %s that are! To view them, <a href="%s?tab=versions">click here</a>.</p>`, word, urlPath)),
	}
	return http.StatusSeeOther, epage
}

// parseDetailsURLPath returns the modulePath (if known),
// pkgPath and version specified by urlPath.
// urlPath is assumed to be a valid path following the structure:
//   /<module-path>[@<version>/<suffix>]
//
// If <version> is not specified, internal.LatestVersion is used for the
// version. modulePath can only be determined if <version> is specified.
//
// Leading and trailing slashes in the urlPath are trimmed.
func parseDetailsURLPath(urlPath string) (pkgPath, modulePath, version string, err error) {
	defer derrors.Wrap(&err, "parseDetailsURLPath(%q)", urlPath)

	// This splits urlPath into either:
	//   /<module-path>[/<suffix>]
	// or
	//   /<module-path>, @<version>/<suffix>
	// or
	//  /<module-path>/<suffix>, @<version>
	// TODO(b/140191811) The last URL route should redirect.
	parts := strings.SplitN(urlPath, "@", 2)
	basePath := strings.TrimSuffix(strings.TrimPrefix(parts[0], "/"), "/")
	if len(parts) == 1 {
		modulePath = internal.UnknownModulePath
		version = internal.LatestVersion
		pkgPath = basePath
	} else {
		// Parse the version and suffix from parts[1].
		endParts := strings.Split(parts[1], "/")
		suffix := strings.Join(endParts[1:], "/")
		version = endParts[0]
		if suffix == "" || version == internal.LatestVersion {
			modulePath = internal.UnknownModulePath
			pkgPath = basePath
		} else {
			modulePath = basePath
			pkgPath = basePath + "/" + suffix
		}
	}
	if err := module.CheckImportPath(pkgPath); err != nil {
		return "", "", "", fmt.Errorf("malformed path %q: %v", pkgPath, err)
	}
	if stdlib.Contains(pkgPath) {
		modulePath = stdlib.ModulePath
	}
	return pkgPath, modulePath, version, nil
}

// handleLatestVersion writes a JSON string with the latest version of the package or module.
// It expects URLs of the form
//   /latest-version/MODULE_PATH
// for modules, or
//   /latest-version/MODULE_PATH?pkg=PACKAGE_PATH
// for packages.
func (s *Server) handleLatestVersion(w http.ResponseWriter, r *http.Request) {
	modulePath := strings.TrimPrefix(r.URL.Path, "/latest-version/")
	packagePath := r.URL.Query().Get("pkg")
	v := s.LatestVersion(r.Context(), modulePath, packagePath)
	if _, err := fmt.Fprintf(w, "%q", v); err != nil {
		log.Errorf("handleLatestVersion: fmt.Fprintf: %v", err)
	}
}

// LatestVersion returns the latest version of the package or module.
// It returns the empty string on error.
// It is intended to be used as an argument to middleware.LatestVersion.
func (s *Server) LatestVersion(ctx context.Context, modulePath, packagePath string) string {
	v, err := s.latestVersion(ctx, modulePath, packagePath)
	if err != nil {
		// We get NotFound errors from directories; they clutter the log.
		if !xerrors.Is(err, derrors.NotFound) {
			log.Errorf("GetLatestVersion: %v", err)
		}
		return ""
	}
	return v
}

func (s *Server) latestVersion(ctx context.Context, modulePath, packagePath string) (_ string, err error) {
	defer derrors.Wrap(&err, "latestVersion(ctx, %q, %q)", modulePath, packagePath)

	var vi *internal.VersionInfo
	if packagePath == "" {
		vi, err = s.ds.GetVersionInfo(ctx, modulePath, internal.LatestVersion)
		if err != nil {
			return "", err
		}
	} else {
		pkg, err := s.ds.GetPackage(ctx, packagePath, modulePath, internal.LatestVersion)
		if err != nil {
			return "", err
		}
		vi = &pkg.VersionInfo
	}
	v := vi.Version
	if modulePath == stdlib.ModulePath {
		v, err = stdlib.TagForVersion(v)
		if err != nil {
			return "", err
		}
	}
	return v, nil
}
<commit_msg>internal/frontend: do not log NotFound errors from fetchPackageOrModule<commit_after>// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package frontend

import (
	"context"
	"fmt"
	"html/template"
	"net/http"
	"strings"

	"golang.org/x/discovery/internal"
	"golang.org/x/discovery/internal/derrors"
	"golang.org/x/discovery/internal/license"
	"golang.org/x/discovery/internal/log"
	"golang.org/x/discovery/internal/stdlib"
	"golang.org/x/discovery/internal/thirdparty/module"
	"golang.org/x/discovery/internal/thirdparty/semver"
	"golang.org/x/xerrors"
)

// DetailsPage contains data for a package of module details template.
type DetailsPage struct {
	basePage
	CanShowDetails bool
	Settings       TabSettings
	Details        interface{}
	Header         interface{}
	BreadcrumbPath template.HTML
	Tabs           []TabSettings
	Namespace      string
}

func (s *Server) handleDetails(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/" {
		s.staticPageHandler("index.tmpl", "go.dev")(w, r)
		return
	}
	parts := strings.SplitN(strings.TrimPrefix(r.URL.Path, "/"), "@", 2)
	if stdlib.Contains(parts[0]) {
		s.handleStdLib(w, r)
		return
	}
	s.handlePackageDetails(w, r)
}

func (s *Server) handlePackageDetails(w http.ResponseWriter, r *http.Request) {
	pkgPath, modulePath, version, err := parseDetailsURLPath(r.URL.Path)
	if err != nil {
		log.Errorf("handlePackageDetails: %v", err)
		s.serveErrorPage(w, r, http.StatusBadRequest, nil)
		return
	}
	s.servePackagePage(w, r, pkgPath, modulePath, version)
}

// handlePackageDetailsRedirect redirects all redirects to "/pkg" to "/".
func (s *Server) handlePackageDetailsRedirect(w http.ResponseWriter, r *http.Request) {
	urlPath := strings.TrimPrefix(r.URL.Path, "/pkg")
	http.Redirect(w, r, urlPath, http.StatusMovedPermanently)
}

// handleModuleDetails applies database data to the appropriate template.
// Handles all endpoints that match "/mod/<module-path>[@<version>?tab=<tab>]".
func (s *Server) handleModuleDetails(w http.ResponseWriter, r *http.Request) {
	urlPath := strings.TrimPrefix(r.URL.Path, "/mod")
	path, _, version, err := parseDetailsURLPath(urlPath)
	if err != nil {
		log.Infof("handleModuleDetails: %v", err)
		s.serveErrorPage(w, r, http.StatusBadRequest, nil)
		return
	}
	s.serveModulePage(w, r, path, version)
}

// servePackagePage applies database data to the appropriate template.
// Handles all endpoints that match "/<import-path>[@<version>?tab=<tab>]".
func (s *Server) servePackagePage(w http.ResponseWriter, r *http.Request, pkgPath, modulePath, version string) {
	if version != internal.LatestVersion && !semver.IsValid(version) {
		epage := &errorPage{Message: fmt.Sprintf("%q is not a valid semantic version.", version)}
		epage.SecondaryMessage = suggestedSearch(pkgPath)
		s.serveErrorPage(w, r, http.StatusBadRequest, epage)
		return
	}

	var pkg *internal.VersionedPackage
	code, epage := fetchPackageOrModule(r.Context(), s.ds, "pkg", pkgPath, version, func(ver string) (string, error) {
		var err error
		pkg, err = s.ds.GetPackage(r.Context(), pkgPath, modulePath, ver)
		return modulePath, err
	})
	if code != http.StatusOK {
		if code == http.StatusNotFound {
			s.serveDirectoryPage(w, r, pkgPath, modulePath, version)
			return
		}
		s.serveErrorPage(w, r, code, epage)
		return
	}

	pkgHeader, err := createPackage(&pkg.Package, &pkg.VersionInfo, version == internal.LatestVersion)
	if err != nil {
		log.Errorf("error creating package header for %s@%s: %v", pkg.Path, pkg.Version, err)
		s.serveErrorPage(w, r, http.StatusInternalServerError, nil)
		return
	}

	tab := r.FormValue("tab")
	settings, ok := packageTabLookup[tab]
	if !ok {
		var tab string
		if pkg.IsRedistributable() {
			tab = "doc"
		} else {
			tab = "overview"
		}
		http.Redirect(w, r, fmt.Sprintf(r.URL.Path+"?tab=%s", tab), http.StatusFound)
		return
	}
	canShowDetails := pkg.IsRedistributable() || settings.AlwaysShowDetails

	var details interface{}
	if canShowDetails {
		var err error
		details, err = fetchDetailsForPackage(r.Context(), r, tab, s.ds, pkg)
		if err != nil {
			log.Errorf("error fetching page for %q: %v", tab, err)
			s.serveErrorPage(w, r, http.StatusInternalServerError, nil)
			return
		}
	}
	page := &DetailsPage{
		basePage:       newBasePage(r, packageTitle(&pkg.Package)),
		Settings:       settings,
		Header:         pkgHeader,
		BreadcrumbPath: breadcrumbPath(pkgHeader.Path, pkgHeader.Module.Path, pkgHeader.Module.Version),
		Details:        details,
		CanShowDetails: canShowDetails,
		Tabs:           packageTabSettings,
		Namespace:      "pkg",
	}
	s.servePage(w, settings.TemplateName, page)
}

// serveModulePage applies database data to the appropriate template.
func (s *Server) serveModulePage(w http.ResponseWriter, r *http.Request, modulePath, version string) {
	if version != internal.LatestVersion && !semver.IsValid(version) {
		epage := &errorPage{Message: fmt.Sprintf("%q is not a valid semantic version.", version)}
		s.serveErrorPage(w, r, http.StatusBadRequest, epage)
		return
	}

	ctx := r.Context()
	var moduleVersion *internal.VersionInfo
	code, epage := fetchPackageOrModule(ctx, s.ds, "mod", modulePath, version, func(ver string) (string, error) {
		var err error
		moduleVersion, err = s.ds.GetVersionInfo(ctx, modulePath, ver)
		return modulePath, err
	})
	if code != http.StatusOK {
		s.serveErrorPage(w, r, code, epage)
		return
	}

	// Here, moduleVersion is a valid *VersionInfo.
	licenses, err := s.ds.GetModuleLicenses(ctx, moduleVersion.ModulePath, moduleVersion.Version)
	if err != nil {
		log.Errorf("error getting module licenses for %s@%s: %v", moduleVersion.ModulePath, moduleVersion.Version, err)
		s.serveErrorPage(w, r, http.StatusInternalServerError, nil)
		return
	}

	modHeader := createModule(moduleVersion, license.ToMetadatas(licenses), version == internal.LatestVersion)
	tab := r.FormValue("tab")
	settings, ok := moduleTabLookup[tab]
	if !ok {
		tab = "overview"
		settings = moduleTabLookup["overview"]
	}
	canShowDetails := modHeader.IsRedistributable || settings.AlwaysShowDetails
	var details interface{}
	if canShowDetails {
		var err error
		details, err = fetchDetailsForModule(ctx, r, tab, s.ds, moduleVersion, licenses)
		if err != nil {
			log.Errorf("error fetching page for %q: %v", tab, err)
			s.serveErrorPage(w, r, http.StatusInternalServerError, nil)
			return
		}
	}
	page := &DetailsPage{
		basePage:       newBasePage(r, moduleTitle(moduleVersion.ModulePath)),
		Settings:       settings,
		Header:         modHeader,
		BreadcrumbPath: breadcrumbPath(moduleVersion.ModulePath, moduleVersion.ModulePath, moduleVersion.Version),
		Details:        details,
		CanShowDetails: canShowDetails,
		Tabs:           moduleTabSettings,
		Namespace:      "mod",
	}
	s.servePage(w, settings.TemplateName, page)
}

// fetchPackageOrModule handles logic common to the initial phase of
// handling both packages and modules: fetching information about the package
// or module.
//
// The get argument is a function that should retrieve a package or module at a
// given version. It returns the error from doing so, as well as the module
// path.
//
// fetchPackageOrModule parses urlPath into an import path and version, then
// calls the get function with those values. If get fails because the version
// cannot be found, fetchPackageOrModule calls get again with the latest
// version, to see if any versions of the package/module exist, in order to
// provide a more helpful error message.
//
// fetchPackageOrModule returns the import path and version requested, an
// HTTP status code, and possibly an error page to display.
func fetchPackageOrModule(ctx context.Context, ds DataSource, namespace, path, version string, get func(v string) (string, error)) (code int, _ *errorPage) {
	excluded, err := ds.IsExcluded(ctx, path)
	if err != nil {
		log.Errorf("error checking excluded path: %v", err)
		return http.StatusInternalServerError, nil
	}
	if excluded {
		// Return NotFound; don't let the user know that the package was excluded.
		return http.StatusNotFound, nil
	}

	// Fetch the package or module from the database.
	_, err = get(version)
	if err == nil {
		// A package or module was found for this path and version.
		return http.StatusOK, nil
	}
	if !xerrors.Is(err, derrors.NotFound) {
		// Something went wrong in executing the get function.
		log.Errorf("fetchPackageOrModule %s@%s: %v", path, version, err)
		return http.StatusInternalServerError, nil
	}
	if version == internal.LatestVersion {
		// We were not able to find a module or package at any version.
		return http.StatusNotFound, nil
	}

	// We did not find the given version, but maybe there is another version
	// available for this package or module.
	modulePath, err := get(internal.LatestVersion)
	if err != nil {
		if !xerrors.Is(err, derrors.NotFound) {
			log.Errorf("error: get(%s, Latest) for %s: %v", path, namespace, err)
		}
		// Couldn't get the latest version, for whatever reason. Treat
		// this like not finding the original version.
		return http.StatusNotFound, nil
	}

	// There is a later version of this package/module.
	word := "package"
	urlPath := "/" + path
	if namespace == "mod" {
		word = "module"
		urlPath = "/mod/" + path
	}
	epage := &errorPage{
		Message: fmt.Sprintf("%s %s@%s is not available.",
			strings.Title(word), path, formattedVersion(version, modulePath)),
		SecondaryMessage: template.HTML(
			fmt.Sprintf(`There are other versions of this %s that are! To view them, <a href="%s?tab=versions">click here</a>.</p>`, word, urlPath)),
	}
	return http.StatusSeeOther, epage
}

// parseDetailsURLPath returns the modulePath (if known),
// pkgPath and version specified by urlPath.
// urlPath is assumed to be a valid path following the structure:
//   /<module-path>[@<version>/<suffix>]
//
// If <version> is not specified, internal.LatestVersion is used for the
// version. modulePath can only be determined if <version> is specified.
//
// Leading and trailing slashes in the urlPath are trimmed.
func parseDetailsURLPath(urlPath string) (pkgPath, modulePath, version string, err error) {
	defer derrors.Wrap(&err, "parseDetailsURLPath(%q)", urlPath)

	// This splits urlPath into either:
	//   /<module-path>[/<suffix>]
	// or
	//   /<module-path>, @<version>/<suffix>
	// or
	//  /<module-path>/<suffix>, @<version>
	// TODO(b/140191811) The last URL route should redirect.
	parts := strings.SplitN(urlPath, "@", 2)
	basePath := strings.TrimSuffix(strings.TrimPrefix(parts[0], "/"), "/")
	if len(parts) == 1 {
		modulePath = internal.UnknownModulePath
		version = internal.LatestVersion
		pkgPath = basePath
	} else {
		// Parse the version and suffix from parts[1].
		endParts := strings.Split(parts[1], "/")
		suffix := strings.Join(endParts[1:], "/")
		version = endParts[0]
		if suffix == "" || version == internal.LatestVersion {
			modulePath = internal.UnknownModulePath
			pkgPath = basePath
		} else {
			modulePath = basePath
			pkgPath = basePath + "/" + suffix
		}
	}
	if err := module.CheckImportPath(pkgPath); err != nil {
		return "", "", "", fmt.Errorf("malformed path %q: %v", pkgPath, err)
	}
	if stdlib.Contains(pkgPath) {
		modulePath = stdlib.ModulePath
	}
	return pkgPath, modulePath, version, nil
}

// handleLatestVersion writes a JSON string with the latest version of the package or module.
// It expects URLs of the form
//   /latest-version/MODULE_PATH
// for modules, or
//   /latest-version/MODULE_PATH?pkg=PACKAGE_PATH
// for packages.
func (s *Server) handleLatestVersion(w http.ResponseWriter, r *http.Request) {
	modulePath := strings.TrimPrefix(r.URL.Path, "/latest-version/")
	packagePath := r.URL.Query().Get("pkg")
	v := s.LatestVersion(r.Context(), modulePath, packagePath)
	if _, err := fmt.Fprintf(w, "%q", v); err != nil {
		log.Errorf("handleLatestVersion: fmt.Fprintf: %v", err)
	}
}

// LatestVersion returns the latest version of the package or module.
// It returns the empty string on error.
// It is intended to be used as an argument to middleware.LatestVersion.
func (s *Server) LatestVersion(ctx context.Context, modulePath, packagePath string) string {
	v, err := s.latestVersion(ctx, modulePath, packagePath)
	if err != nil {
		// We get NotFound errors from directories; they clutter the log.
		if !xerrors.Is(err, derrors.NotFound) {
			log.Errorf("GetLatestVersion: %v", err)
		}
		return ""
	}
	return v
}

func (s *Server) latestVersion(ctx context.Context, modulePath, packagePath string) (_ string, err error) {
	defer derrors.Wrap(&err, "latestVersion(ctx, %q, %q)", modulePath, packagePath)

	var vi *internal.VersionInfo
	if packagePath == "" {
		vi, err = s.ds.GetVersionInfo(ctx, modulePath, internal.LatestVersion)
		if err != nil {
			return "", err
		}
	} else {
		pkg, err := s.ds.GetPackage(ctx, packagePath, modulePath, internal.LatestVersion)
		if err != nil {
			return "", err
		}
		vi = &pkg.VersionInfo
	}
	v := vi.Version
	if modulePath == stdlib.ModulePath {
		v, err = stdlib.TagForVersion(v)
		if err != nil {
			return "", err
		}
	}
	return v, nil
}
<|endoftext|>


<commit_before>// Copyright 2016 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package modules

import (
	"log"
	"net/http"
	"os"
	"testing"

	"github.com/issue9/assert"
	"github.com/issue9/middleware"
	"github.com/issue9/mux/v2"

	"github.com/issue9/web/module"
)

var (
	inits   = map[string]int{}
	router  = mux.New(false, false, false, nil, nil).Prefix("")
	infolog = log.New(os.Stderr, "", 0)
	f1      = func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("f1"))
		w.WriteHeader(http.StatusAccepted)
	}
)

func i(name string) func() error {
	return func() error {
		inits[name] = inits[name] + 1
		return nil
	}
}

func m(name string, f func() error, deps ...string) *module.Module {
	m := module.New(module.TypeModule, name, name, deps...)
	m.AddInit(f)
	return m
}

func mt(name, title string, f func() error, deps ...string) *module.Module {
	m := module.New(module.TypeModule, name, name, deps...)
	m.AddInit(f, title)
	return m
}

func newDep(ms []*module.Module, log *log.Logger) *dependency {
	mux := mux.New(false, false, false, nil, nil)

	return newDepencency(&Modules{
		Manager:  *middleware.NewManager(mux),
		modules:  ms,
		router:   mux.Prefix(""),
		services: make([]*module.Service, 0, 100),
	}, log)
}

func TestDependency_isDep(t *testing.T) {
	a := assert.New(t)
	dep := newDep([]*module.Module{
		m("m1", nil, "d1", "d2"),
		m("d1", nil, "d3"),
	}, nil)
	a.NotNil(dep)

	a.True(dep.isDep("m1", "d1"))
	a.True(dep.isDep("m1", "d2"))
	a.True(dep.isDep("m1", "d3")) // 通过 d1 继承
	a.False(dep.isDep("m1", "m1"))

	// 循环依赖
	dep = newDep([]*module.Module{
		m("m1", nil, "d1", "d2"),
		m("d1", nil, "d3"),
		m("d3", nil, "d1"),
	}, nil)
	a.True(dep.isDep("d1", "d1"))

	// 不存在的模块
	a.False(dep.isDep("d10", "d1"))
}

func TestDependency_checkDeps(t *testing.T) {
	a := assert.New(t)
	dep := newDep([]*module.Module{
		m("m1", nil, "d1", "d2"),
		m("d1", nil, "d3"),
	}, nil)

	m1 := dep.modules["m1"]
	a.Error(dep.checkDeps(m1)) // 依赖项不存在

	dep = newDep([]*module.Module{
		m("m1", nil, "d1", "d2"),
		m("d1", nil, "d3"),
		m("d2", nil, "d3"),
	}, nil)
	a.NotError(dep.checkDeps(m1))

	// 自我依赖
	dep = newDep([]*module.Module{
		m("m1", nil, "d1", "d2"),
		m("d1", nil, "d3"),
		m("d2", nil, "d3"),
		m("d3", nil, "d2"),
	}, nil)
	d2 := dep.modules["d2"]
	a.Error(dep.checkDeps(d2))
}

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

	// 缺少依赖项 d3
	dep := newDep([]*module.Module{
		m("m1", i("m1"), "d1", "d2"),
		m("d1", i("d1"), "d3"),
		m("d2", i("d2"), "d3"),
	}, infolog)
	a.Error(dep.init(""))

	m1 := m("m1", i("m1"), "d1", "d2")
	m1.PutFunc("/put", f1)
	m1.NewTag("install").PostFunc("/install", f1)
	ms := []*module.Module{
		m1,
		m("d1", i("d1"), "d3"),
		m("d2", i("d2"), "d3"),
		m("d3", i("d3")),
	}

	dep = newDep(ms, infolog)
	a.NotError(dep.init(""))
	a.Equal(len(inits), 4).
		Equal(inits["m1"], 1).
		Equal(inits["d1"], 1).
		Equal(inits["d2"], 1)

	dep = newDep(ms, infolog)
	a.NotError(dep.init("install"), infolog)
}
<commit_msg>整理测试内容<commit_after>// Copyright 2016 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package modules

import (
	"log"
	"net/http"
	"os"
	"testing"

	"github.com/issue9/assert"
	"github.com/issue9/middleware"
	"github.com/issue9/mux/v2"

	"github.com/issue9/web/module"
)

func m(name string, f func() error, deps ...string) *module.Module {
	m := module.New(module.TypeModule, name, name, deps...)
	m.AddInit(f)
	return m
}

func newDep(ms []*module.Module, log *log.Logger) *dependency {
	mux := mux.New(false, false, false, nil, nil)

	return newDepencency(&Modules{
		Manager:  *middleware.NewManager(mux),
		modules:  ms,
		router:   mux.Prefix(""),
		services: make([]*module.Service, 0, 100),
	}, log)
}

func TestDependency_isDep(t *testing.T) {
	a := assert.New(t)
	dep := newDep([]*module.Module{
		m("m1", nil, "d1", "d2"),
		m("d1", nil, "d3"),
	}, nil)
	a.NotNil(dep)

	a.True(dep.isDep("m1", "d1"))
	a.True(dep.isDep("m1", "d2"))
	a.True(dep.isDep("m1", "d3")) // 通过 d1 继承
	a.False(dep.isDep("m1", "m1"))

	// 循环依赖
	dep = newDep([]*module.Module{
		m("m1", nil, "d1", "d2"),
		m("d1", nil, "d3"),
		m("d3", nil, "d1"),
	}, nil)
	a.True(dep.isDep("d1", "d1"))

	// 不存在的模块
	a.False(dep.isDep("d10", "d1"))
}

func TestDependency_checkDeps(t *testing.T) {
	a := assert.New(t)
	dep := newDep([]*module.Module{
		m("m1", nil, "d1", "d2"),
		m("d1", nil, "d3"),
	}, nil)

	m1 := dep.modules["m1"]
	a.Error(dep.checkDeps(m1)) // 依赖项不存在

	dep = newDep([]*module.Module{
		m("m1", nil, "d1", "d2"),
		m("d1", nil, "d3"),
		m("d2", nil, "d3"),
	}, nil)
	a.NotError(dep.checkDeps(m1))

	// 自我依赖
	dep = newDep([]*module.Module{
		m("m1", nil, "d1", "d2"),
		m("d1", nil, "d3"),
		m("d2", nil, "d3"),
		m("d3", nil, "d2"),
	}, nil)
	d2 := dep.modules["d2"]
	a.Error(dep.checkDeps(d2))
}

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

	inits := map[string]int{}
	infolog := log.New(os.Stderr, "", 0)
	f1 := func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("f1"))
		w.WriteHeader(http.StatusAccepted)
	}
	i := func(name string) func() error {
		return func() error {
			inits[name] = inits[name] + 1
			return nil
		}
	}

	// 缺少依赖项 d3
	dep := newDep([]*module.Module{
		m("m1", i("m1"), "d1", "d2"),
		m("d1", i("d1"), "d3"),
		m("d2", i("d2"), "d3"),
	}, infolog)
	a.Error(dep.init(""))

	m1 := m("m1", i("m1"), "d1", "d2")
	m1.PutFunc("/put", f1)
	m1.NewTag("install").PostFunc("/install", f1)
	ms := []*module.Module{
		m1,
		m("d1", i("d1"), "d3"),
		m("d2", i("d2"), "d3"),
		m("d3", i("d3")),
	}

	dep = newDep(ms, infolog)
	a.NotError(dep.init(""))
	a.Equal(len(inits), 4).
		Equal(inits["m1"], 1).
		Equal(inits["d1"], 1).
		Equal(inits["d2"], 1)

	dep = newDep(ms, infolog)
	a.NotError(dep.init("install"), infolog)
}
<|endoftext|>


<commit_before>package platform

import (
	"log"
	"os"
	"fmt"
)

func ReadSettingsFile() {
	// Get Path to Settings file
	// First, Assume in site root directory

	// Second, Try to get path to settings.php if in site dir (look for index.php moving backwards?)
	// If we can't find the settings.php, return error

	// Testing things below
	dir, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(dir)
}

/*
  Modules
 */
func moduleEnable(module string) {
	//
}

func moduleDisable(module string) {
	//
}

func moduleScaffold() {
	//
}

func cacheClear(cache string) {
	//
}
<commit_msg>it now can check if site dir is there<commit_after>package platform

import (
	"log"
	"os"
	"fmt"
	"io/ioutil"
)

func ReadSettingsFile() {
	// Get Path to Settings
	// Getting current working directory
	dir, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(dir)

	defaultSettingsFilePath := "sites/default/settings.php"
	inDrupalDocroot := false

	//Try to figure out site root
	files, err := ioutil.ReadDir("./")
	if err != nil {
		log.Fatal(err)
	}

	for _, f := range files {
		fmt.Println(f.Name())
		// @todo Gotta be wary of "sites" dirs that are not from Drupal sites
		if f.Name() == "sites" {
			inDrupalDocroot = true
		}
	}

	if inDrupalDocroot {
		fmt.Println(defaultSettingsFilePath)
	} else {
		fmt.Println("Not in Drupal docroot. Have to do more.")
	}
	// First, Assume in site root directory

	// Second, Try to get path to settings.php if in site dir (look for index.php moving backwards?)
	// If we can't find the settings.php, return error

}

/*
  Modules
 */
func moduleEnable(module string) {
	//
}

func moduleDisable(module string) {
	//
}

func moduleScaffold() {
	//
}

func cacheClear(cache string) {
	//
}
<|endoftext|>


<commit_before>package msgbox

import "DST/Godeps/_workspace/src/github.com/andlabs/ui"

// New creates a new Window and hides the parent window until the "Ok" button is pressed
// once clicked, the done channel is closed so that the calling function can continue
//
// WARNING: New can't be called by the goroutine that created the parent window
func New(p ui.Window, titel, msg string) {
	done := make(chan struct{})
	go ui.Do(func() {
		p.Hide()
		msgField := ui.NewTextField()
		msgField.SetReadOnly(true)
		msgField.SetText(msg)
		btn := ui.NewButton("Ok")
		stack := ui.NewVerticalStack(
			msgField,
			btn,
		)
		stack.SetStretchy(0)
		w := ui.NewWindow(titel, 500, 200, stack)
		btn.OnClicked(func() {
			close(done)
			w.Close()
			p.Show()
		})
		w.Show()
	})
	<-done
}
<commit_msg>msgbox: never check in vendored paths<commit_after>package msgbox

import "github.com/andlabs/ui"

// New creates a new Window and hides the parent window until the "Ok" button is pressed
// once clicked, the done channel is closed so that the calling function can continue
//
// WARNING: New can't be called by the goroutine that created the parent window
func New(p ui.Window, titel, msg string) {
	done := make(chan struct{})
	go ui.Do(func() {
		p.Hide()
		msgField := ui.NewTextField()
		msgField.SetReadOnly(true)
		msgField.SetText(msg)
		btn := ui.NewButton("Ok")
		stack := ui.NewVerticalStack(
			msgField,
			btn,
		)
		stack.SetStretchy(0)
		w := ui.NewWindow(titel, 500, 200, stack)
		btn.OnClicked(func() {
			close(done)
			w.Close()
			p.Show()
		})
		w.Show()
	})
	<-done
}
<|endoftext|>


<commit_before>// Copyright 2020 The Ebiten 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 shaderir_test

import (
	"testing"

	. "github.com/hajimehoshi/ebiten/internal/shaderir"
)

func block(localVars []Type, stmts ...Stmt) Block {
	return Block{
		LocalVars: localVars,
		Stmts:     stmts,
	}
}

func blockStmt(block Block) Stmt {
	return Stmt{
		Type:   BlockStmt,
		Blocks: []Block{block},
	}
}

func assignStmt(lhs Expr, rhs Expr) Stmt {
	return Stmt{
		Type:  Assign,
		Exprs: []Expr{lhs, rhs},
	}
}

func ifStmt(cond Expr, block Block, elseBlock Block) Stmt {
	return Stmt{
		Type:   If,
		Exprs:  []Expr{cond},
		Blocks: []Block{block, elseBlock},
	}
}

func forStmt(init, end int, op Op, delta int, block Block) Stmt {
	return Stmt{
		Type:     For,
		Blocks:   []Block{block},
		ForInit:  init,
		ForEnd:   end,
		ForOp:    op,
		ForDelta: delta,
	}
}

func floatExpr(value float32) Expr {
	return Expr{
		Type:  FloatExpr,
		Float: value,
	}
}

func varNameExpr(vt VariableType, index int) Expr {
	return Expr{
		Type: VarName,
		Variable: Variable{
			Type:  vt,
			Index: index,
		},
	}
}

func binaryExpr(op Op, exprs ...Expr) Expr {
	return Expr{
		Type:  Binary,
		Op:    op,
		Exprs: exprs,
	}
}

func selectionExpr(cond, a, b Expr) Expr {
	return Expr{
		Type:  Selection,
		Exprs: []Expr{cond, a, b},
	}
}

func TestOutput(t *testing.T) {
	tests := []struct {
		Name    string
		Program Program
		Glsl    string
	}{
		{
			Name:    "Empty",
			Program: Program{},
			Glsl:    ``,
		},
		{
			Name: "Uniform",
			Program: Program{
				Uniforms: []Type{
					{Main: Float},
				},
			},
			Glsl: `uniform float U0;`,
		},
		{
			Name: "UniformStruct",
			Program: Program{
				Uniforms: []Type{
					{
						Main: Struct,
						Sub: []Type{
							{Main: Float},
						},
					},
				},
			},
			Glsl: `struct S0 {
	float M0;
};
uniform S0 U0;`,
		},
		{
			Name: "Vars",
			Program: Program{
				Uniforms: []Type{
					{Main: Float},
				},
				Attributes: []Type{
					{Main: Vec2},
				},
				Varyings: []Type{
					{Main: Vec3},
				},
			},
			Glsl: `uniform float U0;
attribute vec2 A0;
varying vec3 V0;`,
		},
		{
			Name: "Func",
			Program: Program{
				Funcs: []Func{
					{
						Name: "F0",
					},
				},
			},
			Glsl: `void F0(void) {
}`,
		},
		{
			Name: "FuncParams",
			Program: Program{
				Funcs: []Func{
					{
						Name: "F0",
						InParams: []Type{
							{Main: Float},
							{Main: Vec2},
							{Main: Vec4},
						},
						InOutParams: []Type{
							{Main: Mat2},
						},
						OutParams: []Type{
							{Main: Mat4},
						},
					},
				},
			},
			Glsl: `void F0(in float l0, in vec2 l1, in vec4 l2, inout mat2 l3, out mat4 l4) {
}`,
		},
		{
			Name: "FuncLocals",
			Program: Program{
				Funcs: []Func{
					{
						Name: "F0",
						InParams: []Type{
							{Main: Float},
						},
						InOutParams: []Type{
							{Main: Float},
						},
						OutParams: []Type{
							{Main: Float},
						},
						Block: block([]Type{
							{Main: Mat4},
							{Main: Mat4},
						}),
					},
				},
			},
			Glsl: `void F0(in float l0, inout float l1, out float l2) {
	mat4 l3;
	mat4 l4;
}`,
		},
		{
			Name: "FuncBlocks",
			Program: Program{
				Funcs: []Func{
					{
						Name: "F0",
						InParams: []Type{
							{Main: Float},
						},
						InOutParams: []Type{
							{Main: Float},
						},
						OutParams: []Type{
							{Main: Float},
						},
						Block: block(
							[]Type{
								{Main: Mat4},
								{Main: Mat4},
							},
							blockStmt(
								block(
									[]Type{
										{Main: Mat4},
										{Main: Mat4},
									},
								),
							),
						),
					},
				},
			},
			Glsl: `void F0(in float l0, inout float l1, out float l2) {
	mat4 l3;
	mat4 l4;
	{
		mat4 l5;
		mat4 l6;
	}
}`,
		},
		{
			Name: "Add",
			Program: Program{
				Funcs: []Func{
					{
						Name: "F0",
						InParams: []Type{
							{Main: Float},
							{Main: Float},
						},
						OutParams: []Type{
							{Main: Float},
						},
						Block: block(
							nil,
							assignStmt(
								varNameExpr(Local, 2),
								binaryExpr(
									Add,
									varNameExpr(Local, 0),
									varNameExpr(Local, 1),
								),
							),
						),
					},
				},
			},
			Glsl: `void F0(in float l0, in float l1, out float l2) {
	l2 = (l0) + (l1);
}`,
		},
		{
			Name: "Selection",
			Program: Program{
				Funcs: []Func{
					{
						Name: "F0",
						InParams: []Type{
							{Main: Bool},
							{Main: Float},
							{Main: Float},
						},
						OutParams: []Type{
							{Main: Float},
						},
						Block: block(
							nil,
							assignStmt(
								varNameExpr(Local, 3),
								selectionExpr(
									varNameExpr(Local, 0),
									varNameExpr(Local, 1),
									varNameExpr(Local, 2),
								),
							),
						),
					},
				},
			},
			Glsl: `void F0(in bool l0, in float l1, in float l2, out float l3) {
	l3 = (l0) ? (l1) : (l2);
}`,
		},
		{
			Name: "If",
			Program: Program{
				Funcs: []Func{
					{
						Name: "F0",
						InParams: []Type{
							{Main: Float},
							{Main: Float},
						},
						OutParams: []Type{
							{Main: Float},
						},
						Block: block(
							nil,
							ifStmt(
								binaryExpr(
									Equal,
									varNameExpr(Local, 0),
									floatExpr(0),
								),
								block(
									nil,
									assignStmt(
										varNameExpr(Local, 2),
										varNameExpr(Local, 0),
									),
								),
								block(
									nil,
									assignStmt(
										varNameExpr(Local, 2),
										varNameExpr(Local, 1),
									),
								),
							),
						),
					},
				},
			},
			Glsl: `void F0(in float l0, in float l1, out float l2) {
	if ((l0) == (0.000000000e+00)) {
		l2 = l0;
	} else {
		l2 = l1;
	}
}`,
		},
		{
			Name: "For",
			Program: Program{
				Funcs: []Func{
					{
						Name: "F0",
						InParams: []Type{
							{Main: Float},
							{Main: Float},
						},
						OutParams: []Type{
							{Main: Float},
						},
						Block: block(
							nil,
							forStmt(
								0,
								100,
								LessThan,
								1,
								block(
									nil,
									assignStmt(
										varNameExpr(Local, 2),
										varNameExpr(Local, 0),
									),
								),
							),
						),
					},
				},
			},
			Glsl: `void F0(in float l0, in float l1, out float l2) {
	for (int l3 = 0; l3 < 100; l3++) {
		l2 = l0;
	}
}`,
		},
	}
	for _, tc := range tests {
		got := tc.Program.Glsl()
		want := tc.Glsl + "\n"
		if got != want {
			t.Errorf("%s: got: %s, want: %s", tc.Name, got, want)
		}
	}
}
<commit_msg>shaderir: Add more tests<commit_after>// Copyright 2020 The Ebiten 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 shaderir_test

import (
	"testing"

	. "github.com/hajimehoshi/ebiten/internal/shaderir"
)

func block(localVars []Type, stmts ...Stmt) Block {
	return Block{
		LocalVars: localVars,
		Stmts:     stmts,
	}
}

func blockStmt(block Block) Stmt {
	return Stmt{
		Type:   BlockStmt,
		Blocks: []Block{block},
	}
}

func assignStmt(lhs Expr, rhs Expr) Stmt {
	return Stmt{
		Type:  Assign,
		Exprs: []Expr{lhs, rhs},
	}
}

func ifStmt(cond Expr, block Block, elseBlock Block) Stmt {
	return Stmt{
		Type:   If,
		Exprs:  []Expr{cond},
		Blocks: []Block{block, elseBlock},
	}
}

func forStmt(init, end int, op Op, delta int, block Block) Stmt {
	return Stmt{
		Type:     For,
		Blocks:   []Block{block},
		ForInit:  init,
		ForEnd:   end,
		ForOp:    op,
		ForDelta: delta,
	}
}

func floatExpr(value float32) Expr {
	return Expr{
		Type:  FloatExpr,
		Float: value,
	}
}

func varNameExpr(vt VariableType, index int) Expr {
	return Expr{
		Type: VarName,
		Variable: Variable{
			Type:  vt,
			Index: index,
		},
	}
}

func identExpr(ident string) Expr {
	return Expr{
		Type:  Ident,
		Ident: ident,
	}
}

func binaryExpr(op Op, exprs ...Expr) Expr {
	return Expr{
		Type:  Binary,
		Op:    op,
		Exprs: exprs,
	}
}

func selectionExpr(cond, a, b Expr) Expr {
	return Expr{
		Type:  Selection,
		Exprs: []Expr{cond, a, b},
	}
}

func fieldSelectorExpr(a, b Expr) Expr {
	return Expr{
		Type:  FieldSelector,
		Exprs: []Expr{a, b},
	}
}

func TestOutput(t *testing.T) {
	tests := []struct {
		Name    string
		Program Program
		Glsl    string
	}{
		{
			Name:    "Empty",
			Program: Program{},
			Glsl:    ``,
		},
		{
			Name: "Uniform",
			Program: Program{
				Uniforms: []Type{
					{Main: Float},
				},
			},
			Glsl: `uniform float U0;`,
		},
		{
			Name: "UniformStruct",
			Program: Program{
				Uniforms: []Type{
					{
						Main: Struct,
						Sub: []Type{
							{Main: Float},
						},
					},
				},
			},
			Glsl: `struct S0 {
	float M0;
};
uniform S0 U0;`,
		},
		{
			Name: "Vars",
			Program: Program{
				Uniforms: []Type{
					{Main: Float},
				},
				Attributes: []Type{
					{Main: Vec2},
				},
				Varyings: []Type{
					{Main: Vec3},
				},
			},
			Glsl: `uniform float U0;
attribute vec2 A0;
varying vec3 V0;`,
		},
		{
			Name: "Func",
			Program: Program{
				Funcs: []Func{
					{
						Name: "F0",
					},
				},
			},
			Glsl: `void F0(void) {
}`,
		},
		{
			Name: "FuncParams",
			Program: Program{
				Funcs: []Func{
					{
						Name: "F0",
						InParams: []Type{
							{Main: Float},
							{Main: Vec2},
							{Main: Vec4},
						},
						InOutParams: []Type{
							{Main: Mat2},
						},
						OutParams: []Type{
							{Main: Mat4},
						},
					},
				},
			},
			Glsl: `void F0(in float l0, in vec2 l1, in vec4 l2, inout mat2 l3, out mat4 l4) {
}`,
		},
		{
			Name: "FuncLocals",
			Program: Program{
				Funcs: []Func{
					{
						Name: "F0",
						InParams: []Type{
							{Main: Float},
						},
						InOutParams: []Type{
							{Main: Float},
						},
						OutParams: []Type{
							{Main: Float},
						},
						Block: block([]Type{
							{Main: Mat4},
							{Main: Mat4},
						}),
					},
				},
			},
			Glsl: `void F0(in float l0, inout float l1, out float l2) {
	mat4 l3;
	mat4 l4;
}`,
		},
		{
			Name: "FuncBlocks",
			Program: Program{
				Funcs: []Func{
					{
						Name: "F0",
						InParams: []Type{
							{Main: Float},
						},
						InOutParams: []Type{
							{Main: Float},
						},
						OutParams: []Type{
							{Main: Float},
						},
						Block: block(
							[]Type{
								{Main: Mat4},
								{Main: Mat4},
							},
							blockStmt(
								block(
									[]Type{
										{Main: Mat4},
										{Main: Mat4},
									},
								),
							),
						),
					},
				},
			},
			Glsl: `void F0(in float l0, inout float l1, out float l2) {
	mat4 l3;
	mat4 l4;
	{
		mat4 l5;
		mat4 l6;
	}
}`,
		},
		{
			Name: "Add",
			Program: Program{
				Funcs: []Func{
					{
						Name: "F0",
						InParams: []Type{
							{Main: Float},
							{Main: Float},
						},
						OutParams: []Type{
							{Main: Float},
						},
						Block: block(
							nil,
							assignStmt(
								varNameExpr(Local, 2),
								binaryExpr(
									Add,
									varNameExpr(Local, 0),
									varNameExpr(Local, 1),
								),
							),
						),
					},
				},
			},
			Glsl: `void F0(in float l0, in float l1, out float l2) {
	l2 = (l0) + (l1);
}`,
		},
		{
			Name: "Selection",
			Program: Program{
				Funcs: []Func{
					{
						Name: "F0",
						InParams: []Type{
							{Main: Bool},
							{Main: Float},
							{Main: Float},
						},
						OutParams: []Type{
							{Main: Float},
						},
						Block: block(
							nil,
							assignStmt(
								varNameExpr(Local, 3),
								selectionExpr(
									varNameExpr(Local, 0),
									varNameExpr(Local, 1),
									varNameExpr(Local, 2),
								),
							),
						),
					},
				},
			},
			Glsl: `void F0(in bool l0, in float l1, in float l2, out float l3) {
	l3 = (l0) ? (l1) : (l2);
}`,
		},
		{
			Name: "FieldSelector",
			Program: Program{
				Funcs: []Func{
					{
						Name: "F0",
						InParams: []Type{
							{Main: Vec4},
						},
						OutParams: []Type{
							{Main: Float},
						},
						Block: block(
							nil,
							assignStmt(
								varNameExpr(Local, 1),
								fieldSelectorExpr(
									varNameExpr(Local, 0),
									identExpr("x"),
								),
							),
						),
					},
				},
			},
			Glsl: `void F0(in vec4 l0, out float l1) {
	l1 = (l0).x;
}`,
		},
		{
			Name: "If",
			Program: Program{
				Funcs: []Func{
					{
						Name: "F0",
						InParams: []Type{
							{Main: Float},
							{Main: Float},
						},
						OutParams: []Type{
							{Main: Float},
						},
						Block: block(
							nil,
							ifStmt(
								binaryExpr(
									Equal,
									varNameExpr(Local, 0),
									floatExpr(0),
								),
								block(
									nil,
									assignStmt(
										varNameExpr(Local, 2),
										varNameExpr(Local, 0),
									),
								),
								block(
									nil,
									assignStmt(
										varNameExpr(Local, 2),
										varNameExpr(Local, 1),
									),
								),
							),
						),
					},
				},
			},
			Glsl: `void F0(in float l0, in float l1, out float l2) {
	if ((l0) == (0.000000000e+00)) {
		l2 = l0;
	} else {
		l2 = l1;
	}
}`,
		},
		{
			Name: "For",
			Program: Program{
				Funcs: []Func{
					{
						Name: "F0",
						InParams: []Type{
							{Main: Float},
							{Main: Float},
						},
						OutParams: []Type{
							{Main: Float},
						},
						Block: block(
							nil,
							forStmt(
								0,
								100,
								LessThan,
								1,
								block(
									nil,
									assignStmt(
										varNameExpr(Local, 2),
										varNameExpr(Local, 0),
									),
								),
							),
						),
					},
				},
			},
			Glsl: `void F0(in float l0, in float l1, out float l2) {
	for (int l3 = 0; l3 < 100; l3++) {
		l2 = l0;
	}
}`,
		},
	}
	for _, tc := range tests {
		got := tc.Program.Glsl()
		want := tc.Glsl + "\n"
		if got != want {
			t.Errorf("%s: got: %s, want: %s", tc.Name, got, want)
		}
	}
}
<|endoftext|>


<commit_before>// Copyright 2014 Google LLC
//
// 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 testutil contains helper functions for writing tests.
package testutil

import (
	"context"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"

	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	"golang.org/x/oauth2/jwt"
)

const (
	envProjID     = "GCLOUD_TESTS_GOLANG_PROJECT_ID"
	envPrivateKey = "GCLOUD_TESTS_GOLANG_KEY"
)

// ProjID returns the project ID to use in integration tests, or the empty
// string if none is configured.
func ProjID() string {
	return os.Getenv(envProjID)
}

// Credentials returns the credentials to use in integration tests, or nil if
// none is configured. It uses the standard environment variable for tests in
// this repo.
func Credentials(ctx context.Context, scopes ...string) *google.Credentials {
	return CredentialsEnv(ctx, envPrivateKey, scopes...)
}

// CredentialsEnv returns the credentials to use in integration tests, or nil
// if none is configured. If the environment variable is unset, CredentialsEnv
// will try to find 'Application Default Credentials'. Else, CredentialsEnv
// will return nil. CredentialsEnv will log.Fatal if the token source is
// specified but missing or invalid.
func CredentialsEnv(ctx context.Context, envVar string, scopes ...string) *google.Credentials {
	key := os.Getenv(envVar)
	if key == "" { // Try for application default credentials.
		creds, err := google.FindDefaultCredentials(ctx, scopes...)
		if err != nil {
			log.Println("No 'Application Default Credentials' found.")
			return nil
		}
		return creds
	}

	data, err := ioutil.ReadFile(key)
	if err != nil {
		log.Fatal(err)
	}

	creds, err := google.CredentialsFromJSON(ctx, data, scopes...)
	if err != nil {
		log.Fatal(err)
	}
	return creds
}

// TokenSource returns the OAuth2 token source to use in integration tests,
// or nil if none is configured. It uses the standard environment variable
// for tests in this repo.
func TokenSource(ctx context.Context, scopes ...string) oauth2.TokenSource {
	return TokenSourceEnv(ctx, envPrivateKey, scopes...)
}

// TokenSourceEnv returns the OAuth2 token source to use in integration tests. or nil
// if none is configured. It tries to get credentials from the filename in the
// environment variable envVar. If the environment variable is unset, TokenSourceEnv
// will try to find 'Application Default Credentials'. Else, TokenSourceEnv will
// return nil. TokenSourceEnv will log.Fatal if the token source is specified but
// missing or invalid.
func TokenSourceEnv(ctx context.Context, envVar string, scopes ...string) oauth2.TokenSource {
	key := os.Getenv(envVar)
	if key == "" { // Try for application default credentials.
		ts, err := google.DefaultTokenSource(ctx, scopes...)
		if err != nil {
			log.Println("No 'Application Default Credentials' found.")
			return nil
		}
		return ts
	}
	conf, err := jwtConfigFromFile(key, scopes)
	if err != nil {
		log.Fatal(err)
	}
	return conf.TokenSource(ctx)
}

// JWTConfig reads the JSON private key file whose name is in the default
// environment variable, and returns the jwt.Config it contains. It ignores
// scopes.
// If the environment variable is empty, it returns (nil, nil).
func JWTConfig() (*jwt.Config, error) {
	return jwtConfigFromFile(os.Getenv(envPrivateKey), nil)
}

// jwtConfigFromFile reads the given JSON private key file, and returns the
// jwt.Config it contains.
// If the filename is empty, it returns (nil, nil).
func jwtConfigFromFile(filename string, scopes []string) (*jwt.Config, error) {
	if filename == "" {
		return nil, nil
	}
	jsonKey, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, fmt.Errorf("cannot read the JSON key file, err: %v", err)
	}
	conf, err := google.JWTConfigFromJSON(jsonKey, scopes...)
	if err != nil {
		return nil, fmt.Errorf("google.JWTConfigFromJSON: %v", err)
	}
	return conf, nil
}

// CanReplay reports whether an integration test can be run in replay mode.
// The replay file must exist, and the GCLOUD_TESTS_GOLANG_ENABLE_REPLAY
// environment variable must be non-empty.
func CanReplay(replayFilename string) bool {
	if os.Getenv("GCLOUD_TESTS_GOLANG_ENABLE_REPLAY") == "" {
		return false
	}
	_, err := os.Stat(replayFilename)
	return err == nil
}

// ErroringTokenSource is a token source for testing purposes,
// to always return a non-nil error to its caller. It is useful
// when testing error responses with bad oauth2 credentials.
type ErroringTokenSource struct{}

// Token implements oauth2.TokenSource, returning a nil oauth2.Token and a non-nil error.
func (fts ErroringTokenSource) Token() (*oauth2.Token, error) {
	return nil, errors.New("intentional error")
}
<commit_msg>feat(internal/testutil): make it easier to test integration tests locally (#6786)<commit_after>// Copyright 2014 Google LLC
//
// 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 testutil contains helper functions for writing tests.
package testutil

import (
	"context"
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"

	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	"golang.org/x/oauth2/jwt"
	"google.golang.org/api/impersonate"
)

const (
	envProjID      = "GCLOUD_TESTS_GOLANG_PROJECT_ID"
	envPrivateKey  = "GCLOUD_TESTS_GOLANG_KEY"
	envImpersonate = "GCLOUD_TESTS_IMPERSONATE_CREDENTIALS"
)

// ProjID returns the project ID to use in integration tests, or the empty
// string if none is configured.
func ProjID() string {
	return os.Getenv(envProjID)
}

// Credentials returns the credentials to use in integration tests, or nil if
// none is configured. It uses the standard environment variable for tests in
// this repo.
func Credentials(ctx context.Context, scopes ...string) *google.Credentials {
	return CredentialsEnv(ctx, envPrivateKey, scopes...)
}

// CredentialsEnv returns the credentials to use in integration tests, or nil
// if none is configured. If the environment variable is unset, CredentialsEnv
// will try to find 'Application Default Credentials'. Else, CredentialsEnv
// will return nil. CredentialsEnv will log.Fatal if the token source is
// specified but missing or invalid.
func CredentialsEnv(ctx context.Context, envVar string, scopes ...string) *google.Credentials {
	if impKey := os.Getenv(envImpersonate); impKey == "true" {
		return &google.Credentials{
			TokenSource: impersonatedTokenSource(ctx, scopes),
			ProjectID:   "dulcet-port-762",
		}
	}
	key := os.Getenv(envVar)
	if key == "" { // Try for application default credentials.
		creds, err := google.FindDefaultCredentials(ctx, scopes...)
		if err != nil {
			log.Println("No 'Application Default Credentials' found.")
			return nil
		}
		return creds
	}

	data, err := ioutil.ReadFile(key)
	if err != nil {
		log.Fatal(err)
	}

	creds, err := google.CredentialsFromJSON(ctx, data, scopes...)
	if err != nil {
		log.Fatal(err)
	}
	return creds
}

// TokenSource returns the OAuth2 token source to use in integration tests,
// or nil if none is configured. It uses the standard environment variable
// for tests in this repo.
func TokenSource(ctx context.Context, scopes ...string) oauth2.TokenSource {
	return TokenSourceEnv(ctx, envPrivateKey, scopes...)
}

// TokenSourceEnv returns the OAuth2 token source to use in integration tests. or nil
// if none is configured. It tries to get credentials from the filename in the
// environment variable envVar. If the environment variable is unset, TokenSourceEnv
// will try to find 'Application Default Credentials'. Else, TokenSourceEnv will
// return nil. TokenSourceEnv will log.Fatal if the token source is specified but
// missing or invalid.
func TokenSourceEnv(ctx context.Context, envVar string, scopes ...string) oauth2.TokenSource {
	if impKey := os.Getenv(envImpersonate); impKey == "true" {
		return impersonatedTokenSource(ctx, scopes)
	}
	key := os.Getenv(envVar)
	if key == "" { // Try for application default credentials.
		ts, err := google.DefaultTokenSource(ctx, scopes...)
		if err != nil {
			log.Println("No 'Application Default Credentials' found.")
			return nil
		}
		return ts
	}
	conf, err := jwtConfigFromFile(key, scopes)
	if err != nil {
		log.Fatal(err)
	}
	return conf.TokenSource(ctx)
}

func impersonatedTokenSource(ctx context.Context, scopes []string) oauth2.TokenSource {
	ts, err := impersonate.CredentialsTokenSource(ctx, impersonate.CredentialsConfig{
		TargetPrincipal: "kokoro@dulcet-port-762.iam.gserviceaccount.com",
		Scopes:          scopes,
	})
	if err != nil {
		log.Fatalf("Unable to impersonate credentials, exiting: %v", err)
	}
	return ts
}

// JWTConfig reads the JSON private key file whose name is in the default
// environment variable, and returns the jwt.Config it contains. It ignores
// scopes.
// If the environment variable is empty, it returns (nil, nil).
func JWTConfig() (*jwt.Config, error) {
	return jwtConfigFromFile(os.Getenv(envPrivateKey), nil)
}

// jwtConfigFromFile reads the given JSON private key file, and returns the
// jwt.Config it contains.
// If the filename is empty, it returns (nil, nil).
func jwtConfigFromFile(filename string, scopes []string) (*jwt.Config, error) {
	if filename == "" {
		return nil, nil
	}
	jsonKey, err := ioutil.ReadFile(filename)
	if err != nil {
		return nil, fmt.Errorf("cannot read the JSON key file, err: %v", err)
	}
	conf, err := google.JWTConfigFromJSON(jsonKey, scopes...)
	if err != nil {
		return nil, fmt.Errorf("google.JWTConfigFromJSON: %v", err)
	}
	return conf, nil
}

// CanReplay reports whether an integration test can be run in replay mode.
// The replay file must exist, and the GCLOUD_TESTS_GOLANG_ENABLE_REPLAY
// environment variable must be non-empty.
func CanReplay(replayFilename string) bool {
	if os.Getenv("GCLOUD_TESTS_GOLANG_ENABLE_REPLAY") == "" {
		return false
	}
	_, err := os.Stat(replayFilename)
	return err == nil
}

// ErroringTokenSource is a token source for testing purposes,
// to always return a non-nil error to its caller. It is useful
// when testing error responses with bad oauth2 credentials.
type ErroringTokenSource struct{}

// Token implements oauth2.TokenSource, returning a nil oauth2.Token and a non-nil error.
func (fts ErroringTokenSource) Token() (*oauth2.Token, error) {
	return nil, errors.New("intentional error")
}
<|endoftext|>


<commit_before>package utils

import (
	"crypto/md5"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"path/filepath"

	iconv "github.com/djimenez/iconv-go"
)

// TempFolderName 快取資料夾名稱
const TempFolderName = ".gogrscache"

// HTTPCache net/http 快取功能
type HTTPCache struct {
	Dir            string
	iconvConverter func([]byte) []byte
}

// NewHTTPCache New 一個 HTTPCache.
//
// dir 為暫存位置，fromEncoding 來源檔案的編碼，一律轉換為 utf8
func NewHTTPCache(dir string, fromEncoding string) *HTTPCache {
	err := os.Mkdir(dir, 0700)
	if os.IsNotExist(err) {
		dir = filepath.Join(os.TempDir(), TempFolderName)
		os.Mkdir(dir, 0700)
	}
	return &HTTPCache{Dir: dir, iconvConverter: renderIconvConverter(fromEncoding)}
}

// Get 透過 http.Get 取得檔案或從暫存中取得檔案
//
// rand 為是否支援網址帶入亂數值，url 需有 '%d' 格式。
func (hc HTTPCache) Get(url string, rand bool) ([]byte, error) {
	filehash := fmt.Sprintf("%x", md5.Sum([]byte(url)))
	content, err := hc.readFile(filehash)
	if err != nil {
		return hc.saveFile(url, filehash, rand, nil)
	}
	return content, nil
}

// PostForm 透過 http.PostForm 取得檔案或從暫存中取得檔案
func (hc HTTPCache) PostForm(url string, data url.Values) ([]byte, error) {
	hash := md5.New()
	io.WriteString(hash, url)
	io.WriteString(hash, data.Encode())

	filehash := fmt.Sprintf("%x", hash.Sum(nil))
	content, err := hc.readFile(filehash)
	if err != nil {
		return hc.saveFile(url, filehash, false, data)
	}
	return content, nil
}

// readFile 從快取資料裡面取得
func (hc HTTPCache) readFile(filehash string) ([]byte, error) {
	f, err := os.Open(filepath.Join(hc.Dir, filehash))
	defer f.Close()
	if err != nil {
		return nil, err
	}
	return ioutil.ReadAll(f)
}

// saveFile 從網路取得資料後放入快取資料夾
func (hc HTTPCache) saveFile(url, filehash string, rand bool, data url.Values) ([]byte, error) {
	if rand {
		url = fmt.Sprintf(url, RandInt())
	}
	var resp *http.Response
	if len(data) == 0 {
		resp, _ = http.Get(url)
	} else {
		resp, _ = http.PostForm(url, data)
	}
	defer resp.Body.Close()

	f, err := os.Create(filepath.Join(hc.Dir, filehash))
	defer f.Close()

	content, _ := ioutil.ReadAll(resp.Body)

	out := hc.iconvConverter(content)
	f.Write(out)
	return out, err
}

// renderIconvConverter wrapper function for iconv converter.
func renderIconvConverter(fromEncoding string) func([]byte) []byte {
	if fromEncoding == "utf8" || fromEncoding == "utf-8" {
		return func(str []byte) []byte {
			return str
		}
	}
	return func(content []byte) []byte {
		converter, _ := iconv.NewConverter(fromEncoding, "utf-8")
		var out []byte
		out = make([]byte, len(content)*2)
		_, outLen, _ := converter.Convert(content, out)
		return out[:outLen]
	}
}
<commit_msg>Fixed http too many open files.<commit_after>package utils

import (
	"crypto/md5"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"time"

	iconv "github.com/djimenez/iconv-go"
)

// TempFolderName 快取資料夾名稱
const TempFolderName = ".gogrscache"

// HTTPCache net/http 快取功能
type HTTPCache struct {
	Dir            string
	iconvConverter func([]byte) []byte
}

// NewHTTPCache New 一個 HTTPCache.
//
// dir 為暫存位置，fromEncoding 來源檔案的編碼，一律轉換為 utf8
func NewHTTPCache(dir string, fromEncoding string) *HTTPCache {
	err := os.Mkdir(dir, 0700)
	if os.IsNotExist(err) {
		dir = filepath.Join(os.TempDir(), TempFolderName)
		os.Mkdir(dir, 0700)
	}
	return &HTTPCache{Dir: dir, iconvConverter: renderIconvConverter(fromEncoding)}
}

// Get 透過 http.Get 取得檔案或從暫存中取得檔案
//
// rand 為是否支援網址帶入亂數值，url 需有 '%d' 格式。
func (hc HTTPCache) Get(url string, rand bool) ([]byte, error) {
	filehash := fmt.Sprintf("%x", md5.Sum([]byte(url)))
	content, err := hc.readFile(filehash)
	if err != nil {
		return hc.saveFile(url, filehash, rand, nil)
	}
	return content, nil
}

// PostForm 透過 http.PostForm 取得檔案或從暫存中取得檔案
func (hc HTTPCache) PostForm(url string, data url.Values) ([]byte, error) {
	hash := md5.New()
	io.WriteString(hash, url)
	io.WriteString(hash, data.Encode())

	filehash := fmt.Sprintf("%x", hash.Sum(nil))
	content, err := hc.readFile(filehash)
	if err != nil {
		return hc.saveFile(url, filehash, false, data)
	}
	return content, nil
}

// readFile 從快取資料裡面取得
func (hc HTTPCache) readFile(filehash string) ([]byte, error) {
	f, err := os.Open(filepath.Join(hc.Dir, filehash))
	defer f.Close()
	if err != nil {
		return nil, err
	}
	return ioutil.ReadAll(f)
}

var transport = &http.Transport{
	Proxy: http.ProxyFromEnvironment,
	Dial: (&net.Dialer{
		Timeout:   0,
		KeepAlive: 0,
	}).Dial,
	TLSHandshakeTimeout: 10 * time.Second,
}

var httpClient = &http.Client{Transport: transport}

// saveFile 從網路取得資料後放入快取資料夾
func (hc HTTPCache) saveFile(url, filehash string, rand bool, data url.Values) ([]byte, error) {
	if rand {
		url = fmt.Sprintf(url, RandInt())
	}
	var resp *http.Response
	var req *http.Request
	var err error
	if len(data) == 0 {
		// http.Get
		req, _ = http.NewRequest("GET", url, nil)
		req.Header.Set("Connection", "close")
	} else {
		// http.PostForm
		req, _ = http.NewRequest("POST", url, strings.NewReader(data.Encode()))
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
		req.Header.Set("Connection", "close")
	}

	resp, _ = httpClient.Do(req)
	defer resp.Body.Close()

	content, _ := ioutil.ReadAll(resp.Body)

	f, _ := os.Create(filepath.Join(hc.Dir, filehash))
	defer f.Close()

	out := hc.iconvConverter(content)
	f.Write(out)

	return out, err
}

// renderIconvConverter wrapper function for iconv converter.
func renderIconvConverter(fromEncoding string) func([]byte) []byte {
	if fromEncoding == "utf8" || fromEncoding == "utf-8" {
		return func(str []byte) []byte {
			return str
		}
	}
	return func(content []byte) []byte {
		converter, _ := iconv.NewConverter(fromEncoding, "utf-8")
		var out []byte
		out = make([]byte, len(content)*2)
		_, outLen, _ := converter.Convert(content, out)
		return out[:outLen]
	}
}
<|endoftext|>