<commit_before>/*
Copyright 2021 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package scheduler

import (
	"context"
	"fmt"
	"testing"
	"time"

	v1 "k8s.io/api/core/v1"
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/uuid"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/kubernetes"
	"k8s.io/kube-scheduler/config/v1beta3"
	apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
	"k8s.io/kubernetes/pkg/scheduler"
	configtesting "k8s.io/kubernetes/pkg/scheduler/apis/config/testing"
	"k8s.io/kubernetes/pkg/scheduler/framework"
	frameworkruntime "k8s.io/kubernetes/pkg/scheduler/framework/runtime"
	st "k8s.io/kubernetes/pkg/scheduler/testing"
	testfwk "k8s.io/kubernetes/test/integration/framework"
	testutils "k8s.io/kubernetes/test/integration/util"
	imageutils "k8s.io/kubernetes/test/utils/image"
	"k8s.io/utils/pointer"
)

// TestCoreResourceEnqueue verify Pods failed by in-tree default plugins can be
// moved properly upon their registered events.
func TestCoreResourceEnqueue(t *testing.T) {
	// Use zero backoff seconds to bypass backoffQ.
	// It's intended to not start the scheduler's queue, and hence to
	// not start any flushing logic. We will pop and schedule the Pods manually later.
	testCtx := testutils.InitTestSchedulerWithOptions(
		t,
		testutils.InitTestAPIServer(t, "core-res-enqueue", nil),
		scheduler.WithPodInitialBackoffSeconds(0),
		scheduler.WithPodMaxBackoffSeconds(0),
	)
	testutils.SyncInformerFactory(testCtx)

	defer testutils.CleanupTest(t, testCtx)

	cs, ns, ctx := testCtx.ClientSet, testCtx.NS.Name, testCtx.Ctx
	// Create one Node with a taint.
	node := st.MakeNode().Name("fake-node").Capacity(map[v1.ResourceName]string{v1.ResourceCPU: "2"}).Obj()
	node.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoSchedule}}
	if _, err := cs.CoreV1().Nodes().Create(ctx, node, metav1.CreateOptions{}); err != nil {
		t.Fatalf("Failed to create Node %q: %v", node.Name, err)
	}

	// Create two Pods that are both unschedulable.
	// - Pod1 is a best-effort Pod, but doesn't have the required toleration.
	// - Pod2 requests a large amount of CPU resource that the node cannot fit.
	//   Note: Pod2 will fail the tainttoleration plugin b/c that's ordered prior to noderesources.
	// - Pod3 has the required toleration, but requests a non-existing PVC.
	pod1 := st.MakePod().Namespace(ns).Name("pod1").Container("image").Obj()
	pod2 := st.MakePod().Namespace(ns).Name("pod2").Req(map[v1.ResourceName]string{v1.ResourceCPU: "4"}).Obj()
	pod3 := st.MakePod().Namespace(ns).Name("pod3").Toleration("foo").PVC("pvc").Container("image").Obj()
	for _, pod := range []*v1.Pod{pod1, pod2, pod3} {
		if _, err := cs.CoreV1().Pods(ns).Create(ctx, pod, metav1.CreateOptions{}); err != nil {
			t.Fatalf("Failed to create Pod %q: %v", pod.Name, err)
		}
	}

	// Wait for the three pods to be present in the scheduling queue.
	if err := wait.Poll(time.Millisecond*200, wait.ForeverTestTimeout, func() (bool, error) {
		return len(testCtx.Scheduler.SchedulingQueue.PendingPods()) == 3, nil
	}); err != nil {
		t.Fatal(err)
	}

	// Pop the three pods out. They should be unschedulable.
	for i := 0; i < 3; i++ {
		podInfo := nextPodOrDie(t, testCtx)
		fwk, ok := testCtx.Scheduler.Profiles[podInfo.Pod.Spec.SchedulerName]
		if !ok {
			t.Fatalf("Cannot find the profile for Pod %v", podInfo.Pod.Name)
		}
		// Schedule the Pod manually.
		_, fitError := testCtx.Scheduler.SchedulePod(ctx, fwk, framework.NewCycleState(), podInfo.Pod)
		if fitError == nil {
			t.Fatalf("Expect Pod %v to fail at scheduling.", podInfo.Pod.Name)
		}
		testCtx.Scheduler.Error(podInfo, fitError)

		// Scheduling cycle is incremented by one after NextPod() is called, so
		// pass a number larger than i to move Pod to unschedulablePods.
		testCtx.Scheduler.SchedulingQueue.AddUnschedulableIfNotPresent(podInfo, int64(i+10))
	}

	// Trigger a NodeTaintChange event.
	// We expect this event to trigger moving the test Pod from unschedulablePods to activeQ.
	node.Spec.Taints = nil
	if _, err := cs.CoreV1().Nodes().Update(ctx, node, metav1.UpdateOptions{}); err != nil {
		t.Fatalf("Failed to remove taints off the node: %v", err)
	}

	// Now we should be able to pop the Pod from activeQ again.
	podInfo := nextPodOrDie(t, testCtx)
	if podInfo.Attempts != 2 {
		t.Fatalf("Expected the Pod to be attempted 2 times, but got %v", podInfo.Attempts)
	}
	if got := podInfo.Pod.Name; got != "pod1" {
		t.Fatalf("Expected pod1 to be popped, but got %v", got)
	}

	// Pod2 and Pod3 are not expected to be popped out.
	// - Although the failure reason has been lifted, Pod2 still won't be moved to active due to
	//   the node event's preCheckForNode().
	// - Regarding Pod3, the NodeTaintChange event is irrelevant with its scheduling failure.
	podInfo = nextPod(t, testCtx)
	if podInfo != nil {
		t.Fatalf("Unexpected pod %v get popped out", podInfo.Pod.Name)
	}
}

var _ framework.FilterPlugin = &fakeCRPlugin{}
var _ framework.EnqueueExtensions = &fakeCRPlugin{}

type fakeCRPlugin struct{}

func (f *fakeCRPlugin) Name() string {
	return "fakeCRPlugin"
}

func (f *fakeCRPlugin) Filter(_ context.Context, _ *framework.CycleState, _ *v1.Pod, _ *framework.NodeInfo) *framework.Status {
	return framework.NewStatus(framework.Unschedulable, "always fail")
}

// EventsToRegister returns the possible events that may make a Pod
// failed by this plugin schedulable.
func (f *fakeCRPlugin) EventsToRegister() []framework.ClusterEvent {
	return []framework.ClusterEvent{
		{Resource: "foos.v1.example.com", ActionType: framework.All},
	}
}

// TestCustomResourceEnqueue constructs a fake plugin that registers custom resources
// to verify Pods failed by this plugin can be moved properly upon CR events.
func TestCustomResourceEnqueue(t *testing.T) {
	// Start API Server with apiextensions supported.
	server := apiservertesting.StartTestServerOrDie(
		t, apiservertesting.NewDefaultTestServerOptions(),
		[]string{"--disable-admission-plugins=ServiceAccount,TaintNodesByCondition", "--runtime-config=api/all=true"},
		testfwk.SharedEtcd(),
	)
	testCtx := &testutils.TestContext{}
	testCtx.Ctx, testCtx.CancelFn = context.WithCancel(context.Background())
	testCtx.CloseFn = func() { server.TearDownFn() }

	apiExtensionClient := apiextensionsclient.NewForConfigOrDie(server.ClientConfig)
	dynamicClient := dynamic.NewForConfigOrDie(server.ClientConfig)

	// Create a Foo CRD.
	fooCRD := &apiextensionsv1.CustomResourceDefinition{
		ObjectMeta: metav1.ObjectMeta{
			Name: "foos.example.com",
		},
		Spec: apiextensionsv1.CustomResourceDefinitionSpec{
			Group: "example.com",
			Scope: apiextensionsv1.NamespaceScoped,
			Names: apiextensionsv1.CustomResourceDefinitionNames{
				Plural: "foos",
				Kind:   "Foo",
			},
			Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
				{
					Name:    "v1",
					Served:  true,
					Storage: true,
					Schema: &apiextensionsv1.CustomResourceValidation{
						OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
							Type: "object",
							Properties: map[string]apiextensionsv1.JSONSchemaProps{
								"field": {Type: "string"},
							},
						},
					},
				},
			},
		},
	}
	var err error
	fooCRD, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Create(testCtx.Ctx, fooCRD, metav1.CreateOptions{})
	if err != nil {
		t.Fatal(err)
	}

	registry := frameworkruntime.Registry{
		"fakeCRPlugin": func(_ runtime.Object, fh framework.Handle) (framework.Plugin, error) {
			return &fakeCRPlugin{}, nil
		},
	}
	cfg := configtesting.V1beta3ToInternalWithDefaults(t, v1beta3.KubeSchedulerConfiguration{
		Profiles: []v1beta3.KubeSchedulerProfile{{
			SchedulerName: pointer.StringPtr(v1.DefaultSchedulerName),
			Plugins: &v1beta3.Plugins{
				Filter: v1beta3.PluginSet{
					Enabled: []v1beta3.Plugin{
						{Name: "fakeCRPlugin"},
					},
				},
			},
		}}})

	testCtx.KubeConfig = server.ClientConfig
	testCtx.ClientSet = kubernetes.NewForConfigOrDie(server.ClientConfig)
	testCtx.NS, err = testCtx.ClientSet.CoreV1().Namespaces().Create(testCtx.Ctx, &v1.Namespace{
		ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("cr-enqueue-%v", string(uuid.NewUUID()))}}, metav1.CreateOptions{})
	if err != nil && !errors.IsAlreadyExists(err) {
		t.Fatalf("Failed to integration test ns: %v", err)
	}

	// Use zero backoff seconds to bypass backoffQ.
	// It's intended to not start the scheduler's queue, and hence to
	// not start any flushing logic. We will pop and schedule the Pods manually later.
	testCtx = testutils.InitTestSchedulerWithOptions(
		t,
		testCtx,
		scheduler.WithProfiles(cfg.Profiles...),
		scheduler.WithFrameworkOutOfTreeRegistry(registry),
		scheduler.WithPodInitialBackoffSeconds(0),
		scheduler.WithPodMaxBackoffSeconds(0),
	)
	testutils.SyncInformerFactory(testCtx)

	defer testutils.CleanupTest(t, testCtx)

	cs, ns, ctx := testCtx.ClientSet, testCtx.NS.Name, testCtx.Ctx
	// Create one Node.
	node := st.MakeNode().Name("fake-node").Obj()
	if _, err := cs.CoreV1().Nodes().Create(ctx, node, metav1.CreateOptions{}); err != nil {
		t.Fatalf("Failed to create Node %q: %v", node.Name, err)
	}

	// Create a testing Pod.
	pause := imageutils.GetPauseImageName()
	pod := st.MakePod().Namespace(ns).Name("fake-pod").Container(pause).Obj()
	if _, err := cs.CoreV1().Pods(ns).Create(ctx, pod, metav1.CreateOptions{}); err != nil {
		t.Fatalf("Failed to create Pod %q: %v", pod.Name, err)
	}

	// Wait for the testing Pod to be present in the scheduling queue.
	if err := wait.Poll(time.Millisecond*200, wait.ForeverTestTimeout, func() (bool, error) {
		return len(testCtx.Scheduler.SchedulingQueue.PendingPods()) == 1, nil
	}); err != nil {
		t.Fatal(err)
	}

	// Pop fake-pod out. It should be unschedulable.
	podInfo := nextPodOrDie(t, testCtx)
	fwk, ok := testCtx.Scheduler.Profiles[podInfo.Pod.Spec.SchedulerName]
	if !ok {
		t.Fatalf("Cannot find the profile for Pod %v", podInfo.Pod.Name)
	}
	// Schedule the Pod manually.
	_, fitError := testCtx.Scheduler.SchedulePod(ctx, fwk, framework.NewCycleState(), podInfo.Pod)
	// The fitError is expected to be non-nil as it failed the fakeCRPlugin plugin.
	if fitError == nil {
		t.Fatalf("Expect Pod %v to fail at scheduling.", podInfo.Pod.Name)
	}
	testCtx.Scheduler.Error(podInfo, fitError)

	// Scheduling cycle is incremented from 0 to 1 after NextPod() is called, so
	// pass a number larger than 1 to move Pod to unschedulablePods.
	testCtx.Scheduler.SchedulingQueue.AddUnschedulableIfNotPresent(podInfo, 10)

	// Trigger a Custom Resource event.
	// We expect this event to trigger moving the test Pod from unschedulablePods to activeQ.
	crdGVR := schema.GroupVersionResource{Group: fooCRD.Spec.Group, Version: fooCRD.Spec.Versions[0].Name, Resource: "foos"}
	crClient := dynamicClient.Resource(crdGVR).Namespace(ns)
	if _, err := crClient.Create(ctx, &unstructured.Unstructured{
		Object: map[string]interface{}{
			"apiVersion": "example.com/v1",
			"kind":       "Foo",
			"metadata":   map[string]interface{}{"name": "foo1"},
		},
	}, metav1.CreateOptions{}); err != nil {
		t.Fatalf("Unable to create cr: %v", err)
	}

	// Now we should be able to pop the Pod from activeQ again.
	podInfo = nextPodOrDie(t, testCtx)
	if podInfo.Attempts != 2 {
		t.Errorf("Expected the Pod to be attempted 2 times, but got %v", podInfo.Attempts)
	}
}
<commit_msg>Delete non-need AddUnschedulableIfNotPresent in e2e<commit_after>/*
Copyright 2021 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package scheduler

import (
	"context"
	"fmt"
	"testing"
	"time"

	v1 "k8s.io/api/core/v1"
	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
	apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/uuid"
	"k8s.io/apimachinery/pkg/util/wait"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/kubernetes"
	"k8s.io/kube-scheduler/config/v1beta3"
	apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
	"k8s.io/kubernetes/pkg/scheduler"
	configtesting "k8s.io/kubernetes/pkg/scheduler/apis/config/testing"
	"k8s.io/kubernetes/pkg/scheduler/framework"
	frameworkruntime "k8s.io/kubernetes/pkg/scheduler/framework/runtime"
	st "k8s.io/kubernetes/pkg/scheduler/testing"
	testfwk "k8s.io/kubernetes/test/integration/framework"
	testutils "k8s.io/kubernetes/test/integration/util"
	imageutils "k8s.io/kubernetes/test/utils/image"
	"k8s.io/utils/pointer"
)

// TestCoreResourceEnqueue verify Pods failed by in-tree default plugins can be
// moved properly upon their registered events.
func TestCoreResourceEnqueue(t *testing.T) {
	// Use zero backoff seconds to bypass backoffQ.
	// It's intended to not start the scheduler's queue, and hence to
	// not start any flushing logic. We will pop and schedule the Pods manually later.
	testCtx := testutils.InitTestSchedulerWithOptions(
		t,
		testutils.InitTestAPIServer(t, "core-res-enqueue", nil),
		scheduler.WithPodInitialBackoffSeconds(0),
		scheduler.WithPodMaxBackoffSeconds(0),
	)
	testutils.SyncInformerFactory(testCtx)

	defer testutils.CleanupTest(t, testCtx)

	cs, ns, ctx := testCtx.ClientSet, testCtx.NS.Name, testCtx.Ctx
	// Create one Node with a taint.
	node := st.MakeNode().Name("fake-node").Capacity(map[v1.ResourceName]string{v1.ResourceCPU: "2"}).Obj()
	node.Spec.Taints = []v1.Taint{{Key: "foo", Effect: v1.TaintEffectNoSchedule}}
	if _, err := cs.CoreV1().Nodes().Create(ctx, node, metav1.CreateOptions{}); err != nil {
		t.Fatalf("Failed to create Node %q: %v", node.Name, err)
	}

	// Create two Pods that are both unschedulable.
	// - Pod1 is a best-effort Pod, but doesn't have the required toleration.
	// - Pod2 requests a large amount of CPU resource that the node cannot fit.
	//   Note: Pod2 will fail the tainttoleration plugin b/c that's ordered prior to noderesources.
	// - Pod3 has the required toleration, but requests a non-existing PVC.
	pod1 := st.MakePod().Namespace(ns).Name("pod1").Container("image").Obj()
	pod2 := st.MakePod().Namespace(ns).Name("pod2").Req(map[v1.ResourceName]string{v1.ResourceCPU: "4"}).Obj()
	pod3 := st.MakePod().Namespace(ns).Name("pod3").Toleration("foo").PVC("pvc").Container("image").Obj()
	for _, pod := range []*v1.Pod{pod1, pod2, pod3} {
		if _, err := cs.CoreV1().Pods(ns).Create(ctx, pod, metav1.CreateOptions{}); err != nil {
			t.Fatalf("Failed to create Pod %q: %v", pod.Name, err)
		}
	}

	// Wait for the three pods to be present in the scheduling queue.
	if err := wait.Poll(time.Millisecond*200, wait.ForeverTestTimeout, func() (bool, error) {
		return len(testCtx.Scheduler.SchedulingQueue.PendingPods()) == 3, nil
	}); err != nil {
		t.Fatal(err)
	}

	// Pop the three pods out. They should be unschedulable.
	for i := 0; i < 3; i++ {
		podInfo := nextPodOrDie(t, testCtx)
		fwk, ok := testCtx.Scheduler.Profiles[podInfo.Pod.Spec.SchedulerName]
		if !ok {
			t.Fatalf("Cannot find the profile for Pod %v", podInfo.Pod.Name)
		}
		// Schedule the Pod manually.
		_, fitError := testCtx.Scheduler.SchedulePod(ctx, fwk, framework.NewCycleState(), podInfo.Pod)
		if fitError == nil {
			t.Fatalf("Expect Pod %v to fail at scheduling.", podInfo.Pod.Name)
		}
		testCtx.Scheduler.Error(podInfo, fitError)
	}

	// Trigger a NodeTaintChange event.
	// We expect this event to trigger moving the test Pod from unschedulablePods to activeQ.
	node.Spec.Taints = nil
	if _, err := cs.CoreV1().Nodes().Update(ctx, node, metav1.UpdateOptions{}); err != nil {
		t.Fatalf("Failed to remove taints off the node: %v", err)
	}

	// Now we should be able to pop the Pod from activeQ again.
	podInfo := nextPodOrDie(t, testCtx)
	if podInfo.Attempts != 2 {
		t.Fatalf("Expected the Pod to be attempted 2 times, but got %v", podInfo.Attempts)
	}
	if got := podInfo.Pod.Name; got != "pod1" {
		t.Fatalf("Expected pod1 to be popped, but got %v", got)
	}

	// Pod2 and Pod3 are not expected to be popped out.
	// - Although the failure reason has been lifted, Pod2 still won't be moved to active due to
	//   the node event's preCheckForNode().
	// - Regarding Pod3, the NodeTaintChange event is irrelevant with its scheduling failure.
	podInfo = nextPod(t, testCtx)
	if podInfo != nil {
		t.Fatalf("Unexpected pod %v get popped out", podInfo.Pod.Name)
	}
}

var _ framework.FilterPlugin = &fakeCRPlugin{}
var _ framework.EnqueueExtensions = &fakeCRPlugin{}

type fakeCRPlugin struct{}

func (f *fakeCRPlugin) Name() string {
	return "fakeCRPlugin"
}

func (f *fakeCRPlugin) Filter(_ context.Context, _ *framework.CycleState, _ *v1.Pod, _ *framework.NodeInfo) *framework.Status {
	return framework.NewStatus(framework.Unschedulable, "always fail")
}

// EventsToRegister returns the possible events that may make a Pod
// failed by this plugin schedulable.
func (f *fakeCRPlugin) EventsToRegister() []framework.ClusterEvent {
	return []framework.ClusterEvent{
		{Resource: "foos.v1.example.com", ActionType: framework.All},
	}
}

// TestCustomResourceEnqueue constructs a fake plugin that registers custom resources
// to verify Pods failed by this plugin can be moved properly upon CR events.
func TestCustomResourceEnqueue(t *testing.T) {
	// Start API Server with apiextensions supported.
	server := apiservertesting.StartTestServerOrDie(
		t, apiservertesting.NewDefaultTestServerOptions(),
		[]string{"--disable-admission-plugins=ServiceAccount,TaintNodesByCondition", "--runtime-config=api/all=true"},
		testfwk.SharedEtcd(),
	)
	testCtx := &testutils.TestContext{}
	testCtx.Ctx, testCtx.CancelFn = context.WithCancel(context.Background())
	testCtx.CloseFn = func() { server.TearDownFn() }

	apiExtensionClient := apiextensionsclient.NewForConfigOrDie(server.ClientConfig)
	dynamicClient := dynamic.NewForConfigOrDie(server.ClientConfig)

	// Create a Foo CRD.
	fooCRD := &apiextensionsv1.CustomResourceDefinition{
		ObjectMeta: metav1.ObjectMeta{
			Name: "foos.example.com",
		},
		Spec: apiextensionsv1.CustomResourceDefinitionSpec{
			Group: "example.com",
			Scope: apiextensionsv1.NamespaceScoped,
			Names: apiextensionsv1.CustomResourceDefinitionNames{
				Plural: "foos",
				Kind:   "Foo",
			},
			Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
				{
					Name:    "v1",
					Served:  true,
					Storage: true,
					Schema: &apiextensionsv1.CustomResourceValidation{
						OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
							Type: "object",
							Properties: map[string]apiextensionsv1.JSONSchemaProps{
								"field": {Type: "string"},
							},
						},
					},
				},
			},
		},
	}
	var err error
	fooCRD, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Create(testCtx.Ctx, fooCRD, metav1.CreateOptions{})
	if err != nil {
		t.Fatal(err)
	}

	registry := frameworkruntime.Registry{
		"fakeCRPlugin": func(_ runtime.Object, fh framework.Handle) (framework.Plugin, error) {
			return &fakeCRPlugin{}, nil
		},
	}
	cfg := configtesting.V1beta3ToInternalWithDefaults(t, v1beta3.KubeSchedulerConfiguration{
		Profiles: []v1beta3.KubeSchedulerProfile{{
			SchedulerName: pointer.StringPtr(v1.DefaultSchedulerName),
			Plugins: &v1beta3.Plugins{
				Filter: v1beta3.PluginSet{
					Enabled: []v1beta3.Plugin{
						{Name: "fakeCRPlugin"},
					},
				},
			},
		}}})

	testCtx.KubeConfig = server.ClientConfig
	testCtx.ClientSet = kubernetes.NewForConfigOrDie(server.ClientConfig)
	testCtx.NS, err = testCtx.ClientSet.CoreV1().Namespaces().Create(testCtx.Ctx, &v1.Namespace{
		ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("cr-enqueue-%v", string(uuid.NewUUID()))}}, metav1.CreateOptions{})
	if err != nil && !errors.IsAlreadyExists(err) {
		t.Fatalf("Failed to integration test ns: %v", err)
	}

	// Use zero backoff seconds to bypass backoffQ.
	// It's intended to not start the scheduler's queue, and hence to
	// not start any flushing logic. We will pop and schedule the Pods manually later.
	testCtx = testutils.InitTestSchedulerWithOptions(
		t,
		testCtx,
		scheduler.WithProfiles(cfg.Profiles...),
		scheduler.WithFrameworkOutOfTreeRegistry(registry),
		scheduler.WithPodInitialBackoffSeconds(0),
		scheduler.WithPodMaxBackoffSeconds(0),
	)
	testutils.SyncInformerFactory(testCtx)

	defer testutils.CleanupTest(t, testCtx)

	cs, ns, ctx := testCtx.ClientSet, testCtx.NS.Name, testCtx.Ctx
	// Create one Node.
	node := st.MakeNode().Name("fake-node").Obj()
	if _, err := cs.CoreV1().Nodes().Create(ctx, node, metav1.CreateOptions{}); err != nil {
		t.Fatalf("Failed to create Node %q: %v", node.Name, err)
	}

	// Create a testing Pod.
	pause := imageutils.GetPauseImageName()
	pod := st.MakePod().Namespace(ns).Name("fake-pod").Container(pause).Obj()
	if _, err := cs.CoreV1().Pods(ns).Create(ctx, pod, metav1.CreateOptions{}); err != nil {
		t.Fatalf("Failed to create Pod %q: %v", pod.Name, err)
	}

	// Wait for the testing Pod to be present in the scheduling queue.
	if err := wait.Poll(time.Millisecond*200, wait.ForeverTestTimeout, func() (bool, error) {
		return len(testCtx.Scheduler.SchedulingQueue.PendingPods()) == 1, nil
	}); err != nil {
		t.Fatal(err)
	}

	// Pop fake-pod out. It should be unschedulable.
	podInfo := nextPodOrDie(t, testCtx)
	fwk, ok := testCtx.Scheduler.Profiles[podInfo.Pod.Spec.SchedulerName]
	if !ok {
		t.Fatalf("Cannot find the profile for Pod %v", podInfo.Pod.Name)
	}
	// Schedule the Pod manually.
	_, fitError := testCtx.Scheduler.SchedulePod(ctx, fwk, framework.NewCycleState(), podInfo.Pod)
	// The fitError is expected to be non-nil as it failed the fakeCRPlugin plugin.
	if fitError == nil {
		t.Fatalf("Expect Pod %v to fail at scheduling.", podInfo.Pod.Name)
	}
	testCtx.Scheduler.Error(podInfo, fitError)

	// Scheduling cycle is incremented from 0 to 1 after NextPod() is called, so
	// pass a number larger than 1 to move Pod to unschedulablePods.
	testCtx.Scheduler.SchedulingQueue.AddUnschedulableIfNotPresent(podInfo, 10)

	// Trigger a Custom Resource event.
	// We expect this event to trigger moving the test Pod from unschedulablePods to activeQ.
	crdGVR := schema.GroupVersionResource{Group: fooCRD.Spec.Group, Version: fooCRD.Spec.Versions[0].Name, Resource: "foos"}
	crClient := dynamicClient.Resource(crdGVR).Namespace(ns)
	if _, err := crClient.Create(ctx, &unstructured.Unstructured{
		Object: map[string]interface{}{
			"apiVersion": "example.com/v1",
			"kind":       "Foo",
			"metadata":   map[string]interface{}{"name": "foo1"},
		},
	}, metav1.CreateOptions{}); err != nil {
		t.Fatalf("Unable to create cr: %v", err)
	}

	// Now we should be able to pop the Pod from activeQ again.
	podInfo = nextPodOrDie(t, testCtx)
	if podInfo.Attempts != 2 {
		t.Errorf("Expected the Pod to be attempted 2 times, but got %v", podInfo.Attempts)
	}
}
<|endoftext|>


<commit_before>package godom

import (
	"github.com/gopherjs/gopherjs/js"
)

type DOMTokenList struct {
	*js.Object
}

func (t *DOMTokenList) Contains(s string) bool {
	return t.Call("contains", s).Bool()
}
<commit_msg>DOMTokenList implementation<commit_after>// This file implements DOMTokenList interface
// https://developer.mozilla.org/en/docs/Web/API/DOMTokenList
package godom

import (
	"github.com/gopherjs/gopherjs/js"
)

type DOMTokenList struct {
	*js.Object
}

func (t *DOMTokenList) Length() int {
	return t.Get("length").Int()
}

func (t *DOMTokenList) Contains(s string) bool {
	return t.Call("contains", s).Bool()
}

func (t *DOMTokenList) Add(s string) {
	t.Call("add", s)
}

func (t *DOMTokenList) Remove(s string) {
	t.Call("remove", s)
}

func (t *DOMTokenList) Toggle(s string) {
	t.Call("toggle", s)
}
<|endoftext|>


<commit_before>package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

var m map[string]Vertex

func main() {
	m = make(map[string]Vertex)
	m["Bell Labs"] = Vertex{
		40.68433, -74.39967,
	}
  m["Bucharest"] = Vertex{
    44.439663, 26.096306,
  }
	fmt.Println(m["Bell Labs"])
}
<commit_msg>Update maps.go<commit_after>package main

import "fmt"

type Vertex struct {
	Lat, Long float64
}

var m map[string]Vertex

func main() {
	var m = map[string]Vertex{
		"Bell Labs": {40.68433, -74.39967},
		"Bucharest": {44.439663, 26.096306},
		"Google":    {37.42202, -122.08408},
	}

	coord, ok := m["Bucharest"]
	if ok {
		fmt.Println(coord)
	}
}
<|endoftext|>


<commit_before>// Code generated by protoc-gen-gogo.
// source: cmds.proto
// DO NOT EDIT!

package msg

import proto "code.google.com/p/gogoprotobuf/proto"
import json "encoding/json"
import math "math"

// Reference proto, json, and math imports to suppress error if they are not otherwise used.
var _ = proto.Marshal
var _ = &json.SyntaxError{}
var _ = math.Inf

type Cmds struct {
	Cmd              []*Cmd `protobuf:"bytes,1,rep,name=cmd" json:"cmd,omitempty"`
	XXX_unrecognized []byte `json:"-"`
}

func (m *Cmds) Reset()         { *m = Cmds{} }
func (m *Cmds) String() string { return proto.CompactTextString(m) }
func (*Cmds) ProtoMessage()    {}

func (m *Cmds) GetCmd() []*Cmd {
	if m != nil {
		return m.Cmd
	}
	return nil
}

func init() {
}
<commit_msg>fixed the source link<commit_after>// Code generated by protoc-gen-gogo.
// source: cmds.proto
// DO NOT EDIT!

package msg

import proto "github.com/gogo/protobuf/proto"
import json "encoding/json"
import math "math"

// Reference proto, json, and math imports to suppress error if they are not otherwise used.
var _ = proto.Marshal
var _ = &json.SyntaxError{}
var _ = math.Inf

type Cmds struct {
	Cmd              []*Cmd `protobuf:"bytes,1,rep,name=cmd" json:"cmd,omitempty"`
	XXX_unrecognized []byte `json:"-"`
}

func (m *Cmds) Reset()         { *m = Cmds{} }
func (m *Cmds) String() string { return proto.CompactTextString(m) }
func (*Cmds) ProtoMessage()    {}

func (m *Cmds) GetCmd() []*Cmd {
	if m != nil {
		return m.Cmd
	}
	return nil
}

func init() {
}
<|endoftext|>


<commit_before>// Copyright (c) 2014 The SkyDNS Authors. All rights reserved.
// Use of this source code is governed by The MIT License (MIT) that can be
// found in the LICENSE file.

package msg

import (
	"net"
	"path"
	"strings"

	"github.com/miekg/dns"
)

// This *is* the rdata from a SRV record, but with a twist.
// Host (Target in SRV) must be a domain name, but if it looks like an IP
// address (4/6), we will treat it like an IP address.
type Service struct {
	Host     string `json:"host,omitempty"`
	Port     int    `json:"port,omitempty"`
	Priority int    `json:"priority,omitempty"`
	Weight   int    `json:"weight,omitempty"`
	Text     string `json:"text,omitempty"`
	Ttl      uint32 `json:"ttl,omitempty"`

	// When a SRV record with a "Host: IP-address" is added, we synthesize a srv.Target
	// domain name.
	// Normally we convert the full Key where the record lives to a DNS name and use
	// this as the srv.Target.
	// When TargetStrip > 0 we strip the left most TargetStrip labels from the DNS name.
	TargetStrip int `json:"targetstrip",omitempty"`

	// Etcd key where we found this service and ignored from json un-/marshalling
	Key string `json:"-"`
}

// NewSRV returns a new SRV record based on the Service.
func (s *Service) NewSRV(name string, weight uint16) *dns.SRV {
	host := dns.Fqdn(s.Host)

	offset, end := 0, false
	for i := 0; i < s.TargetStrip; i++ {
		offset, end = dns.NextLabel(host, offset)
	}
	if end {
		// We overshot the name, use the orignal one.
		offset = 0
	}
	host = host[offset:]

	return &dns.SRV{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: s.Ttl},
		Priority: uint16(s.Priority), Weight: weight, Port: uint16(s.Port), Target: host}
}

// NewA returns a new A record based on the Service.
func (s *Service) NewA(name string, ip net.IP) *dns.A {
	return &dns.A{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: s.Ttl}, A: ip}
}

// NewAAAA returns a new AAAA record based on the Service.
func (s *Service) NewAAAA(name string, ip net.IP) *dns.AAAA {
	return &dns.AAAA{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: s.Ttl}, AAAA: ip}
}

// NewCNAME returns a new CNAME record based on the Service.
func (s *Service) NewCNAME(name string, target string) *dns.CNAME {
	return &dns.CNAME{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: s.Ttl}, Target: target}
}

// NewNS returns a new NS record based on the Service.
func (s *Service) NewNS(name string, target string) *dns.NS {
	return &dns.NS{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: s.Ttl}, Ns: target}
}

// NewTXT returns a new TXT record based on the Service.
func (s *Service) NewTXT(name string) *dns.TXT {
	return &dns.TXT{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: s.Ttl}, Txt: split255(s.Text)}
}

// NewPTR returns a new PTR record based on the Service.
func (s *Service) NewPTR(name string, ttl uint32) *dns.PTR {
	return &dns.PTR{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: ttl}, Ptr: dns.Fqdn(s.Host)}
}

// As Path, but
// if a name contains wildcards (*), the name will be chopped of before the (first) wildcard, and
// we do a highler evel search and later find the matching names.
// So service.*.skydns.local, will look for all services under skydns.local and will later check
// for names that match service.*.skydns.local.  If a wildcard is found the returned bool is true.
func PathWithWildcard(s string) (string, bool) {
	l := dns.SplitDomainName(s)
	for i, j := 0, len(l)-1; i < j; i, j = i+1, j-1 {
		l[i], l[j] = l[j], l[i]
	}
	for i, k := range l {
		if k == "*" {
			return path.Join(append([]string{"/skydns/"}, l[:i]...)...), true
		}
	}
	return path.Join(append([]string{"/skydns/"}, l...)...), false
}

// Path converts a domainname to an etcd path. If s looks like service.staging.skydns.local.,
// the resulting key will be /skydns/local/skydns/staging/service .
func Path(s string) string {
	l := dns.SplitDomainName(s)
	for i, j := 0, len(l)-1; i < j; i, j = i+1, j-1 {
		l[i], l[j] = l[j], l[i]
	}
	return path.Join(append([]string{"/skydns/"}, l...)...)
}

// Domain is the opposite of Path.
func Domain(s string) string {
	l := strings.Split(s, "/")
	// start with 1, to strip /skydns
	for i, j := 1, len(l)-1; i < j; i, j = i+1, j-1 {
		l[i], l[j] = l[j], l[i]
	}
	return dns.Fqdn(strings.Join(l[1:len(l)-1], "."))
}

// Split255 splits a string into 255 byte chunks.
func split255(s string) []string {
	if len(s) < 255 {
		return []string{s}
	}
	sx := []string{}
	p, i := 0, 255
	for {
		if i <= len(s) {
			sx = append(sx, s[p:i])
		} else {
			sx = append(sx, s[p:])
			break

		}
		p, i = p+255, i+255
	}

	return sx
}
<commit_msg>Add Mail boolean to MX records<commit_after>// Copyright (c) 2014 The SkyDNS Authors. All rights reserved.
// Use of this source code is governed by The MIT License (MIT) that can be
// found in the LICENSE file.

package msg

import (
	"net"
	"path"
	"strings"

	"github.com/miekg/dns"
)

// This *is* the rdata from a SRV record, but with a twist.
// Host (Target in SRV) must be a domain name, but if it looks like an IP
// address (4/6), we will treat it like an IP address.
type Service struct {
	Host     string `json:"host,omitempty"`
	Port     int    `json:"port,omitempty"`
	Priority int    `json:"priority,omitempty"`
	Weight   int    `json:"weight,omitempty"`
	Text     string `json:"text,omitempty"`
	Mail     bool   `json:"mail,omitempty"` // Be an MX record. Priority becomes Preference.
	Ttl      uint32 `json:"ttl,omitempty"`

	// When a SRV record with a "Host: IP-address" is added, we synthesize
	// a srv.Target domain name.  Normally we convert the full Key where
	// the record lives to a DNS name and use this as the srv.Target.  When
	// TargetStrip > 0 we strip the left most TargetStrip labels from the
	// DNS name.
	TargetStrip int `json:"targetstrip",omitempty"`

	// Etcd key where we found this service and ignored from json un-/marshalling
	Key string `json:"-"`
}

// NewSRV returns a new SRV record based on the Service.
func (s *Service) NewSRV(name string, weight uint16) *dns.SRV {
	host := dns.Fqdn(s.Host)

	offset, end := 0, false
	for i := 0; i < s.TargetStrip; i++ {
		offset, end = dns.NextLabel(host, offset)
	}
	if end {
		// We overshot the name, use the orignal one.
		offset = 0
	}
	host = host[offset:]

	return &dns.SRV{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeSRV, Class: dns.ClassINET, Ttl: s.Ttl},
		Priority: uint16(s.Priority), Weight: weight, Port: uint16(s.Port), Target: host}
}

// NewMX returns a new MX record based on the Service.
func (s *Service) NewMX(name string, weight uint16) *dns.MX {
	host := dns.Fqdn(s.Host)

	offset, end := 0, false
	for i := 0; i < s.TargetStrip; i++ {
		offset, end = dns.NextLabel(host, offset)
	}
	if end {
		// We overshot the name, use the orignal one.
		offset = 0
	}
	host = host[offset:]

	return &dns.MX{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeMX, Class: dns.ClassINET, Ttl: s.Ttl},
		Preference: uint16(s.Priority), Mx: host}
}

// NewA returns a new A record based on the Service.
func (s *Service) NewA(name string, ip net.IP) *dns.A {
	return &dns.A{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeA, Class: dns.ClassINET, Ttl: s.Ttl}, A: ip}
}

// NewAAAA returns a new AAAA record based on the Service.
func (s *Service) NewAAAA(name string, ip net.IP) *dns.AAAA {
	return &dns.AAAA{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Ttl: s.Ttl}, AAAA: ip}
}

// NewCNAME returns a new CNAME record based on the Service.
func (s *Service) NewCNAME(name string, target string) *dns.CNAME {
	return &dns.CNAME{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: s.Ttl}, Target: target}
}

// NewNS returns a new NS record based on the Service.
func (s *Service) NewNS(name string, target string) *dns.NS {
	return &dns.NS{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: s.Ttl}, Ns: target}
}

// NewTXT returns a new TXT record based on the Service.
func (s *Service) NewTXT(name string) *dns.TXT {
	return &dns.TXT{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypeTXT, Class: dns.ClassINET, Ttl: s.Ttl}, Txt: split255(s.Text)}
}

// NewPTR returns a new PTR record based on the Service.
func (s *Service) NewPTR(name string, ttl uint32) *dns.PTR {
	return &dns.PTR{Hdr: dns.RR_Header{Name: name, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: ttl}, Ptr: dns.Fqdn(s.Host)}
}

// As Path, but
// if a name contains wildcards (*), the name will be chopped of before the (first) wildcard, and
// we do a highler evel search and later find the matching names.
// So service.*.skydns.local, will look for all services under skydns.local and will later check
// for names that match service.*.skydns.local.  If a wildcard is found the returned bool is true.
func PathWithWildcard(s string) (string, bool) {
	l := dns.SplitDomainName(s)
	for i, j := 0, len(l)-1; i < j; i, j = i+1, j-1 {
		l[i], l[j] = l[j], l[i]
	}
	for i, k := range l {
		if k == "*" {
			return path.Join(append([]string{"/skydns/"}, l[:i]...)...), true
		}
	}
	return path.Join(append([]string{"/skydns/"}, l...)...), false
}

// Path converts a domainname to an etcd path. If s looks like service.staging.skydns.local.,
// the resulting key will be /skydns/local/skydns/staging/service .
func Path(s string) string {
	l := dns.SplitDomainName(s)
	for i, j := 0, len(l)-1; i < j; i, j = i+1, j-1 {
		l[i], l[j] = l[j], l[i]
	}
	return path.Join(append([]string{"/skydns/"}, l...)...)
}

// Domain is the opposite of Path.
func Domain(s string) string {
	l := strings.Split(s, "/")
	// start with 1, to strip /skydns
	for i, j := 1, len(l)-1; i < j; i, j = i+1, j-1 {
		l[i], l[j] = l[j], l[i]
	}
	return dns.Fqdn(strings.Join(l[1:len(l)-1], "."))
}

// Split255 splits a string into 255 byte chunks.
func split255(s string) []string {
	if len(s) < 255 {
		return []string{s}
	}
	sx := []string{}
	p, i := 0, 255
	for {
		if i <= len(s) {
			sx = append(sx, s[p:i])
		} else {
			sx = append(sx, s[p:])
			break

		}
		p, i = p+255, i+255
	}

	return sx
}
<|endoftext|>


<commit_before>// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package state_test

import (
	gc "launchpad.net/gocheck"
	"launchpad.net/juju-core/environs"
	coretesting "launchpad.net/juju-core/testing"

	"launchpad.net/juju-core/environs/config"
	"launchpad.net/juju-core/errors"
	"launchpad.net/juju-core/state"
)

type ConfigValidatorSuite struct {
	ConnSuite
	configValidator mockConfigValidator
}

var _ = gc.Suite(&ConfigValidatorSuite{})

type mockConfigValidator struct {
	validateError error
	validateCfg   *config.Config
	validateOld   *config.Config
	validateValid *config.Config
}

func (p *mockConfigValidator) Validate(cfg, old *config.Config) (valid *config.Config, err error) {
	p.validateCfg = cfg
	p.validateOld = old
	return p.validateValid, p.validateError
}

func (s *ConfigValidatorSuite) SetUpTest(c *gc.C) {
	s.ConnSuite.SetUpTest(c)
	s.configValidator = mockConfigValidator{}
	s.policy.getConfigValidator = func(*config.Config) (state.ConfigValidator, error) {
		return &s.configValidator, nil
	}
}

func getConfig(c *gc.C) *config.Config {
	testConfig, err := config.New(config.UseDefaults, coretesting.FakeConfig())
	c.Assert(err, gc.IsNil)
	return testConfig
}

func getProvider(c *gc.C, cfg *config.Config) environs.EnvironProvider {
	//Get provider type from config
	providerType := cfg.AllAttrs()["type"].(string)
	//Get provider
	provider, err := environs.Provider(providerType)
	c.Assert(err, gc.IsNil)
	return provider
}

func (s *ConfigValidatorSuite) validate(c *gc.C) (valid *config.Config, err error) {
	testConfig := getConfig(c)
	provider := getProvider(c, testConfig)
	return provider.Validate(testConfig, nil)
}

func (s *ConfigValidatorSuite) TestConfigValidate(c *gc.C) {
	_, err := s.validate(c)
	c.Assert(err, gc.IsNil)
}

func (s *ConfigValidatorSuite) TestConfigValidateUnimplemented(c *gc.C) {
	var configValidatorErr error
	s.policy.getConfigValidator = func(*config.Config) (state.ConfigValidator, error) {
		return nil, configValidatorErr
	}

	_, err := s.validate(c)
	c.Assert(err, gc.ErrorMatches, "cannot validate config: policy returned nil validator without an error")
	configValidatorErr = errors.NewNotImplementedError("Validator")
	_, err = s.validate(c)
	c.Assert(err, gc.IsNil)
}

func (s *ConfigValidatorSuite) TestConfigValidateNoPolicy(c *gc.C) {
	s.policy.getConfigValidator = func(*config.Config) (state.ConfigValidator, error) {
		c.Errorf("should not have been invoked")
		return nil, nil
	}

	state.SetPolicy(s.State, nil)
	_, err := s.validate(c)
	c.Assert(err, gc.IsNil)
}
<commit_msg>changes as per axw's review<commit_after>// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package state_test

// import (
// 	gc "launchpad.net/gocheck"
// 	"launchpad.net/juju-core/environs/config"
// 	"launchpad.net/juju-core/errors"
// 	"launchpad.net/juju-core/state"
// 	coretesting "launchpad.net/juju-core/testing"
// )

// type ConfigValidatorSuite struct {
// 	ConnSuite
// 	configValidator mockConfigValidator
// }

// var _ = gc.Suite(&ConfigValidatorSuite{})

// type mockConfigValidator struct {
// 	validateError error
// 	validateCfg   *config.Config
// 	validateOld   *config.Config
// 	validateValid *config.Config
// }

// // To test SetEnvironConfig updates state, Validate returns a config
// // different to both input configs
// func mockValidCfg() (valid *config.Config, err error) {
// 	cfg, err := config.New(config.UseDefaults, coretesting.FakeConfig())
// 	if err != nil {
// 		return nil, err
// 	}
// 	valid, err = cfg.Apply(map[string]interface{}{
// 		"arbitrary-key": "cptn-marvel",
// 	})
// 	if err != nil {
// 		return nil, err
// 	}
// 	return valid, nil
// }

// func (p *mockConfigValidator) Validate(cfg, old *config.Config) (valid *config.Config, err error) {
// 	p.validateCfg = cfg
// 	p.validateOld = old
// 	p.validateValid, p.validateError = mockValidCfg()
// 	return p.validateValid, p.validateError
// }

// func (s *ConfigValidatorSuite) SetUpTest(c *gc.C) {
// 	s.ConnSuite.SetUpTest(c)
// 	s.configValidator = mockConfigValidator{}
// 	s.policy.getConfigValidator = func(*config.Config) (state.ConfigValidator, error) {
// 		return &s.configValidator, nil
// 	}
// }

// func (s *ConfigValidatorSuite) setEnvironConfig(c *gc.C) error {
// 	cfg, err := s.State.EnvironConfig()
// 	c.Assert(err, gc.IsNil)
// 	change, err := cfg.Apply(map[string]interface{}{
// 		"authorized-keys": "different-keys",
// 		"arbitrary-key":   "shazam!",
// 	})
// 	c.Assert(err, gc.IsNil)
// 	return s.State.SetEnvironConfig(change, cfg)
// }

// func (s *ConfigValidatorSuite) TestConfigValidate(c *gc.C) {
// 	err := s.setEnvironConfig(c)
// 	c.Assert(err, gc.IsNil)
// }

// func (s *ConfigValidatorSuite) TestSetEnvironConfigFailsOnConfigValidateError(c *gc.C) {
// 	var configValidatorErr error
// 	s.policy.getConfigValidator = func(*config.Config) (state.ConfigValidator, error) {
// 		configValidatorErr = errors.NotFoundf("")
// 		return &s.configValidator, configValidatorErr
// 	}

// 	err := s.setEnvironConfig(c)
// 	c.Assert(err, gc.ErrorMatches, " not found")
// }

// func (s *ConfigValidatorSuite) TestSetEnvironConfigUpdatesState(c *gc.C) {
// 	s.setEnvironConfig(c)
// 	stateCfg, err := s.State.EnvironConfig()
// 	c.Assert(err, gc.IsNil)
// 	newValidCfg, err := mockValidCfg()
// 	c.Assert(err, gc.IsNil)
// 	c.Assert(stateCfg, gc.DeepEquals, newValidCfg)
// }

// func (s *ConfigValidatorSuite) TestConfigValidateUnimplemented(c *gc.C) {
// 	var configValidatorErr error
// 	s.policy.getConfigValidator = func(*config.Config) (state.ConfigValidator, error) {
// 		return nil, configValidatorErr
// 	}

// 	err := s.setEnvironConfig(c)
// 	c.Assert(err, gc.ErrorMatches, "policy returned nil configValidator without an error")
// 	configValidatorErr = errors.NewNotImplementedError("Validator")
// 	err = s.setEnvironConfig(c)
// 	c.Assert(err, gc.IsNil)
// }

// func (s *ConfigValidatorSuite) TestConfigValidateNoPolicy(c *gc.C) {
// 	s.policy.getConfigValidator = func(*config.Config) (state.ConfigValidator, error) {
// 		c.Errorf("should not have been invoked")
// 		return nil, nil
// 	}

// 	state.SetPolicy(s.State, nil)
// 	err := s.setEnvironConfig(c)
// 	c.Assert(err, gc.IsNil)
// }
<|endoftext|>


<commit_before>/*
Copyright 2018 The Ceph-CSI 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 cephfs

import (
	klog "k8s.io/klog/v2"

	csicommon "github.com/ceph/ceph-csi/internal/csi-common"
	"github.com/ceph/ceph-csi/internal/journal"
	"github.com/ceph/ceph-csi/internal/util"

	"github.com/container-storage-interface/spec/lib/go/csi"
)

const (
	// volIDVersion is the version number of volume ID encoding scheme
	volIDVersion uint16 = 1

	// RADOS namespace to store CSI specific objects and keys
	radosNamespace = "csi"
)

// Driver contains the default identity,node and controller struct.
type Driver struct {
	cd *csicommon.CSIDriver

	is *IdentityServer
	ns *NodeServer
	cs *ControllerServer
}

var (
	// CSIInstanceID is the instance ID that is unique to an instance of CSI, used when sharing
	// ceph clusters across CSI instances, to differentiate omap names per CSI instance
	CSIInstanceID = "default"

	// volJournal is used to maintain RADOS based journals for CO generated
	// VolumeName to backing CephFS subvolumes
	volJournal *journal.Config

	// snapJournal is used to maintain RADOS based journals for CO generated
	// SnapshotName to backing CephFS subvolumes
	snapJournal *journal.Config
)

// NewDriver returns new ceph driver.
func NewDriver() *Driver {
	return &Driver{}
}

// NewIdentityServer initialize a identity server for ceph CSI driver.
func NewIdentityServer(d *csicommon.CSIDriver) *IdentityServer {
	return &IdentityServer{
		DefaultIdentityServer: csicommon.NewDefaultIdentityServer(d),
	}
}

// NewControllerServer initialize a controller server for ceph CSI driver.
func NewControllerServer(d *csicommon.CSIDriver) *ControllerServer {
	return &ControllerServer{
		DefaultControllerServer: csicommon.NewDefaultControllerServer(d),
		VolumeLocks:             util.NewVolumeLocks(),
		SnapshotLocks:           util.NewVolumeLocks(),
		OperationLocks:          util.NewOperationLock(),
	}
}

// NewNodeServer initialize a node server for ceph CSI driver.
func NewNodeServer(d *csicommon.CSIDriver, t string, topology map[string]string) *NodeServer {
	return &NodeServer{
		DefaultNodeServer: csicommon.NewDefaultNodeServer(d, t, topology),
		VolumeLocks:       util.NewVolumeLocks(),
	}
}

// Run start a non-blocking grpc controller,node and identityserver for
// ceph CSI driver which can serve multiple parallel requests.
func (fs *Driver) Run(conf *util.Config) {
	var err error
	var topology map[string]string

	// Configuration
	if err = loadAvailableMounters(conf); err != nil {
		klog.Fatalf("cephfs: failed to load ceph mounters: %v", err)
	}

	if err = util.WriteCephConfig(); err != nil {
		klog.Fatalf("failed to write ceph configuration file: %v", err)
	}

	// Use passed in instance ID, if provided for omap suffix naming
	if conf.InstanceID != "" {
		CSIInstanceID = conf.InstanceID
	}
	// Create an instance of the volume journal
	volJournal = journal.NewCSIVolumeJournalWithNamespace(CSIInstanceID, radosNamespace)

	snapJournal = journal.NewCSISnapshotJournalWithNamespace(CSIInstanceID, radosNamespace)
	// Initialize default library driver

	fs.cd = csicommon.NewCSIDriver(conf.DriverName, util.DriverVersion, conf.NodeID)
	if fs.cd == nil {
		klog.Fatalln("failed to initialize CSI driver")
	}

	if conf.IsControllerServer || !conf.IsNodeServer {
		fs.cd.AddControllerServiceCapabilities([]csi.ControllerServiceCapability_RPC_Type{
			csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
			csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT,
			csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
			csi.ControllerServiceCapability_RPC_CLONE_VOLUME,
		})

		fs.cd.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{
			csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
		})
	}
	// Create gRPC servers

	fs.is = NewIdentityServer(fs.cd)

	if conf.IsNodeServer {
		topology, err = util.GetTopologyFromDomainLabels(conf.DomainLabels, conf.NodeID, conf.DriverName)
		if err != nil {
			klog.Fatalln(err)
		}
		fs.ns = NewNodeServer(fs.cd, conf.Vtype, topology)
	}

	if conf.IsControllerServer {
		fs.cs = NewControllerServer(fs.cd)
	}
	if !conf.IsControllerServer && !conf.IsNodeServer {
		topology, err = util.GetTopologyFromDomainLabels(conf.DomainLabels, conf.NodeID, conf.DriverName)
		if err != nil {
			klog.Fatalln(err)
		}
		fs.ns = NewNodeServer(fs.cd, conf.Vtype, topology)
		fs.cs = NewControllerServer(fs.cd)
	}

	server := csicommon.NewNonBlockingGRPCServer()
	server.Start(conf.Endpoint, conf.HistogramOption, fs.is, fs.cs, fs.ns, conf.EnableGRPCMetrics)
	if conf.EnableGRPCMetrics {
		klog.Warning("EnableGRPCMetrics is deprecated")
		go util.StartMetricsServer(conf)
	}
	server.Wait()
}
<commit_msg>cephfs: replace fatal with util.FatalLog in Run<commit_after>/*
Copyright 2018 The Ceph-CSI 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 cephfs

import (
	klog "k8s.io/klog/v2"

	csicommon "github.com/ceph/ceph-csi/internal/csi-common"
	"github.com/ceph/ceph-csi/internal/journal"
	"github.com/ceph/ceph-csi/internal/util"

	"github.com/container-storage-interface/spec/lib/go/csi"
)

const (
	// volIDVersion is the version number of volume ID encoding scheme
	volIDVersion uint16 = 1

	// RADOS namespace to store CSI specific objects and keys
	radosNamespace = "csi"
)

// Driver contains the default identity,node and controller struct.
type Driver struct {
	cd *csicommon.CSIDriver

	is *IdentityServer
	ns *NodeServer
	cs *ControllerServer
}

var (
	// CSIInstanceID is the instance ID that is unique to an instance of CSI, used when sharing
	// ceph clusters across CSI instances, to differentiate omap names per CSI instance
	CSIInstanceID = "default"

	// volJournal is used to maintain RADOS based journals for CO generated
	// VolumeName to backing CephFS subvolumes
	volJournal *journal.Config

	// snapJournal is used to maintain RADOS based journals for CO generated
	// SnapshotName to backing CephFS subvolumes
	snapJournal *journal.Config
)

// NewDriver returns new ceph driver.
func NewDriver() *Driver {
	return &Driver{}
}

// NewIdentityServer initialize a identity server for ceph CSI driver.
func NewIdentityServer(d *csicommon.CSIDriver) *IdentityServer {
	return &IdentityServer{
		DefaultIdentityServer: csicommon.NewDefaultIdentityServer(d),
	}
}

// NewControllerServer initialize a controller server for ceph CSI driver.
func NewControllerServer(d *csicommon.CSIDriver) *ControllerServer {
	return &ControllerServer{
		DefaultControllerServer: csicommon.NewDefaultControllerServer(d),
		VolumeLocks:             util.NewVolumeLocks(),
		SnapshotLocks:           util.NewVolumeLocks(),
		OperationLocks:          util.NewOperationLock(),
	}
}

// NewNodeServer initialize a node server for ceph CSI driver.
func NewNodeServer(d *csicommon.CSIDriver, t string, topology map[string]string) *NodeServer {
	return &NodeServer{
		DefaultNodeServer: csicommon.NewDefaultNodeServer(d, t, topology),
		VolumeLocks:       util.NewVolumeLocks(),
	}
}

// Run start a non-blocking grpc controller,node and identityserver for
// ceph CSI driver which can serve multiple parallel requests.
func (fs *Driver) Run(conf *util.Config) {
	var err error
	var topology map[string]string

	// Configuration
	if err = loadAvailableMounters(conf); err != nil {
		util.FatalLogMsg("cephfs: failed to load ceph mounters: %v", err)
	}

	if err = util.WriteCephConfig(); err != nil {
		util.FatalLogMsg("failed to write ceph configuration file: %v", err)
	}

	// Use passed in instance ID, if provided for omap suffix naming
	if conf.InstanceID != "" {
		CSIInstanceID = conf.InstanceID
	}
	// Create an instance of the volume journal
	volJournal = journal.NewCSIVolumeJournalWithNamespace(CSIInstanceID, radosNamespace)

	snapJournal = journal.NewCSISnapshotJournalWithNamespace(CSIInstanceID, radosNamespace)
	// Initialize default library driver

	fs.cd = csicommon.NewCSIDriver(conf.DriverName, util.DriverVersion, conf.NodeID)
	if fs.cd == nil {
		util.FatalLogMsg("failed to initialize CSI driver")
	}

	if conf.IsControllerServer || !conf.IsNodeServer {
		fs.cd.AddControllerServiceCapabilities([]csi.ControllerServiceCapability_RPC_Type{
			csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
			csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT,
			csi.ControllerServiceCapability_RPC_EXPAND_VOLUME,
			csi.ControllerServiceCapability_RPC_CLONE_VOLUME,
		})

		fs.cd.AddVolumeCapabilityAccessModes([]csi.VolumeCapability_AccessMode_Mode{
			csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER,
		})
	}
	// Create gRPC servers

	fs.is = NewIdentityServer(fs.cd)

	if conf.IsNodeServer {
		topology, err = util.GetTopologyFromDomainLabels(conf.DomainLabels, conf.NodeID, conf.DriverName)
		if err != nil {
			util.FatalLogMsg(err.Error())
		}
		fs.ns = NewNodeServer(fs.cd, conf.Vtype, topology)
	}

	if conf.IsControllerServer {
		fs.cs = NewControllerServer(fs.cd)
	}
	if !conf.IsControllerServer && !conf.IsNodeServer {
		topology, err = util.GetTopologyFromDomainLabels(conf.DomainLabels, conf.NodeID, conf.DriverName)
		if err != nil {
			util.FatalLogMsg(err.Error())
		}
		fs.ns = NewNodeServer(fs.cd, conf.Vtype, topology)
		fs.cs = NewControllerServer(fs.cd)
	}

	server := csicommon.NewNonBlockingGRPCServer()
	server.Start(conf.Endpoint, conf.HistogramOption, fs.is, fs.cs, fs.ns, conf.EnableGRPCMetrics)
	if conf.EnableGRPCMetrics {
		klog.Warning("EnableGRPCMetrics is deprecated")
		go util.StartMetricsServer(conf)
	}
	server.Wait()
}
<|endoftext|>


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

// +build go1.15

package main

import (
	"fmt"
	"go/ast"
	"go/doc"
	"log"
	"sort"
	"strings"

	"cloud.google.com/go/third_party/pkgsite"
	"golang.org/x/tools/go/packages"
)

// tableOfContents represents a TOC.
type tableOfContents []*tocItem

// tocItem is an item in a TOC.
type tocItem struct {
	UID   string     `yaml:"uid,omitempty"`
	Name  string     `yaml:"name,omitempty"`
	Items []*tocItem `yaml:"items,omitempty"`
}

func (t *tocItem) addItem(i *tocItem) {
	t.Items = append(t.Items, i)
}

// page represents a single DocFX page.
//
// There is one page per package.
type page struct {
	Items      []*item `yaml:"items"`
	References []*item `yaml:"references,omitempty"`
}

// child represents an item child.
type child string

// syntax represents syntax.
type syntax struct {
	Content string `yaml:"content,omitempty"`
}

// item represents a DocFX item.
type item struct {
	UID      string   `yaml:"uid"`
	Name     string   `yaml:"name,omitempty"`
	ID       string   `yaml:"id,omitempty"`
	Summary  string   `yaml:"summary,omitempty"`
	Parent   string   `yaml:"parent,omitempty"`
	Type     string   `yaml:"type,omitempty"`
	Langs    []string `yaml:"langs,omitempty"`
	Syntax   syntax   `yaml:"syntax,omitempty"`
	Children []child  `yaml:"children,omitempty"`
}

func (p *page) addItem(i *item) {
	p.Items = append(p.Items, i)
}

func (i *item) addChild(c child) {
	i.Children = append(i.Children, c)
}

var onlyGo = []string{"go"}

// parse parses the directory into a map of import path -> page and a TOC.
//
// glob is the path to parse, usually ending in `...`. glob is passed directly
// to packages.Load as-is.
func parse(glob string) (map[string]*page, tableOfContents, *packages.Module, error) {
	config := &packages.Config{
		Mode:  packages.NeedName | packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedModule,
		Tests: true,
	}

	pkgs, err := packages.Load(config, glob)
	if err != nil {
		return nil, nil, nil, fmt.Errorf("packages.Load: %v", err)
	}
	packages.PrintErrors(pkgs) // Don't fail everything because of one package.

	if len(pkgs) == 0 {
		return nil, nil, nil, fmt.Errorf("pattern %q matched 0 packages", glob)
	}

	pages := map[string]*page{}
	toc := tableOfContents{}

	module := pkgs[0].Module
	skippedModules := map[string]struct{}{}

	log.Printf("Processing %s@%s", module.Path, module.Version)

	for _, pkg := range pkgs {
		if pkg == nil || pkg.Module == nil {
			continue
		}
		if pkg.Module.Path != module.Path {
			skippedModules[pkg.Module.Path] = struct{}{}
			continue
		}
		// Don't generate docs for tests or internal.
		switch {
		case strings.HasSuffix(pkg.ID, ".test"),
			strings.HasSuffix(pkg.ID, ".test]"),
			strings.Contains(pkg.ID, "internal"):
			continue
		}

		// Collect all .go files.
		files := []*ast.File{}
		for _, f := range pkg.Syntax {
			tf := pkg.Fset.File(f.Pos())
			if strings.HasSuffix(tf.Name(), ".go") {
				files = append(files, f)
			}
		}

		// Parse out GoDoc.
		docPkg, err := doc.NewFromFiles(pkg.Fset, files, pkg.PkgPath)
		if err != nil {
			return nil, nil, nil, fmt.Errorf("doc.NewFromFiles: %v", err)
		}

		toc = append(toc, &tocItem{
			UID:  pkg.ID,
			Name: pkg.PkgPath,
		})

		pkgItem := &item{
			UID:     pkg.ID,
			Name:    pkg.PkgPath,
			ID:      pkg.Name,
			Summary: docPkg.Doc,
			Langs:   onlyGo,
			Type:    "package",
		}
		pkgPage := &page{Items: []*item{pkgItem}}
		pages[pkg.PkgPath] = pkgPage

		for _, c := range docPkg.Consts {
			name := strings.Join(c.Names, ", ")
			id := strings.Join(c.Names, ",")
			uid := pkg.PkgPath + "." + id
			pkgItem.addChild(child(uid))
			pkgPage.addItem(&item{
				UID:     uid,
				Name:    name,
				ID:      id,
				Parent:  pkg.PkgPath,
				Type:    "const",
				Summary: c.Doc,
				Langs:   onlyGo,
				Syntax:  syntax{Content: pkgsite.PrintType(pkg.Fset, c.Decl)},
			})
		}
		for _, v := range docPkg.Vars {
			name := strings.Join(v.Names, ", ")
			id := strings.Join(v.Names, ",")
			uid := pkg.PkgPath + "." + id
			pkgItem.addChild(child(uid))
			pkgPage.addItem(&item{
				UID:     uid,
				Name:    name,
				ID:      id,
				Parent:  pkg.PkgPath,
				Type:    "variable",
				Summary: v.Doc,
				Langs:   onlyGo,
				Syntax:  syntax{Content: pkgsite.PrintType(pkg.Fset, v.Decl)},
			})
		}
		for _, t := range docPkg.Types {
			uid := pkg.PkgPath + "." + t.Name
			pkgItem.addChild(child(uid))
			typeItem := &item{
				UID:     uid,
				Name:    t.Name,
				ID:      t.Name,
				Parent:  pkg.PkgPath,
				Type:    "type",
				Summary: t.Doc,
				Langs:   onlyGo,
				Syntax:  syntax{Content: pkgsite.PrintType(pkg.Fset, t.Decl)},
			}
			// TODO: items are added as page.Children, rather than
			// typeItem.Children, as a workaround for the DocFX template.
			// That also means methods are called functions.
			pkgPage.addItem(typeItem)
			for _, c := range t.Consts {
				name := strings.Join(c.Names, ", ")
				id := strings.Join(c.Names, ",")
				cUID := pkg.PkgPath + "." + id
				pkgItem.addChild(child(cUID))
				pkgPage.addItem(&item{
					UID:     cUID,
					Name:    name,
					ID:      id,
					Parent:  uid,
					Type:    "const",
					Summary: c.Doc,
					Langs:   onlyGo,
					Syntax:  syntax{Content: pkgsite.PrintType(pkg.Fset, c.Decl)},
				})
			}
			for _, v := range t.Vars {
				name := strings.Join(v.Names, ", ")
				id := strings.Join(v.Names, ",")
				cUID := pkg.PkgPath + "." + id
				pkgItem.addChild(child(cUID))
				pkgPage.addItem(&item{
					UID:     cUID,
					Name:    name,
					ID:      id,
					Parent:  uid,
					Type:    "variable",
					Summary: v.Doc,
					Langs:   onlyGo,
					Syntax:  syntax{Content: pkgsite.PrintType(pkg.Fset, v.Decl)},
				})
			}

			for _, fn := range t.Funcs {
				fnUID := uid + "." + fn.Name
				pkgItem.addChild(child(fnUID))
				s := pkgsite.Synopsis(pkg.Fset, fn.Decl)
				pkgPage.addItem(&item{
					UID:     fnUID,
					Name:    s,
					ID:      fn.Name,
					Parent:  uid,
					Type:    "function",
					Summary: fn.Doc,
					Langs:   onlyGo,
					// Note: Name has the syntax already.
					// Syntax:  Syntax{Content: s},
				})
			}
			for _, fn := range t.Methods {
				fnUID := uid + "." + fn.Name
				pkgItem.addChild(child(fnUID))
				s := pkgsite.Synopsis(pkg.Fset, fn.Decl)
				pkgPage.addItem(&item{
					UID:     fnUID,
					Name:    s,
					ID:      fn.Name,
					Parent:  uid,
					Type:    "function",
					Summary: fn.Doc,
					Langs:   onlyGo,
					// Note: Name has the syntax already.
					// Syntax:  Syntax{Content: s},
				})
			}
		}
		for _, fn := range docPkg.Funcs {
			uid := pkg.PkgPath + "." + fn.Name
			pkgItem.addChild(child(uid))
			s := pkgsite.Synopsis(pkg.Fset, fn.Decl)
			pkgPage.addItem(&item{
				UID:     uid,
				Name:    s,
				ID:      fn.Name,
				Parent:  pkg.PkgPath,
				Type:    "function",
				Summary: fn.Doc,
				Langs:   onlyGo,
				// Note: Name has the syntax already.
				// Syntax:  Syntax{Content: s},
			})
		}
	}
	if len(skippedModules) > 0 {
		skipped := []string{}
		for s := range skippedModules {
			skipped = append(skipped, "* "+s)
		}
		sort.Strings(skipped)
		log.Printf("Warning: Only processed %s@%s, skipped:\n%s\n", module.Path, module.Version, strings.Join(skipped, "\n"))
	}
	return pages, toc, module, nil
}
<commit_msg>fix(godocfx): shorten function names (#2880)<commit_after>// Copyright 2020 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.

// +build go1.15

package main

import (
	"fmt"
	"go/ast"
	"go/doc"
	"log"
	"sort"
	"strings"

	"cloud.google.com/go/third_party/pkgsite"
	"golang.org/x/tools/go/packages"
)

// tableOfContents represents a TOC.
type tableOfContents []*tocItem

// tocItem is an item in a TOC.
type tocItem struct {
	UID   string     `yaml:"uid,omitempty"`
	Name  string     `yaml:"name,omitempty"`
	Items []*tocItem `yaml:"items,omitempty"`
}

func (t *tocItem) addItem(i *tocItem) {
	t.Items = append(t.Items, i)
}

// page represents a single DocFX page.
//
// There is one page per package.
type page struct {
	Items      []*item `yaml:"items"`
	References []*item `yaml:"references,omitempty"`
}

// child represents an item child.
type child string

// syntax represents syntax.
type syntax struct {
	Content string `yaml:"content,omitempty"`
}

// item represents a DocFX item.
type item struct {
	UID      string   `yaml:"uid"`
	Name     string   `yaml:"name,omitempty"`
	ID       string   `yaml:"id,omitempty"`
	Summary  string   `yaml:"summary,omitempty"`
	Parent   string   `yaml:"parent,omitempty"`
	Type     string   `yaml:"type,omitempty"`
	Langs    []string `yaml:"langs,omitempty"`
	Syntax   syntax   `yaml:"syntax,omitempty"`
	Children []child  `yaml:"children,omitempty"`
}

func (p *page) addItem(i *item) {
	p.Items = append(p.Items, i)
}

func (i *item) addChild(c child) {
	i.Children = append(i.Children, c)
}

var onlyGo = []string{"go"}

// parse parses the directory into a map of import path -> page and a TOC.
//
// glob is the path to parse, usually ending in `...`. glob is passed directly
// to packages.Load as-is.
func parse(glob string) (map[string]*page, tableOfContents, *packages.Module, error) {
	config := &packages.Config{
		Mode:  packages.NeedName | packages.NeedSyntax | packages.NeedTypes | packages.NeedTypesInfo | packages.NeedModule,
		Tests: true,
	}

	pkgs, err := packages.Load(config, glob)
	if err != nil {
		return nil, nil, nil, fmt.Errorf("packages.Load: %v", err)
	}
	packages.PrintErrors(pkgs) // Don't fail everything because of one package.

	if len(pkgs) == 0 {
		return nil, nil, nil, fmt.Errorf("pattern %q matched 0 packages", glob)
	}

	pages := map[string]*page{}
	toc := tableOfContents{}

	module := pkgs[0].Module
	skippedModules := map[string]struct{}{}

	log.Printf("Processing %s@%s", module.Path, module.Version)

	for _, pkg := range pkgs {
		if pkg == nil || pkg.Module == nil {
			continue
		}
		if pkg.Module.Path != module.Path {
			skippedModules[pkg.Module.Path] = struct{}{}
			continue
		}
		// Don't generate docs for tests or internal.
		switch {
		case strings.HasSuffix(pkg.ID, ".test"),
			strings.HasSuffix(pkg.ID, ".test]"),
			strings.Contains(pkg.ID, "internal"):
			continue
		}

		// Collect all .go files.
		files := []*ast.File{}
		for _, f := range pkg.Syntax {
			tf := pkg.Fset.File(f.Pos())
			if strings.HasSuffix(tf.Name(), ".go") {
				files = append(files, f)
			}
		}

		// Parse out GoDoc.
		docPkg, err := doc.NewFromFiles(pkg.Fset, files, pkg.PkgPath)
		if err != nil {
			return nil, nil, nil, fmt.Errorf("doc.NewFromFiles: %v", err)
		}

		toc = append(toc, &tocItem{
			UID:  pkg.ID,
			Name: pkg.PkgPath,
		})

		pkgItem := &item{
			UID:     pkg.ID,
			Name:    pkg.PkgPath,
			ID:      pkg.Name,
			Summary: docPkg.Doc,
			Langs:   onlyGo,
			Type:    "package",
		}
		pkgPage := &page{Items: []*item{pkgItem}}
		pages[pkg.PkgPath] = pkgPage

		for _, c := range docPkg.Consts {
			name := strings.Join(c.Names, ", ")
			id := strings.Join(c.Names, ",")
			uid := pkg.PkgPath + "." + id
			pkgItem.addChild(child(uid))
			pkgPage.addItem(&item{
				UID:     uid,
				Name:    name,
				ID:      id,
				Parent:  pkg.PkgPath,
				Type:    "const",
				Summary: c.Doc,
				Langs:   onlyGo,
				Syntax:  syntax{Content: pkgsite.PrintType(pkg.Fset, c.Decl)},
			})
		}
		for _, v := range docPkg.Vars {
			name := strings.Join(v.Names, ", ")
			id := strings.Join(v.Names, ",")
			uid := pkg.PkgPath + "." + id
			pkgItem.addChild(child(uid))
			pkgPage.addItem(&item{
				UID:     uid,
				Name:    name,
				ID:      id,
				Parent:  pkg.PkgPath,
				Type:    "variable",
				Summary: v.Doc,
				Langs:   onlyGo,
				Syntax:  syntax{Content: pkgsite.PrintType(pkg.Fset, v.Decl)},
			})
		}
		for _, t := range docPkg.Types {
			uid := pkg.PkgPath + "." + t.Name
			pkgItem.addChild(child(uid))
			typeItem := &item{
				UID:     uid,
				Name:    t.Name,
				ID:      t.Name,
				Parent:  pkg.PkgPath,
				Type:    "type",
				Summary: t.Doc,
				Langs:   onlyGo,
				Syntax:  syntax{Content: pkgsite.PrintType(pkg.Fset, t.Decl)},
			}
			// TODO: items are added as page.Children, rather than
			// typeItem.Children, as a workaround for the DocFX template.
			// That also means methods are called functions.
			pkgPage.addItem(typeItem)
			for _, c := range t.Consts {
				name := strings.Join(c.Names, ", ")
				id := strings.Join(c.Names, ",")
				cUID := pkg.PkgPath + "." + id
				pkgItem.addChild(child(cUID))
				pkgPage.addItem(&item{
					UID:     cUID,
					Name:    name,
					ID:      id,
					Parent:  uid,
					Type:    "const",
					Summary: c.Doc,
					Langs:   onlyGo,
					Syntax:  syntax{Content: pkgsite.PrintType(pkg.Fset, c.Decl)},
				})
			}
			for _, v := range t.Vars {
				name := strings.Join(v.Names, ", ")
				id := strings.Join(v.Names, ",")
				cUID := pkg.PkgPath + "." + id
				pkgItem.addChild(child(cUID))
				pkgPage.addItem(&item{
					UID:     cUID,
					Name:    name,
					ID:      id,
					Parent:  uid,
					Type:    "variable",
					Summary: v.Doc,
					Langs:   onlyGo,
					Syntax:  syntax{Content: pkgsite.PrintType(pkg.Fset, v.Decl)},
				})
			}

			for _, fn := range t.Funcs {
				fnUID := uid + "." + fn.Name
				pkgItem.addChild(child(fnUID))
				pkgPage.addItem(&item{
					UID:     fnUID,
					Name:    fmt.Sprintf("func %s\n", fn.Name),
					ID:      fn.Name,
					Parent:  uid,
					Type:    "function",
					Summary: fn.Doc,
					Langs:   onlyGo,
					Syntax:  syntax{Content: pkgsite.Synopsis(pkg.Fset, fn.Decl)},
				})
			}
			for _, fn := range t.Methods {
				fnUID := uid + "." + fn.Name
				pkgItem.addChild(child(fnUID))
				pkgPage.addItem(&item{
					UID:     fnUID,
					Name:    fmt.Sprintf("func (%s) %s\n", fn.Recv, fn.Name),
					ID:      fn.Name,
					Parent:  uid,
					Type:    "function", // Note: this is actually a method.
					Summary: fn.Doc,
					Langs:   onlyGo,
					Syntax:  syntax{Content: pkgsite.Synopsis(pkg.Fset, fn.Decl)},
				})
			}
		}
		for _, fn := range docPkg.Funcs {
			uid := pkg.PkgPath + "." + fn.Name
			pkgItem.addChild(child(uid))
			pkgPage.addItem(&item{
				UID:     uid,
				Name:    fmt.Sprintf("func %s\n", fn.Name),
				ID:      fn.Name,
				Parent:  pkg.PkgPath,
				Type:    "function",
				Summary: fn.Doc,
				Langs:   onlyGo,
				Syntax:  syntax{Content: pkgsite.Synopsis(pkg.Fset, fn.Decl)},
			})
		}
	}
	if len(skippedModules) > 0 {
		skipped := []string{}
		for s := range skippedModules {
			skipped = append(skipped, "* "+s)
		}
		sort.Strings(skipped)
		log.Printf("Warning: Only processed %s@%s, skipped:\n%s\n", module.Path, module.Version, strings.Join(skipped, "\n"))
	}
	return pages, toc, module, nil
}
<|endoftext|>


<commit_before>package anidb

import (
	"github.com/Kovensky/go-fscache"
	"strconv"
	"time"
)

// These are all pointers because they're not
// sent at all if they're nil
type MyListSet struct {
	State    *MyListState
	Watched  *bool
	ViewDate *time.Time
	Source   *string
	Storage  *string
	Other    *string
}

func (set *MyListSet) toParamMap() (pm paramMap) {
	pm = paramMap{}
	if set == nil {
		return
	}

	if set.State != nil {
		pm["state"] = *set.State
	}
	if set.Watched != nil {
		pm["viewed"] = *set.Watched
	}
	if set.ViewDate != nil {
		if set.ViewDate.IsZero() {
			pm["viewdate"] = 0
		} else {
			pm["viewdate"] = int(int32(set.ViewDate.Unix()))
		}
	}
	if set.Source != nil {
		pm["source"] = *set.Source
	}
	if set.Storage != nil {
		pm["storage"] = *set.Storage
	}
	if set.Other != nil {
		pm["other"] = *set.Other
	}
	return
}

func (set *MyListSet) update(uid UID, f *File, lid LID) {
	if f.LID[uid] != lid {
		f.LID[uid] = lid
		Cache.Set(f, "fid", f.FID)
		Cache.Chtime(f.Cached, "fid", f.FID)
	}

	mla := uid.MyListAnime(f.AID)
	if mla == nil {
		mla = &MyListAnime{
			EpisodesWithState: MyListStateMap{},
			EpisodesPerGroup:  GroupEpisodes{},
		}
	}
	// We only ever add, not remove -- we don't know if other files also satisfy the list
	eg := mla.EpisodesPerGroup[f.GID]
	eg.Add(f.EpisodeNumber)
	mla.EpisodesPerGroup[f.GID] = eg

	if set != nil {
		if set.State != nil {
			es := mla.EpisodesWithState[*set.State]
			es.Add(f.EpisodeNumber)
			mla.EpisodesWithState[*set.State] = es
		}

		if set.Watched != nil && *set.Watched ||
			set.ViewDate != nil && !set.ViewDate.IsZero() {
			mla.WatchedEpisodes.Add(f.EpisodeNumber)
		}
	}

	Cache.Set(mla, "mylist-anime", uid, f.AID)
	Cache.Chtime(mla.Cached, "mylist-anime", uid, f.AID)

	if set == nil ||
		(set.ViewDate == nil && set.Watched == nil && set.State == nil &&
			set.Source == nil && set.Storage == nil && set.Other == nil) {
		return
	}

	e := lid.MyListEntry()
	if set.ViewDate != nil {
		e.DateWatched = *set.ViewDate
	} else if set.Watched != nil {
		if *set.Watched {
			e.DateWatched = time.Now()
		} else {
			e.DateWatched = time.Time{}
		}
	}
	if set.State != nil {
		e.MyListState = *set.State
	}
	if set.Source != nil {
		e.Source = *set.Source
	}
	if set.Storage != nil {
		e.Storage = *set.Storage
	}
	if set.Other != nil {
		e.Other = *set.Other
	}
	Cache.Set(e, "mylist", lid)
	Cache.Chtime(e.Cached, "mylist", lid)
}

func (adb *AniDB) MyListAdd(f *File, set *MyListSet) <-chan LID {
	ch := make(chan LID, 1)
	if f == nil {
		ch <- 0
		close(ch)
		return ch
	}

	go func() {
		user := <-adb.GetCurrentUser()
		if user == nil || user.UID < 1 {
			ch <- 0
			close(ch)
			return
		}

		// for the intent map; doesn't get cached
		key := []fscache.CacheKey{"mylist-add", user.UID, f.FID}

		ic := make(chan notification, 1)
		go func() { ch <- (<-ic).(LID); close(ch) }()
		if intentMap.Intent(ic, key...) {
			return
		}

		pm := set.toParamMap()
		pm["fid"] = f.FID

		reply := <-adb.udp.SendRecv("MYLISTADD", pm)

		lid := LID(0)

		switch reply.Code() {
		case 310:
			e := adb.parseMylistReply(reply)
			if e != nil {
				lid = e.LID
			}
		case 210:
			id, _ := strconv.ParseInt(reply.Lines()[1], 10, 64)
			lid = LID(id)

			// the 310 case does this in parseMylistReply
			set.update(user.UID, f, lid)
		}

		intentMap.NotifyClose(lid, key...)
	}()

	return ch
}

func (adb *AniDB) MyListAddByEd2kSize(ed2k string, size int64, set *MyListSet) <-chan LID {
	ch := make(chan LID, 1)
	if size < 1 || !validEd2kHash.MatchString(ed2k) {
		ch <- 0
		close(ch)
		return ch
	}

	go func() {
		ch <- <-adb.MyListAdd(<-adb.FileByEd2kSize(ed2k, size), set)
		close(ch)
	}()
	return ch
}

func (adb *AniDB) MyListEdit(f *File, set *MyListSet) <-chan bool {
	ch := make(chan bool, 1)
	if f == nil {
		ch <- false
		close(ch)
		return ch
	}

	go func() {
		user := <-adb.GetCurrentUser()
		if user == nil || user.UID < 1 {
			ch <- false
			close(ch)
			return
		}

		// for the intent map; doesn't get cached
		key := []fscache.CacheKey{"mylist-edit", user.UID, f.FID}

		ic := make(chan notification, 1)
		go func() { ch <- (<-ic).(bool); close(ch) }()
		if intentMap.Intent(ic, key...) {
			return
		}

		pm := set.toParamMap()
		pm["edit"] = 1
		if lid := f.LID[user.UID]; lid > 0 {
			pm["lid"] = lid
		} else {
			pm["fid"] = f.FID
		}

		reply := <-adb.udp.SendRecv("MYLISTADD", pm)

		switch reply.Code() {
		case 311:
			intentMap.NotifyClose(true, key...)

			set.update(user.UID, f, 0)
		default:
			intentMap.NotifyClose(false, key...)
		}
	}()

	return ch
}

func (adb *AniDB) MyListDel(f *File) <-chan bool {
	ch := make(chan bool)
	if f == nil {
		ch <- false
		close(ch)
		return ch
	}

	go func() {
		user := <-adb.GetCurrentUser()
		if user == nil || user.UID < 1 {
			ch <- false
			close(ch)
			return
		}

		// for the intent map; doesn't get cached
		key := []fscache.CacheKey{"mylist-del", user.UID, f.FID}

		ic := make(chan notification, 1)
		go func() { ch <- (<-ic).(bool); close(ch) }()
		if intentMap.Intent(ic, key...) {
			return
		}

		pm := paramMap{}
		if lid := f.LID[user.UID]; lid > 0 {
			pm["lid"] = lid
		} else {
			pm["fid"] = f.FID
		}

		reply := <-adb.udp.SendRecv("MYLISTDEL", pm)

		switch reply.Code() {
		case 211:
			delete(f.LID, user.UID)
			Cache.Set(f, "fid", f.FID)
			Cache.Chtime(f.Cached, "fid", f.FID)

			intentMap.NotifyClose(true, key...)
		default:
			intentMap.NotifyClose(false, key...)
		}
	}()

	return ch
}
<commit_msg>anidb: Add new mylist entry as Unknown if MyListSet doesn't say anything<commit_after>package anidb

import (
	"github.com/Kovensky/go-fscache"
	"strconv"
	"time"
)

// These are all pointers because they're not
// sent at all if they're nil
type MyListSet struct {
	State    *MyListState
	Watched  *bool
	ViewDate *time.Time
	Source   *string
	Storage  *string
	Other    *string
}

func (set *MyListSet) toParamMap() (pm paramMap) {
	pm = paramMap{}
	if set == nil {
		return
	}

	if set.State != nil {
		pm["state"] = *set.State
	}
	if set.Watched != nil {
		pm["viewed"] = *set.Watched
	}
	if set.ViewDate != nil {
		if set.ViewDate.IsZero() {
			pm["viewdate"] = 0
		} else {
			pm["viewdate"] = int(int32(set.ViewDate.Unix()))
		}
	}
	if set.Source != nil {
		pm["source"] = *set.Source
	}
	if set.Storage != nil {
		pm["storage"] = *set.Storage
	}
	if set.Other != nil {
		pm["other"] = *set.Other
	}
	return
}

func (set *MyListSet) update(uid UID, f *File, lid LID) {
	if f.LID[uid] != lid {
		f.LID[uid] = lid
		Cache.Set(f, "fid", f.FID)
		Cache.Chtime(f.Cached, "fid", f.FID)
	}

	mla := uid.MyListAnime(f.AID)
	if mla == nil {
		mla = &MyListAnime{
			EpisodesWithState: MyListStateMap{},
			EpisodesPerGroup:  GroupEpisodes{},
		}
	}
	// We only ever add, not remove -- we don't know if other files also satisfy the list
	eg := mla.EpisodesPerGroup[f.GID]
	eg.Add(f.EpisodeNumber)
	mla.EpisodesPerGroup[f.GID] = eg

	newState := MyListStateUnknown
	if set != nil {
		if set.State != nil {
			newState = *set.State
		}

		if set.Watched != nil && *set.Watched ||
			set.ViewDate != nil && !set.ViewDate.IsZero() {
			mla.WatchedEpisodes.Add(f.EpisodeNumber)
		}
	}

	es := mla.EpisodesWithState[newState]
	es.Add(f.EpisodeNumber)
	mla.EpisodesWithState[newState] = es

	Cache.Set(mla, "mylist-anime", uid, f.AID)
	Cache.Chtime(mla.Cached, "mylist-anime", uid, f.AID)

	if set == nil ||
		(set.ViewDate == nil && set.Watched == nil && set.State == nil &&
			set.Source == nil && set.Storage == nil && set.Other == nil) {
		return
	}

	e := lid.MyListEntry()
	if set.ViewDate != nil {
		e.DateWatched = *set.ViewDate
	} else if set.Watched != nil {
		if *set.Watched {
			e.DateWatched = time.Now()
		} else {
			e.DateWatched = time.Time{}
		}
	}
	if set.State != nil {
		e.MyListState = *set.State
	}
	if set.Source != nil {
		e.Source = *set.Source
	}
	if set.Storage != nil {
		e.Storage = *set.Storage
	}
	if set.Other != nil {
		e.Other = *set.Other
	}
	Cache.Set(e, "mylist", lid)
	Cache.Chtime(e.Cached, "mylist", lid)
}

func (adb *AniDB) MyListAdd(f *File, set *MyListSet) <-chan LID {
	ch := make(chan LID, 1)
	if f == nil {
		ch <- 0
		close(ch)
		return ch
	}

	go func() {
		user := <-adb.GetCurrentUser()
		if user == nil || user.UID < 1 {
			ch <- 0
			close(ch)
			return
		}

		// for the intent map; doesn't get cached
		key := []fscache.CacheKey{"mylist-add", user.UID, f.FID}

		ic := make(chan notification, 1)
		go func() { ch <- (<-ic).(LID); close(ch) }()
		if intentMap.Intent(ic, key...) {
			return
		}

		pm := set.toParamMap()
		pm["fid"] = f.FID

		reply := <-adb.udp.SendRecv("MYLISTADD", pm)

		lid := LID(0)

		switch reply.Code() {
		case 310:
			e := adb.parseMylistReply(reply)
			if e != nil {
				lid = e.LID
			}
		case 210:
			id, _ := strconv.ParseInt(reply.Lines()[1], 10, 64)
			lid = LID(id)

			// the 310 case does this in parseMylistReply
			set.update(user.UID, f, lid)
		}

		intentMap.NotifyClose(lid, key...)
	}()

	return ch
}

func (adb *AniDB) MyListAddByEd2kSize(ed2k string, size int64, set *MyListSet) <-chan LID {
	ch := make(chan LID, 1)
	if size < 1 || !validEd2kHash.MatchString(ed2k) {
		ch <- 0
		close(ch)
		return ch
	}

	go func() {
		ch <- <-adb.MyListAdd(<-adb.FileByEd2kSize(ed2k, size), set)
		close(ch)
	}()
	return ch
}

func (adb *AniDB) MyListEdit(f *File, set *MyListSet) <-chan bool {
	ch := make(chan bool, 1)
	if f == nil {
		ch <- false
		close(ch)
		return ch
	}

	go func() {
		user := <-adb.GetCurrentUser()
		if user == nil || user.UID < 1 {
			ch <- false
			close(ch)
			return
		}

		// for the intent map; doesn't get cached
		key := []fscache.CacheKey{"mylist-edit", user.UID, f.FID}

		ic := make(chan notification, 1)
		go func() { ch <- (<-ic).(bool); close(ch) }()
		if intentMap.Intent(ic, key...) {
			return
		}

		pm := set.toParamMap()
		pm["edit"] = 1
		if lid := f.LID[user.UID]; lid > 0 {
			pm["lid"] = lid
		} else {
			pm["fid"] = f.FID
		}

		reply := <-adb.udp.SendRecv("MYLISTADD", pm)

		switch reply.Code() {
		case 311:
			intentMap.NotifyClose(true, key...)

			set.update(user.UID, f, 0)
		default:
			intentMap.NotifyClose(false, key...)
		}
	}()

	return ch
}

func (adb *AniDB) MyListDel(f *File) <-chan bool {
	ch := make(chan bool)
	if f == nil {
		ch <- false
		close(ch)
		return ch
	}

	go func() {
		user := <-adb.GetCurrentUser()
		if user == nil || user.UID < 1 {
			ch <- false
			close(ch)
			return
		}

		// for the intent map; doesn't get cached
		key := []fscache.CacheKey{"mylist-del", user.UID, f.FID}

		ic := make(chan notification, 1)
		go func() { ch <- (<-ic).(bool); close(ch) }()
		if intentMap.Intent(ic, key...) {
			return
		}

		pm := paramMap{}
		if lid := f.LID[user.UID]; lid > 0 {
			pm["lid"] = lid
		} else {
			pm["fid"] = f.FID
		}

		reply := <-adb.udp.SendRecv("MYLISTDEL", pm)

		switch reply.Code() {
		case 211:
			delete(f.LID, user.UID)
			Cache.Set(f, "fid", f.FID)
			Cache.Chtime(f.Cached, "fid", f.FID)

			intentMap.NotifyClose(true, key...)
		default:
			intentMap.NotifyClose(false, key...)
		}
	}()

	return ch
}
<|endoftext|>


<commit_before>// Copyright 2018 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 mipmap

import (
	"fmt"
	"image/color"
	"math"

	"github.com/hajimehoshi/ebiten/internal/affine"
	"github.com/hajimehoshi/ebiten/internal/driver"
	"github.com/hajimehoshi/ebiten/internal/graphics"
	"github.com/hajimehoshi/ebiten/internal/shaderir"
	"github.com/hajimehoshi/ebiten/internal/shareable"
)

var graphicsDriver driver.Graphics

func SetGraphicsDriver(graphics driver.Graphics) {
	graphicsDriver = graphics
}

func BeginFrame() error {
	return shareable.BeginFrame()
}

func EndFrame() error {
	return shareable.EndFrame()
}

// Mipmap is a set of shareable.Image sorted by the order of mipmap level.
// The level 0 image is a regular image and higher-level images are used for mipmap.
type Mipmap struct {
	width    int
	height   int
	volatile bool
	orig     *shareable.Image
	imgs     map[int]*shareable.Image
}

func New(width, height int, volatile bool) *Mipmap {
	return &Mipmap{
		width:    width,
		height:   height,
		volatile: volatile,
		orig:     shareable.NewImage(width, height, volatile),
		imgs:     map[int]*shareable.Image{},
	}
}

func NewScreenFramebufferMipmap(width, height int) *Mipmap {
	return &Mipmap{
		width:  width,
		height: height,
		orig:   shareable.NewScreenFramebufferImage(width, height),
		imgs:   map[int]*shareable.Image{},
	}
}

func (m *Mipmap) Dump(name string, blackbg bool) error {
	return m.orig.Dump(name, blackbg)
}

func (m *Mipmap) Fill(clr color.RGBA) {
	m.orig.Fill(clr)
	m.disposeMipmaps()
}

func (m *Mipmap) ReplacePixels(pix []byte) {
	m.orig.ReplacePixels(pix)
	m.disposeMipmaps()
}

func (m *Mipmap) Pixels(x, y, width, height int) ([]byte, error) {
	return m.orig.Pixels(x, y, width, height)
}

func (m *Mipmap) DrawTriangles(src *Mipmap, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader, uniforms []interface{}, images []*Mipmap) {
	level := math.MaxInt32
	for i := 0; i < len(indices)/3; i++ {
		const n = graphics.VertexFloatNum
		dx0 := vertices[n*indices[3*i]+0]
		dy0 := vertices[n*indices[3*i]+1]
		sx0 := vertices[n*indices[3*i]+2]
		sy0 := vertices[n*indices[3*i]+3]
		dx1 := vertices[n*indices[3*i+1]+0]
		dy1 := vertices[n*indices[3*i+1]+1]
		sx1 := vertices[n*indices[3*i+1]+2]
		sy1 := vertices[n*indices[3*i+1]+3]
		dx2 := vertices[n*indices[3*i+2]+0]
		dy2 := vertices[n*indices[3*i+2]+1]
		sx2 := vertices[n*indices[3*i+2]+2]
		sy2 := vertices[n*indices[3*i+2]+3]
		if l := src.mipmapLevelFromDistance(dx0, dy0, dx1, dy1, sx0, sy0, sx1, sy1, filter); level > l {
			level = l
		}
		if l := src.mipmapLevelFromDistance(dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, filter); level > l {
			level = l
		}
		if l := src.mipmapLevelFromDistance(dx2, dy2, dx0, dy0, sx2, sy2, sx0, sy0, filter); level > l {
			level = l
		}
	}

	if colorm != nil && colorm.ScaleOnly() {
		body, _ := colorm.UnsafeElements()
		cr := body[0]
		cg := body[5]
		cb := body[10]
		ca := body[15]
		colorm = nil
		const n = graphics.VertexFloatNum
		for i := 0; i < len(vertices)/n; i++ {
			vertices[i*n+4] *= cr
			vertices[i*n+5] *= cg
			vertices[i*n+6] *= cb
			vertices[i*n+7] *= ca
		}
	}

	var s *shareable.Shader
	if shader != nil {
		s = shader.shader
	}

	var srcimg *shareable.Image
	if src != nil {
		srcimg = src.orig
	}

	if level != 0 {
		if img := src.level(level); img != nil {
			srcimg = img
			const n = graphics.VertexFloatNum
			s := float32(pow2(level))
			for i := 0; i < len(vertices)/n; i++ {
				vertices[i*n+2] /= s
				vertices[i*n+3] /= s
			}
		}
	}

	// TODO: Do we need to consider mipmaps here?
	var imgs []*shareable.Image
	for _, img := range images {
		imgs = append(imgs, img.orig)
	}

	m.orig.DrawTriangles(srcimg, vertices, indices, colorm, mode, filter, address, sourceRegion, s, uniforms, imgs)
	m.disposeMipmaps()
}

func (m *Mipmap) level(level int) *shareable.Image {
	if level == 0 {
		panic("ebiten: level must be non-zero at level")
	}

	if m.volatile {
		panic("ebiten: mipmap images for a volatile image is not implemented yet")
	}

	if img, ok := m.imgs[level]; ok {
		return img
	}

	var src *shareable.Image
	var vs []float32
	var filter driver.Filter
	switch {
	case level == 1:
		src = m.orig
		vs = graphics.QuadVertices(0, 0, float32(m.width), float32(m.height), 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1, false)
		filter = driver.FilterLinear
	case level > 1:
		src = m.level(level - 1)
		if src == nil {
			m.imgs[level] = nil
			return nil
		}
		w := sizeForLevel(m.width, level-1)
		h := sizeForLevel(m.height, level-1)
		vs = graphics.QuadVertices(0, 0, float32(w), float32(h), 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1, false)
		filter = driver.FilterLinear
	case level == -1:
		src = m.orig
		vs = graphics.QuadVertices(0, 0, float32(m.width), float32(m.height), 2, 0, 0, 2, 0, 0, 1, 1, 1, 1, false)
		filter = driver.FilterNearest
	case level < -1:
		src = m.level(level + 1)
		if src == nil {
			m.imgs[level] = nil
			return nil
		}
		w := sizeForLevel(m.width, level-1)
		h := sizeForLevel(m.height, level-1)
		vs = graphics.QuadVertices(0, 0, float32(w), float32(h), 2, 0, 0, 2, 0, 0, 1, 1, 1, 1, false)
		filter = driver.FilterNearest
	default:
		panic(fmt.Sprintf("ebiten: invalid level: %d", level))
	}
	is := graphics.QuadIndices()

	w2 := sizeForLevel(m.width, level-1)
	h2 := sizeForLevel(m.height, level-1)
	if w2 == 0 || h2 == 0 {
		m.imgs[level] = nil
		return nil
	}
	s := shareable.NewImage(w2, h2, m.volatile)
	s.DrawTriangles(src, vs, is, nil, driver.CompositeModeCopy, filter, driver.AddressUnsafe, driver.Region{}, nil, nil, nil)
	m.imgs[level] = s

	return m.imgs[level]
}

func sizeForLevel(x int, level int) int {
	if level > 0 {
		for i := 0; i < level; i++ {
			x /= 2
			if x == 0 {
				return 0
			}
		}
	} else {
		for i := 0; i < -level; i++ {
			x *= 2
		}
	}
	return x
}

func (m *Mipmap) MarkDisposed() {
	m.disposeMipmaps()
	m.orig.MarkDisposed()
	m.orig = nil
}

func (m *Mipmap) disposeMipmaps() {
	for _, img := range m.imgs {
		img.MarkDisposed()
	}
	for k := range m.imgs {
		delete(m.imgs, k)
	}
}

// mipmapLevel returns an appropriate mipmap level for the given distance.
func (m *Mipmap) mipmapLevelFromDistance(dx0, dy0, dx1, dy1, sx0, sy0, sx1, sy1 float32, filter driver.Filter) int {
	if filter == driver.FilterScreen {
		return 0
	}
	if m.volatile {
		return 0
	}

	d := (dx1-dx0)*(dx1-dx0) + (dy1-dy0)*(dy1-dy0)
	s := (sx1-sx0)*(sx1-sx0) + (sy1-sy0)*(sy1-sy0)
	if s == 0 {
		return 0
	}
	scale := d / s

	// Use 'negative' mipmap to render edges correctly (#611, #907).
	// It looks like 128 is the enlargement factor that causes edge missings to pass the test TestImageStretch.
	var tooBigScale float32 = 128
	if !graphicsDriver.HasHighPrecisionFloat() {
		tooBigScale = 4
	}

	if scale >= tooBigScale*tooBigScale {
		// If the filter is not nearest, the target needs to be rendered with graduation. Don't use mipmaps.
		if filter != driver.FilterNearest {
			return 0
		}

		const mipmapMaxSize = 1024
		w, h := sx1-sx0, sy1-sy0
		if w >= mipmapMaxSize || h >= mipmapMaxSize {
			return 0
		}

		level := 0
		for scale >= tooBigScale*tooBigScale {
			level--
			scale /= 4
			w *= 2
			h *= 2
			if w >= mipmapMaxSize || h >= mipmapMaxSize {
				break
			}
		}

		// If tooBigScale is 4, level -10 means that the maximum scale is 4 * 2^10 = 4096. This should be
		// enough.
		if level < -10 {
			level = -10
		}
		return level
	}

	if filter != driver.FilterLinear {
		return 0
	}

	level := 0
	for scale < 0.25 {
		level++
		scale *= 4
	}

	if level > 0 {
		// If the image can be scaled into 0 size, adjust the level. (#839)
		w, h := int(sx1-sx0), int(sy1-sy0)
		for level >= 0 {
			s := 1 << uint(level)
			if (w > 0 && w/s == 0) || (h > 0 && h/s == 0) {
				level--
				continue
			}
			break
		}

		if level < 0 {
			// As the render source is too small, nothing is rendered.
			return 0
		}
	}

	if level > 6 {
		level = 6
	}

	return level
}

func pow2(power int) float32 {
	if power >= 0 {
		x := 1
		return float32(x << uint(power))
	}

	x := float32(1)
	for i := 0; i < -power; i++ {
		x /= 2
	}
	return x
}

type Shader struct {
	shader *shareable.Shader
}

func NewShader(program *shaderir.Program) *Shader {
	return &Shader{
		shader: shareable.NewShader(program),
	}
}

func (s *Shader) MarkDisposed() {
	s.shader.MarkDisposed()
	s.shader = nil
}
<commit_msg>mipmap: Bug fix: Crash at DrawTrianglesWithShader<commit_after>// Copyright 2018 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 mipmap

import (
	"fmt"
	"image/color"
	"math"

	"github.com/hajimehoshi/ebiten/internal/affine"
	"github.com/hajimehoshi/ebiten/internal/driver"
	"github.com/hajimehoshi/ebiten/internal/graphics"
	"github.com/hajimehoshi/ebiten/internal/shaderir"
	"github.com/hajimehoshi/ebiten/internal/shareable"
)

var graphicsDriver driver.Graphics

func SetGraphicsDriver(graphics driver.Graphics) {
	graphicsDriver = graphics
}

func BeginFrame() error {
	return shareable.BeginFrame()
}

func EndFrame() error {
	return shareable.EndFrame()
}

// Mipmap is a set of shareable.Image sorted by the order of mipmap level.
// The level 0 image is a regular image and higher-level images are used for mipmap.
type Mipmap struct {
	width    int
	height   int
	volatile bool
	orig     *shareable.Image
	imgs     map[int]*shareable.Image
}

func New(width, height int, volatile bool) *Mipmap {
	return &Mipmap{
		width:    width,
		height:   height,
		volatile: volatile,
		orig:     shareable.NewImage(width, height, volatile),
		imgs:     map[int]*shareable.Image{},
	}
}

func NewScreenFramebufferMipmap(width, height int) *Mipmap {
	return &Mipmap{
		width:  width,
		height: height,
		orig:   shareable.NewScreenFramebufferImage(width, height),
		imgs:   map[int]*shareable.Image{},
	}
}

func (m *Mipmap) Dump(name string, blackbg bool) error {
	return m.orig.Dump(name, blackbg)
}

func (m *Mipmap) Fill(clr color.RGBA) {
	m.orig.Fill(clr)
	m.disposeMipmaps()
}

func (m *Mipmap) ReplacePixels(pix []byte) {
	m.orig.ReplacePixels(pix)
	m.disposeMipmaps()
}

func (m *Mipmap) Pixels(x, y, width, height int) ([]byte, error) {
	return m.orig.Pixels(x, y, width, height)
}

func (m *Mipmap) DrawTriangles(src *Mipmap, vertices []float32, indices []uint16, colorm *affine.ColorM, mode driver.CompositeMode, filter driver.Filter, address driver.Address, sourceRegion driver.Region, shader *Shader, uniforms []interface{}, images []*Mipmap) {
	level := 0
	if src != nil && !src.volatile && filter != driver.FilterScreen {
		level = math.MaxInt32
		for i := 0; i < len(indices)/3; i++ {
			const n = graphics.VertexFloatNum
			dx0 := vertices[n*indices[3*i]+0]
			dy0 := vertices[n*indices[3*i]+1]
			sx0 := vertices[n*indices[3*i]+2]
			sy0 := vertices[n*indices[3*i]+3]
			dx1 := vertices[n*indices[3*i+1]+0]
			dy1 := vertices[n*indices[3*i+1]+1]
			sx1 := vertices[n*indices[3*i+1]+2]
			sy1 := vertices[n*indices[3*i+1]+3]
			dx2 := vertices[n*indices[3*i+2]+0]
			dy2 := vertices[n*indices[3*i+2]+1]
			sx2 := vertices[n*indices[3*i+2]+2]
			sy2 := vertices[n*indices[3*i+2]+3]
			if l := mipmapLevelFromDistance(dx0, dy0, dx1, dy1, sx0, sy0, sx1, sy1, filter); level > l {
				level = l
			}
			if l := mipmapLevelFromDistance(dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, filter); level > l {
				level = l
			}
			if l := mipmapLevelFromDistance(dx2, dy2, dx0, dy0, sx2, sy2, sx0, sy0, filter); level > l {
				level = l
			}
		}
	}

	if colorm != nil && colorm.ScaleOnly() {
		body, _ := colorm.UnsafeElements()
		cr := body[0]
		cg := body[5]
		cb := body[10]
		ca := body[15]
		colorm = nil
		const n = graphics.VertexFloatNum
		for i := 0; i < len(vertices)/n; i++ {
			vertices[i*n+4] *= cr
			vertices[i*n+5] *= cg
			vertices[i*n+6] *= cb
			vertices[i*n+7] *= ca
		}
	}

	var s *shareable.Shader
	if shader != nil {
		s = shader.shader
	}

	var srcimg *shareable.Image
	if src != nil {
		srcimg = src.orig
	}

	if level != 0 {
		if img := src.level(level); img != nil {
			srcimg = img
			const n = graphics.VertexFloatNum
			s := float32(pow2(level))
			for i := 0; i < len(vertices)/n; i++ {
				vertices[i*n+2] /= s
				vertices[i*n+3] /= s
			}
		}
	}

	// TODO: Do we need to consider mipmaps here?
	var imgs []*shareable.Image
	for _, img := range images {
		imgs = append(imgs, img.orig)
	}

	m.orig.DrawTriangles(srcimg, vertices, indices, colorm, mode, filter, address, sourceRegion, s, uniforms, imgs)
	m.disposeMipmaps()
}

func (m *Mipmap) level(level int) *shareable.Image {
	if level == 0 {
		panic("ebiten: level must be non-zero at level")
	}

	if m.volatile {
		panic("ebiten: mipmap images for a volatile image is not implemented yet")
	}

	if img, ok := m.imgs[level]; ok {
		return img
	}

	var src *shareable.Image
	var vs []float32
	var filter driver.Filter
	switch {
	case level == 1:
		src = m.orig
		vs = graphics.QuadVertices(0, 0, float32(m.width), float32(m.height), 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1, false)
		filter = driver.FilterLinear
	case level > 1:
		src = m.level(level - 1)
		if src == nil {
			m.imgs[level] = nil
			return nil
		}
		w := sizeForLevel(m.width, level-1)
		h := sizeForLevel(m.height, level-1)
		vs = graphics.QuadVertices(0, 0, float32(w), float32(h), 0.5, 0, 0, 0.5, 0, 0, 1, 1, 1, 1, false)
		filter = driver.FilterLinear
	case level == -1:
		src = m.orig
		vs = graphics.QuadVertices(0, 0, float32(m.width), float32(m.height), 2, 0, 0, 2, 0, 0, 1, 1, 1, 1, false)
		filter = driver.FilterNearest
	case level < -1:
		src = m.level(level + 1)
		if src == nil {
			m.imgs[level] = nil
			return nil
		}
		w := sizeForLevel(m.width, level-1)
		h := sizeForLevel(m.height, level-1)
		vs = graphics.QuadVertices(0, 0, float32(w), float32(h), 2, 0, 0, 2, 0, 0, 1, 1, 1, 1, false)
		filter = driver.FilterNearest
	default:
		panic(fmt.Sprintf("ebiten: invalid level: %d", level))
	}
	is := graphics.QuadIndices()

	w2 := sizeForLevel(m.width, level-1)
	h2 := sizeForLevel(m.height, level-1)
	if w2 == 0 || h2 == 0 {
		m.imgs[level] = nil
		return nil
	}
	s := shareable.NewImage(w2, h2, m.volatile)
	s.DrawTriangles(src, vs, is, nil, driver.CompositeModeCopy, filter, driver.AddressUnsafe, driver.Region{}, nil, nil, nil)
	m.imgs[level] = s

	return m.imgs[level]
}

func sizeForLevel(x int, level int) int {
	if level > 0 {
		for i := 0; i < level; i++ {
			x /= 2
			if x == 0 {
				return 0
			}
		}
	} else {
		for i := 0; i < -level; i++ {
			x *= 2
		}
	}
	return x
}

func (m *Mipmap) MarkDisposed() {
	m.disposeMipmaps()
	m.orig.MarkDisposed()
	m.orig = nil
}

func (m *Mipmap) disposeMipmaps() {
	for _, img := range m.imgs {
		img.MarkDisposed()
	}
	for k := range m.imgs {
		delete(m.imgs, k)
	}
}

// mipmapLevel returns an appropriate mipmap level for the given distance.
func mipmapLevelFromDistance(dx0, dy0, dx1, dy1, sx0, sy0, sx1, sy1 float32, filter driver.Filter) int {
	if filter == driver.FilterScreen {
		return 0
	}

	d := (dx1-dx0)*(dx1-dx0) + (dy1-dy0)*(dy1-dy0)
	s := (sx1-sx0)*(sx1-sx0) + (sy1-sy0)*(sy1-sy0)
	if s == 0 {
		return 0
	}
	scale := d / s

	// Use 'negative' mipmap to render edges correctly (#611, #907).
	// It looks like 128 is the enlargement factor that causes edge missings to pass the test TestImageStretch.
	var tooBigScale float32 = 128
	if !graphicsDriver.HasHighPrecisionFloat() {
		tooBigScale = 4
	}

	if scale >= tooBigScale*tooBigScale {
		// If the filter is not nearest, the target needs to be rendered with graduation. Don't use mipmaps.
		if filter != driver.FilterNearest {
			return 0
		}

		const mipmapMaxSize = 1024
		w, h := sx1-sx0, sy1-sy0
		if w >= mipmapMaxSize || h >= mipmapMaxSize {
			return 0
		}

		level := 0
		for scale >= tooBigScale*tooBigScale {
			level--
			scale /= 4
			w *= 2
			h *= 2
			if w >= mipmapMaxSize || h >= mipmapMaxSize {
				break
			}
		}

		// If tooBigScale is 4, level -10 means that the maximum scale is 4 * 2^10 = 4096. This should be
		// enough.
		if level < -10 {
			level = -10
		}
		return level
	}

	if filter != driver.FilterLinear {
		return 0
	}

	level := 0
	for scale < 0.25 {
		level++
		scale *= 4
	}

	if level > 0 {
		// If the image can be scaled into 0 size, adjust the level. (#839)
		w, h := int(sx1-sx0), int(sy1-sy0)
		for level >= 0 {
			s := 1 << uint(level)
			if (w > 0 && w/s == 0) || (h > 0 && h/s == 0) {
				level--
				continue
			}
			break
		}

		if level < 0 {
			// As the render source is too small, nothing is rendered.
			return 0
		}
	}

	if level > 6 {
		level = 6
	}

	return level
}

func pow2(power int) float32 {
	if power >= 0 {
		x := 1
		return float32(x << uint(power))
	}

	x := float32(1)
	for i := 0; i < -power; i++ {
		x /= 2
	}
	return x
}

type Shader struct {
	shader *shareable.Shader
}

func NewShader(program *shaderir.Program) *Shader {
	return &Shader{
		shader: shareable.NewShader(program),
	}
}

func (s *Shader) MarkDisposed() {
	s.shader.MarkDisposed()
	s.shader = nil
}
<|endoftext|>