<commit_before>package oci

import "testing"

func TestDockerSystemInfo(t *testing.T) {
	testCases := []struct {
		Name        string // test case bane
		OciBin      string // Docker or Podman
		RawJSON     string // raw response from json
		ShouldError bool
		CPUs        int
		Memory      int64
		OS          string
	}{
		{
			Name:        "linux_docker",
			OciBin:      "docker",
			RawJSON:     `{"ID":"7PYP:53DU:MLWX:EDQG:YG2Y:UJLB:J7SD:4SAI:XF2Y:N2MR:MU53:DR3N","Containers":3,"ContainersRunning":1,"ContainersPaused":0,"ContainersStopped":2,"Images":76,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Native Overlay Diff","true"]],"SystemStatus":null,"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","local","logentries","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":false,"KernelMemory":true,"KernelMemoryTCP":false,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"BridgeNfIptables":true,"BridgeNfIp6tables":true,"Debug":false,"NFd":27,"OomKillDisable":true,"NGoroutines":48,"SystemTime":"2020-08-11T18:16:17.494440681Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.9.0-8-amd64","OperatingSystem":"Debian GNU/Linux 9 (stretch)","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"AllowNondistributableArtifactsCIDRs":[],"AllowNondistributableArtifactsHostnames":[],"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":[],"Secure":true,"Official":true}},"Mirrors":[]},"NCPU":16,"MemTotal":63336071168,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"image-builder-cloud-shell-v20200811-102837","Labels":[],"ExperimentalBuild":false,"ServerVersion":"18.09.0","ClusterStore":"","ClusterAdvertise":"","Runtimes":{"runc":{"path":"runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"7ad184331fa3e55e52b890ea95e65ba581ae3429","Expected":"7ad184331fa3e55e52b890ea95e65ba581ae3429"},"RuncCommit":{"ID":"dc9208a3303feef5b3839f4323d9beb36df0a9dd","Expected":"dc9208a3303feef5b3839f4323d9beb36df0a9dd"},"InitCommit":{"ID":"fec3683","Expected":"fec3683"},"SecurityOptions":["name=seccomp,profile=default"],"ProductLicense":"Community Engine","Warnings":["WARNING: No swap limit support"],"ClientInfo":{"Debug":false,"Plugins":[],"Warnings":null}}`,
			ShouldError: false,
			CPUs:        16,
			Memory:      63336071168,
			OS:          "linux",
		},
		{
			Name:   "macos_docker",
			OciBin: "docker",
			RawJSON: `{"ID":"T54Z:I56K:XRG5:BTMK:BI72:IMI3:QBBF:H2PD:DGAF:EQLJ:7JFZ:PF54","Containers":5,"ContainersRunning":1,"ContainersPaused":0,"ContainersStopped":4,"Images":84,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Native Overlay Diff","true"]],"SystemStatus":null,"Plugins":{"Volume":["local"],"Network":["bridge","host","ipvlan","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","local","logentries","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"KernelMemoryTCP":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":true,"IPv4Forwarding":true,"BridgeNfIptables":true,"BridgeNfIp6tables":true,"Debug":true,"NFd":46,"OomKillDisable":true,"NGoroutines":56,"SystemTime":"2020-08-11T19:33:23.8936297Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":3,"KernelVersion":"4.19.76-linuxkit","OperatingSystem":"Docker Desktop","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"AllowNondistributableArtifactsCIDRs":[],"AllowNondistributableArtifactsHostnames":[],"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":[],"Secure":true,"Official":true}},"Mirrors":[]},"NCPU":4,"MemTotal":3142250496,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"gateway.docker.internal:3128","HttpsProxy":"gateway.docker.internal:3129","NoProxy":"","Name":"docker-desktop","Labels":[],"ExperimentalBuild":false,"ServerVersion":"19.03.12","ClusterStore":"","ClusterAdvertise":"","Runtimes":{"runc":{"path":"runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"7ad184331fa3e55e52b890ea95e65ba581ae3429","Expected":"7ad184331fa3e55e52b890ea95e65ba581ae3429"},"RuncCommit":{"ID":"dc9208a3303feef5b3839f4323d9beb36df0a9dd","Expected":"dc9208a3303feef5b3839f4323d9beb36df0a9dd"},"InitCommit":{"ID":"fec3683","Expected":"fec3683"},"SecurityOptions":["name=seccomp,profile=default"],"ProductLicense":"Community Engine","Warnings":null,"ClientInfo":{"Debug":false,"Plugins":[],"Warnings":null}}
`,
			ShouldError: false,
			CPUs:        4,
			Memory:      3142250496,
			OS:          "linux",
		},
		{
			Name:   "windows_docker",
			OciBin: "docker",
			RawJSON: `{"ID":"CVVH:7ZIB:S5EO:L6VO:MGZ3:TRLS:JGIS:4ZI2:27Z7:MQAQ:YSLT:HEHB","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":3,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Native Overlay Diff","true"]],"SystemStatus":null,"Plugins":{"Volume":["local"],"Network":["bridge","host","ipvlan","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","local","logentries","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"KernelMemoryTCP":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":true,"IPv4Forwarding":true,"BridgeNfIptables":true,"BridgeNfIp6tables":true,"Debug":true,"NFd":35,"OomKillDisable":true,"NGoroutines":45,"SystemTime":"2020-08-11T19:39:26.083212722Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":1,"KernelVersion":"4.19.76-linuxkit","OperatingSystem":"Docker Desktop","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"AllowNondistributableArtifactsCIDRs":[],"AllowNondistributableArtifactsHostnames":[],"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":[],"Secure":true,"Official":true}},"Mirrors":[]},"NCPU":4,"MemTotal":10454695936,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"docker-desktop","Labels":[],"ExperimentalBuild":false,"ServerVersion":"19.03.12","ClusterStore":"","ClusterAdvertise":"","Runtimes":{"runc":{"path":"runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"7ad184331fa3e55e52b890ea95e65ba581ae3429","Expected":"7ad184331fa3e55e52b890ea95e65ba581ae3429"},"RuncCommit":{"ID":"dc9208a3303feef5b3839f4323d9beb36df0a9dd","Expected":"dc9208a3303feef5b3839f4323d9beb36df0a9dd"},"InitCommit":{"ID":"fec3683","Expected":"fec3683"},"SecurityOptions":["name=seccomp,profile=default"],"ProductLicense":"Community Engine","Warnings":null,"ClientInfo":{"Debug":false,"Plugins":[],"Warnings":null}}
`,
			ShouldError: false,
			CPUs:        4,
			Memory:      10454695936,
			OS:          "linux",
		}, {
			Name:   "podman_1.8_linux",
			OciBin: "podman",
			RawJSON: `{
				"host": {
					"BuildahVersion": "1.13.1",
					"CgroupVersion": "v1",
					"Conmon": {
						"package": "conmon: /usr/libexec/podman/conmon",
						"path": "/usr/libexec/podman/conmon",
						"version": "conmon version 2.0.10, commit: unknown"
					},
					"Distribution": {
						"distribution": "debian",
						"version": "10"
					},
					"MemFree": 4907147264,
					"MemTotal": 7839653888,
					"OCIRuntime": {
						"name": "runc",
						"package": "runc: /usr/sbin/runc",
						"path": "/usr/sbin/runc",
						"version": "runc version 1.0.0~rc6+dfsg1\ncommit: 1.0.0~rc6+dfsg1-3 spec: 1.0.1"
					},
					"SwapFree": 0,
					"SwapTotal": 0,
					"arch": "amd64",
					"cpus": 2,
					"eventlogger": "journald",
					"hostname": "podman-exp-temp",
					"kernel": "4.19.0-8-cloud-amd64",
					"os": "linux",
					"rootless": false,
					"uptime": "2690h 47m 23.31s (Approximately 112.08 days)"
				},
				"registries": {
					"search": [
						"docker.io",
						"quay.io"
					]
				},
				"store": {
					"ConfigFile": "/etc/containers/storage.conf",
					"ContainerStore": {
						"number": 1
					},
					"GraphDriverName": "overlay",
					"GraphOptions": {},
					"GraphRoot": "/var/lib/containers/storage",
					"GraphStatus": {
						"Backing Filesystem": "extfs",
					  "Native Overlay Diff": "true",
						"Supports d_type": "true",
						"Using metacopy": "false"
					},
					"ImageStore": {
						"number": 2
					},
					"RunRoot": "/var/run/containers/storage",
					"VolumePath": "/var/lib/containers/storage/volumes"
				}
			}
`, CPUs: 2,
			Memory: 7839653888,
			OS:     "linux"},
	}

	for _, tc := range testCases {
		t.Run(tc.Name, func(t *testing.T) {
			s, err := DaemonInfo(tc.OciBin, tc.RawJSON)
			if err != nil && !tc.ShouldError {
				t.Errorf("Expected not to have error but got %v", err)
			}
			if s.CPUs != tc.CPUs {
				t.Errorf("Expected CPUs to be %d but got %d", tc.CPUs, s.CPUs)
			}
			if s.TotalMemory != tc.Memory {
				t.Errorf("Expected Memory to be %d but got %d", tc.Memory, s.TotalMemory)
			}

		})

	}
}
<commit_msg>add one more case<commit_after>package oci

import "testing"

func TestDockerSystemInfo(t *testing.T) {
	testCases := []struct {
		Name        string // test case bane
		OciBin      string // Docker or Podman
		RawJSON     string // raw response from json
		ShouldError bool
		CPUs        int
		Memory      int64
		OS          string
	}{
		{
			Name:        "linux_docker",
			OciBin:      "docker",
			RawJSON:     `{"ID":"7PYP:53DU:MLWX:EDQG:YG2Y:UJLB:J7SD:4SAI:XF2Y:N2MR:MU53:DR3N","Containers":3,"ContainersRunning":1,"ContainersPaused":0,"ContainersStopped":2,"Images":76,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Native Overlay Diff","true"]],"SystemStatus":null,"Plugins":{"Volume":["local"],"Network":["bridge","host","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","local","logentries","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":false,"KernelMemory":true,"KernelMemoryTCP":false,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":false,"IPv4Forwarding":true,"BridgeNfIptables":true,"BridgeNfIp6tables":true,"Debug":false,"NFd":27,"OomKillDisable":true,"NGoroutines":48,"SystemTime":"2020-08-11T18:16:17.494440681Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":0,"KernelVersion":"4.9.0-8-amd64","OperatingSystem":"Debian GNU/Linux 9 (stretch)","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"AllowNondistributableArtifactsCIDRs":[],"AllowNondistributableArtifactsHostnames":[],"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":[],"Secure":true,"Official":true}},"Mirrors":[]},"NCPU":16,"MemTotal":63336071168,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"image-builder-cloud-shell-v20200811-102837","Labels":[],"ExperimentalBuild":false,"ServerVersion":"18.09.0","ClusterStore":"","ClusterAdvertise":"","Runtimes":{"runc":{"path":"runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"7ad184331fa3e55e52b890ea95e65ba581ae3429","Expected":"7ad184331fa3e55e52b890ea95e65ba581ae3429"},"RuncCommit":{"ID":"dc9208a3303feef5b3839f4323d9beb36df0a9dd","Expected":"dc9208a3303feef5b3839f4323d9beb36df0a9dd"},"InitCommit":{"ID":"fec3683","Expected":"fec3683"},"SecurityOptions":["name=seccomp,profile=default"],"ProductLicense":"Community Engine","Warnings":["WARNING: No swap limit support"],"ClientInfo":{"Debug":false,"Plugins":[],"Warnings":null}}`,
			ShouldError: false,
			CPUs:        16,
			Memory:      63336071168,
			OS:          "linux",
		},
		{
			Name:   "macos_docker",
			OciBin: "docker",
			RawJSON: `{"ID":"T54Z:I56K:XRG5:BTMK:BI72:IMI3:QBBF:H2PD:DGAF:EQLJ:7JFZ:PF54","Containers":5,"ContainersRunning":1,"ContainersPaused":0,"ContainersStopped":4,"Images":84,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Native Overlay Diff","true"]],"SystemStatus":null,"Plugins":{"Volume":["local"],"Network":["bridge","host","ipvlan","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","local","logentries","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"KernelMemoryTCP":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":true,"IPv4Forwarding":true,"BridgeNfIptables":true,"BridgeNfIp6tables":true,"Debug":true,"NFd":46,"OomKillDisable":true,"NGoroutines":56,"SystemTime":"2020-08-11T19:33:23.8936297Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":3,"KernelVersion":"4.19.76-linuxkit","OperatingSystem":"Docker Desktop","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"AllowNondistributableArtifactsCIDRs":[],"AllowNondistributableArtifactsHostnames":[],"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":[],"Secure":true,"Official":true}},"Mirrors":[]},"NCPU":4,"MemTotal":3142250496,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"gateway.docker.internal:3128","HttpsProxy":"gateway.docker.internal:3129","NoProxy":"","Name":"docker-desktop","Labels":[],"ExperimentalBuild":false,"ServerVersion":"19.03.12","ClusterStore":"","ClusterAdvertise":"","Runtimes":{"runc":{"path":"runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"7ad184331fa3e55e52b890ea95e65ba581ae3429","Expected":"7ad184331fa3e55e52b890ea95e65ba581ae3429"},"RuncCommit":{"ID":"dc9208a3303feef5b3839f4323d9beb36df0a9dd","Expected":"dc9208a3303feef5b3839f4323d9beb36df0a9dd"},"InitCommit":{"ID":"fec3683","Expected":"fec3683"},"SecurityOptions":["name=seccomp,profile=default"],"ProductLicense":"Community Engine","Warnings":null,"ClientInfo":{"Debug":false,"Plugins":[],"Warnings":null}}
`,
			ShouldError: false,
			CPUs:        4,
			Memory:      3142250496,
			OS:          "linux",
		},
		{
			Name:   "windows_docker",
			OciBin: "docker",
			RawJSON: `{"ID":"CVVH:7ZIB:S5EO:L6VO:MGZ3:TRLS:JGIS:4ZI2:27Z7:MQAQ:YSLT:HEHB","Containers":0,"ContainersRunning":0,"ContainersPaused":0,"ContainersStopped":0,"Images":3,"Driver":"overlay2","DriverStatus":[["Backing Filesystem","extfs"],["Supports d_type","true"],["Native Overlay Diff","true"]],"SystemStatus":null,"Plugins":{"Volume":["local"],"Network":["bridge","host","ipvlan","macvlan","null","overlay"],"Authorization":null,"Log":["awslogs","fluentd","gcplogs","gelf","journald","json-file","local","logentries","splunk","syslog"]},"MemoryLimit":true,"SwapLimit":true,"KernelMemory":true,"KernelMemoryTCP":true,"CpuCfsPeriod":true,"CpuCfsQuota":true,"CPUShares":true,"CPUSet":true,"PidsLimit":true,"IPv4Forwarding":true,"BridgeNfIptables":true,"BridgeNfIp6tables":true,"Debug":true,"NFd":35,"OomKillDisable":true,"NGoroutines":45,"SystemTime":"2020-08-11T19:39:26.083212722Z","LoggingDriver":"json-file","CgroupDriver":"cgroupfs","NEventsListener":1,"KernelVersion":"4.19.76-linuxkit","OperatingSystem":"Docker Desktop","OSType":"linux","Architecture":"x86_64","IndexServerAddress":"https://index.docker.io/v1/","RegistryConfig":{"AllowNondistributableArtifactsCIDRs":[],"AllowNondistributableArtifactsHostnames":[],"InsecureRegistryCIDRs":["127.0.0.0/8"],"IndexConfigs":{"docker.io":{"Name":"docker.io","Mirrors":[],"Secure":true,"Official":true}},"Mirrors":[]},"NCPU":4,"MemTotal":10454695936,"GenericResources":null,"DockerRootDir":"/var/lib/docker","HttpProxy":"","HttpsProxy":"","NoProxy":"","Name":"docker-desktop","Labels":[],"ExperimentalBuild":false,"ServerVersion":"19.03.12","ClusterStore":"","ClusterAdvertise":"","Runtimes":{"runc":{"path":"runc"}},"DefaultRuntime":"runc","Swarm":{"NodeID":"","NodeAddr":"","LocalNodeState":"inactive","ControlAvailable":false,"Error":"","RemoteManagers":null},"LiveRestoreEnabled":false,"Isolation":"","InitBinary":"docker-init","ContainerdCommit":{"ID":"7ad184331fa3e55e52b890ea95e65ba581ae3429","Expected":"7ad184331fa3e55e52b890ea95e65ba581ae3429"},"RuncCommit":{"ID":"dc9208a3303feef5b3839f4323d9beb36df0a9dd","Expected":"dc9208a3303feef5b3839f4323d9beb36df0a9dd"},"InitCommit":{"ID":"fec3683","Expected":"fec3683"},"SecurityOptions":["name=seccomp,profile=default"],"ProductLicense":"Community Engine","Warnings":null,"ClientInfo":{"Debug":false,"Plugins":[],"Warnings":null}}
`,
			ShouldError: false,
			CPUs:        4,
			Memory:      10454695936,
			OS:          "linux",
		}, {
			Name:   "podman_1.8_linux",
			OciBin: "podman",
			RawJSON: `{
				"host": {
					"BuildahVersion": "1.13.1",
					"CgroupVersion": "v1",
					"Conmon": {
						"package": "conmon: /usr/libexec/podman/conmon",
						"path": "/usr/libexec/podman/conmon",
						"version": "conmon version 2.0.10, commit: unknown"
					},
					"Distribution": {
						"distribution": "debian",
						"version": "10"
					},
					"MemFree": 4907147264,
					"MemTotal": 7839653888,
					"OCIRuntime": {
						"name": "runc",
						"package": "runc: /usr/sbin/runc",
						"path": "/usr/sbin/runc",
						"version": "runc version 1.0.0~rc6+dfsg1\ncommit: 1.0.0~rc6+dfsg1-3 spec: 1.0.1"
					},
					"SwapFree": 0,
					"SwapTotal": 0,
					"arch": "amd64",
					"cpus": 2,
					"eventlogger": "journald",
					"hostname": "podman-exp-temp",
					"kernel": "4.19.0-8-cloud-amd64",
					"os": "linux",
					"rootless": false,
					"uptime": "2690h 47m 23.31s (Approximately 112.08 days)"
				},
				"registries": {
					"search": [
						"docker.io",
						"quay.io"
					]
				},
				"store": {
					"ConfigFile": "/etc/containers/storage.conf",
					"ContainerStore": {
						"number": 1
					},
					"GraphDriverName": "overlay",
					"GraphOptions": {},
					"GraphRoot": "/var/lib/containers/storage",
					"GraphStatus": {
						"Backing Filesystem": "extfs",
					  "Native Overlay Diff": "true",
						"Supports d_type": "true",
						"Using metacopy": "false"
					},
					"ImageStore": {
						"number": 2
					},
					"RunRoot": "/var/run/containers/storage",
					"VolumePath": "/var/lib/containers/storage/volumes"
				}
			}
`, CPUs: 2,
			Memory: 7839653888,
			OS:     "linux"},
	}

	for _, tc := range testCases {
		t.Run(tc.Name, func(t *testing.T) {
			s, err := DaemonInfo(tc.OciBin, tc.RawJSON)
			if err != nil && !tc.ShouldError {
				t.Errorf("Expected not to have error but got %v", err)
			}
			if s.CPUs != tc.CPUs {
				t.Errorf("Expected CPUs to be %d but got %d", tc.CPUs, s.CPUs)
			}
			if s.TotalMemory != tc.Memory {
				t.Errorf("Expected Memory to be %d but got %d", tc.Memory, s.TotalMemory)
			}
			if s.OSType != tc.OS {
				t.Errorf("Expected OS type to be %q but got %q", tc.OS, s.OSType)
			}

		})

	}
}
<|endoftext|>


<commit_before>/*
Copyright 2017 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 handlers

import (
	"context"
	"fmt"
	"net/http"
	"sync"
	"time"

	"k8s.io/apimachinery/pkg/api/errors"
	metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apiserver/pkg/admission"
	"k8s.io/apiserver/pkg/audit"
	"k8s.io/apiserver/pkg/authorization/authorizer"
	"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
	"k8s.io/apiserver/pkg/endpoints/request"
	"k8s.io/apiserver/pkg/features"
	"k8s.io/apiserver/pkg/registry/rest"
	"k8s.io/apiserver/pkg/util/dryrun"
	utilfeature "k8s.io/apiserver/pkg/util/feature"
	utiltrace "k8s.io/utils/trace"
)

// UpdateResource returns a function that will handle a resource update
func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interface) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		// For performance tracking purposes.
		trace := utiltrace.New("Update " + req.URL.Path)
		defer trace.LogIfLong(500 * time.Millisecond)

		if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) {
			scope.err(errors.NewBadRequest("the dryRun alpha feature is disabled"), w, req)
			return
		}

		// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
		timeout := parseTimeout(req.URL.Query().Get("timeout"))

		namespace, name, err := scope.Namer.Name(req)
		if err != nil {
			scope.err(err, w, req)
			return
		}
		ctx := req.Context()
		ctx = request.WithNamespace(ctx, namespace)

		outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, &scope)
		if err != nil {
			scope.err(err, w, req)
			return
		}

		body, err := readBody(req)
		if err != nil {
			scope.err(err, w, req)
			return
		}

		options := &metav1.UpdateOptions{}
		if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, options); err != nil {
			err = errors.NewBadRequest(err.Error())
			scope.err(err, w, req)
			return
		}
		if errs := validation.ValidateUpdateOptions(options); len(errs) > 0 {
			err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "UpdateOptions"}, "", errs)
			scope.err(err, w, req)
			return
		}

		s, err := negotiation.NegotiateInputSerializer(req, false, scope.Serializer)
		if err != nil {
			scope.err(err, w, req)
			return
		}
		defaultGVK := scope.Kind
		original := r.New()

		trace.Step("About to convert to expected version")
		decoder := scope.Serializer.DecoderToVersion(s.Serializer, scope.HubGroupVersion)
		obj, gvk, err := decoder.Decode(body, &defaultGVK, original)
		if err != nil {
			err = transformDecodeError(scope.Typer, err, original, gvk, body)
			scope.err(err, w, req)
			return
		}
		if gvk.GroupVersion() != defaultGVK.GroupVersion() {
			err = errors.NewBadRequest(fmt.Sprintf("the API version in the data (%s) does not match the expected API version (%s)", gvk.GroupVersion(), defaultGVK.GroupVersion()))
			scope.err(err, w, req)
			return
		}
		trace.Step("Conversion done")

		ae := request.AuditEventFrom(ctx)
		audit.LogRequestObject(ae, obj, scope.Resource, scope.Subresource, scope.Serializer)
		admit = admission.WithAudit(admit, ae)

		if err := checkName(obj, name, namespace, scope.Namer); err != nil {
			scope.err(err, w, req)
			return
		}

		userInfo, _ := request.UserFrom(ctx)
		transformers := []rest.TransformFunc{}
		if scope.FieldManager != nil {
			transformers = append(transformers, func(_ context.Context, newObj, liveObj runtime.Object) (runtime.Object, error) {
				if obj, err = scope.FieldManager.Update(liveObj, newObj, prefixFromUserAgent(req.UserAgent())); err != nil {
					return nil, fmt.Errorf("failed to update object (Update for %v) managed fields: %v", scope.Kind, err)
				}
				return obj, nil
			})
		}
		if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
			transformers = append(transformers, func(ctx context.Context, newObj, oldObj runtime.Object) (runtime.Object, error) {
				isNotZeroObject, err := hasUID(oldObj)
				if err != nil {
					return nil, fmt.Errorf("unexpected error when extracting UID from oldObj: %v", err.Error())
				} else if !isNotZeroObject {
					if mutatingAdmission.Handles(admission.Create) {
						return newObj, mutatingAdmission.Admit(admission.NewAttributesRecord(newObj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, dryrun.IsDryRun(options.DryRun), userInfo))
					}
				} else {
					if mutatingAdmission.Handles(admission.Update) {
						return newObj, mutatingAdmission.Admit(admission.NewAttributesRecord(newObj, oldObj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, dryrun.IsDryRun(options.DryRun), userInfo))
					}
				}
				return newObj, nil
			})

		}

		createAuthorizerAttributes := authorizer.AttributesRecord{
			User:            userInfo,
			ResourceRequest: true,
			Path:            req.URL.Path,
			Verb:            "create",
			APIGroup:        scope.Resource.Group,
			APIVersion:      scope.Resource.Version,
			Resource:        scope.Resource.Resource,
			Subresource:     scope.Subresource,
			Namespace:       namespace,
			Name:            name,
		}

		trace.Step("About to store object in database")
		wasCreated := false
		result, err := finishRequest(timeout, func() (runtime.Object, error) {
			obj, created, err := r.Update(
				ctx,
				name,
				rest.DefaultUpdatedObjectInfo(obj, transformers...),
				withAuthorization(rest.AdmissionToValidateObjectFunc(
					admit,
					admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, dryrun.IsDryRun(options.DryRun), userInfo)),
					scope.Authorizer, createAuthorizerAttributes),
				rest.AdmissionToValidateObjectUpdateFunc(
					admit,
					admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, dryrun.IsDryRun(options.DryRun), userInfo)),
				false,
				options,
			)
			wasCreated = created
			return obj, err
		})
		if err != nil {
			scope.err(err, w, req)
			return
		}
		trace.Step("Object stored in database")

		status := http.StatusOK
		if wasCreated {
			status = http.StatusCreated
		}

		scope.Trace = trace
		transformResponseObject(ctx, scope, req, w, status, outputMediaType, result)
	}
}

func withAuthorization(validate rest.ValidateObjectFunc, a authorizer.Authorizer, attributes authorizer.Attributes) rest.ValidateObjectFunc {
	var once sync.Once
	var authorizerDecision authorizer.Decision
	var authorizerReason string
	var authorizerErr error
	return func(obj runtime.Object) error {
		if a == nil {
			return errors.NewInternalError(fmt.Errorf("no authorizer provided, unable to authorize a create on update"))
		}
		once.Do(func() {
			authorizerDecision, authorizerReason, authorizerErr = a.Authorize(attributes)
		})
		// an authorizer like RBAC could encounter evaluation errors and still allow the request, so authorizer decision is checked before error here.
		if authorizerDecision == authorizer.DecisionAllow {
			// Continue to validating admission
			return validate(obj)
		}
		if authorizerErr != nil {
			return errors.NewInternalError(authorizerErr)
		}

		// The user is not authorized to perform this action, so we need to build the error response
		gr := schema.GroupResource{
			Group:    attributes.GetAPIGroup(),
			Resource: attributes.GetResource(),
		}
		name := attributes.GetName()
		err := fmt.Errorf("%v", authorizerReason)
		return errors.NewForbidden(gr, name, err)
	}
}
<commit_msg>Avoid closure<commit_after>/*
Copyright 2017 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 handlers

import (
	"context"
	"fmt"
	"net/http"
	"sync"
	"time"

	"k8s.io/apimachinery/pkg/api/errors"
	metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apiserver/pkg/admission"
	"k8s.io/apiserver/pkg/audit"
	"k8s.io/apiserver/pkg/authorization/authorizer"
	"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
	"k8s.io/apiserver/pkg/endpoints/request"
	"k8s.io/apiserver/pkg/features"
	"k8s.io/apiserver/pkg/registry/rest"
	"k8s.io/apiserver/pkg/util/dryrun"
	utilfeature "k8s.io/apiserver/pkg/util/feature"
	utiltrace "k8s.io/utils/trace"
)

// UpdateResource returns a function that will handle a resource update
func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interface) http.HandlerFunc {
	return func(w http.ResponseWriter, req *http.Request) {
		// For performance tracking purposes.
		trace := utiltrace.New("Update " + req.URL.Path)
		defer trace.LogIfLong(500 * time.Millisecond)

		if isDryRun(req.URL) && !utilfeature.DefaultFeatureGate.Enabled(features.DryRun) {
			scope.err(errors.NewBadRequest("the dryRun alpha feature is disabled"), w, req)
			return
		}

		// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
		timeout := parseTimeout(req.URL.Query().Get("timeout"))

		namespace, name, err := scope.Namer.Name(req)
		if err != nil {
			scope.err(err, w, req)
			return
		}
		ctx := req.Context()
		ctx = request.WithNamespace(ctx, namespace)

		outputMediaType, _, err := negotiation.NegotiateOutputMediaType(req, scope.Serializer, &scope)
		if err != nil {
			scope.err(err, w, req)
			return
		}

		body, err := readBody(req)
		if err != nil {
			scope.err(err, w, req)
			return
		}

		options := &metav1.UpdateOptions{}
		if err := metainternalversion.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, options); err != nil {
			err = errors.NewBadRequest(err.Error())
			scope.err(err, w, req)
			return
		}
		if errs := validation.ValidateUpdateOptions(options); len(errs) > 0 {
			err := errors.NewInvalid(schema.GroupKind{Group: metav1.GroupName, Kind: "UpdateOptions"}, "", errs)
			scope.err(err, w, req)
			return
		}

		s, err := negotiation.NegotiateInputSerializer(req, false, scope.Serializer)
		if err != nil {
			scope.err(err, w, req)
			return
		}
		defaultGVK := scope.Kind
		original := r.New()

		trace.Step("About to convert to expected version")
		decoder := scope.Serializer.DecoderToVersion(s.Serializer, scope.HubGroupVersion)
		obj, gvk, err := decoder.Decode(body, &defaultGVK, original)
		if err != nil {
			err = transformDecodeError(scope.Typer, err, original, gvk, body)
			scope.err(err, w, req)
			return
		}
		if gvk.GroupVersion() != defaultGVK.GroupVersion() {
			err = errors.NewBadRequest(fmt.Sprintf("the API version in the data (%s) does not match the expected API version (%s)", gvk.GroupVersion(), defaultGVK.GroupVersion()))
			scope.err(err, w, req)
			return
		}
		trace.Step("Conversion done")

		ae := request.AuditEventFrom(ctx)
		audit.LogRequestObject(ae, obj, scope.Resource, scope.Subresource, scope.Serializer)
		admit = admission.WithAudit(admit, ae)

		if err := checkName(obj, name, namespace, scope.Namer); err != nil {
			scope.err(err, w, req)
			return
		}

		userInfo, _ := request.UserFrom(ctx)
		transformers := []rest.TransformFunc{}
		if scope.FieldManager != nil {
			transformers = append(transformers, func(_ context.Context, newObj, liveObj runtime.Object) (runtime.Object, error) {
				obj, err := scope.FieldManager.Update(liveObj, newObj, prefixFromUserAgent(req.UserAgent()))
				if err != nil {
					return nil, fmt.Errorf("failed to update object (Update for %v) managed fields: %v", scope.Kind, err)
				}
				return obj, nil
			})
		}
		if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
			transformers = append(transformers, func(ctx context.Context, newObj, oldObj runtime.Object) (runtime.Object, error) {
				isNotZeroObject, err := hasUID(oldObj)
				if err != nil {
					return nil, fmt.Errorf("unexpected error when extracting UID from oldObj: %v", err.Error())
				} else if !isNotZeroObject {
					if mutatingAdmission.Handles(admission.Create) {
						return newObj, mutatingAdmission.Admit(admission.NewAttributesRecord(newObj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, dryrun.IsDryRun(options.DryRun), userInfo))
					}
				} else {
					if mutatingAdmission.Handles(admission.Update) {
						return newObj, mutatingAdmission.Admit(admission.NewAttributesRecord(newObj, oldObj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, dryrun.IsDryRun(options.DryRun), userInfo))
					}
				}
				return newObj, nil
			})

		}

		createAuthorizerAttributes := authorizer.AttributesRecord{
			User:            userInfo,
			ResourceRequest: true,
			Path:            req.URL.Path,
			Verb:            "create",
			APIGroup:        scope.Resource.Group,
			APIVersion:      scope.Resource.Version,
			Resource:        scope.Resource.Resource,
			Subresource:     scope.Subresource,
			Namespace:       namespace,
			Name:            name,
		}

		trace.Step("About to store object in database")
		wasCreated := false
		result, err := finishRequest(timeout, func() (runtime.Object, error) {
			obj, created, err := r.Update(
				ctx,
				name,
				rest.DefaultUpdatedObjectInfo(obj, transformers...),
				withAuthorization(rest.AdmissionToValidateObjectFunc(
					admit,
					admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, dryrun.IsDryRun(options.DryRun), userInfo)),
					scope.Authorizer, createAuthorizerAttributes),
				rest.AdmissionToValidateObjectUpdateFunc(
					admit,
					admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, dryrun.IsDryRun(options.DryRun), userInfo)),
				false,
				options,
			)
			wasCreated = created
			return obj, err
		})
		if err != nil {
			scope.err(err, w, req)
			return
		}
		trace.Step("Object stored in database")

		status := http.StatusOK
		if wasCreated {
			status = http.StatusCreated
		}

		scope.Trace = trace
		transformResponseObject(ctx, scope, req, w, status, outputMediaType, result)
	}
}

func withAuthorization(validate rest.ValidateObjectFunc, a authorizer.Authorizer, attributes authorizer.Attributes) rest.ValidateObjectFunc {
	var once sync.Once
	var authorizerDecision authorizer.Decision
	var authorizerReason string
	var authorizerErr error
	return func(obj runtime.Object) error {
		if a == nil {
			return errors.NewInternalError(fmt.Errorf("no authorizer provided, unable to authorize a create on update"))
		}
		once.Do(func() {
			authorizerDecision, authorizerReason, authorizerErr = a.Authorize(attributes)
		})
		// an authorizer like RBAC could encounter evaluation errors and still allow the request, so authorizer decision is checked before error here.
		if authorizerDecision == authorizer.DecisionAllow {
			// Continue to validating admission
			return validate(obj)
		}
		if authorizerErr != nil {
			return errors.NewInternalError(authorizerErr)
		}

		// The user is not authorized to perform this action, so we need to build the error response
		gr := schema.GroupResource{
			Group:    attributes.GetAPIGroup(),
			Resource: attributes.GetResource(),
		}
		name := attributes.GetName()
		err := fmt.Errorf("%v", authorizerReason)
		return errors.NewForbidden(gr, name, err)
	}
}
<|endoftext|>


<commit_before>package filesystem

import (
	"fmt"
	"os"
	"os/user"
	"path/filepath"
	"runtime"
	"testing"
	"strings"

	"github.com/pkg/errors"
)

// TestTildeNotPathSeparator ensures that ~ is not considered a path separator
// on the platform. This is essentially guaranteed, but since we rely on this
// behavior, it's best to have an explicit check of it.
func TestTildeNotPathSeparator(t *testing.T) {
	if os.IsPathSeparator('~') {
		t.Fatal("tilde considered path separator")
	}
}

func TestTildeExpandHome(t *testing.T) {
	// Perform expansion.
	expanded, err := tildeExpand("~")
	if err != nil {
		t.Fatal("tilde expansion failed:", err)
	}

	// Ensure that the result matches the expected values.
	if expanded != HomeDirectory {
		t.Error("tilde-expanded path does not match expected")
	}
}

func TestTildeExpandHomeSlash(t *testing.T) {
	// Perform expansion.
	expanded, err := tildeExpand("~/")
	if err != nil {
		t.Fatal("tilde expansion failed:", err)
	}

	// Ensure that the result matches the expected values.
	if expanded != HomeDirectory {
		t.Error("tilde-expanded path does not match expected")
	}
}

func TestTildeExpandHomeBackslash(t *testing.T) {
	// Set expectations.
	expectFailure := runtime.GOOS != "windows"

	// Perform expansion.
	expanded, err := tildeExpand("~\\")
	if expectFailure && err == nil {
		t.Error("tilde expansion succeeded unexpectedly")
	} else if !expectFailure && err != nil {
		t.Fatal("tilde expansion failed:", err)
	}

	// Bail if we're done.
	if expectFailure {
		return
	}

	// Ensure that the result matches the expected values.
	if expanded != HomeDirectory {
		t.Error("tilde-expanded path does not match expected")
	}
}

// currentUsername is a utility wrapper around user.Current for Windows systems,
// where the Username field will be of the form DOMAIN\username.
func currentUsername() (string, error) {
	// Grab the user.
	user, err := user.Current()
	if err != nil {
		return "", errors.Wrap(err, "unable to get current user")
	}

	// If we're on a POSIX system, we're done.
	if runtime.GOOS != "windows" {
		return user.Username, nil
	}

	// If we're on Windows, there may be a DOMAIN\ prefix on the username.
	if index := strings.IndexByte(user.Username, '\\'); index >= 0 {
		if index == len(user.Username) {
			return "", errors.New("domain extends to end of username")
		}
		return user.Username[index+1:], nil
	}
	return user.Username, nil
}

func TestTildeExpandLookup(t *testing.T) {
	// Grab the current username.
	username, err := currentUsername()
	if err != nil {
		t.Fatal("unable to look up current username:", err)
	}

	// Perform expansion.
	expanded, err := tildeExpand("~" + username)
	if err != nil {
		t.Fatal("tilde expansion failed:", err)
	}

	// Ensure that the result matches the expected values.
	if expanded != HomeDirectory {
		t.Error("tilde-expanded path does not match expected")
	}
}

func TestTildeExpandLookupSlash(t *testing.T) {
	// Grab the current username.
	username, err := currentUsername()
	if err != nil {
		t.Fatal("unable to look up current username:", err)
	}

	// Perform expansion.
	expanded, err := tildeExpand(fmt.Sprintf("~%s/", username))
	if err != nil {
		t.Fatal("tilde expansion failed:", err)
	}

	// Ensure that the result matches the expected values.
	if expanded != HomeDirectory {
		t.Error("tilde-expanded path does not match expected")
	}
}

func TestTildeExpandLookupBackslash(t *testing.T) {
	// Set expectations.
	expectFailure := runtime.GOOS != "windows"

	// Grab the current username.
	username, err := currentUsername()
	if err != nil {
		t.Fatal("unable to look up current username:", err)
	}

	// Perform expansion.
	expanded, err := tildeExpand(fmt.Sprintf("~%s\\", username))
	if expectFailure && err == nil {
		t.Error("tilde expansion succeeded unexpectedly")
	} else if !expectFailure && err != nil {
		t.Fatal("tilde expansion failed:", err)
	}

	// Bail if we're done.
	if expectFailure {
		return
	}

	// Ensure that the result matches the expected values.
	if expanded != HomeDirectory {
		t.Error("tilde-expanded path does not match expected")
	}
}

func TestNormalizeHome(t *testing.T) {
	// Compute a path relative to the home directory.
	normalized, err := Normalize("~/somepath")
	if err != nil {
		t.Fatal("unable to normalize path:", err)
	}

	// Ensure that it's what we expect.
	if normalized != filepath.Join(HomeDirectory, "somepath") {
		t.Error("normalized path does not match expected")
	}
}
<commit_msg>Fixed import ordering in filesystem tests.<commit_after>package filesystem

import (
	"fmt"
	"os"
	"os/user"
	"path/filepath"
	"runtime"
	"strings"
	"testing"

	"github.com/pkg/errors"
)

// TestTildeNotPathSeparator ensures that ~ is not considered a path separator
// on the platform. This is essentially guaranteed, but since we rely on this
// behavior, it's best to have an explicit check of it.
func TestTildeNotPathSeparator(t *testing.T) {
	if os.IsPathSeparator('~') {
		t.Fatal("tilde considered path separator")
	}
}

func TestTildeExpandHome(t *testing.T) {
	// Perform expansion.
	expanded, err := tildeExpand("~")
	if err != nil {
		t.Fatal("tilde expansion failed:", err)
	}

	// Ensure that the result matches the expected values.
	if expanded != HomeDirectory {
		t.Error("tilde-expanded path does not match expected")
	}
}

func TestTildeExpandHomeSlash(t *testing.T) {
	// Perform expansion.
	expanded, err := tildeExpand("~/")
	if err != nil {
		t.Fatal("tilde expansion failed:", err)
	}

	// Ensure that the result matches the expected values.
	if expanded != HomeDirectory {
		t.Error("tilde-expanded path does not match expected")
	}
}

func TestTildeExpandHomeBackslash(t *testing.T) {
	// Set expectations.
	expectFailure := runtime.GOOS != "windows"

	// Perform expansion.
	expanded, err := tildeExpand("~\\")
	if expectFailure && err == nil {
		t.Error("tilde expansion succeeded unexpectedly")
	} else if !expectFailure && err != nil {
		t.Fatal("tilde expansion failed:", err)
	}

	// Bail if we're done.
	if expectFailure {
		return
	}

	// Ensure that the result matches the expected values.
	if expanded != HomeDirectory {
		t.Error("tilde-expanded path does not match expected")
	}
}

// currentUsername is a utility wrapper around user.Current for Windows systems,
// where the Username field will be of the form DOMAIN\username.
func currentUsername() (string, error) {
	// Grab the user.
	user, err := user.Current()
	if err != nil {
		return "", errors.Wrap(err, "unable to get current user")
	}

	// If we're on a POSIX system, we're done.
	if runtime.GOOS != "windows" {
		return user.Username, nil
	}

	// If we're on Windows, there may be a DOMAIN\ prefix on the username.
	if index := strings.IndexByte(user.Username, '\\'); index >= 0 {
		if index == len(user.Username) {
			return "", errors.New("domain extends to end of username")
		}
		return user.Username[index+1:], nil
	}
	return user.Username, nil
}

func TestTildeExpandLookup(t *testing.T) {
	// Grab the current username.
	username, err := currentUsername()
	if err != nil {
		t.Fatal("unable to look up current username:", err)
	}

	// Perform expansion.
	expanded, err := tildeExpand("~" + username)
	if err != nil {
		t.Fatal("tilde expansion failed:", err)
	}

	// Ensure that the result matches the expected values.
	if expanded != HomeDirectory {
		t.Error("tilde-expanded path does not match expected")
	}
}

func TestTildeExpandLookupSlash(t *testing.T) {
	// Grab the current username.
	username, err := currentUsername()
	if err != nil {
		t.Fatal("unable to look up current username:", err)
	}

	// Perform expansion.
	expanded, err := tildeExpand(fmt.Sprintf("~%s/", username))
	if err != nil {
		t.Fatal("tilde expansion failed:", err)
	}

	// Ensure that the result matches the expected values.
	if expanded != HomeDirectory {
		t.Error("tilde-expanded path does not match expected")
	}
}

func TestTildeExpandLookupBackslash(t *testing.T) {
	// Set expectations.
	expectFailure := runtime.GOOS != "windows"

	// Grab the current username.
	username, err := currentUsername()
	if err != nil {
		t.Fatal("unable to look up current username:", err)
	}

	// Perform expansion.
	expanded, err := tildeExpand(fmt.Sprintf("~%s\\", username))
	if expectFailure && err == nil {
		t.Error("tilde expansion succeeded unexpectedly")
	} else if !expectFailure && err != nil {
		t.Fatal("tilde expansion failed:", err)
	}

	// Bail if we're done.
	if expectFailure {
		return
	}

	// Ensure that the result matches the expected values.
	if expanded != HomeDirectory {
		t.Error("tilde-expanded path does not match expected")
	}
}

func TestNormalizeHome(t *testing.T) {
	// Compute a path relative to the home directory.
	normalized, err := Normalize("~/somepath")
	if err != nil {
		t.Fatal("unable to normalize path:", err)
	}

	// Ensure that it's what we expect.
	if normalized != filepath.Join(HomeDirectory, "somepath") {
		t.Error("normalized path does not match expected")
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2017 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 apps

import (
	"fmt"

	"k8s.io/apimachinery/pkg/runtime/schema"
)

// KindVisitor is used with GroupKindElement to call a particular function depending on the
// Kind of a schema.GroupKind
type KindVisitor interface {
	VisitDaemonSet(kind GroupKindElement)
	VisitDeployment(kind GroupKindElement)
	VisitJob(kind GroupKindElement)
	VisitPod(kind GroupKindElement)
	VisitReplicaSet(kind GroupKindElement)
	VisitReplicationController(kind GroupKindElement)
	VisitStatefulSet(kind GroupKindElement)
}

// GroupKindElement defines a Kubernetes API group elem
type GroupKindElement schema.GroupKind

// Accept calls the Visit method on visitor that corresponds to elem's Kind
func (elem GroupKindElement) Accept(visitor KindVisitor) error {
	if elem.GroupMatch("apps", "extensions") && elem.Kind == "DaemonSet" {
		visitor.VisitDaemonSet(elem)
		return nil
	}
	if elem.GroupMatch("apps", "extensions") && elem.Kind == "Deployment" {
		visitor.VisitDeployment(elem)
		return nil
	}
	if elem.GroupMatch("batch") && elem.Kind == "Job" {
		visitor.VisitJob(elem)
		return nil
	}
	if elem.GroupMatch("", "core") && elem.Kind == "Pod" {
		visitor.VisitPod(elem)
		return nil
	}
	if elem.GroupMatch("extensions") && elem.Kind == "ReplicaSet" {
		visitor.VisitReplicaSet(elem)
		return nil
	}
	if elem.GroupMatch("", "core") && elem.Kind == "ReplicationController" {
		visitor.VisitReplicationController(elem)
		return nil
	}
	if elem.GroupMatch("apps") && elem.Kind == "StatefulSet" {
		visitor.VisitStatefulSet(elem)
		return nil
	}
	return fmt.Errorf("no visitor method exists for %v", elem)
}

// GroupMatch returns true if and only if elem's group matches one
// of the group arguments
func (elem GroupKindElement) GroupMatch(groups ...string) bool {
	for _, g := range groups {
		if elem.Group == g {
			return true
		}
	}
	return false
}

// NoOpKindVisitor implements KindVisitor with no-op functions.
type NoOpKindVisitor struct{}

var _ KindVisitor = &NoOpKindVisitor{}

func (*NoOpKindVisitor) VisitDaemonSet(kind GroupKindElement)             {}
func (*NoOpKindVisitor) VisitDeployment(kind GroupKindElement)            {}
func (*NoOpKindVisitor) VisitJob(kind GroupKindElement)                   {}
func (*NoOpKindVisitor) VisitPod(kind GroupKindElement)                   {}
func (*NoOpKindVisitor) VisitReplicaSet(kind GroupKindElement)            {}
func (*NoOpKindVisitor) VisitReplicationController(kind GroupKindElement) {}
func (*NoOpKindVisitor) VisitStatefulSet(kind GroupKindElement)           {}
<commit_msg>optimize then function in kind visitor<commit_after>/*
Copyright 2017 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 apps

import (
	"fmt"

	"k8s.io/apimachinery/pkg/runtime/schema"
)

// KindVisitor is used with GroupKindElement to call a particular function depending on the
// Kind of a schema.GroupKind
type KindVisitor interface {
	VisitDaemonSet(kind GroupKindElement)
	VisitDeployment(kind GroupKindElement)
	VisitJob(kind GroupKindElement)
	VisitPod(kind GroupKindElement)
	VisitReplicaSet(kind GroupKindElement)
	VisitReplicationController(kind GroupKindElement)
	VisitStatefulSet(kind GroupKindElement)
}

// GroupKindElement defines a Kubernetes API group elem
type GroupKindElement schema.GroupKind

// Accept calls the Visit method on visitor that corresponds to elem's Kind
func (elem GroupKindElement) Accept(visitor KindVisitor) error {
	switch {
	case elem.GroupMatch("apps", "extensions") && elem.Kind == "DaemonSet":
		visitor.VisitDaemonSet(elem)
	case elem.GroupMatch("apps", "extensions") && elem.Kind == "Deployment":
		visitor.VisitDeployment(elem)
	case elem.GroupMatch("batch") && elem.Kind == "Job":
		visitor.VisitJob(elem)
	case elem.GroupMatch("", "core") && elem.Kind == "Pod":
		visitor.VisitPod(elem)
	case elem.GroupMatch("extensions") && elem.Kind == "ReplicaSet":
		visitor.VisitReplicaSet(elem)
	case elem.GroupMatch("", "core") && elem.Kind == "ReplicationController":
		visitor.VisitReplicationController(elem)
	case elem.GroupMatch("apps") && elem.Kind == "StatefulSet":
		visitor.VisitStatefulSet(elem)
	default:
		return fmt.Errorf("no visitor method exists for %v", elem)
	}
	return nil
}

// GroupMatch returns true if and only if elem's group matches one
// of the group arguments
func (elem GroupKindElement) GroupMatch(groups ...string) bool {
	for _, g := range groups {
		if elem.Group == g {
			return true
		}
	}
	return false
}

// NoOpKindVisitor implements KindVisitor with no-op functions.
type NoOpKindVisitor struct{}

var _ KindVisitor = &NoOpKindVisitor{}

func (*NoOpKindVisitor) VisitDaemonSet(kind GroupKindElement)             {}
func (*NoOpKindVisitor) VisitDeployment(kind GroupKindElement)            {}
func (*NoOpKindVisitor) VisitJob(kind GroupKindElement)                   {}
func (*NoOpKindVisitor) VisitPod(kind GroupKindElement)                   {}
func (*NoOpKindVisitor) VisitReplicaSet(kind GroupKindElement)            {}
func (*NoOpKindVisitor) VisitReplicationController(kind GroupKindElement) {}
func (*NoOpKindVisitor) VisitStatefulSet(kind GroupKindElement)           {}
<|endoftext|>


<commit_before>package rollinghash_test

import (
	"hash"
	"log"

	_adler32 "github.com/chmduquesne/rollinghash/adler32"
)

func Example() {
	s := []byte("The quick brown fox jumps over the lazy dog")

	// You can substitute _adler32 for any other subpackage
	classic := hash.Hash32(_adler32.New())
	rolling := _adler32.New()

	// Window len
	n := 16

	// You MUST load an initial window into the rolling hash before being
	// able to roll bytes
	rolling.Write(s[:n])

	// Roll it and compare the result with full re-calculus every time
	for i := n; i < len(s); i++ {

		// Reset and write the window in classic
		classic.Reset()
		classic.Write(s[i-n+1 : i+1])

		// Roll the incoming byte in rolling
		rolling.Roll(s[i])

		// Compare the hashes
		if classic.Sum32() != rolling.Sum32() {
			log.Fatalf("%v: expected %s, got %s",
				s[i-n+1:i+1], classic.Sum32(), rolling.Sum32())
		}
	}

}
<commit_msg>Updating example comments<commit_after>package rollinghash_test

import (
	"hash"
	"log"

	_adler32 "github.com/chmduquesne/rollinghash/adler32"
)

func Example() {
	s := []byte("The quick brown fox jumps over the lazy dog")

	// This example works with adler32, but the api is identical for all
	// other rolling checksums. Consult the documentation of the checksum
	// you are interested in to find similar examples.
	classic := hash.Hash32(_adler32.New())
	rolling := _adler32.New()

	// Window len
	n := 16

	// You MUST load an initial window into the rolling hash before being
	// able to roll bytes
	rolling.Write(s[:n])

	// Roll it and compare the result with full re-calculus every time
	for i := n; i < len(s); i++ {

		// Reset and write the window in classic
		classic.Reset()
		classic.Write(s[i-n+1 : i+1])

		// Roll the incoming byte in rolling
		rolling.Roll(s[i])

		// Compare the hashes
		if classic.Sum32() != rolling.Sum32() {
			log.Fatalf("%v: expected %s, got %s",
				s[i-n+1:i+1], classic.Sum32(), rolling.Sum32())
		}
	}

}
<|endoftext|>


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

// +build freebsd

package cgo

import _ "unsafe"

// Supply environ and __progname, because we don't
// link against the standard FreeBSD crt0.o and the
// libc dynamic library needs them.

//go:linkname _environ environ
//go:linkname _progname __progname

var _environ uintptr
var _progname uintptr
<commit_msg>[dev.cc] runtime/cgo: fix freebsd build?<commit_after>// Copyright 2010 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build freebsd

package cgo

import _ "unsafe"

// Supply environ and __progname, because we don't
// link against the standard FreeBSD crt0.o and the
// libc dynamic library needs them.

//go:linkname _environ environ
//go:linkname _progname __progname

//go:cgo_export_static environ
//go:cgo_export_static __progname

var _environ uintptr
var _progname uintptr
<|endoftext|>


<commit_before>package githubprovider

import (
	"fmt"
	"strings"

	"github.com/google/go-github/github"
	"github.com/hashicorp/terraform/helper/schema"
)

// required field are here for adding a user to the organization
func resourceGithubAddUser() *schema.Resource {
	return &schema.Resource{
		Create: resourceGithubAddUserCreate,
		Read:   resourceGithubAddUserRead,
		Update: resourceGithubAddUserCreate,
		Delete: resourceGithubAddUserDelete,

		Schema: map[string]*schema.Schema{
			"username": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},

			// role is the required for the membership
			// its value is member as default.
			"role": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "member",
			},

			// repos is the repos that the organization has
			"repos": &schema.Schema{
				Type:     schema.TypeList,
				Elem:     &schema.Schema{Type: schema.TypeString},
				Required: true,
			},

			// organization is the name of the organization
			"organization": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},

			"teams": &schema.Schema{
				Type:     schema.TypeList,
				Elem:     &schema.Schema{Type: schema.TypeString},
				Required: true,
			},

			// title is the title of the SSH Key
			"title": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},

			// SSHKey is the public key of the user
			"SSHKey": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
		},
	}
}

// GetTeamIDs gets the teams id of the organization
func GetTeamIDs(client *github.Client, org string, teamNames []string) ([]int, error) {
	currentPage := 1

	var teamIDs []int

	for {
		options := &github.ListOptions{
			PerPage: 100,
			Page:    currentPage,
		}

		teams, resp, err := client.Organizations.ListTeams(org, options)
		if err != nil {
			return nil, err
		}

		if len(teams) == 0 {
			break
		}
		// Iterate over all teams and add current user to related team(s)
		for i, team := range teams {
			for _, teamName := range teamNames {
				if *team.Name == teamName {
					teamIDs = append(teamIDs, *teams[i].ID)
				}
			}
		}

		if currentPage == resp.LastPage {
			break
		}

		currentPage = resp.NextPage
	}

	return teamIDs, nil
}

// resourceGithubAddUserCreate adds the user to the organization & the teams
func resourceGithubAddUserCreate(d *schema.ResourceData, meta interface{}) error {
	clientOrg := meta.(*Clients).OrgClient
	client := meta.(*Clients).UserClient

	org := d.Get("organization").(string)
	user := d.Get("username").(string)
	teamNames := interfaceToStringSlice(d.Get("teams"))
	role := d.Get("role").(string)

	if err := checkScopePermissions(client, user); err != nil {
		return err
	}

	teamIDs, err := GetTeamIDs(clientOrg, org, teamNames)

	optAddOrgMembership := &github.OrganizationAddTeamMembershipOptions{
		Role: role,
	}

	for _, teamID := range teamIDs {
		_, _, err := clientOrg.Organizations.AddTeamMembership(teamID, user, optAddOrgMembership)
		if err != nil {
			return err
		}
	}

	active := "active"

	membership := &github.Membership{
		// state should be active to add the user into organization
		State: &active,

		// Role is the required for the membership
		Role: &role,
	}

	// EditOrgMembership edits the membership for user in specified organization.
	// if user is authenticated, we dont need to set 1.parameter as user
	_, _, err = client.Organizations.EditOrgMembership("", org, membership)
	if err != nil {
		return err
	}

	for _, repo := range interfaceToStringSlice(d.Get("repos")) {
		// Creates a fork for the authenticated user.
		_, _, err = client.Repositories.CreateFork(org, repo, nil)
		if err != nil {
			return err
		}
	}

	title := d.Get("title").(string)
	keySSH := d.Get("SSHKey").(string)

	key := &github.Key{
		Title: &title,
		Key:   &keySSH,
	}

	// CreateKey creates a public key. Requires that you are authenticated via Basic Auth,
	// or OAuth with at least `write:public_key` scope.
	//
	// If SSH key is already set up, when u try to add same SSHKEY then
	//you are gonna get 422: Validation error.
	_, _, err = client.Users.CreateKey(key)
	if err != nil && !isErr422ValidationFailed(err) {
		return err
	}

	d.SetId(user)

	return nil
}

func resourceGithubAddUserRead(d *schema.ResourceData, meta interface{}) error {
	org := d.Get("organization").(string)
	user := d.Get("username").(string)
	role := d.Get("role").(string)
	teamNames := interfaceToStringSlice(d.Get("teams"))
	repos := interfaceToStringSlice(d.Get("repos"))
	fmt.Println("org: %v, user: %v,role: %v, teamnames: %v, repos: %v",
		org,
		user,
		role,
		teamNames,
		repos,
	)

	return nil
}

// resourceGithubAddUserCreate removes the user from the organization & the teams
func resourceGithubAddUserDelete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*Clients).OrgClient

	user := d.Get("username").(string)
	org := d.Get("organization").(string)

	// Removing a user from this list will remove them from all teams and
	// they will no longer have any access to the organization’s repositories.
	_, err := client.Organizations.RemoveMember(org, user)
	return err
}

func getKeyID(client *github.Client, user, title string) (int, error) {
	keys, _, err := client.Users.ListKeys(user, nil)
	if err != nil {
		return 0, err
	}

	for _, key := range keys {
		if *key.Title == title {
			return *key.ID, nil
		}
	}

	return 0, err
}

// interfaceToStringSlice converts the interface to slice of string
func interfaceToStringSlice(s interface{}) []string {
	slice, ok := s.([]interface{})
	if !ok {
		return nil
	}

	sslice := make([]string, len(slice))
	for i := range slice {
		sslice[i] = slice[i].(string)
	}

	return sslice
}

func checkScopePermissions(client *github.Client, username string) error {
	arr, err := getScopes(client, username)
	if err != nil {
		return err
	}

	scopeArr := []string{"write:public_key", "public_repo", "user", "repo"}
	for _, scopeElement := range scopeArr {
		if !(isInArray(arr, scopeElement)) {
			scopeErr := fmt.Errorf("Could not find required scope :", scopeElement)
			return scopeErr
		}
	}

	return nil

}

func getScopes(client *github.Client, username string) ([]string, error) {
	var scopes []string
	_, resp, err := client.Users.Get(username)
	if err != nil {
		return scopes, err
	}

	list := resp.Header.Get("X-Oauth-Scopes")
	scopes = strings.Split(list, ", ")

	return scopes, nil
}

func isInArray(arr []string, item string) bool {
	for _, a := range arr {
		if a == item {
			return true
		}
	}
	return false
}

// isErr422ValidationFailed return true if error contains the string:
// '422 Validation Failed'. This error is special cased so we can ignore it on
// when it occurs during rebuilding of stack template.
func isErr422ValidationFailed(err error) bool {
	return err != nil && strings.Contains(err.Error(), "422 Validation Failed")
}
<commit_msg>go/github: change scope control<commit_after>package githubprovider

import (
	"fmt"
	"strings"

	"github.com/google/go-github/github"
	"github.com/hashicorp/terraform/helper/schema"
)

// required field are here for adding a user to the organization
func resourceGithubAddUser() *schema.Resource {
	return &schema.Resource{
		Create: resourceGithubAddUserCreate,
		Read:   resourceGithubAddUserRead,
		Update: resourceGithubAddUserCreate,
		Delete: resourceGithubAddUserDelete,

		Schema: map[string]*schema.Schema{
			"username": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},

			// role is the required for the membership
			// its value is member as default.
			"role": &schema.Schema{
				Type:     schema.TypeString,
				Optional: true,
				Default:  "member",
			},

			// repos is the repos that the organization has
			"repos": &schema.Schema{
				Type:     schema.TypeList,
				Elem:     &schema.Schema{Type: schema.TypeString},
				Required: true,
			},

			// organization is the name of the organization
			"organization": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},

			"teams": &schema.Schema{
				Type:     schema.TypeList,
				Elem:     &schema.Schema{Type: schema.TypeString},
				Required: true,
			},

			// title is the title of the SSH Key
			"title": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},

			// SSHKey is the public key of the user
			"SSHKey": &schema.Schema{
				Type:     schema.TypeString,
				Required: true,
			},
		},
	}
}

// GetTeamIDs gets the teams id of the organization
func GetTeamIDs(client *github.Client, org string, teamNames []string) ([]int, error) {
	currentPage := 1

	var teamIDs []int

	for {
		options := &github.ListOptions{
			PerPage: 100,
			Page:    currentPage,
		}

		teams, resp, err := client.Organizations.ListTeams(org, options)
		if err != nil {
			return nil, err
		}

		if len(teams) == 0 {
			break
		}
		// Iterate over all teams and add current user to related team(s)
		for i, team := range teams {
			for _, teamName := range teamNames {
				if *team.Name == teamName {
					teamIDs = append(teamIDs, *teams[i].ID)
				}
			}
		}

		if currentPage == resp.LastPage {
			break
		}

		currentPage = resp.NextPage
	}

	return teamIDs, nil
}

// resourceGithubAddUserCreate adds the user to the organization & the teams
func resourceGithubAddUserCreate(d *schema.ResourceData, meta interface{}) error {
	clientOrg := meta.(*Clients).OrgClient
	client := meta.(*Clients).UserClient

	org := d.Get("organization").(string)
	user := d.Get("username").(string)
	teamNames := interfaceToStringSlice(d.Get("teams"))
	role := d.Get("role").(string)

	if err := checkScopePermissions(client, user); err != nil {
		return err
	}

	teamIDs, err := GetTeamIDs(clientOrg, org, teamNames)

	optAddOrgMembership := &github.OrganizationAddTeamMembershipOptions{
		Role: role,
	}

	for _, teamID := range teamIDs {
		_, _, err := clientOrg.Organizations.AddTeamMembership(teamID, user, optAddOrgMembership)
		if err != nil {
			return err
		}
	}

	active := "active"

	membership := &github.Membership{
		// state should be active to add the user into organization
		State: &active,

		// Role is the required for the membership
		Role: &role,
	}

	// EditOrgMembership edits the membership for user in specified organization.
	// if user is authenticated, we dont need to set 1.parameter as user
	_, _, err = client.Organizations.EditOrgMembership("", org, membership)
	if err != nil {
		return err
	}

	for _, repo := range interfaceToStringSlice(d.Get("repos")) {
		// Creates a fork for the authenticated user.
		_, _, err = client.Repositories.CreateFork(org, repo, nil)
		if err != nil {
			return err
		}
	}

	title := d.Get("title").(string)
	keySSH := d.Get("SSHKey").(string)

	key := &github.Key{
		Title: &title,
		Key:   &keySSH,
	}

	// CreateKey creates a public key. Requires that you are authenticated via Basic Auth,
	// or OAuth with at least `write:public_key` scope.
	//
	// If SSH key is already set up, when u try to add same SSHKEY then
	//you are gonna get 422: Validation error.
	_, _, err = client.Users.CreateKey(key)
	if err != nil && !isErr422ValidationFailed(err) {
		return err
	}

	d.SetId(user)

	return nil
}

func resourceGithubAddUserRead(d *schema.ResourceData, meta interface{}) error {
	org := d.Get("organization").(string)
	user := d.Get("username").(string)
	role := d.Get("role").(string)
	teamNames := interfaceToStringSlice(d.Get("teams"))
	repos := interfaceToStringSlice(d.Get("repos"))
	fmt.Println("org: %v, user: %v,role: %v, teamnames: %v, repos: %v",
		org,
		user,
		role,
		teamNames,
		repos,
	)

	return nil
}

// resourceGithubAddUserCreate removes the user from the organization & the teams
func resourceGithubAddUserDelete(d *schema.ResourceData, meta interface{}) error {
	client := meta.(*Clients).OrgClient

	user := d.Get("username").(string)
	org := d.Get("organization").(string)

	// Removing a user from this list will remove them from all teams and
	// they will no longer have any access to the organization’s repositories.
	_, err := client.Organizations.RemoveMember(org, user)
	return err
}

func getKeyID(client *github.Client, user, title string) (int, error) {
	keys, _, err := client.Users.ListKeys(user, nil)
	if err != nil {
		return 0, err
	}

	for _, key := range keys {
		if *key.Title == title {
			return *key.ID, nil
		}
	}

	return 0, err
}

// interfaceToStringSlice converts the interface to slice of string
func interfaceToStringSlice(s interface{}) []string {
	slice, ok := s.([]interface{})
	if !ok {
		return nil
	}

	sslice := make([]string, len(slice))
	for i := range slice {
		sslice[i] = slice[i].(string)
	}

	return sslice
}

func checkScopePermissions(client *github.Client, username string) error {
	arr, err := getScopes(client, username)
	if err != nil {
		return err
	}

	// we created 2-dimensional array for scopes.
	scopeArray := [][]string{
		// if user enables one of this scopes, then its OK to go..
		{"write:public_key", "admin:public_key"},
		{"user"},
		{"repo", "public_repo"},
		{"admin:org"},
	}

	for _, scopeElement := range scopeArray {
		if !(isInArray(arr, scopeElement)) {
			scopeErr := fmt.Errorf("Could not find required scope :", scopeElement)
			return scopeErr
		}
	}

	return nil
}

func getScopes(client *github.Client, username string) ([]string, error) {
	var scopes []string
	_, resp, err := client.Users.Get(username)
	if err != nil {
		return scopes, err
	}

	list := resp.Header.Get("X-Oauth-Scopes")
	scopes = strings.Split(list, ", ")

	return scopes, nil
}

func isInArray(arr, item []string) bool {
	for _, a := range arr {
		for _, i := range item {
			if a == i {
				return true
			}
		}
	}
	return false
}

// isErr422ValidationFailed return true if error contains the string:
// '422 Validation Failed'. This error is special cased so we can ignore it on
// when it occurs during rebuilding of stack template.
func isErr422ValidationFailed(err error) bool {
	return err != nil && strings.Contains(err.Error(), "422 Validation Failed")
}
<|endoftext|>


<commit_before>package http

import (
	"fmt"
	"net/http"
	"path"
	"runtime"
	"time"

	"github.com/getlantern/flashlight/config"
	"github.com/getlantern/flashlight/util"
	"github.com/getlantern/golog"
	"github.com/getlantern/proxiedsites"
	"github.com/getlantern/tarfs"
	"github.com/gorilla/websocket"
	"github.com/skratchdot/open-golang/open"
)

const (
	UIUrl = "http://%s"
	// Assume UI directory to be a sibling directory
	// of flashlight parent dir
	LocalUIDir = "../../../ui/app"
	// Determines the chunking size of messages used by gorilla
	MaxMessageSize = 1024
)

var (
	log      = golog.LoggerFor("http")
	upgrader = &websocket.Upgrader{ReadBufferSize: 1024, WriteBufferSize: MaxMessageSize}
	UIDir    string
)

// represents a UI client
type Client struct {
	// UI websocket connection
	Conn *websocket.Conn

	// Buffered channel of proxied sites
	msg chan *proxiedsites.Config
}

type UIServer struct {
	connections map[*Client]bool // pool of UI client connections
	Addr        string
	// current set of proxied sites
	ProxiedSites *proxiedsites.ProxiedSites

	requests         chan *Client
	connClose        chan *Client // handles client disconnects
	ProxiedSitesChan chan *proxiedsites.Config
	ConfigUpdates    chan *config.Config
}

// Assume the default directory containing UI assets is
// a sibling directory to the current directory
func init() {
	_, curDir, _, ok := runtime.Caller(1)
	if !ok {
		log.Errorf("Unable to determine current directory")
		return
	}
	UIDir = path.Join(curDir, LocalUIDir)
}

// returns the Proxy auto-config file
func servePacFile(w http.ResponseWriter, r *http.Request) {
	pacFile := proxiedsites.GetPacFile()
	http.ServeFile(w, r, pacFile)
}

func serveHome(r *http.ServeMux) {
	UIDirExists, err := util.DirExists(UIDir)
	if err != nil {
		log.Debugf("UI Directory does not exist %s", err)
	}

	if UIDirExists {
		// UI directory found--serve assets directly from it
		log.Debugf("Serving UI assets from directory %s", UIDir)
		r.Handle("/", http.FileServer(http.Dir(UIDir)))
	} else {
		start := time.Now()
		fs, err := tarfs.New(Resources, "../ui/app")
		if err != nil {
			panic(err)
		}
		delta := time.Now().Sub(start)
		log.Debugf("tarfs startup time: %v", delta)
		r.Handle("/", http.FileServer(fs))
	}
}

// when websocket connection is first opened, we write
// proxied sites (global list + additions) - deletions
// the gorilla websocket module automatically
// chunks messages according to WriteBufferSize
func (srv UIServer) writeGlobalList(client *Client) {
	initMsg := proxiedsites.Config{
		Additions: srv.ProxiedSites.GetEntries(),
	}
	// write the JSON encoding of the proxied sites to the
	// websocket connection
	if err := client.Conn.WriteJSON(initMsg); err != nil {
		log.Errorf("Error writing initial proxied sites: %s", err)
	}
}

func (srv UIServer) writeProxiedSites(client *Client) {
	defer client.Conn.Close()
	for {
		select {
		case msg, recv := <-client.msg:
			if !recv {
				// write empty message to close connection on error sending
				client.Conn.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}
			if err := client.Conn.WriteJSON(msg); err != nil {
				srv.connClose <- client
				log.Errorf("Error writing proxied sites to UI instance: %s", err)
				return
			}
		}
	}
}

func (srv UIServer) readClientMessage(client *Client) {
	defer func() {
		srv.connClose <- client
		client.Conn.Close()
	}()
	for {
		var updates proxiedsites.Config
		err := client.Conn.ReadJSON(&updates)
		if err != nil {
			break
		}
		log.Debugf("Received proxied sites update from client: %+v", &updates)
		srv.ProxiedSites.Update(&updates)
		srv.ProxiedSitesChan <- srv.ProxiedSites.GetConfig()
	}
}

// if the openui flag is specified, the UI is automatically
// opened in the default browser
func OpenUI(shouldOpen bool, uiAddr string) {

	uiAddr = fmt.Sprintf(UIUrl, uiAddr)

	if shouldOpen {
		err := open.Run(uiAddr)
		if err != nil {
			log.Errorf("Could not open UI! %s", err)
		}
	}
}

// handles websocket requests from the client
func (srv UIServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var err error

	if r.Method != "GET" {
		http.Error(w, "Method not allowed", 405)
		return
	}
	// Upgrade with a HTTP request returns a websocket connection
	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Error(err)
		return
	}
	client := &Client{Conn: ws, msg: make(chan *proxiedsites.Config)}
	srv.requests <- client
	// write initial proxied sites list
	srv.writeGlobalList(client)
	go srv.writeProxiedSites(client)
	srv.readClientMessage(client)
}

func (srv UIServer) processRequests() {
	for {
		select {
		// wait for YAML config updates
		case cfg := <-srv.ConfigUpdates:
			log.Debugf("Proxied sites updated in config file; applying changes %+v", cfg.Client.ProxiedSites)
			newPs := proxiedsites.New(cfg.Client.ProxiedSites)
			diff := srv.ProxiedSites.Diff(newPs)
			srv.ProxiedSites = newPs
			// write proxied sites update to every UI instance
			for c := range srv.connections {
				select {
				// write the JSON encoding of the proxied sites to the
				// websocket connections
				case c.msg <- diff:
				default:
					close(c.msg)
					delete(srv.connections, c)
				}
			}
		case c := <-srv.requests:
			log.Debug("Adding new UI instance..")
			srv.connections[c] = true
		case c := <-srv.connClose:
			log.Debug("Disconnecting UI instance..")
			if _, ok := srv.connections[c]; ok {
				delete(srv.connections, c)
				close(c.msg)
			}
		}
	}
}

// poll for config updates to the proxiedsites
// with this immediately see flashlight.yaml
// changes in the UI
func (srv UIServer) StartServer() {

	r := http.NewServeMux()

	// initial request, connection close channels and
	// connection pool for this UI server
	srv.connClose = make(chan *Client)
	srv.requests = make(chan *Client)
	srv.connections = make(map[*Client]bool)

	go srv.processRequests()

	r.Handle("/data", srv)
	r.HandleFunc("/proxy_on.pac", servePacFile)
	serveHome(r)

	log.Debugf("Starting UI HTTP server at %s", srv.Addr)
	httpServer := &http.Server{
		Addr:    srv.Addr,
		Handler: r,
	}

	// Run the UI websocket server asynchronously
	go log.Fatal(httpServer.ListenAndServe())
}
<commit_msg>clean up UI server comments<commit_after>package http

import (
	"fmt"
	"net/http"
	"path"
	"runtime"
	"time"

	"github.com/getlantern/flashlight/config"
	"github.com/getlantern/flashlight/util"
	"github.com/getlantern/golog"
	"github.com/getlantern/proxiedsites"
	"github.com/getlantern/tarfs"
	"github.com/gorilla/websocket"
	"github.com/skratchdot/open-golang/open"
)

const (
	UIUrl = "http://%s"
	// Assume UI directory to be a sibling directory
	// of flashlight parent dir
	LocalUIDir = "../../../ui/app"
	// Determines the chunking size of messages used by gorilla
	MaxMessageSize = 1024
)

var (
	log      = golog.LoggerFor("http")
	upgrader = &websocket.Upgrader{ReadBufferSize: 1024, WriteBufferSize: MaxMessageSize}
	UIDir    string
)

// represents a UI client
type Client struct {
	// UI websocket connection
	Conn *websocket.Conn

	// Buffered channel of proxied sites
	msg chan *proxiedsites.Config
}

type UIServer struct {
	connections map[*Client]bool // pool of UI client connections
	Addr        string
	// current set of proxied sites
	ProxiedSites *proxiedsites.ProxiedSites

	requests         chan *Client
	connClose        chan *Client // handles client disconnects
	ProxiedSitesChan chan *proxiedsites.Config
	ConfigUpdates    chan *config.Config
}

// Assume the default directory containing UI assets is
// a sibling directory to the current directory
func init() {
	_, curDir, _, ok := runtime.Caller(1)
	if !ok {
		log.Errorf("Unable to determine current directory")
		return
	}
	UIDir = path.Join(curDir, LocalUIDir)
}

// returns the Proxy auto-config file
func servePacFile(w http.ResponseWriter, r *http.Request) {
	pacFile := proxiedsites.GetPacFile()
	http.ServeFile(w, r, pacFile)
}

func serveHome(r *http.ServeMux) {
	UIDirExists, err := util.DirExists(UIDir)
	if err != nil {
		log.Debugf("UI Directory does not exist %s", err)
	}

	if UIDirExists {
		// UI directory found--serve assets directly from it
		log.Debugf("Serving UI assets from directory %s", UIDir)
		r.Handle("/", http.FileServer(http.Dir(UIDir)))
	} else {
		start := time.Now()
		fs, err := tarfs.New(Resources, "../ui/app")
		if err != nil {
			panic(err)
		}
		delta := time.Now().Sub(start)
		log.Debugf("tarfs startup time: %v", delta)
		r.Handle("/", http.FileServer(fs))
	}
}

// when websocket connection is first opened, we write
// proxied sites (global list + additions) - deletions
// the gorilla websocket module automatically
// chunks messages according to WriteBufferSize
func (srv UIServer) writeGlobalList(client *Client) {
	initMsg := proxiedsites.Config{
		Additions: srv.ProxiedSites.GetEntries(),
	}
	// write the JSON encoding of the proxied sites to the
	// websocket connection
	if err := client.Conn.WriteJSON(initMsg); err != nil {
		log.Errorf("Error writing initial proxied sites: %s", err)
	}
}

func (srv UIServer) writeProxiedSites(client *Client) {
	defer client.Conn.Close()
	for {
		select {
		case msg, recv := <-client.msg:
			if !recv {
				// write empty message to close connection on error sending
				client.Conn.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}
			if err := client.Conn.WriteJSON(msg); err != nil {
				srv.connClose <- client
				log.Errorf("Error writing proxied sites to UI instance: %s", err)
				return
			}
		}
	}
}

func (srv UIServer) readClientMessage(client *Client) {
	defer func() {
		srv.connClose <- client
		client.Conn.Close()
	}()
	for {
		var updates proxiedsites.Config
		err := client.Conn.ReadJSON(&updates)
		if err != nil {
			break
		}
		log.Debugf("Received proxied sites update from client: %+v", &updates)
		srv.ProxiedSites.Update(&updates)
		srv.ProxiedSitesChan <- srv.ProxiedSites.GetConfig()
	}
}

// if the openui flag is specified, the UI is automatically
// opened in the default browser
func OpenUI(shouldOpen bool, uiAddr string) {

	uiAddr = fmt.Sprintf(UIUrl, uiAddr)
	if shouldOpen {
		err := open.Run(uiAddr)
		if err != nil {
			log.Errorf("Could not open UI! %s", err)
		}
	}
}

// handles websocket requests from the client
func (srv UIServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	var err error

	if r.Method != "GET" {
		http.Error(w, "Method not allowed", 405)
		return
	}
	// Upgrade with a HTTP request returns a websocket connection
	ws, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Error(err)
		return
	}
	client := &Client{Conn: ws, msg: make(chan *proxiedsites.Config)}
	srv.requests <- client
	// write initial proxied sites list
	srv.writeGlobalList(client)
	go srv.writeProxiedSites(client)
	srv.readClientMessage(client)
}

func (srv UIServer) processRequests() {
	for {
		select {
		// wait for YAML config updates
		case cfg := <-srv.ConfigUpdates:
			log.Debugf("Proxied sites updated in config file; applying changes %+v", cfg.Client.ProxiedSites)
			newPs := proxiedsites.New(cfg.Client.ProxiedSites)
			diff := srv.ProxiedSites.Diff(newPs)
			srv.ProxiedSites = newPs
			// write proxied sites update to every UI instance
			for c := range srv.connections {
				select {
				// write the JSON encoding of the proxied sites to the
				// websocket connections
				case c.msg <- diff:
				default:
					close(c.msg)
					delete(srv.connections, c)
				}
			}
		case c := <-srv.requests:
			log.Debug("Adding new UI instance..")
			srv.connections[c] = true
		case c := <-srv.connClose:
			log.Debug("Disconnecting UI instance..")
			if _, ok := srv.connections[c]; ok {
				delete(srv.connections, c)
				close(c.msg)
			}
		}
	}
}

func (srv UIServer) StartServer() {

	r := http.NewServeMux()

	// initial request, connection close channels and
	// connection pool for this UI server
	srv.connClose = make(chan *Client)
	srv.requests = make(chan *Client)
	srv.connections = make(map[*Client]bool)

	go srv.processRequests()

	r.Handle("/data", srv)
	r.HandleFunc("/proxy_on.pac", servePacFile)
	serveHome(r)

	log.Debugf("Starting UI HTTP server at %s", srv.Addr)
	httpServer := &http.Server{
		Addr:    srv.Addr,
		Handler: r,
	}

	// Run the UI websocket server asynchronously
	go log.Fatal(httpServer.ListenAndServe())
}
<|endoftext|>


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

import (
	"context"
	"errors"
	"io"
	"sync"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
	"google.golang.org/grpc"

	mcp "istio.io/api/mcp/v1alpha1"
	"istio.io/istio/pkg/mcp/internal/test"
	"istio.io/istio/pkg/mcp/testing/monitoring"
)

type clientHarness struct {
	grpc.ClientStream
	*sinkTestHarness
}

func (h *clientHarness) EstablishResourceStream(ctx context.Context, opts ...grpc.CallOption) (mcp.ResourceSource_EstablishResourceStreamClient, error) {
	return h, h.openError()
}

// avoid ambiguity between grpc.ClientStream and test.sinkTestHarness
func (h *clientHarness) Context() context.Context {
	return h.sinkTestHarness.Context()
}

func TestClientSink(t *testing.T) {
	h := &clientHarness{
		sinkTestHarness: newSinkTestHarness(),
	}
	options := &Options{
		CollectionOptions: CollectionOptionsFromSlice(test.SupportedCollections),
		Updater:           h,
		ID:                test.NodeID,
		Metadata:          test.NodeMetadata,
		Reporter:          monitoring.NewInMemoryStatsContext(),
	}
	c := NewClient(h, options)

	ctx, cancel := context.WithCancel(context.Background())

	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		c.Run(ctx)
		wg.Done()
	}()

	defer func() {
		h.setOpenError(errors.New("done"))
		h.recvErrorChan <- io.EOF
		h.sendErrorChan <- io.EOF
		cancel()
		wg.Wait()
	}()

	want := &Change{
		Collection: test.FakeType0Collection,
		Objects: []*Object{
			{
				TypeURL:  test.FakeType0TypeURL,
				Metadata: test.Type0A[0].Metadata,
				Body:     test.Type0A[0].Proto,
			},
			{
				TypeURL:  test.FakeType0TypeURL,
				Metadata: test.Type0B[0].Metadata,
				Body:     test.Type0B[0].Proto,
			},
		},
	}

	h.resourcesChan <- &mcp.Resources{
		Collection: test.FakeType0Collection,
		Nonce:      "n0",
		Resources: []mcp.Resource{
			*test.Type0A[0].Resource,
			*test.Type0B[0].Resource,
		},
	}

	<-h.changeUpdatedChans
	h.mu.Lock()
	got := h.changes[test.FakeType0Collection]
	h.mu.Unlock()
	if diff := cmp.Diff(got, want); diff != "" {
		t.Fatalf("wrong change on first update: \n got %v \nwant %v \ndiff %v", got, want, diff)
	}

	prevDelay := reestablishStreamDelay
	reestablishStreamDelay = 100 * time.Millisecond
	defer func() { reestablishStreamDelay = prevDelay }()

	reconnectChan := make(chan struct{}, 10)
	c.reconnectTestProbe = func() {
		reconnectChan <- struct{}{}
	}

	h.changes[test.FakeType0Collection] = nil

	// force a disconnect and unsuccessful reconnects
	h.setOpenError(errors.New("fake connection error"))
	h.recvErrorChan <- errors.New("non-EOF error")
	<-reconnectChan

	// allow connection to succeed
	h.setOpenError(nil)
	h.recvErrorChan <- io.EOF
	<-reconnectChan

	want = &Change{
		Collection: test.FakeType0Collection,
		Objects: []*Object{
			{
				TypeURL:  test.FakeType0TypeURL,
				Metadata: test.Type0A[1].Metadata,
				Body:     test.Type0A[1].Proto,
			},
			{
				TypeURL:  test.FakeType0TypeURL,
				Metadata: test.Type0B[1].Metadata,
				Body:     test.Type0B[1].Proto,
			},
		},
	}

	h.resourcesChan <- &mcp.Resources{
		Collection: test.FakeType0Collection,
		Nonce:      "n1",
		Resources: []mcp.Resource{
			*test.Type0A[1].Resource,
			*test.Type0B[1].Resource,
		},
	}

	<-h.changeUpdatedChans

	h.mu.Lock()
	got = h.changes[test.FakeType0Collection]
	h.mu.Unlock()

	if diff := cmp.Diff(got, want); diff != "" {
		t.Fatalf("wrong change on second update: \n got %v \nwant %v \ndiff %v", got, want, diff)
	}
}
<commit_msg>fix TestClientSink flake (#21261)<commit_after>// Copyright 2018 Istio 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 sink

import (
	"context"
	"errors"
	"io"
	"sync"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
	"google.golang.org/grpc"

	mcp "istio.io/api/mcp/v1alpha1"
	"istio.io/istio/pkg/mcp/internal/test"
	"istio.io/istio/pkg/mcp/testing/monitoring"
)

type clientHarness struct {
	grpc.ClientStream
	*sinkTestHarness
}

func (h *clientHarness) EstablishResourceStream(ctx context.Context, opts ...grpc.CallOption) (mcp.ResourceSource_EstablishResourceStreamClient, error) {
	return h, h.openError()
}

// avoid ambiguity between grpc.ClientStream and test.sinkTestHarness
func (h *clientHarness) Context() context.Context {
	return h.sinkTestHarness.Context()
}

func TestClientSink(t *testing.T) {
	h := &clientHarness{
		sinkTestHarness: newSinkTestHarness(),
	}
	options := &Options{
		CollectionOptions: CollectionOptionsFromSlice(test.SupportedCollections),
		Updater:           h,
		ID:                test.NodeID,
		Metadata:          test.NodeMetadata,
		Reporter:          monitoring.NewInMemoryStatsContext(),
	}
	c := NewClient(h, options)

	ctx, cancel := context.WithCancel(context.Background())

	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		c.Run(ctx)
		wg.Done()
	}()

	defer func() {
		h.setOpenError(errors.New("done"))
		h.recvErrorChan <- io.EOF
		h.sendErrorChan <- io.EOF
		cancel()
		wg.Wait()
	}()

	want := &Change{
		Collection: test.FakeType0Collection,
		Objects: []*Object{
			{
				TypeURL:  test.FakeType0TypeURL,
				Metadata: test.Type0A[0].Metadata,
				Body:     test.Type0A[0].Proto,
			},
			{
				TypeURL:  test.FakeType0TypeURL,
				Metadata: test.Type0B[0].Metadata,
				Body:     test.Type0B[0].Proto,
			},
		},
	}

	h.resourcesChan <- &mcp.Resources{
		Collection: test.FakeType0Collection,
		Nonce:      "n0",
		Resources: []mcp.Resource{
			*test.Type0A[0].Resource,
			*test.Type0B[0].Resource,
		},
	}

	<-h.changeUpdatedChans
	h.mu.Lock()
	got := h.changes[test.FakeType0Collection]
	h.mu.Unlock()
	if diff := cmp.Diff(got, want); diff != "" {
		t.Fatalf("wrong change on first update: \n got %v \nwant %v \ndiff %v", got, want, diff)
	}

	prevDelay := reestablishStreamDelay
	reestablishStreamDelay = 100 * time.Millisecond
	defer func() { reestablishStreamDelay = prevDelay }()

	reconnectChan := make(chan struct{}, 1)
	c.reconnectTestProbe = func() {
		select {
		case reconnectChan <- struct{}{}:
		default:
		}
	}

	h.changes[test.FakeType0Collection] = nil

	// force a disconnect and unsuccessful reconnects
	h.setOpenError(errors.New("fake connection error"))
	h.recvErrorChan <- errors.New("non-EOF error")
	<-reconnectChan

	// allow connection to succeed
	h.setOpenError(nil)
	h.recvErrorChan <- io.EOF
	<-reconnectChan

	want = &Change{
		Collection: test.FakeType0Collection,
		Objects: []*Object{
			{
				TypeURL:  test.FakeType0TypeURL,
				Metadata: test.Type0A[1].Metadata,
				Body:     test.Type0A[1].Proto,
			},
			{
				TypeURL:  test.FakeType0TypeURL,
				Metadata: test.Type0B[1].Metadata,
				Body:     test.Type0B[1].Proto,
			},
		},
	}

	h.resourcesChan <- &mcp.Resources{
		Collection: test.FakeType0Collection,
		Nonce:      "n1",
		Resources: []mcp.Resource{
			*test.Type0A[1].Resource,
			*test.Type0B[1].Resource,
		},
	}

	<-h.changeUpdatedChans

	h.mu.Lock()
	got = h.changes[test.FakeType0Collection]
	h.mu.Unlock()

	if diff := cmp.Diff(got, want); diff != "" {
		t.Fatalf("wrong change on second update: \n got %v \nwant %v \ndiff %v", got, want, diff)
	}
}
<|endoftext|>