text:
package prune

import (
	"encoding/json"
	"flag"
	"fmt"
	"reflect"
	"testing"
	"time"

	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
	buildapi "github.com/projectatomic/atomic-enterprise/pkg/build/api"
	"github.com/projectatomic/atomic-enterprise/pkg/client/testclient"
	deployapi "github.com/projectatomic/atomic-enterprise/pkg/deploy/api"
	imageapi "github.com/projectatomic/atomic-enterprise/pkg/image/api"
)

func imageList(images ...imageapi.Image) imageapi.ImageList {
	return imageapi.ImageList{
		Items: images,
	}
}

func agedImage(id, ref string, ageInMinutes int64) imageapi.Image {
	image := imageWithLayers(id, ref,
		"tarsum.dev+sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
		"tarsum.dev+sha256:b194de3772ebbcdc8f244f663669799ac1cb141834b7cb8b69100285d357a2b0",
		"tarsum.dev+sha256:c937c4bb1c1a21cc6d94340812262c6472092028972ae69b551b1a70d4276171",
		"tarsum.dev+sha256:2aaacc362ac6be2b9e9ae8c6029f6f616bb50aec63746521858e47841b90fabd",
		"tarsum.dev+sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
	)

	if ageInMinutes >= 0 {
		image.CreationTimestamp = util.NewTime(util.Now().Add(time.Duration(-1*ageInMinutes) * time.Minute))
	}

	return image
}

func image(id, ref string) imageapi.Image {
	return agedImage(id, ref, -1)
}

func imageWithLayers(id, ref string, layers ...string) imageapi.Image {
	image := imageapi.Image{
		ObjectMeta: kapi.ObjectMeta{
			Name: id,
			Annotations: map[string]string{
				imageapi.ManagedByOpenShiftAnnotation: "true",
			},
		},
		DockerImageReference: ref,
	}

	manifest := imageapi.DockerImageManifest{
		FSLayers: []imageapi.DockerFSLayer{},
	}

	for _, layer := range layers {
		manifest.FSLayers = append(manifest.FSLayers, imageapi.DockerFSLayer{DockerBlobSum: layer})
	}

	manifestBytes, err := json.Marshal(&manifest)
	if err != nil {
		panic(err)
	}

	image.DockerImageManifest = string(manifestBytes)

	return image
}

func unmanagedImage(id, ref string, hasAnnotations bool, annotation, value string) imageapi.Image {
	image := imageWithLayers(id, ref)
	if !hasAnnotations {
		image.Annotations = nil
	} else {
		delete(image.Annotations, imageapi.ManagedByOpenShiftAnnotation)
		image.Annotations[annotation] = value
	}
	return image
}

func imageWithBadManifest(id, ref string) imageapi.Image {
	image := image(id, ref)
	image.DockerImageManifest = "asdf"
	return image
}

func podList(pods ...kapi.Pod) kapi.PodList {
	return kapi.PodList{
		Items: pods,
	}
}

func pod(namespace, name string, phase kapi.PodPhase, containerImages ...string) kapi.Pod {
	return agedPod(namespace, name, phase, -1, containerImages...)
}

func agedPod(namespace, name string, phase kapi.PodPhase, ageInMinutes int64, containerImages ...string) kapi.Pod {
	pod := kapi.Pod{
		ObjectMeta: kapi.ObjectMeta{
			Namespace: namespace,
			Name:      name,
		},
		Spec: podSpec(containerImages...),
		Status: kapi.PodStatus{
			Phase: phase,
		},
	}

	if ageInMinutes >= 0 {
		pod.CreationTimestamp = util.NewTime(util.Now().Add(time.Duration(-1*ageInMinutes) * time.Minute))
	}

	return pod
}

func podSpec(containerImages ...string) kapi.PodSpec {
	spec := kapi.PodSpec{
		Containers: []kapi.Container{},
	}
	for _, image := range containerImages {
		container := kapi.Container{
			Image: image,
		}
		spec.Containers = append(spec.Containers, container)
	}
	return spec
}

func streamList(streams ...imageapi.ImageStream) imageapi.ImageStreamList {
	return imageapi.ImageStreamList{
		Items: streams,
	}
}

func stream(registry, namespace, name string, tags map[string]imageapi.TagEventList) imageapi.ImageStream {
	return agedStream(registry, namespace, name, -1, tags)
}

func agedStream(registry, namespace, name string, ageInMinutes int64, tags map[string]imageapi.TagEventList) imageapi.ImageStream {
	stream := imageapi.ImageStream{
		ObjectMeta: kapi.ObjectMeta{
			Namespace: namespace,
			Name:      name,
		},
		Status: imageapi.ImageStreamStatus{
			DockerImageRepository: fmt.Sprintf("%s/%s/%s", registry, namespace, name),
			Tags: tags,
		},
	}

	if ageInMinutes >= 0 {
		stream.CreationTimestamp = util.NewTime(util.Now().Add(time.Duration(-1*ageInMinutes) * time.Minute))
	}

	return stream
}

func streamPtr(registry, namespace, name string, tags map[string]imageapi.TagEventList) *imageapi.ImageStream {
	s := stream(registry, namespace, name, tags)
	return &s
}

func tags(list ...namedTagEventList) map[string]imageapi.TagEventList {
	m := make(map[string]imageapi.TagEventList, len(list))
	for _, tag := range list {
		m[tag.name] = tag.events
	}
	return m
}

type namedTagEventList struct {
	name   string
	events imageapi.TagEventList
}

func tag(name string, events ...imageapi.TagEvent) namedTagEventList {
	return namedTagEventList{
		name: name,
		events: imageapi.TagEventList{
			Items: events,
		},
	}
}

func tagEvent(id, ref string) imageapi.TagEvent {
	return imageapi.TagEvent{
		Image:                id,
		DockerImageReference: ref,
	}
}

func rcList(rcs ...kapi.ReplicationController) kapi.ReplicationControllerList {
	return kapi.ReplicationControllerList{
		Items: rcs,
	}
}

func rc(namespace, name string, containerImages ...string) kapi.ReplicationController {
	return kapi.ReplicationController{
		ObjectMeta: kapi.ObjectMeta{
			Namespace: namespace,
			Name:      name,
		},
		Spec: kapi.ReplicationControllerSpec{
			Template: &kapi.PodTemplateSpec{
				Spec: podSpec(containerImages...),
			},
		},
	}
}

func dcList(dcs ...deployapi.DeploymentConfig) deployapi.DeploymentConfigList {
	return deployapi.DeploymentConfigList{
		Items: dcs,
	}
}

func dc(namespace, name string, containerImages ...string) deployapi.DeploymentConfig {
	return deployapi.DeploymentConfig{
		ObjectMeta: kapi.ObjectMeta{
			Namespace: namespace,
			Name:      name,
		},
		Template: deployapi.DeploymentTemplate{
			ControllerTemplate: kapi.ReplicationControllerSpec{
				Template: &kapi.PodTemplateSpec{
					Spec: podSpec(containerImages...),
				},
			},
		},
	}
}

func bcList(bcs ...buildapi.BuildConfig) buildapi.BuildConfigList {
	return buildapi.BuildConfigList{
		Items: bcs,
	}
}

func bc(namespace, name string, strategyType buildapi.BuildStrategyType, fromKind, fromNamespace, fromName string) buildapi.BuildConfig {
	return buildapi.BuildConfig{
		ObjectMeta: kapi.ObjectMeta{
			Namespace: namespace,
			Name:      name,
		},
		Parameters: buildParameters(strategyType, fromKind, fromNamespace, fromName),
	}
}

func buildList(builds ...buildapi.Build) buildapi.BuildList {
	return buildapi.BuildList{
		Items: builds,
	}
}

func build(namespace, name string, strategyType buildapi.BuildStrategyType, fromKind, fromNamespace, fromName string) buildapi.Build {
	return buildapi.Build{
		ObjectMeta: kapi.ObjectMeta{
			Namespace: namespace,
			Name:      name,
		},
		Parameters: buildParameters(strategyType, fromKind, fromNamespace, fromName),
	}
}

func buildParameters(strategyType buildapi.BuildStrategyType, fromKind, fromNamespace, fromName string) buildapi.BuildParameters {
	params := buildapi.BuildParameters{
		Strategy: buildapi.BuildStrategy{
			Type: strategyType,
		},
	}
	switch strategyType {
	case buildapi.SourceBuildStrategyType:
		params.Strategy.SourceStrategy = &buildapi.SourceBuildStrategy{
			From: &kapi.ObjectReference{
				Kind:      fromKind,
				Namespace: fromNamespace,
				Name:      fromName,
			},
		}
	case buildapi.DockerBuildStrategyType:
		params.Strategy.DockerStrategy = &buildapi.DockerBuildStrategy{
			From: &kapi.ObjectReference{
				Kind:      fromKind,
				Namespace: fromNamespace,
				Name:      fromName,
			},
		}
	case buildapi.CustomBuildStrategyType:
		params.Strategy.CustomStrategy = &buildapi.CustomBuildStrategy{
			From: &kapi.ObjectReference{
				Kind:      fromKind,
				Namespace: fromNamespace,
				Name:      fromName,
			},
		}
	}

	return params
}

var logLevel = flag.Int("loglevel", 0, "")
var testCase = flag.String("testcase", "", "")

func TestImagePruning(t *testing.T) {
	flag.Lookup("v").Value.Set(fmt.Sprint(*logLevel))
	registryURL := "registry"

	tests := map[string]struct {
		registryURLs           []string
		images                 imageapi.ImageList
		pods                   kapi.PodList
		streams                imageapi.ImageStreamList
		rcs                    kapi.ReplicationControllerList
		bcs                    buildapi.BuildConfigList
		builds                 buildapi.BuildList
		dcs                    deployapi.DeploymentConfigList
		expectedDeletions      []string
		expectedUpdatedStreams []string
	}{
		"1 pod - phase pending - don't prune": {
			images:            imageList(image("id", registryURL+"/foo/bar@id")),
			pods:              podList(pod("foo", "pod1", kapi.PodPending, registryURL+"/foo/bar@id")),
			expectedDeletions: []string{},
		},
		"3 pods - last phase pending - don't prune": {
			images: imageList(image("id", registryURL+"/foo/bar@id")),
			pods: podList(
				pod("foo", "pod1", kapi.PodSucceeded, registryURL+"/foo/bar@id"),
				pod("foo", "pod2", kapi.PodSucceeded, registryURL+"/foo/bar@id"),
				pod("foo", "pod3", kapi.PodPending, registryURL+"/foo/bar@id"),
			),
			expectedDeletions: []string{},
		},
		"1 pod - phase running - don't prune": {
			images:            imageList(image("id", registryURL+"/foo/bar@id")),
			pods:              podList(pod("foo", "pod1", kapi.PodRunning, registryURL+"/foo/bar@id")),
			expectedDeletions: []string{},
		},
		"3 pods - last phase running - don't prune": {
			images: imageList(image("id", registryURL+"/foo/bar@id")),
			pods: podList(
				pod("foo", "pod1", kapi.PodSucceeded, registryURL+"/foo/bar@id"),
				pod("foo", "pod2", kapi.PodSucceeded, registryURL+"/foo/bar@id"),
				pod("foo", "pod3", kapi.PodRunning, registryURL+"/foo/bar@id"),
			),
			expectedDeletions: []string{},
		},
		"pod phase succeeded - prune": {
			images:            imageList(image("id", registryURL+"/foo/bar@id")),
			pods:              podList(pod("foo", "pod1", kapi.PodSucceeded, registryURL+"/foo/bar@id")),
			expectedDeletions: []string{"id"},
		},
		"pod phase succeeded, pod less than min pruning age - don't prune": {
			images:            imageList(image("id", registryURL+"/foo/bar@id")),
			pods:              podList(agedPod("foo", "pod1", kapi.PodSucceeded, 5, registryURL+"/foo/bar@id")),
			expectedDeletions: []string{},
		},
		"pod phase succeeded, image less than min pruning age - don't prune": {
			images:            imageList(agedImage("id", registryURL+"/foo/bar@id", 5)),
			pods:              podList(pod("foo", "pod1", kapi.PodSucceeded, registryURL+"/foo/bar@id")),
			expectedDeletions: []string{},
		},
		"pod phase failed - prune": {
			images: imageList(image("id", registryURL+"/foo/bar@id")),
			pods: podList(
				pod("foo", "pod1", kapi.PodFailed, registryURL+"/foo/bar@id"),
				pod("foo", "pod2", kapi.PodFailed, registryURL+"/foo/bar@id"),
				pod("foo", "pod3", kapi.PodFailed, registryURL+"/foo/bar@id"),
			),
			expectedDeletions: []string{"id"},
		},
		"pod phase unknown - prune": {
			images: imageList(image("id", registryURL+"/foo/bar@id")),
			pods: podList(
				pod("foo", "pod1", kapi.PodUnknown, registryURL+"/foo/bar@id"),
				pod("foo", "pod2", kapi.PodUnknown, registryURL+"/foo/bar@id"),
				pod("foo", "pod3", kapi.PodUnknown, registryURL+"/foo/bar@id"),
			),
			expectedDeletions: []string{"id"},
		},
		"pod container image not parsable": {
			images: imageList(image("id", registryURL+"/foo/bar@id")),
			pods: podList(
				pod("foo", "pod1", kapi.PodRunning, "a/b/c/d/e"),
			),
			expectedDeletions: []string{"id"},
		},
		"pod container image doesn't have an id": {
			images: imageList(image("id", registryURL+"/foo/bar@id")),
			pods: podList(
				pod("foo", "pod1", kapi.PodRunning, "foo/bar:latest"),
			),
			expectedDeletions: []string{"id"},
		},
		"pod refers to image not in graph": {
			images: imageList(image("id", registryURL+"/foo/bar@id")),
			pods: podList(
				pod("foo", "pod1", kapi.PodRunning, registryURL+"/foo/bar@otherid"),
			),
			expectedDeletions: []string{"id"},
		},
		"referenced by rc - don't prune": {
			images:            imageList(image("id", registryURL+"/foo/bar@id")),
			rcs:               rcList(rc("foo", "rc1", registryURL+"/foo/bar@id")),
			expectedDeletions: []string{},
		},
		"referenced by dc - don't prune": {
			images:            imageList(image("id", registryURL+"/foo/bar@id")),
			dcs:               dcList(dc("foo", "rc1", registryURL+"/foo/bar@id")),
			expectedDeletions: []string{},
		},
		"referenced by bc - sti - ImageStreamImage - don't prune": {
			images:            imageList(image("id", registryURL+"/foo/bar@id")),
			bcs:               bcList(bc("foo", "bc1", buildapi.SourceBuildStrategyType, "ImageStreamImage", "foo", "bar@id")),
			expectedDeletions: []string{},
		},
		"referenced by bc - docker - ImageStreamImage - don't prune": {
			images:            imageList(image("id", registryURL+"/foo/bar@id")),
			bcs:               bcList(bc("foo", "bc1", buildapi.DockerBuildStrategyType, "ImageStreamImage", "foo", "bar@id")),
			expectedDeletions: []string{},
		},
		"referenced by bc - custom - ImageStreamImage - don't prune": {
			images:            imageList(image("id", registryURL+"/foo/bar@id")),
			bcs:               bcList(bc("foo", "bc1", buildapi.CustomBuildStrategyType, "ImageStreamImage", "foo", "bar@id")),
			expectedDeletions: []string{},
		},
		"referenced by bc - sti - DockerImage - don't prune": {
			images:            imageList(image("id", registryURL+"/foo/bar@id")),
			bcs:               bcList(bc("foo", "bc1", buildapi.SourceBuildStrategyType, "DockerImage", "foo", registryURL+"/foo/bar@id")),
			expectedDeletions: []string{},
		},
		"referenced by bc - docker - DockerImage - don't prune": {
			images:            imageList(image("id", registryURL+"/foo/bar@id")),
			bcs:               bcList(bc("foo", "bc1", buildapi.DockerBuildStrategyType, "DockerImage", "foo", registryURL+"/foo/bar@id")),
			expectedDeletions: []string{},
		},
		"referenced by bc - custom - DockerImage - don't prune": {
			images:            imageList(image("id", registryURL+"/foo/bar@id")),
			bcs:               bcList(bc("foo", "bc1", buildapi.CustomBuildStrategyType, "DockerImage", "foo", registryURL+"/foo/bar@id")),
			expectedDeletions: []string{},
		},
		"referenced by build - sti - ImageStreamImage - don't prune": {
			images:            imageList(image("id", registryURL+"/foo/bar@id")),
			builds:            buildList(build("foo", "build1", buildapi.SourceBuildStrategyType, "ImageStreamImage", "foo", "bar@id")),
			expectedDeletions: []string{},
		},
		"referenced by build - docker - ImageStreamImage - don't prune": {
			images:            imageList(image("id", registryURL+"/foo/bar@id")),
			builds:            buildList(build("foo", "build1", buildapi.DockerBuildStrategyType, "ImageStreamImage", "foo", "bar@id")),
			expectedDeletions: []string{},
		},
		"referenced by build - custom - ImageStreamImage - don't prune": {
			images:            imageList(image("id", registryURL+"/foo/bar@id")),
			builds:            buildList(build("foo", "build1", buildapi.CustomBuildStrategyType, "ImageStreamImage", "foo", "bar@id")),
			expectedDeletions: []string{},
		},
		"referenced by build - sti - DockerImage - don't prune": {
			images:            imageList(image("id", registryURL+"/foo/bar@id")),
			builds:            buildList(build("foo", "build1", buildapi.SourceBuildStrategyType, "DockerImage", "foo", registryURL+"/foo/bar@id")),
			expectedDeletions: []string{},
		},
		"referenced by build - docker - DockerImage - don't prune": {
			images:            imageList(image("id", registryURL+"/foo/bar@id")),
			builds:            buildList(build("foo", "build1", buildapi.DockerBuildStrategyType, "DockerImage", "foo", registryURL+"/foo/bar@id")),
			expectedDeletions: []string{},
		},
		"referenced by build - custom - DockerImage - don't prune": {
			images:            imageList(image("id", registryURL+"/foo/bar@id")),
			builds:            buildList(build("foo", "build1", buildapi.CustomBuildStrategyType, "DockerImage", "foo", registryURL+"/foo/bar@id")),
			expectedDeletions: []string{},
		},
		"image stream - keep most recent n images": {
			images: imageList(
				unmanagedImage("id", "otherregistry/foo/bar@id", false, "", ""),
				image("id2", registryURL+"/foo/bar@id2"),
				image("id3", registryURL+"/foo/bar@id3"),
				image("id4", registryURL+"/foo/bar@id4"),
			),
			streams: streamList(
				stream(registryURL, "foo", "bar", tags(
					tag("latest",
						tagEvent("id", "otherregistry/foo/bar@id"),
						tagEvent("id2", registryURL+"/foo/bar@id2"),
						tagEvent("id3", registryURL+"/foo/bar@id3"),
						tagEvent("id4", registryURL+"/foo/bar@id4"),
					),
				)),
			),
			expectedDeletions:      []string{"id4"},
			expectedUpdatedStreams: []string{"foo/bar"},
		},
		"image stream - same manifest listed multiple times in tag history": {
			images: imageList(
				image("id1", registryURL+"/foo/bar@id1"),
				image("id2", registryURL+"/foo/bar@id2"),
			),
			streams: streamList(
				stream(registryURL, "foo", "bar", tags(
					tag("latest",
						tagEvent("id1", registryURL+"/foo/bar@id1"),
						tagEvent("id2", registryURL+"/foo/bar@id2"),
						tagEvent("id1", registryURL+"/foo/bar@id1"),
						tagEvent("id2", registryURL+"/foo/bar@id2"),
					),
				)),
			),
		},
		"image stream age less than min pruning age - don't prune": {
			images: imageList(
				image("id", registryURL+"/foo/bar@id"),
				image("id2", registryURL+"/foo/bar@id2"),
				image("id3", registryURL+"/foo/bar@id3"),
				image("id4", registryURL+"/foo/bar@id4"),
			),
			streams: streamList(
				agedStream(registryURL, "foo", "bar", 5, tags(
					tag("latest",
						tagEvent("id", registryURL+"/foo/bar@id"),
						tagEvent("id2", registryURL+"/foo/bar@id2"),
						tagEvent("id3", registryURL+"/foo/bar@id3"),
						tagEvent("id4", registryURL+"/foo/bar@id4"),
					),
				)),
			),
			expectedDeletions:      []string{},
			expectedUpdatedStreams: []string{},
		},
		"multiple resources pointing to image - don't prune": {
			images: imageList(
				image("id", registryURL+"/foo/bar@id"),
				image("id2", registryURL+"/foo/bar@id2"),
			),
			streams: streamList(
				stream(registryURL, "foo", "bar", tags(
					tag("latest",
						tagEvent("id", registryURL+"/foo/bar@id"),
						tagEvent("id2", registryURL+"/foo/bar@id2"),
					),
				)),
			),
			rcs:                    rcList(rc("foo", "rc1", registryURL+"/foo/bar@id2")),
			pods:                   podList(pod("foo", "pod1", kapi.PodRunning, registryURL+"/foo/bar@id2")),
			dcs:                    dcList(dc("foo", "rc1", registryURL+"/foo/bar@id")),
			bcs:                    bcList(bc("foo", "bc1", buildapi.SourceBuildStrategyType, "DockerImage", "foo", registryURL+"/foo/bar@id")),
			builds:                 buildList(build("foo", "build1", buildapi.CustomBuildStrategyType, "ImageStreamImage", "foo", "bar@id")),
			expectedDeletions:      []string{},
			expectedUpdatedStreams: []string{},
		},
		"image with nil annotations": {
			images: imageList(
				unmanagedImage("id", "someregistry/foo/bar@id", false, "", ""),
			),
			expectedDeletions:      []string{},
			expectedUpdatedStreams: []string{},
		},
		"image missing managed annotation": {
			images: imageList(
				unmanagedImage("id", "someregistry/foo/bar@id", true, "foo", "bar"),
			),
			expectedDeletions:      []string{},
			expectedUpdatedStreams: []string{},
		},
		"image with managed annotation != true": {
			images: imageList(
				unmanagedImage("id", "someregistry/foo/bar@id", true, imageapi.ManagedByOpenShiftAnnotation, "false"),
				unmanagedImage("id", "someregistry/foo/bar@id", true, imageapi.ManagedByOpenShiftAnnotation, "0"),
				unmanagedImage("id", "someregistry/foo/bar@id", true, imageapi.ManagedByOpenShiftAnnotation, "1"),
				unmanagedImage("id", "someregistry/foo/bar@id", true, imageapi.ManagedByOpenShiftAnnotation, "True"),
				unmanagedImage("id", "someregistry/foo/bar@id", true, imageapi.ManagedByOpenShiftAnnotation, "yes"),
				unmanagedImage("id", "someregistry/foo/bar@id", true, imageapi.ManagedByOpenShiftAnnotation, "Yes"),
			),
			expectedDeletions:      []string{},
			expectedUpdatedStreams: []string{},
		},
		"image with bad manifest is pruned ok": {
			images: imageList(
				imageWithBadManifest("id", "someregistry/foo/bar@id"),
			),
			expectedDeletions:      []string{"id"},
			expectedUpdatedStreams: []string{},
		},
	}

	for name, test := range tests {
		tcFilter := flag.Lookup("testcase").Value.String()
		if len(tcFilter) > 0 && name != tcFilter {
			continue
		}
		p := NewImagePruner(60*time.Minute, 3, &test.images, &test.streams, &test.pods, &test.rcs, &test.bcs, &test.builds, &test.dcs)
		actualDeletions := util.NewStringSet()
		actualUpdatedStreams := util.NewStringSet()

		pruneImage := func(image *imageapi.Image) error {
			actualDeletions.Insert(image.Name)
			return nil
		}

		pruneStream := func(stream *imageapi.ImageStream, image *imageapi.Image) (*imageapi.ImageStream, error) {
			actualUpdatedStreams.Insert(fmt.Sprintf("%s/%s", stream.Namespace, stream.Name))
			return stream, nil
		}

		pruneLayer := func(registryURL, repo, layer string) error {
			return nil
		}

		pruneBlob := func(registryURL, blob string) error {
			return nil
		}

		pruneManifest := func(registryURL, repo, manifest string) error {
			return nil
		}

		p.Run(pruneImage, pruneStream, pruneLayer, pruneBlob, pruneManifest)

		expectedDeletions := util.NewStringSet(test.expectedDeletions...)
		if !reflect.DeepEqual(expectedDeletions, actualDeletions) {
			t.Errorf("%s: expected image deletions %q, got %q", name, expectedDeletions.List(), actualDeletions.List())
		}

		expectedUpdatedStreams := util.NewStringSet(test.expectedUpdatedStreams...)
		if !reflect.DeepEqual(expectedUpdatedStreams, actualUpdatedStreams) {
			t.Errorf("%s: expected stream updates %q, got %q", name, expectedUpdatedStreams.List(), actualUpdatedStreams.List())
		}
	}
}

func TestDeletingImagePruneFunc(t *testing.T) {
	flag.Lookup("v").Value.Set(fmt.Sprint(*logLevel))

	tests := map[string]struct {
		imageDeletionError error
	}{
		"no error": {},
		"delete error": {
			imageDeletionError: fmt.Errorf("foo"),
		},
	}

	for name, test := range tests {
		imageClient := testclient.Fake{
			Err: test.imageDeletionError,
		}
		pruneFunc := DeletingImagePruneFunc(imageClient.Images())
		err := pruneFunc(&imageapi.Image{ObjectMeta: kapi.ObjectMeta{Name: "id2"}})
		if test.imageDeletionError != nil {
			if e, a := fmt.Sprintf("error deleting image: %v", test.imageDeletionError), err.Error(); e != a {
				t.Errorf("%s: err: expected %v, got %v", name, e, a)
			}
			continue
		}

		if e, a := 1, len(imageClient.Actions); e != a {
			t.Errorf("%s: expected %d actions, got %d: %#v", name, e, a, imageClient.Actions)
			continue
		}

		if e, a := "delete-image", imageClient.Actions[0].Action; e != a {
			t.Errorf("%s: expected action %q, got %q", name, e, a)
		}
	}
}

func TestRegistryPruning(t *testing.T) {
	flag.Lookup("v").Value.Set(fmt.Sprint(*logLevel))

	tests := map[string]struct {
		images                    imageapi.ImageList
		streams                   imageapi.ImageStreamList
		expectedLayerDeletions    util.StringSet
		expectedBlobDeletions     util.StringSet
		expectedManifestDeletions util.StringSet
	}{
		"layers unique to id1 pruned": {
			images: imageList(
				imageWithLayers("id1", "registry1/foo/bar@id1", "layer1", "layer2", "layer3", "layer4"),
				imageWithLayers("id2", "registry1/foo/bar@id2", "layer3", "layer4", "layer5", "layer6"),
			),
			streams: streamList(
				stream("registry1", "foo", "bar", tags(
					tag("latest",
						tagEvent("id2", "registry1/foo/bar@id2"),
						tagEvent("id1", "registry1/foo/bar@id1"),
					),
				)),
				stream("registry1", "foo", "other", tags(
					tag("latest",
						tagEvent("id2", "registry1/foo/other@id2"),
					),
				)),
				stream("registry2", "foo", "bar", tags(
					tag("latest",
						tagEvent("id2", "registry2/foo/bar@id2"),
						tagEvent("id1", "registry2/foo/bar@id1"),
					),
				)),
				stream("registry2", "foo", "other", tags(
					tag("latest",
						tagEvent("id2", "registry2/foo/other@id2"),
					),
				)),
			),
			expectedLayerDeletions: util.NewStringSet(
				"registry1|foo/bar|layer1",
				"registry1|foo/bar|layer2",
				"registry2|foo/bar|layer1",
				"registry2|foo/bar|layer2",
			),
			expectedBlobDeletions: util.NewStringSet(
				"registry1|layer1",
				"registry1|layer2",
				"registry2|layer1",
				"registry2|layer2",
			),
			expectedManifestDeletions: util.NewStringSet(
				"registry1|foo/bar|id1",
				"registry2|foo/bar|id1",
			),
		},
		"no pruning when no images are pruned": {
			images: imageList(
				imageWithLayers("id1", "registry1/foo/bar@id1", "layer1", "layer2", "layer3", "layer4"),
			),
			streams: streamList(
				stream("registry1", "foo", "bar", tags(
					tag("latest",
						tagEvent("id1", "registry1/foo/bar@id1"),
					),
				)),
			),
			expectedLayerDeletions:    util.NewStringSet(),
			expectedBlobDeletions:     util.NewStringSet(),
			expectedManifestDeletions: util.NewStringSet(),
		},
	}

	for name, test := range tests {
		t.Logf("Running test case %s", name)
		actualLayerDeletions := util.NewStringSet()
		actualBlobDeletions := util.NewStringSet()
		actualManifestDeletions := util.NewStringSet()

		pruneImage := func(image *imageapi.Image) error {
			return nil
		}

		pruneStream := func(stream *imageapi.ImageStream, image *imageapi.Image) (*imageapi.ImageStream, error) {
			return stream, nil
		}

		pruneLayer := func(registryURL, repo, layer string) error {
			actualLayerDeletions.Insert(fmt.Sprintf("%s|%s|%s", registryURL, repo, layer))
			return nil
		}

		pruneBlob := func(registryURL, blob string) error {
			actualBlobDeletions.Insert(fmt.Sprintf("%s|%s", registryURL, blob))
			return nil
		}

		pruneManifest := func(registryURL, repo, manifest string) error {
			actualManifestDeletions.Insert(fmt.Sprintf("%s|%s|%s", registryURL, repo, manifest))
			return nil
		}

		p := NewImagePruner(60, 1, &test.images, &test.streams, &kapi.PodList{}, &kapi.ReplicationControllerList{}, &buildapi.BuildConfigList{}, &buildapi.BuildList{}, &deployapi.DeploymentConfigList{})

		p.Run(pruneImage, pruneStream, pruneLayer, pruneBlob, pruneManifest)

		if !reflect.DeepEqual(test.expectedLayerDeletions, actualLayerDeletions) {
			t.Errorf("%s: expected layer deletions %#v, got %#v", name, test.expectedLayerDeletions, actualLayerDeletions)
		}
		if !reflect.DeepEqual(test.expectedBlobDeletions, actualBlobDeletions) {
			t.Errorf("%s: expected blob deletions %#v, got %#v", name, test.expectedBlobDeletions, actualBlobDeletions)
		}
		if !reflect.DeepEqual(test.expectedManifestDeletions, actualManifestDeletions) {
			t.Errorf("%s: expected manifest deletions %#v, got %#v", name, test.expectedManifestDeletions, actualManifestDeletions)
		}
	}
}


text:
package blog.bn;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import blog.common.DefaultDGraph;
import blog.common.DefaultDGraph.NodeInfo;

/**
 * This class provides a default implementation of CBNs. Over the next few weeks,
 * all inference algorithms will be modified to use CBNs.
 */

public class DefaultCBN extends DefaultDGraph implements CBN {
	/**
	 * Uses a DefaultCBN rather than a DefaultDGraph, so as to avoid ClassCastExceptions
	 */
	public Object clone() {
		DefaultCBN clone = new DefaultCBN();
		clone.nodeInfo = (Map) ((HashMap) nodeInfo).clone();
		for (Iterator iter = clone.nodeInfo.entrySet().iterator(); iter.hasNext();) {
			Map.Entry entry = (Map.Entry) iter.next();
			entry.setValue(((NodeInfo) entry.getValue()).clone());
		}
		return clone;
	}
}

redpajama_set_name: RedPajamaGithub


text:
package org.schedoscope.scheduler.rest.client

import akka.actor.ActorSystem
import akka.event.Logging
import akka.util.Timeout
import org.schedoscope.scheduler.rest.SchedoscopeJsonDataFormat
import org.schedoscope.scheduler.service.{ QueueStatusList, SchedoscopeCommandStatus, SchedoscopeService, TransformationStatusList, ViewStatusList }
import spray.client.pipelining.{ Get, WithTransformerConcatenation, sendReceive, unmarshal }
import spray.http.Uri
import spray.httpx.SprayJsonSupport.sprayJsonUnmarshaller

import scala.collection.immutable.Map
import scala.concurrent.{ Await, Future }
import scala.concurrent.duration.DurationInt
import scala.language.postfixOps

/**
 * Implementation of the schedoscope service that maps the given scheduling commands
 * to REST web service calls and parse the returned results.
 */
class SchedoscopeServiceRestClientImpl(val host: String, val port: Int) extends SchedoscopeService {

  import SchedoscopeJsonDataFormat._

  implicit val system = ActorSystem("schedoscope-rest-client")

  import system.dispatcher

  // execution context for futures below
  implicit val timeout = Timeout(10.days)
  val log = Logging(system, getClass)

  def get[T](path: String, query: Map[String, String]): Future[T] = {
    val pipeline = path match {
      case u: String if u.startsWith("/views") => sendReceive ~> unmarshal[ViewStatusList]
      case u: String if u.startsWith("/transformations") => sendReceive ~> unmarshal[TransformationStatusList]
      case u: String if u.startsWith("/queues") => sendReceive ~> unmarshal[QueueStatusList]
      case u: String if u.startsWith("/materialize") => sendReceive ~> unmarshal[SchedoscopeCommandStatus]
      case u: String if u.startsWith("/invalidate") => sendReceive ~> unmarshal[SchedoscopeCommandStatus]
      case u: String if u.startsWith("/newdata") => sendReceive ~> unmarshal[SchedoscopeCommandStatus]
      case u: String if u.startsWith("/commands") => sendReceive ~> unmarshal[List[SchedoscopeCommandStatus]]
      case _ => throw new RuntimeException("Unsupported query path: " + path)
    }
    val uri = Uri.from("http", "", host, port, path) withQuery (query)
    println("Calling Schedoscope API URL: " + uri)
    pipeline(Get(uri)).asInstanceOf[Future[T]]
  }

  private def paramsFrom(params: (String, Option[Any])*): Map[String, String] = {
    params.filter(_._2.isDefined)
      .map(p => (p._1 -> p._2.get.toString))
      .toMap
  }

  def shutdown(): Boolean = {
    system.shutdown()
    system.isTerminated
  }

  def materialize(viewUrlPath: Option[String], status: Option[String], filter: Option[String], mode: Option[String]): SchedoscopeCommandStatus = {
    Await.result(get[SchedoscopeCommandStatus](s"/materialize/${viewUrlPath.getOrElse("")}", paramsFrom(("status", status), ("filter", filter), ("mode", mode))), 10.days)
  }

  def invalidate(viewUrlPath: Option[String], status: Option[String], filter: Option[String], dependencies: Option[Boolean]): SchedoscopeCommandStatus = {
    Await.result(get[SchedoscopeCommandStatus](s"/invalidate/${viewUrlPath.getOrElse("")}", paramsFrom(("status", status), ("filter", filter), ("dependencies", dependencies))), 3600 seconds)
  }

  def newdata(viewUrlPath: Option[String], status: Option[String], filter: Option[String]): SchedoscopeCommandStatus = {
    Await.result(get[SchedoscopeCommandStatus](s"/newdata/${viewUrlPath.getOrElse("")}", paramsFrom(("status", status), ("filter", filter))), 3600 seconds)
  }

  def commandStatus(commandId: String): SchedoscopeCommandStatus = {
    null
  } // TODO

  def commands(status: Option[String], filter: Option[String]): List[SchedoscopeCommandStatus] = {
    Await.result(get[List[SchedoscopeCommandStatus]](s"/commands", paramsFrom(("status", status), ("filter", filter))), 3600 seconds)
  }

  def views(viewUrlPath: Option[String], status: Option[String], filter: Option[String], dependencies: Option[Boolean], overview: Option[Boolean]): ViewStatusList = {
    Await.result(get[ViewStatusList](s"/views/${viewUrlPath.getOrElse("")}", paramsFrom(("status", status), ("filter", filter), ("dependencies", dependencies), ("overview", overview))), 3600 seconds)
  }

  def transformations(status: Option[String], filter: Option[String]): TransformationStatusList = {
    Await.result(get[TransformationStatusList](s"/transformations", paramsFrom(("status", status), ("filter", filter))), 3600 seconds)
  }

  def queues(typ: Option[String], filter: Option[String]): QueueStatusList = {
    Await.result(get[QueueStatusList](s"/queues", paramsFrom(("typ", typ), ("filter", filter))), 3600 seconds)
  }
}


text:
* **deps:** Allow protobuf 3.19.5 ([#139](https://github.com/googleapis/python-bigquery-migration/issues/139)) ([5258fa6](https://github.com/googleapis/python-bigquery-migration/commit/5258fa6112f7f47ec0b3351f45dd90f18eaa5181))

## [0.7.1](https://github.com/googleapis/python-bigquery-migration/compare/v0.7.0...v0.7.1) (2022-09-29)

### Bug Fixes

* **deps:** Require protobuf >= 3.20.2 ([#136](https://github.com/googleapis/python-bigquery-migration/issues/136)) ([f69ed02](https://github.com/googleapis/python-bigquery-migration/commit/f69ed025709bf30d4dca51d09a9fd9b3a3d964cd))

## [0.7.0](https://github.com/googleapis/python-bigquery-migration/compare/v0.6.0...v0.7.0) (2022-08-11)

### Features

* **v2:** Add MySQL dialect ([#116](https://github.com/googleapis/python-bigquery-migration/issues/116)) ([9f66ca4](https://github.com/googleapis/python-bigquery-migration/commit/9f66ca4c1e8b1438a8f11919c24fb6506a5790ea))

### Bug Fixes

* **deps:** allow protobuf < 5.0.0 ([#118](https://github.com/googleapis/python-bigquery-migration/issues/118)) ([b217543](https://github.com/googleapis/python-bigquery-migration/commit/b2175431fd5b2391f9490a5900977c5768d6712a))
* **deps:** require proto-plus >= 1.22.0 ([b217543](https://github.com/googleapis/python-bigquery-migration/commit/b2175431fd5b2391f9490a5900977c5768d6712a))

## [0.6.0](https://github.com/googleapis/python-bigquery-migration/compare/v0.5.0...v0.6.0) (2022-07-16)

### Features

* add audience parameter ([77e4c52](https://github.com/googleapis/python-bigquery-migration/commit/77e4c52d597e62956e2349dfff2f1b88df013450))
* Add Presto and Postgresql dialects ([#103](https://github.com/googleapis/python-bigquery-migration/issues/103)) ([77e4c52](https://github.com/googleapis/python-bigquery-migration/commit/77e4c52d597e62956e2349dfff2f1b88df013450))

### Bug Fixes

* **deps:** require google-api-core>=1.32.0,>=2.8.0 ([77e4c52](https://github.com/googleapis/python-bigquery-migration/commit/77e4c52d597e62956e2349dfff2f1b88df013450))
* require python 3.7+ ([#105](https://github.com/googleapis/python-bigquery-migration/issues/105)) ([13f50a7](https://github.com/googleapis/python-bigquery-migration/commit/13f50a7b845f5d4ce0aa03821578b730ea37dbb2))

## [0.5.0](https://github.com/googleapis/python-bigquery-migration/compare/v0.4.3...v0.5.0) (2022-06-15)

### Features

* Add SQL Server dialect to bigquerymigration v2 client library ([#99](https://github.com/googleapis/python-bigquery-migration/issues/99)) ([35a1099](https://github.com/googleapis/python-bigquery-migration/commit/35a10990d6d9019511d9c1813f4f6d5889004189))

## [0.4.3](https://github.com/googleapis/python-bigquery-migration/compare/v0.4.2...v0.4.3) (2022-06-03)

### Bug Fixes

* **deps:** require protobuf <4.0.0dev ([#95](https://github.com/googleapis/python-bigquery-migration/issues/95)) ([0b93e8d](https://github.com/googleapis/python-bigquery-migration/commit/0b93e8d0332269cf837d146106d8d5fbdcd42cc4))

### Documentation

* fix changelog header to consistent size ([#96](https://github.com/googleapis/python-bigquery-migration/issues/96)) ([e95f0fc](https://github.com/googleapis/python-bigquery-migration/commit/e95f0fcad0f6c3366ade1637b51295a89d2bc1b2))

## [0.4.2](https://github.com/googleapis/python-bigquery-migration/compare/v0.4.1...v0.4.2) (2022-05-22)

### Documentation

* **samples:** add create_migration_workflow snippet ([#71](https://github.com/googleapis/python-bigquery-migration/issues/71)) ([761b5f5](https://github.com/googleapis/python-bigquery-migration/commit/761b5f5045edbe8c81a31f501bf3b14de7dffe20))

## [0.4.1](https://github.com/googleapis/python-bigquery-migration/compare/v0.4.0...v0.4.1) (2022-05-05)

### Documentation

* fix type in docstring for map fields ([6c5982b](https://github.com/googleapis/python-bigquery-migration/commit/6c5982bf2fcc0d90d6a6951a7dd676e9b7974627))

## [0.4.0](https://github.com/googleapis/python-bigquery-migration/compare/v0.3.1...v0.4.0) (2022-04-03)

### Features

* Add bigquery_migration v2 client library  ([#54](https://github.com/googleapis/python-bigquery-migration/issues/54)) ([776ea61](https://github.com/googleapis/python-bigquery-migration/commit/776ea6189f6a94c5daa5af6b4fa7e0e3b21015ec))

### Bug Fixes

* set bigquery_migration_v2 as the default import ([776ea61](https://github.com/googleapis/python-bigquery-migration/commit/776ea6189f6a94c5daa5af6b4fa7e0e3b21015ec))

## [0.3.1](https://github.com/googleapis/python-bigquery-migration/compare/v0.3.0...v0.3.1) (2022-03-05)

### Bug Fixes

* **deps:** require google-api-core>=1.31.5, >=2.3.2 ([#48](https://github.com/googleapis/python-bigquery-migration/issues/48)) ([2d8b0b5](https://github.com/googleapis/python-bigquery-migration/commit/2d8b0b5845573d2784b6bfa925285c6ddac5c1e7))

## [0.3.0](https://github.com/googleapis/python-bigquery-migration/compare/v0.2.1...v0.3.0) (2022-02-14)

### Features

* add api key support ([#34](https://github.com/googleapis/python-bigquery-migration/issues/34)) ([124de81](https://github.com/googleapis/python-bigquery-migration/commit/124de81e97b39694433820678704b3f6079ce1e2))
* Add task details and orchestration result details ([#32](https://github.com/googleapis/python-bigquery-migration/issues/32)) ([44c10e1](https://github.com/googleapis/python-bigquery-migration/commit/44c10e17767135b7a5c9a5e22b82260be75459b1))

### Bug Fixes

* **deps:** remove unused dependency libcst ([#39](https://github.com/googleapis/python-bigquery-migration/issues/39)) ([496abc7](https://github.com/googleapis/python-bigquery-migration/commit/496abc7854985c6f1bfd8463330f2f07a0f3048c))
* resolve DuplicateCredentialArgs error when using credentials_file ([b3b1ee2](https://github.com/googleapis/python-bigquery-migration/commit/b3b1ee2c0075adadedeef28a5853a440fc1e6535))

### Documentation

* add generated snippets ([#38](https://github.com/googleapis/python-bigquery-migration/issues/38)) ([13b7ac7](https://github.com/googleapis/python-bigquery-migration/commit/13b7ac71ace1cc226d6fa5b43dde345c3ac3e489))

## [0.2.1](https://www.github.com/googleapis/python-bigquery-migration/compare/v0.2.0...v0.2.1) (2021-11-01)

### Bug Fixes

* **deps:** drop packaging dependency ([70ec0c5](https://www.github.com/googleapis/python-bigquery-migration/commit/70ec0c5da7cf18ed632bfb19c5f2d6bfb8d2334a))
* **deps:** require google-api-core >= 1.28.0 ([70ec0c5](https://www.github.com/googleapis/python-bigquery-migration/commit/70ec0c5da7cf18ed632bfb19c5f2d6bfb8d2334a))

### Documentation

* list oneofs in docstring ([70ec0c5](https://www.github.com/googleapis/python-bigquery-migration/commit/70ec0c5da7cf18ed632bfb19c5f2d6bfb8d2334a))

## [0.2.0](https://www.github.com/googleapis/python-bigquery-migration/compare/v0.1.0...v0.2.0) (2021-10-08)

### Features

* add context manager support in client ([#8](https://www.github.com/googleapis/python-bigquery-migration/issues/8)) ([d9dbb32](https://www.github.com/googleapis/python-bigquery-migration/commit/d9dbb32edeffee1e559f979300713a4a10cd9806))

## 0.1.0 (2021-09-30)

### Features

* generate v2alpha ([ccb7ca8](https://www.github.com/googleapis/python-bigquery-migration/commit/ccb7ca8473252da1eeab1ba4338c65a5ff0e6e8e))

### Bug Fixes

* address testing failures from initial generation ([#1](https://www.github.com/googleapis/python-bigquery-migration/issues/1)) ([85284a7](https://www.github.com/googleapis/python-bigquery-migration/commit/85284a7f1f7ed39cd2de61ecae5ed40656283533))
* correct python namespace for migration API ([#3](https://www.github.com/googleapis/python-bigquery-migration/issues/3)) ([3dda870](https://www.github.com/googleapis/python-bigquery-migration/commit/3dda8702d54ee39897322215c2a551921356ae61))


text:
/**
 * 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
 *
 *      https://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.
 */
import React from 'react'
import PropTypes from 'prop-types'
import AceEditor from 'react-ace'

import autocomplete from './ace/autocomplete.js'
import { logger } from '../../../helpers/logger'

import 'ace-builds/src-noconflict/ext-language_tools'
import 'ace-builds/src-noconflict/ext-searchbox'
import 'ace-builds/src-noconflict/theme-xcode'
import 'ace-builds/src-noconflict/theme-merbivore'

import vim from './ace/vim'
import './ace/mnky'

class CodeEditor extends React.Component {
  constructor(props) {
    super(props)
    this.bound = false
    vim(() => {
      props.onVimWrite()
    })
    // Make sure that an error in auto completion does not break the whole editor.
    try {
      autocomplete(props.getRepository, props.variables)
    } catch (e) {
      logger('error', e).write()
    }
  }

  static propTypes = {
    value: PropTypes.string.isRequired,
    getRepository: PropTypes.func.isRequired,
    onChange: PropTypes.func.isRequired,
    onAutoSave: PropTypes.func.isRequired,
    onVimWrite: PropTypes.func.isRequired,
    readOnly: PropTypes.bool,
    editorAutocomplete: PropTypes.bool.isRequired,
    annotations: PropTypes.func.isRequired,
    keyboardHandler: PropTypes.string,
    isDarkMode: PropTypes.bool.isRequired,
    variables: PropTypes.array.isRequired
  }

  autoSave() {
    this.props.onAutoSave()
  }

  handleChange(content, event) {
    if (event) {
      event.preventDefault()
    }
    this.props.onChange(content)
    // annotations={annotations} on the AceEditor
    // does not work properly (probably due my misunderstanding of react;) )
    // the following works and also adds some soft delay on the annotation update
    setTimeout(() => this._updateAnnotations(), 150)
  }

  _updateAnnotations() {
    if (this.editor) {
      this.editor.session.setAnnotations(this.props.annotations(this.editor.getValue()))
    }
  }

  render() {
    return (
      <div className="editor-box">
        <AceEditor
          value={this.props.value}
          onChange={(content) => this.handleChange(content)}
          onLoad={(editor) => {
            this.editor = editor
            window.editor = editor
            this._updateAnnotations()
          }}
          width="100%"
          height="calc(100% - 40px)"
          theme={this.props.isDarkMode ? 'merbivore' : 'xcode'}
          mode="mnky"
          readOnly={this.props.readOnly === true}
          className={this.props.readOnly === true ? 'disabled' : ''}
          enableBasicAutocompletion={true}
          enableLiveAutocompletion={this.props.editorAutocomplete}
          enableSnippets={false}
          keyboardHandler={this.props.keyboardHandler}
          editorProps={{ $blockScrolling: 'Infinity' }}
          name="contentarea"
          commands={[
            {
              name: 'Toggle Comment',
              bindKey: { win: 'Ctrl-Shift-7', mac: 'Cmd-Shift-7' },
              exec: 'togglecomment'
            },
            {
              name: 'Save On Enter',
              bindKey: { win: 'Enter', mac: 'Enter' },
              exec: (editor) => {
                editor.insert('\n')
                this.autoSave()
              }
            }
          ]}
        />
      </div>
    )
  }
}
export default CodeEditor