mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 10:33:35 +00:00
9
vendor/k8s.io/utils/.travis.yml
generated
vendored
9
vendor/k8s.io/utils/.travis.yml
generated
vendored
@ -1,9 +0,0 @@
|
||||
language: go
|
||||
dist: xenial
|
||||
go:
|
||||
- 1.9.x
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
go_import_path: k8s.io/utils
|
||||
script:
|
||||
- make verify
|
9
vendor/k8s.io/utils/CONTRIBUTING.md
generated
vendored
9
vendor/k8s.io/utils/CONTRIBUTING.md
generated
vendored
@ -1,9 +0,0 @@
|
||||
# Contributing
|
||||
|
||||
Thanks for taking the time to join our community and start contributing!
|
||||
|
||||
The [Contributor Guide](https://github.com/kubernetes/community/blob/master/contributors/guide/README.md)
|
||||
provides detailed instructions on how to get your ideas and bug fixes seen and accepted.
|
||||
|
||||
Please remember to sign the [CNCF CLA](https://github.com/kubernetes/community/blob/master/CLA.md) and
|
||||
read and observe the [Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
31
vendor/k8s.io/utils/HOWTOMOVE.md
generated
vendored
31
vendor/k8s.io/utils/HOWTOMOVE.md
generated
vendored
@ -1,31 +0,0 @@
|
||||
# How to move a utility pkg from other kubernetes repos
|
||||
|
||||
It has 2 steps to move a pkg from other Kubernetes repos to `k8s.io/utils` repo:
|
||||
- copy the pkg to `k8s.io/utils` repo
|
||||
- update the import paths and `vendor/` in the repos that refer this pkg
|
||||
|
||||
## Copy the pkg to `k8s.io/utils` repo
|
||||
|
||||
Copying should preserve all the git history associated with it.
|
||||
[Here](http://gbayer.com/development/moving-files-from-one-git-repository-to-another-preserving-history/) is a working approach.
|
||||
Note: You may need to use `--allow-unrelated-histories` if you get error when running `git pull` following the post above.
|
||||
|
||||
Then, you may need to restructure the package to make sure it has the following structure.
|
||||
|
||||
.
|
||||
├── doc.go # Description for this package
|
||||
├── <utilname1>.go # utility go file
|
||||
├── <utilname>_test.go # go unit tests
|
||||
└── testing # All the testing framework
|
||||
└── fake_<utilname>.go # Testing framework go file
|
||||
|
||||
[#5](https://github.com/kubernetes/utils/pull/5) is an example for this step.
|
||||
|
||||
## Update the repos that refer the pkg
|
||||
|
||||
You should update the import paths.
|
||||
Then follow [this doc](https://github.com/kubernetes/community/blob/master/contributors/devel/godep.md) to update `vendor/` and `Godeps/`.
|
||||
|
||||
You may want to run `make bazel-test` to make sure all new references work.
|
||||
|
||||
[kubernetes/kubernetes#49234](https://github.com/kubernetes/kubernetes/pull/49234) is an example for this step.
|
37
vendor/k8s.io/utils/Makefile
generated
vendored
37
vendor/k8s.io/utils/Makefile
generated
vendored
@ -1,37 +0,0 @@
|
||||
# Copyright 2018 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.
|
||||
|
||||
.PHONY: verify
|
||||
verify: depend verify-fmt verify-lint vet
|
||||
go test -v -race ./...
|
||||
|
||||
.PHONY: depend
|
||||
depend:
|
||||
go get -t -v ./...
|
||||
|
||||
.PHONY: verify-fmt
|
||||
verify-fmt:
|
||||
./hack/verify-gofmt.sh
|
||||
|
||||
.PHONY: verify-lint
|
||||
verify-lint:
|
||||
./hack/verify-golint.sh
|
||||
|
||||
.PHONY: vet
|
||||
vet:
|
||||
go tool vet .
|
||||
|
||||
.PHONY: update-fmt
|
||||
update-fmt:
|
||||
gofmt -s -w .
|
18
vendor/k8s.io/utils/OWNERS
generated
vendored
18
vendor/k8s.io/utils/OWNERS
generated
vendored
@ -1,18 +0,0 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
approvers:
|
||||
- apelisse
|
||||
- dashpole
|
||||
- deads2k
|
||||
- lavalamp
|
||||
- mengqiy
|
||||
- thockin
|
||||
reviewers:
|
||||
- apelisse
|
||||
- dashpole
|
||||
- deads2k
|
||||
- lavalamp
|
||||
- mengqiy
|
||||
- thockin
|
||||
- andrewsykim
|
||||
- cheftako
|
||||
- mcrute
|
69
vendor/k8s.io/utils/README.md
generated
vendored
69
vendor/k8s.io/utils/README.md
generated
vendored
@ -1,69 +0,0 @@
|
||||
# Utils
|
||||
|
||||
[![Build Status]](https://travis-ci.org/kubernetes/utils) [](https://godoc.org/k8s.io/utils)
|
||||
|
||||
A set of Go libraries that provide low-level,
|
||||
kubernetes-independent packages supplementing the [Go
|
||||
standard libs].
|
||||
|
||||
## Purpose
|
||||
|
||||
As Kubernetes grows and spins functionality out of its
|
||||
[core] and into cooperating repositories like
|
||||
[apiserver], [kubectl], [kubeadm], etc., the need
|
||||
arises for leaf repositories to house shared code and
|
||||
avoid cycles in repository relationships.
|
||||
|
||||
This repository is intended to hold shared utilities
|
||||
with no Kubernetes dependence that may be of interest
|
||||
to any Go project. See these [instructions for moving]
|
||||
an existing package to this repository.
|
||||
|
||||
|
||||
## Criteria for adding code here
|
||||
|
||||
- Used by multiple Kubernetes repositories.
|
||||
|
||||
- Full unit test coverage.
|
||||
|
||||
- Go tools compliant (`go get`, `go test`, etc.).
|
||||
|
||||
- Complex enough to be worth vendoring, rather than copying.
|
||||
|
||||
- Stable, or backward compatible, API.
|
||||
|
||||
- _No dependence on any Kubernetes repository_.
|
||||
|
||||
## Libraries
|
||||
|
||||
- [Exec](/exec) provides an interface for `os/exec`. It makes it easier
|
||||
to mock and replace in tests, especially with
|
||||
the [FakeExec](exec/testing/fake_exec.go) struct.
|
||||
|
||||
- [Temp](/temp) provides an interface to create temporary directories. It also
|
||||
provides a [FakeDir](temp/temptest) implementation to replace in tests.
|
||||
|
||||
- [Clock](/clock) provides an interface for time-based operations. It allows
|
||||
mocking time for testing.
|
||||
|
||||
- [Pointers](/pointers) provides some functions for pointer-based operations.
|
||||
|
||||
- [Io](/io) provides interfaces for working with file IO. Currently it provides
|
||||
functionality for consistently reading a file.
|
||||
|
||||
- [NSEnter](/nsenter) provies interfaces for executing and interacting with
|
||||
processes running within a namespace.
|
||||
|
||||
[Build Status]: https://travis-ci.org/kubernetes/utils.svg?branch=master
|
||||
[Go standard libs]: https://golang.org/pkg/#stdlib
|
||||
[api]: https://github.com/kubernetes/api
|
||||
[apiserver]: https://github.com/kubernetes/apiserver
|
||||
[core]: https://github.com/kubernetes/kubernetes
|
||||
[ingress]: https://github.com/kubernetes/ingress
|
||||
[kubeadm]: https://github.com/kubernetes/kubeadm
|
||||
[kubectl]: https://github.com/kubernetes/kubectl
|
||||
[instructions for moving]: ./HOWTOMOVE.md
|
||||
|
||||
## Contributing
|
||||
|
||||
Please see [CONTRIBUTING.md](CONTRIBUTING.md) for instructions on how to contribute.
|
13
vendor/k8s.io/utils/SECURITY_CONTACTS
generated
vendored
13
vendor/k8s.io/utils/SECURITY_CONTACTS
generated
vendored
@ -1,13 +0,0 @@
|
||||
# Defined below are the security contacts for this repo.
|
||||
#
|
||||
# They are the contact point for the Product Security Team to reach out
|
||||
# to for triaging and handling of incoming issues.
|
||||
#
|
||||
# The below names agree to abide by the
|
||||
# [Embargo Policy](https://github.com/kubernetes/sig-release/blob/master/security-release-process-documentation/security-release-process.md#embargo-policy)
|
||||
# and will be removed and replaced if they violate that agreement.
|
||||
#
|
||||
# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
|
||||
# INSTRUCTIONS AT https://kubernetes.io/security/
|
||||
|
||||
liggitt
|
97
vendor/k8s.io/utils/clock/clock.go
generated
vendored
97
vendor/k8s.io/utils/clock/clock.go
generated
vendored
@ -1,97 +0,0 @@
|
||||
/*
|
||||
Copyright 2014 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 clock
|
||||
|
||||
import "time"
|
||||
|
||||
// Clock allows for injecting fake or real clocks into code that
|
||||
// needs to do arbitrary things based on time.
|
||||
type Clock interface {
|
||||
Now() time.Time
|
||||
Since(time.Time) time.Duration
|
||||
After(d time.Duration) <-chan time.Time
|
||||
NewTimer(d time.Duration) Timer
|
||||
Sleep(d time.Duration)
|
||||
Tick(d time.Duration) <-chan time.Time
|
||||
}
|
||||
|
||||
var _ = Clock(RealClock{})
|
||||
|
||||
// RealClock really calls time.Now()
|
||||
type RealClock struct{}
|
||||
|
||||
// Now returns the current time.
|
||||
func (RealClock) Now() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
// Since returns time since the specified timestamp.
|
||||
func (RealClock) Since(ts time.Time) time.Duration {
|
||||
return time.Since(ts)
|
||||
}
|
||||
|
||||
// After is the same as time.After(d).
|
||||
func (RealClock) After(d time.Duration) <-chan time.Time {
|
||||
return time.After(d)
|
||||
}
|
||||
|
||||
// NewTimer is the same as time.NewTimer(d)
|
||||
func (RealClock) NewTimer(d time.Duration) Timer {
|
||||
return &realTimer{
|
||||
timer: time.NewTimer(d),
|
||||
}
|
||||
}
|
||||
|
||||
// Tick is the same as time.Tick(d)
|
||||
func (RealClock) Tick(d time.Duration) <-chan time.Time {
|
||||
return time.Tick(d)
|
||||
}
|
||||
|
||||
// Sleep is the same as time.Sleep(d)
|
||||
func (RealClock) Sleep(d time.Duration) {
|
||||
time.Sleep(d)
|
||||
}
|
||||
|
||||
// Timer allows for injecting fake or real timers into code that
|
||||
// needs to do arbitrary things based on time.
|
||||
type Timer interface {
|
||||
C() <-chan time.Time
|
||||
Stop() bool
|
||||
Reset(d time.Duration) bool
|
||||
}
|
||||
|
||||
var _ = Timer(&realTimer{})
|
||||
|
||||
// realTimer is backed by an actual time.Timer.
|
||||
type realTimer struct {
|
||||
timer *time.Timer
|
||||
}
|
||||
|
||||
// C returns the underlying timer's channel.
|
||||
func (r *realTimer) C() <-chan time.Time {
|
||||
return r.timer.C
|
||||
}
|
||||
|
||||
// Stop calls Stop() on the underlying timer.
|
||||
func (r *realTimer) Stop() bool {
|
||||
return r.timer.Stop()
|
||||
}
|
||||
|
||||
// Reset calls Reset() on the underlying timer.
|
||||
func (r *realTimer) Reset(d time.Duration) bool {
|
||||
return r.timer.Reset(d)
|
||||
}
|
274
vendor/k8s.io/utils/clock/testing/fake_clock.go
generated
vendored
274
vendor/k8s.io/utils/clock/testing/fake_clock.go
generated
vendored
@ -1,274 +0,0 @@
|
||||
/*
|
||||
Copyright 2014 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 testing
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
var (
|
||||
_ = clock.Clock(&FakeClock{})
|
||||
_ = clock.Clock(&IntervalClock{})
|
||||
)
|
||||
|
||||
// FakeClock implements clock.Clock, but returns an arbitrary time.
|
||||
type FakeClock struct {
|
||||
lock sync.RWMutex
|
||||
time time.Time
|
||||
|
||||
// waiters are waiting for the fake time to pass their specified time
|
||||
waiters []*fakeClockWaiter
|
||||
}
|
||||
|
||||
type fakeClockWaiter struct {
|
||||
targetTime time.Time
|
||||
stepInterval time.Duration
|
||||
skipIfBlocked bool
|
||||
destChan chan time.Time
|
||||
fired bool
|
||||
}
|
||||
|
||||
// NewFakeClock constructs a fake clock set to the provided time.
|
||||
func NewFakeClock(t time.Time) *FakeClock {
|
||||
return &FakeClock{
|
||||
time: t,
|
||||
}
|
||||
}
|
||||
|
||||
// Now returns f's time.
|
||||
func (f *FakeClock) Now() time.Time {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
return f.time
|
||||
}
|
||||
|
||||
// Since returns time since the time in f.
|
||||
func (f *FakeClock) Since(ts time.Time) time.Duration {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
return f.time.Sub(ts)
|
||||
}
|
||||
|
||||
// After is the fake version of time.After(d).
|
||||
func (f *FakeClock) After(d time.Duration) <-chan time.Time {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
stopTime := f.time.Add(d)
|
||||
ch := make(chan time.Time, 1) // Don't block!
|
||||
f.waiters = append(f.waiters, &fakeClockWaiter{
|
||||
targetTime: stopTime,
|
||||
destChan: ch,
|
||||
})
|
||||
return ch
|
||||
}
|
||||
|
||||
// NewTimer constructs a fake timer, akin to time.NewTimer(d).
|
||||
func (f *FakeClock) NewTimer(d time.Duration) clock.Timer {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
stopTime := f.time.Add(d)
|
||||
ch := make(chan time.Time, 1) // Don't block!
|
||||
timer := &fakeTimer{
|
||||
fakeClock: f,
|
||||
waiter: fakeClockWaiter{
|
||||
targetTime: stopTime,
|
||||
destChan: ch,
|
||||
},
|
||||
}
|
||||
f.waiters = append(f.waiters, &timer.waiter)
|
||||
return timer
|
||||
}
|
||||
|
||||
// Tick constructs a fake ticker, akin to time.Tick
|
||||
func (f *FakeClock) Tick(d time.Duration) <-chan time.Time {
|
||||
if d <= 0 {
|
||||
return nil
|
||||
}
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
tickTime := f.time.Add(d)
|
||||
ch := make(chan time.Time, 1) // hold one tick
|
||||
f.waiters = append(f.waiters, &fakeClockWaiter{
|
||||
targetTime: tickTime,
|
||||
stepInterval: d,
|
||||
skipIfBlocked: true,
|
||||
destChan: ch,
|
||||
})
|
||||
|
||||
return ch
|
||||
}
|
||||
|
||||
// Step moves the clock by Duration and notifies anyone that's called After,
|
||||
// Tick, or NewTimer.
|
||||
func (f *FakeClock) Step(d time.Duration) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
f.setTimeLocked(f.time.Add(d))
|
||||
}
|
||||
|
||||
// SetTime sets the time.
|
||||
func (f *FakeClock) SetTime(t time.Time) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
f.setTimeLocked(t)
|
||||
}
|
||||
|
||||
// Actually changes the time and checks any waiters. f must be write-locked.
|
||||
func (f *FakeClock) setTimeLocked(t time.Time) {
|
||||
f.time = t
|
||||
newWaiters := make([]*fakeClockWaiter, 0, len(f.waiters))
|
||||
for i := range f.waiters {
|
||||
w := f.waiters[i]
|
||||
if !w.targetTime.After(t) {
|
||||
|
||||
if w.skipIfBlocked {
|
||||
select {
|
||||
case w.destChan <- t:
|
||||
w.fired = true
|
||||
default:
|
||||
}
|
||||
} else {
|
||||
w.destChan <- t
|
||||
w.fired = true
|
||||
}
|
||||
|
||||
if w.stepInterval > 0 {
|
||||
for !w.targetTime.After(t) {
|
||||
w.targetTime = w.targetTime.Add(w.stepInterval)
|
||||
}
|
||||
newWaiters = append(newWaiters, w)
|
||||
}
|
||||
|
||||
} else {
|
||||
newWaiters = append(newWaiters, f.waiters[i])
|
||||
}
|
||||
}
|
||||
f.waiters = newWaiters
|
||||
}
|
||||
|
||||
// HasWaiters returns true if After has been called on f but not yet satisfied (so you can
|
||||
// write race-free tests).
|
||||
func (f *FakeClock) HasWaiters() bool {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
return len(f.waiters) > 0
|
||||
}
|
||||
|
||||
// Sleep is akin to time.Sleep
|
||||
func (f *FakeClock) Sleep(d time.Duration) {
|
||||
f.Step(d)
|
||||
}
|
||||
|
||||
// IntervalClock implements clock.Clock, but each invocation of Now steps the clock forward the specified duration
|
||||
type IntervalClock struct {
|
||||
Time time.Time
|
||||
Duration time.Duration
|
||||
}
|
||||
|
||||
// Now returns i's time.
|
||||
func (i *IntervalClock) Now() time.Time {
|
||||
i.Time = i.Time.Add(i.Duration)
|
||||
return i.Time
|
||||
}
|
||||
|
||||
// Since returns time since the time in i.
|
||||
func (i *IntervalClock) Since(ts time.Time) time.Duration {
|
||||
return i.Time.Sub(ts)
|
||||
}
|
||||
|
||||
// After is unimplemented, will panic.
|
||||
// TODO: make interval clock use FakeClock so this can be implemented.
|
||||
func (*IntervalClock) After(d time.Duration) <-chan time.Time {
|
||||
panic("IntervalClock doesn't implement After")
|
||||
}
|
||||
|
||||
// NewTimer is unimplemented, will panic.
|
||||
// TODO: make interval clock use FakeClock so this can be implemented.
|
||||
func (*IntervalClock) NewTimer(d time.Duration) clock.Timer {
|
||||
panic("IntervalClock doesn't implement NewTimer")
|
||||
}
|
||||
|
||||
// Tick is unimplemented, will panic.
|
||||
// TODO: make interval clock use FakeClock so this can be implemented.
|
||||
func (*IntervalClock) Tick(d time.Duration) <-chan time.Time {
|
||||
panic("IntervalClock doesn't implement Tick")
|
||||
}
|
||||
|
||||
// Sleep is unimplemented, will panic.
|
||||
func (*IntervalClock) Sleep(d time.Duration) {
|
||||
panic("IntervalClock doesn't implement Sleep")
|
||||
}
|
||||
|
||||
var _ = clock.Timer(&fakeTimer{})
|
||||
|
||||
// fakeTimer implements clock.Timer based on a FakeClock.
|
||||
type fakeTimer struct {
|
||||
fakeClock *FakeClock
|
||||
waiter fakeClockWaiter
|
||||
}
|
||||
|
||||
// C returns the channel that notifies when this timer has fired.
|
||||
func (f *fakeTimer) C() <-chan time.Time {
|
||||
return f.waiter.destChan
|
||||
}
|
||||
|
||||
// Stop stops the timer and returns true if the timer has not yet fired, or false otherwise.
|
||||
func (f *fakeTimer) Stop() bool {
|
||||
f.fakeClock.lock.Lock()
|
||||
defer f.fakeClock.lock.Unlock()
|
||||
|
||||
newWaiters := make([]*fakeClockWaiter, 0, len(f.fakeClock.waiters))
|
||||
for i := range f.fakeClock.waiters {
|
||||
w := f.fakeClock.waiters[i]
|
||||
if w != &f.waiter {
|
||||
newWaiters = append(newWaiters, w)
|
||||
}
|
||||
}
|
||||
|
||||
f.fakeClock.waiters = newWaiters
|
||||
|
||||
return !f.waiter.fired
|
||||
}
|
||||
|
||||
// Reset resets the timer to the fake clock's "now" + d. It returns true if the timer has not yet
|
||||
// fired, or false otherwise.
|
||||
func (f *fakeTimer) Reset(d time.Duration) bool {
|
||||
f.fakeClock.lock.Lock()
|
||||
defer f.fakeClock.lock.Unlock()
|
||||
|
||||
active := !f.waiter.fired
|
||||
|
||||
f.waiter.fired = false
|
||||
f.waiter.targetTime = f.fakeClock.time.Add(d)
|
||||
|
||||
var isWaiting bool
|
||||
for i := range f.fakeClock.waiters {
|
||||
w := f.fakeClock.waiters[i]
|
||||
if w == &f.waiter {
|
||||
isWaiting = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isWaiting {
|
||||
f.fakeClock.waiters = append(f.fakeClock.waiters, &f.waiter)
|
||||
}
|
||||
|
||||
return active
|
||||
}
|
274
vendor/k8s.io/utils/clock/testing/fake_clock_test.go
generated
vendored
274
vendor/k8s.io/utils/clock/testing/fake_clock_test.go
generated
vendored
@ -1,274 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 testing
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestFakeClock(t *testing.T) {
|
||||
startTime := time.Now()
|
||||
tc := NewFakeClock(startTime)
|
||||
tc.Step(time.Second)
|
||||
now := tc.Now()
|
||||
if now.Sub(startTime) != time.Second {
|
||||
t.Errorf("input: %s now=%s gap=%s expected=%s", startTime, now, now.Sub(startTime), time.Second)
|
||||
}
|
||||
|
||||
tt := tc.Now()
|
||||
tc.SetTime(tt.Add(time.Hour))
|
||||
if tc.Now().Sub(tt) != time.Hour {
|
||||
t.Errorf("input: %s now=%s gap=%s expected=%s", tt, tc.Now(), tc.Now().Sub(tt), time.Hour)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFakeClockSleep(t *testing.T) {
|
||||
startTime := time.Now()
|
||||
tc := NewFakeClock(startTime)
|
||||
tc.Sleep(time.Duration(1) * time.Hour)
|
||||
now := tc.Now()
|
||||
if now.Sub(startTime) != time.Hour {
|
||||
t.Errorf("Fake sleep failed, expected time to advance by one hour, instead, its %v", now.Sub(startTime))
|
||||
}
|
||||
}
|
||||
|
||||
func TestFakeAfter(t *testing.T) {
|
||||
tc := NewFakeClock(time.Now())
|
||||
if tc.HasWaiters() {
|
||||
t.Errorf("unexpected waiter?")
|
||||
}
|
||||
oneSec := tc.After(time.Second)
|
||||
if !tc.HasWaiters() {
|
||||
t.Errorf("unexpected lack of waiter?")
|
||||
}
|
||||
|
||||
oneOhOneSec := tc.After(time.Second + time.Millisecond)
|
||||
twoSec := tc.After(2 * time.Second)
|
||||
select {
|
||||
case <-oneSec:
|
||||
t.Errorf("unexpected channel read")
|
||||
case <-oneOhOneSec:
|
||||
t.Errorf("unexpected channel read")
|
||||
case <-twoSec:
|
||||
t.Errorf("unexpected channel read")
|
||||
default:
|
||||
}
|
||||
|
||||
tc.Step(999 * time.Millisecond)
|
||||
select {
|
||||
case <-oneSec:
|
||||
t.Errorf("unexpected channel read")
|
||||
case <-oneOhOneSec:
|
||||
t.Errorf("unexpected channel read")
|
||||
case <-twoSec:
|
||||
t.Errorf("unexpected channel read")
|
||||
default:
|
||||
}
|
||||
|
||||
tc.Step(time.Millisecond)
|
||||
select {
|
||||
case <-oneSec:
|
||||
// Expected!
|
||||
case <-oneOhOneSec:
|
||||
t.Errorf("unexpected channel read")
|
||||
case <-twoSec:
|
||||
t.Errorf("unexpected channel read")
|
||||
default:
|
||||
t.Errorf("unexpected non-channel read")
|
||||
}
|
||||
tc.Step(time.Millisecond)
|
||||
select {
|
||||
case <-oneSec:
|
||||
// should not double-trigger!
|
||||
t.Errorf("unexpected channel read")
|
||||
case <-oneOhOneSec:
|
||||
// Expected!
|
||||
case <-twoSec:
|
||||
t.Errorf("unexpected channel read")
|
||||
default:
|
||||
t.Errorf("unexpected non-channel read")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFakeTick(t *testing.T) {
|
||||
tc := NewFakeClock(time.Now())
|
||||
if tc.HasWaiters() {
|
||||
t.Errorf("unexpected waiter?")
|
||||
}
|
||||
oneSec := tc.Tick(time.Second)
|
||||
if !tc.HasWaiters() {
|
||||
t.Errorf("unexpected lack of waiter?")
|
||||
}
|
||||
|
||||
oneOhOneSec := tc.Tick(time.Second + time.Millisecond)
|
||||
twoSec := tc.Tick(2 * time.Second)
|
||||
select {
|
||||
case <-oneSec:
|
||||
t.Errorf("unexpected channel read")
|
||||
case <-oneOhOneSec:
|
||||
t.Errorf("unexpected channel read")
|
||||
case <-twoSec:
|
||||
t.Errorf("unexpected channel read")
|
||||
default:
|
||||
}
|
||||
|
||||
tc.Step(999 * time.Millisecond) // t=.999
|
||||
select {
|
||||
case <-oneSec:
|
||||
t.Errorf("unexpected channel read")
|
||||
case <-oneOhOneSec:
|
||||
t.Errorf("unexpected channel read")
|
||||
case <-twoSec:
|
||||
t.Errorf("unexpected channel read")
|
||||
default:
|
||||
}
|
||||
|
||||
tc.Step(time.Millisecond) // t=1.000
|
||||
select {
|
||||
case <-oneSec:
|
||||
// Expected!
|
||||
case <-oneOhOneSec:
|
||||
t.Errorf("unexpected channel read")
|
||||
case <-twoSec:
|
||||
t.Errorf("unexpected channel read")
|
||||
default:
|
||||
t.Errorf("unexpected non-channel read")
|
||||
}
|
||||
tc.Step(time.Millisecond) // t=1.001
|
||||
select {
|
||||
case <-oneSec:
|
||||
// should not double-trigger!
|
||||
t.Errorf("unexpected channel read")
|
||||
case <-oneOhOneSec:
|
||||
// Expected!
|
||||
case <-twoSec:
|
||||
t.Errorf("unexpected channel read")
|
||||
default:
|
||||
t.Errorf("unexpected non-channel read")
|
||||
}
|
||||
|
||||
tc.Step(time.Second) // t=2.001
|
||||
tc.Step(time.Second) // t=3.001
|
||||
tc.Step(time.Second) // t=4.001
|
||||
tc.Step(time.Second) // t=5.001
|
||||
|
||||
// The one second ticker should not accumulate ticks
|
||||
accumulatedTicks := 0
|
||||
drained := false
|
||||
for !drained {
|
||||
select {
|
||||
case <-oneSec:
|
||||
accumulatedTicks++
|
||||
default:
|
||||
drained = true
|
||||
}
|
||||
}
|
||||
if accumulatedTicks != 1 {
|
||||
t.Errorf("unexpected number of accumulated ticks: %d", accumulatedTicks)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFakeStop(t *testing.T) {
|
||||
tc := NewFakeClock(time.Now())
|
||||
timer := tc.NewTimer(time.Second)
|
||||
if !tc.HasWaiters() {
|
||||
t.Errorf("expected a waiter to be present, but it is not")
|
||||
}
|
||||
timer.Stop()
|
||||
if tc.HasWaiters() {
|
||||
t.Errorf("expected existing waiter to be cleaned up, but it is still present")
|
||||
}
|
||||
}
|
||||
|
||||
// This tests the pattern documented in the go docs here: https://golang.org/pkg/time/#Timer.Stop
|
||||
// This pattern is required to safely reset a timer, so should be common.
|
||||
// This also tests resetting the timer
|
||||
func TestFakeStopDrain(t *testing.T) {
|
||||
start := time.Time{}
|
||||
tc := NewFakeClock(start)
|
||||
timer := tc.NewTimer(time.Second)
|
||||
tc.Step(1 * time.Second)
|
||||
// Effectively `if !timer.Stop { <-t.C }` but with more asserts
|
||||
if timer.Stop() {
|
||||
t.Errorf("stop should report the timer had triggered")
|
||||
}
|
||||
if readTime := assertReadTime(t, timer.C()); !readTime.Equal(start.Add(1 * time.Second)) {
|
||||
t.Errorf("timer should have ticked after 1 second, got %v", readTime)
|
||||
}
|
||||
|
||||
timer.Reset(time.Second)
|
||||
if !tc.HasWaiters() {
|
||||
t.Errorf("expected a waiter to be present, but it is not")
|
||||
}
|
||||
select {
|
||||
case <-timer.C():
|
||||
t.Fatal("got time early on clock; haven't stepped yet")
|
||||
default:
|
||||
}
|
||||
tc.Step(1 * time.Second)
|
||||
if readTime := assertReadTime(t, timer.C()); !readTime.Equal(start.Add(2 * time.Second)) {
|
||||
t.Errorf("timer should have ticked again after reset + 1 more second, got %v", readTime)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTimerNegative(t *testing.T) {
|
||||
tc := NewFakeClock(time.Now())
|
||||
timer := tc.NewTimer(-1 * time.Second)
|
||||
if !tc.HasWaiters() {
|
||||
t.Errorf("expected a waiter to be present, but it is not")
|
||||
}
|
||||
// force waiters to be called
|
||||
tc.Step(0)
|
||||
tick := assertReadTime(t, timer.C())
|
||||
if tick != tc.Now() {
|
||||
t.Errorf("expected -1s to turn into now: %v != %v", tick, tc.Now())
|
||||
}
|
||||
}
|
||||
|
||||
func TestTickNegative(t *testing.T) {
|
||||
// The stdlib 'Tick' returns nil for negative and zero values, so our fake
|
||||
// should too.
|
||||
tc := NewFakeClock(time.Now())
|
||||
if tick := tc.Tick(-1 * time.Second); tick != nil {
|
||||
t.Errorf("expected negative tick to be nil: %v", tick)
|
||||
}
|
||||
if tick := tc.Tick(0); tick != nil {
|
||||
t.Errorf("expected negative tick to be nil: %v", tick)
|
||||
}
|
||||
}
|
||||
|
||||
// assertReadTime asserts that the channel can be read and returns the time it
|
||||
// reads from the channel.
|
||||
func assertReadTime(t testing.TB, c <-chan time.Time) time.Time {
|
||||
type helper interface {
|
||||
Helper()
|
||||
}
|
||||
if h, ok := t.(helper); ok {
|
||||
h.Helper()
|
||||
}
|
||||
select {
|
||||
case ti, ok := <-c:
|
||||
if !ok {
|
||||
t.Fatalf("expected to read time from channel, but it was closed")
|
||||
}
|
||||
return ti
|
||||
default:
|
||||
t.Fatalf("expected to read time from channel, but couldn't")
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
3
vendor/k8s.io/utils/code-of-conduct.md
generated
vendored
3
vendor/k8s.io/utils/code-of-conduct.md
generated
vendored
@ -1,3 +0,0 @@
|
||||
# Kubernetes Community Code of Conduct
|
||||
|
||||
Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md)
|
215
vendor/k8s.io/utils/exec/exec_test.go
generated
vendored
215
vendor/k8s.io/utils/exec/exec_test.go
generated
vendored
@ -1,215 +0,0 @@
|
||||
/*
|
||||
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 exec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
osexec "os/exec"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestExecutorNoArgs(t *testing.T) {
|
||||
ex := New()
|
||||
|
||||
cmd := ex.Command("true")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Errorf("expected success, got %v", err)
|
||||
}
|
||||
if len(out) != 0 {
|
||||
t.Errorf("expected no output, got %q", string(out))
|
||||
}
|
||||
|
||||
cmd = ex.Command("false")
|
||||
out, err = cmd.CombinedOutput()
|
||||
if err == nil {
|
||||
t.Errorf("expected failure, got nil error")
|
||||
}
|
||||
if len(out) != 0 {
|
||||
t.Errorf("expected no output, got %q", string(out))
|
||||
}
|
||||
ee, ok := err.(ExitError)
|
||||
if !ok {
|
||||
t.Errorf("expected an ExitError, got %+v", err)
|
||||
}
|
||||
if ee.Exited() {
|
||||
if code := ee.ExitStatus(); code != 1 {
|
||||
t.Errorf("expected exit status 1, got %d", code)
|
||||
}
|
||||
}
|
||||
|
||||
cmd = ex.Command("/does/not/exist")
|
||||
out, err = cmd.CombinedOutput()
|
||||
if err == nil {
|
||||
t.Errorf("expected failure, got nil error")
|
||||
}
|
||||
if ee, ok := err.(ExitError); ok {
|
||||
t.Errorf("expected non-ExitError, got %+v", ee)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecutorWithArgs(t *testing.T) {
|
||||
ex := New()
|
||||
|
||||
cmd := ex.Command("echo", "stdout")
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Errorf("expected success, got %+v", err)
|
||||
}
|
||||
if string(out) != "stdout\n" {
|
||||
t.Errorf("unexpected output: %q", string(out))
|
||||
}
|
||||
|
||||
cmd = ex.Command("/bin/sh", "-c", "echo stderr > /dev/stderr")
|
||||
out, err = cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Errorf("expected success, got %+v", err)
|
||||
}
|
||||
if string(out) != "stderr\n" {
|
||||
t.Errorf("unexpected output: %q", string(out))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLookPath(t *testing.T) {
|
||||
ex := New()
|
||||
|
||||
shExpected, _ := osexec.LookPath("sh")
|
||||
sh, _ := ex.LookPath("sh")
|
||||
if sh != shExpected {
|
||||
t.Errorf("unexpected result for LookPath: got %s, expected %s", sh, shExpected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestExecutableNotFound(t *testing.T) {
|
||||
exec := New()
|
||||
|
||||
cmd := exec.Command("fake_executable_name")
|
||||
_, err := cmd.CombinedOutput()
|
||||
if err != ErrExecutableNotFound {
|
||||
t.Errorf("cmd.CombinedOutput(): Expected error ErrExecutableNotFound but got %v", err)
|
||||
}
|
||||
|
||||
cmd = exec.Command("fake_executable_name")
|
||||
_, err = cmd.Output()
|
||||
if err != ErrExecutableNotFound {
|
||||
t.Errorf("cmd.Output(): Expected error ErrExecutableNotFound but got %v", err)
|
||||
}
|
||||
|
||||
cmd = exec.Command("fake_executable_name")
|
||||
err = cmd.Run()
|
||||
if err != ErrExecutableNotFound {
|
||||
t.Errorf("cmd.Run(): Expected error ErrExecutableNotFound but got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStopBeforeStart(t *testing.T) {
|
||||
cmd := New().Command("echo", "hello")
|
||||
|
||||
// no panic calling Stop before calling Run
|
||||
cmd.Stop()
|
||||
|
||||
cmd.Run()
|
||||
|
||||
// no panic calling Stop after command is done
|
||||
cmd.Stop()
|
||||
}
|
||||
|
||||
func TestTimeout(t *testing.T) {
|
||||
exec := New()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Nanosecond)
|
||||
defer cancel()
|
||||
|
||||
err := exec.CommandContext(ctx, "sleep", "2").Run()
|
||||
if err != context.DeadlineExceeded {
|
||||
t.Errorf("expected %v but got %v", context.DeadlineExceeded, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetEnv(t *testing.T) {
|
||||
ex := New()
|
||||
|
||||
out, err := ex.Command("/bin/sh", "-c", "echo $FOOBAR").CombinedOutput()
|
||||
if err != nil {
|
||||
t.Errorf("expected success, got %+v", err)
|
||||
}
|
||||
if string(out) != "\n" {
|
||||
t.Errorf("unexpected output: %q", string(out))
|
||||
}
|
||||
|
||||
cmd := ex.Command("/bin/sh", "-c", "echo $FOOBAR")
|
||||
cmd.SetEnv([]string{"FOOBAR=baz"})
|
||||
out, err = cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
t.Errorf("expected success, got %+v", err)
|
||||
}
|
||||
if string(out) != "baz\n" {
|
||||
t.Errorf("unexpected output: %q", string(out))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStdIOPipes(t *testing.T) {
|
||||
cmd := New().Command("/bin/sh", "-c", "echo 'OUT'>&1; echo 'ERR'>&2")
|
||||
|
||||
stdoutPipe, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("expected StdoutPipe() not to error, got: %v", err)
|
||||
}
|
||||
stderrPipe, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
t.Fatalf("expected StderrPipe() not to error, got: %v", err)
|
||||
}
|
||||
|
||||
stdout := make(chan string)
|
||||
stderr := make(chan string)
|
||||
|
||||
go func() {
|
||||
stdout <- readAll(t, stdoutPipe, "StdOut")
|
||||
}()
|
||||
go func() {
|
||||
stderr <- readAll(t, stderrPipe, "StdErr")
|
||||
}()
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
t.Errorf("expected Start() not to error, got: %v", err)
|
||||
}
|
||||
|
||||
if e, a := "OUT\n", <-stdout; e != a {
|
||||
t.Errorf("expected StdOut to be '%s', got: '%v'", e, a)
|
||||
}
|
||||
|
||||
if e, a := "ERR\n", <-stderr; e != a {
|
||||
t.Errorf("expected StdErr to be '%s', got: '%v'", e, a)
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
t.Errorf("expected Wait() not to error, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func readAll(t *testing.T, r io.Reader, n string) string {
|
||||
t.Helper()
|
||||
|
||||
b, err := ioutil.ReadAll(r)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error when reading from %s: %v", n, err)
|
||||
}
|
||||
|
||||
return string(b)
|
||||
}
|
37
vendor/k8s.io/utils/exec/new_test.go
generated
vendored
37
vendor/k8s.io/utils/exec/new_test.go
generated
vendored
@ -1,37 +0,0 @@
|
||||
/*
|
||||
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 exec_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/utils/exec"
|
||||
)
|
||||
|
||||
func ExampleNew() {
|
||||
exec := exec.New()
|
||||
|
||||
cmd := exec.Command("echo", "Bonjour!")
|
||||
buff := bytes.Buffer{}
|
||||
cmd.SetStdout(&buff)
|
||||
if err := cmd.Run(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(buff.String())
|
||||
// Output: Bonjour!
|
||||
}
|
55
vendor/k8s.io/utils/exec/stdiopipe_test.go
generated
vendored
55
vendor/k8s.io/utils/exec/stdiopipe_test.go
generated
vendored
@ -1,55 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 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 exec_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
|
||||
"k8s.io/utils/exec"
|
||||
)
|
||||
|
||||
func ExampleNew_stderrPipe() {
|
||||
cmd := exec.New().Command("/bin/sh", "-c", "echo 'We can read from stderr via pipe!' >&2")
|
||||
|
||||
stderrPipe, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
stderr := make(chan []byte)
|
||||
go func() {
|
||||
b, err := ioutil.ReadAll(stderrPipe)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
stderr <- b
|
||||
}()
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
received := <-stderr
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(string(received))
|
||||
// Output: We can read from stderr via pipe!
|
||||
}
|
217
vendor/k8s.io/utils/exec/testing/fake_exec.go
generated
vendored
217
vendor/k8s.io/utils/exec/testing/fake_exec.go
generated
vendored
@ -1,217 +0,0 @@
|
||||
/*
|
||||
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 testingexec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"k8s.io/utils/exec"
|
||||
)
|
||||
|
||||
// FakeExec is a simple scripted Interface type.
|
||||
type FakeExec struct {
|
||||
CommandScript []FakeCommandAction
|
||||
CommandCalls int
|
||||
LookPathFunc func(string) (string, error)
|
||||
}
|
||||
|
||||
var _ exec.Interface = &FakeExec{}
|
||||
|
||||
// FakeCommandAction is the function to be executed
|
||||
type FakeCommandAction func(cmd string, args ...string) exec.Cmd
|
||||
|
||||
// Command is to track the commands that are executed
|
||||
func (fake *FakeExec) Command(cmd string, args ...string) exec.Cmd {
|
||||
if fake.CommandCalls > len(fake.CommandScript)-1 {
|
||||
panic(fmt.Sprintf("ran out of Command() actions. Could not handle command [%d]: %s args: %v", fake.CommandCalls, cmd, args))
|
||||
}
|
||||
i := fake.CommandCalls
|
||||
fake.CommandCalls++
|
||||
return fake.CommandScript[i](cmd, args...)
|
||||
}
|
||||
|
||||
// CommandContext wraps arguments into exec.Cmd
|
||||
func (fake *FakeExec) CommandContext(ctx context.Context, cmd string, args ...string) exec.Cmd {
|
||||
return fake.Command(cmd, args...)
|
||||
}
|
||||
|
||||
// LookPath is for finding the path of a file
|
||||
func (fake *FakeExec) LookPath(file string) (string, error) {
|
||||
return fake.LookPathFunc(file)
|
||||
}
|
||||
|
||||
// FakeCmd is a simple scripted Cmd type.
|
||||
type FakeCmd struct {
|
||||
Argv []string
|
||||
CombinedOutputScript []FakeCombinedOutputAction
|
||||
CombinedOutputCalls int
|
||||
CombinedOutputLog [][]string
|
||||
RunScript []FakeRunAction
|
||||
RunCalls int
|
||||
RunLog [][]string
|
||||
Dirs []string
|
||||
Stdin io.Reader
|
||||
Stdout io.Writer
|
||||
Stderr io.Writer
|
||||
Env []string
|
||||
StdoutPipeResponse FakeStdIOPipeResponse
|
||||
StderrPipeResponse FakeStdIOPipeResponse
|
||||
WaitResponse error
|
||||
StartResponse error
|
||||
}
|
||||
|
||||
var _ exec.Cmd = &FakeCmd{}
|
||||
|
||||
// InitFakeCmd is for creating a fake exec.Cmd
|
||||
func InitFakeCmd(fake *FakeCmd, cmd string, args ...string) exec.Cmd {
|
||||
fake.Argv = append([]string{cmd}, args...)
|
||||
return fake
|
||||
}
|
||||
|
||||
// FakeStdIOPipeResponse holds responses to use as fakes for the StdoutPipe and
|
||||
// StderrPipe method calls
|
||||
type FakeStdIOPipeResponse struct {
|
||||
ReadCloser io.ReadCloser
|
||||
Error error
|
||||
}
|
||||
|
||||
// FakeCombinedOutputAction is a function type
|
||||
type FakeCombinedOutputAction func() ([]byte, error)
|
||||
|
||||
// FakeRunAction is a function type
|
||||
type FakeRunAction func() ([]byte, []byte, error)
|
||||
|
||||
// SetDir sets the directory
|
||||
func (fake *FakeCmd) SetDir(dir string) {
|
||||
fake.Dirs = append(fake.Dirs, dir)
|
||||
}
|
||||
|
||||
// SetStdin sets the stdin
|
||||
func (fake *FakeCmd) SetStdin(in io.Reader) {
|
||||
fake.Stdin = in
|
||||
}
|
||||
|
||||
// SetStdout sets the stdout
|
||||
func (fake *FakeCmd) SetStdout(out io.Writer) {
|
||||
fake.Stdout = out
|
||||
}
|
||||
|
||||
// SetStderr sets the stderr
|
||||
func (fake *FakeCmd) SetStderr(out io.Writer) {
|
||||
fake.Stderr = out
|
||||
}
|
||||
|
||||
// SetEnv sets the environment variables
|
||||
func (fake *FakeCmd) SetEnv(env []string) {
|
||||
fake.Env = env
|
||||
}
|
||||
|
||||
// StdoutPipe returns an injected ReadCloser & error (via StdoutPipeResponse)
|
||||
// to be able to inject an output stream on Stdout
|
||||
func (fake *FakeCmd) StdoutPipe() (io.ReadCloser, error) {
|
||||
return fake.StdoutPipeResponse.ReadCloser, fake.StdoutPipeResponse.Error
|
||||
}
|
||||
|
||||
// StderrPipe returns an injected ReadCloser & error (via StderrPipeResponse)
|
||||
// to be able to inject an output stream on Stderr
|
||||
func (fake *FakeCmd) StderrPipe() (io.ReadCloser, error) {
|
||||
return fake.StderrPipeResponse.ReadCloser, fake.StderrPipeResponse.Error
|
||||
}
|
||||
|
||||
// Start mimicks starting the process (in the background) and returns the
|
||||
// injected StartResponse
|
||||
func (fake *FakeCmd) Start() error {
|
||||
return fake.StartResponse
|
||||
}
|
||||
|
||||
// Wait mimicks waiting for the process to exit returns the
|
||||
// injected WaitResponse
|
||||
func (fake *FakeCmd) Wait() error {
|
||||
return fake.WaitResponse
|
||||
}
|
||||
|
||||
// Run sets runs the command
|
||||
func (fake *FakeCmd) Run() error {
|
||||
if fake.RunCalls > len(fake.RunScript)-1 {
|
||||
panic("ran out of Run() actions")
|
||||
}
|
||||
if fake.RunLog == nil {
|
||||
fake.RunLog = [][]string{}
|
||||
}
|
||||
i := fake.RunCalls
|
||||
fake.RunLog = append(fake.RunLog, append([]string{}, fake.Argv...))
|
||||
fake.RunCalls++
|
||||
stdout, stderr, err := fake.RunScript[i]()
|
||||
if stdout != nil {
|
||||
fake.Stdout.Write(stdout)
|
||||
}
|
||||
if stderr != nil {
|
||||
fake.Stderr.Write(stderr)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// CombinedOutput returns the output from the command
|
||||
func (fake *FakeCmd) CombinedOutput() ([]byte, error) {
|
||||
if fake.CombinedOutputCalls > len(fake.CombinedOutputScript)-1 {
|
||||
panic("ran out of CombinedOutput() actions")
|
||||
}
|
||||
if fake.CombinedOutputLog == nil {
|
||||
fake.CombinedOutputLog = [][]string{}
|
||||
}
|
||||
i := fake.CombinedOutputCalls
|
||||
fake.CombinedOutputLog = append(fake.CombinedOutputLog, append([]string{}, fake.Argv...))
|
||||
fake.CombinedOutputCalls++
|
||||
return fake.CombinedOutputScript[i]()
|
||||
}
|
||||
|
||||
// Output is the response from the command
|
||||
func (fake *FakeCmd) Output() ([]byte, error) {
|
||||
return nil, fmt.Errorf("unimplemented")
|
||||
}
|
||||
|
||||
// Stop is to stop the process
|
||||
func (fake *FakeCmd) Stop() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
// FakeExitError is a simple fake ExitError type.
|
||||
type FakeExitError struct {
|
||||
Status int
|
||||
}
|
||||
|
||||
var _ exec.ExitError = FakeExitError{}
|
||||
|
||||
func (fake FakeExitError) String() string {
|
||||
return fmt.Sprintf("exit %d", fake.Status)
|
||||
}
|
||||
|
||||
func (fake FakeExitError) Error() string {
|
||||
return fake.String()
|
||||
}
|
||||
|
||||
// Exited always returns true
|
||||
func (fake FakeExitError) Exited() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// ExitStatus returns the fake status
|
||||
func (fake FakeExitError) ExitStatus() int {
|
||||
return fake.Status
|
||||
}
|
32
vendor/k8s.io/utils/hack/verify-gofmt.sh
generated
vendored
32
vendor/k8s.io/utils/hack/verify-gofmt.sh
generated
vendored
@ -1,32 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# 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.
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
if ! which gofmt > /dev/null; then
|
||||
echo "Can not find gofmt"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
diff=$(gofmt -s -d . 2>&1)
|
||||
if [[ -n "${diff}" ]]; then
|
||||
echo "${diff}"
|
||||
echo
|
||||
echo "Please run 'make update-fmt'"
|
||||
exit 1
|
||||
fi
|
32
vendor/k8s.io/utils/hack/verify-golint.sh
generated
vendored
32
vendor/k8s.io/utils/hack/verify-golint.sh
generated
vendored
@ -1,32 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2018 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.
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
if ! which golint > /dev/null; then
|
||||
echo "installing golint"
|
||||
go get golang.org/x/lint/golint
|
||||
fi
|
||||
|
||||
errors="$(golint ./...)"
|
||||
if [[ -n "$errors" ]]; then
|
||||
echo "Errors from golint:"
|
||||
echo
|
||||
echo "${errors}"
|
||||
exit 1
|
||||
fi
|
55
vendor/k8s.io/utils/io/consistentread.go
generated
vendored
55
vendor/k8s.io/utils/io/consistentread.go
generated
vendored
@ -1,55 +0,0 @@
|
||||
/*
|
||||
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 io
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// ConsistentRead repeatedly reads a file until it gets the same content twice.
|
||||
// This is useful when reading files in /proc that are larger than page size
|
||||
// and kernel may modify them between individual read() syscalls.
|
||||
func ConsistentRead(filename string, attempts int) ([]byte, error) {
|
||||
return consistentReadSync(filename, attempts, nil)
|
||||
}
|
||||
|
||||
// consistentReadSync is the main functionality of ConsistentRead but
|
||||
// introduces a sync callback that can be used by the tests to mutate the file
|
||||
// from which the test data is being read
|
||||
func consistentReadSync(filename string, attempts int, sync func(int)) ([]byte, error) {
|
||||
oldContent, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := 0; i < attempts; i++ {
|
||||
if sync != nil {
|
||||
sync(i)
|
||||
}
|
||||
newContent, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if bytes.Compare(oldContent, newContent) == 0 {
|
||||
return newContent, nil
|
||||
}
|
||||
// Files are different, continue reading
|
||||
oldContent = newContent
|
||||
}
|
||||
return nil, fmt.Errorf("could not get consistent content of %s after %d attempts", filename, attempts)
|
||||
}
|
94
vendor/k8s.io/utils/io/consistentread_test.go
generated
vendored
94
vendor/k8s.io/utils/io/consistentread_test.go
generated
vendored
@ -1,94 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 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 io
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func writeToPipe(namedPipe string, flaky bool, i int) {
|
||||
pipe, err := os.OpenFile(namedPipe, os.O_WRONLY, 0600)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// The first two reads should never be consistent but all
|
||||
// subsequent reads should be
|
||||
outstr := fmt.Sprintf("Foobar %t", (i <= 0))
|
||||
|
||||
if flaky {
|
||||
outstr = fmt.Sprintf("Foobar %d", i)
|
||||
}
|
||||
|
||||
pipe.Write([]byte(outstr))
|
||||
pipe.Close()
|
||||
}
|
||||
|
||||
func makePipe(t *testing.T) string {
|
||||
tmp, err := ioutil.TempDir("", "pipe-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pipe := filepath.Join(tmp, "pipe")
|
||||
syscall.Mkfifo(pipe, 0600)
|
||||
|
||||
return pipe
|
||||
}
|
||||
|
||||
func writer(namedPipe string, flaky bool, c <-chan int, d <-chan bool) {
|
||||
// Make sure something is in the fifo otherwise the first iteration of
|
||||
// ConsistentRead will block forever
|
||||
writeToPipe(namedPipe, flaky, -1)
|
||||
|
||||
for {
|
||||
select {
|
||||
case i := <-c:
|
||||
writeToPipe(namedPipe, flaky, i)
|
||||
case <-d:
|
||||
os.RemoveAll(namedPipe)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConsistentRead(t *testing.T) {
|
||||
pipe := makePipe(t)
|
||||
prog, done := make(chan int), make(chan bool)
|
||||
go writer(pipe, false, prog, done)
|
||||
|
||||
if _, err := consistentReadSync(pipe, 3, func(i int) { prog <- i }); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
done <- true
|
||||
}
|
||||
|
||||
func TestConsistentReadFlakyReader(t *testing.T) {
|
||||
pipe := makePipe(t)
|
||||
prog, done := make(chan int), make(chan bool)
|
||||
go writer(pipe, true, prog, done)
|
||||
|
||||
if _, err := consistentReadSync(pipe, 3, func(i int) { prog <- i }); err == nil {
|
||||
t.Fatal("flaky reader returned consistent results")
|
||||
}
|
||||
}
|
8
vendor/k8s.io/utils/nsenter/OWNERS
generated
vendored
8
vendor/k8s.io/utils/nsenter/OWNERS
generated
vendored
@ -1,8 +0,0 @@
|
||||
reviewers:
|
||||
- jsafrane
|
||||
- msau42
|
||||
- cofyc
|
||||
approvers:
|
||||
- jsafrane
|
||||
- msau42
|
||||
- cofyc
|
67
vendor/k8s.io/utils/nsenter/exec.go
generated
vendored
67
vendor/k8s.io/utils/nsenter/exec.go
generated
vendored
@ -1,67 +0,0 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright 2018 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 nsenter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/utils/exec"
|
||||
)
|
||||
|
||||
// Executor wraps executor interface to be executed via nsenter
|
||||
type Executor struct {
|
||||
// Exec implementation
|
||||
executor exec.Interface
|
||||
// Path to the host's root proc path
|
||||
hostProcMountNsPath string
|
||||
}
|
||||
|
||||
// NewNsenterExecutor returns new nsenter based executor
|
||||
func NewNsenterExecutor(hostRootFsPath string, executor exec.Interface) *Executor {
|
||||
hostProcMountNsPath := filepath.Join(hostRootFsPath, mountNsPath)
|
||||
nsExecutor := &Executor{
|
||||
hostProcMountNsPath: hostProcMountNsPath,
|
||||
executor: executor,
|
||||
}
|
||||
return nsExecutor
|
||||
}
|
||||
|
||||
// Command returns a command wrapped with nenter
|
||||
func (nsExecutor *Executor) Command(cmd string, args ...string) exec.Cmd {
|
||||
fullArgs := append([]string{fmt.Sprintf("--mount=%s", nsExecutor.hostProcMountNsPath), "--"},
|
||||
append([]string{cmd}, args...)...)
|
||||
glog.V(5).Infof("Running nsenter command: %v %v", nsenterPath, fullArgs)
|
||||
return nsExecutor.executor.Command(nsenterPath, fullArgs...)
|
||||
}
|
||||
|
||||
// CommandContext returns a CommandContext wrapped with nsenter
|
||||
func (nsExecutor *Executor) CommandContext(ctx context.Context, cmd string, args ...string) exec.Cmd {
|
||||
fullArgs := append([]string{fmt.Sprintf("--mount=%s", nsExecutor.hostProcMountNsPath), "--"},
|
||||
append([]string{cmd}, args...)...)
|
||||
glog.V(5).Infof("Running nsenter command: %v %v", nsenterPath, fullArgs)
|
||||
return nsExecutor.executor.CommandContext(ctx, nsenterPath, fullArgs...)
|
||||
}
|
||||
|
||||
// LookPath returns a LookPath wrapped with nsenter
|
||||
func (nsExecutor *Executor) LookPath(file string) (string, error) {
|
||||
return "", fmt.Errorf("not implemented, error looking up : %s", file)
|
||||
}
|
58
vendor/k8s.io/utils/nsenter/exec_unsupported.go
generated
vendored
58
vendor/k8s.io/utils/nsenter/exec_unsupported.go
generated
vendored
@ -1,58 +0,0 @@
|
||||
// +build !linux
|
||||
|
||||
/*
|
||||
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 nsenter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/utils/exec"
|
||||
)
|
||||
|
||||
// Executor wraps executor interface to be executed via nsenter
|
||||
type Executor struct {
|
||||
// Exec implementation
|
||||
executor exec.Interface
|
||||
// Path to the host's root proc path
|
||||
hostProcMountNsPath string
|
||||
}
|
||||
|
||||
// NewNsenterExecutor returns new nsenter based executor
|
||||
func NewNsenterExecutor(hostRootFsPath string, executor exec.Interface) *Executor {
|
||||
nsExecutor := &Executor{
|
||||
hostProcMountNsPath: hostRootFsPath,
|
||||
executor: executor,
|
||||
}
|
||||
return nsExecutor
|
||||
}
|
||||
|
||||
// Command returns a command wrapped with nenter
|
||||
func (nsExecutor *Executor) Command(cmd string, args ...string) exec.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommandContext returns a CommandContext wrapped with nsenter
|
||||
func (nsExecutor *Executor) CommandContext(ctx context.Context, cmd string, args ...string) exec.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// LookPath returns a LookPath wrapped with nsenter
|
||||
func (nsExecutor *Executor) LookPath(file string) (string, error) {
|
||||
return "", fmt.Errorf("not implemented, error looking up : %s", file)
|
||||
}
|
236
vendor/k8s.io/utils/nsenter/nsenter.go
generated
vendored
236
vendor/k8s.io/utils/nsenter/nsenter.go
generated
vendored
@ -1,236 +0,0 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
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 nsenter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"k8s.io/utils/exec"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultHostRootFsPath is path to host's filesystem mounted into container
|
||||
// with kubelet.
|
||||
DefaultHostRootFsPath = "/rootfs"
|
||||
// mountNsPath is the default mount namespace of the host
|
||||
mountNsPath = "/proc/1/ns/mnt"
|
||||
// nsenterPath is the default nsenter command
|
||||
nsenterPath = "nsenter"
|
||||
)
|
||||
|
||||
// Nsenter is part of experimental support for running the kubelet
|
||||
// in a container.
|
||||
//
|
||||
// Nsenter requires:
|
||||
//
|
||||
// 1. Docker >= 1.6 due to the dependency on the slave propagation mode
|
||||
// of the bind-mount of the kubelet root directory in the container.
|
||||
// Docker 1.5 used a private propagation mode for bind-mounts, so mounts
|
||||
// performed in the host's mount namespace do not propagate out to the
|
||||
// bind-mount in this docker version.
|
||||
// 2. The host's root filesystem must be available at /rootfs
|
||||
// 3. The nsenter binary must be on the Kubelet process' PATH in the container's
|
||||
// filesystem.
|
||||
// 4. The Kubelet process must have CAP_SYS_ADMIN (required by nsenter); at
|
||||
// the present, this effectively means that the kubelet is running in a
|
||||
// privileged container.
|
||||
// 5. The volume path used by the Kubelet must be the same inside and outside
|
||||
// the container and be writable by the container (to initialize volume)
|
||||
// contents. TODO: remove this requirement.
|
||||
// 6. The host image must have "mount", "findmnt", "umount", "stat", "touch",
|
||||
// "mkdir", "ls", "sh" and "chmod" binaries in /bin, /usr/sbin, or /usr/bin
|
||||
// 7. The host image should have systemd-run in /bin, /usr/sbin, or /usr/bin if
|
||||
// systemd is installed/enabled in the operating system.
|
||||
// For more information about mount propagation modes, see:
|
||||
// https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt
|
||||
type Nsenter struct {
|
||||
// a map of commands to their paths on the host filesystem
|
||||
paths map[string]string
|
||||
|
||||
// Path to the host filesystem, typically "/rootfs". Used only for testing.
|
||||
hostRootFsPath string
|
||||
|
||||
// Exec implementation, used only for testing
|
||||
executor exec.Interface
|
||||
}
|
||||
|
||||
// NewNsenter constructs a new instance of Nsenter
|
||||
func NewNsenter(hostRootFsPath string, executor exec.Interface) (*Nsenter, error) {
|
||||
ne := &Nsenter{
|
||||
hostRootFsPath: hostRootFsPath,
|
||||
executor: executor,
|
||||
}
|
||||
if err := ne.initPaths(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ne, nil
|
||||
}
|
||||
|
||||
func (ne *Nsenter) initPaths() error {
|
||||
ne.paths = map[string]string{}
|
||||
binaries := []string{
|
||||
"mount",
|
||||
"findmnt",
|
||||
"umount",
|
||||
"systemd-run",
|
||||
"stat",
|
||||
"touch",
|
||||
"mkdir",
|
||||
"sh",
|
||||
"chmod",
|
||||
"realpath",
|
||||
}
|
||||
// search for the required commands in other locations besides /usr/bin
|
||||
for _, binary := range binaries {
|
||||
// check for binary under the following directories
|
||||
for _, path := range []string{"/", "/bin", "/usr/sbin", "/usr/bin"} {
|
||||
binPath := filepath.Join(path, binary)
|
||||
if _, err := os.Stat(filepath.Join(ne.hostRootFsPath, binPath)); err != nil {
|
||||
continue
|
||||
}
|
||||
ne.paths[binary] = binPath
|
||||
break
|
||||
}
|
||||
// systemd-run is optional, bailout if we don't find any of the other binaries
|
||||
if ne.paths[binary] == "" && binary != "systemd-run" {
|
||||
return fmt.Errorf("unable to find %v", binary)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exec executes nsenter commands in hostProcMountNsPath mount namespace
|
||||
func (ne *Nsenter) Exec(cmd string, args []string) exec.Cmd {
|
||||
hostProcMountNsPath := filepath.Join(ne.hostRootFsPath, mountNsPath)
|
||||
fullArgs := append([]string{fmt.Sprintf("--mount=%s", hostProcMountNsPath), "--"},
|
||||
append([]string{ne.AbsHostPath(cmd)}, args...)...)
|
||||
glog.V(5).Infof("Running nsenter command: %v %v", nsenterPath, fullArgs)
|
||||
return ne.executor.Command(nsenterPath, fullArgs...)
|
||||
}
|
||||
|
||||
// AbsHostPath returns the absolute runnable path for a specified command
|
||||
func (ne *Nsenter) AbsHostPath(command string) string {
|
||||
path, ok := ne.paths[command]
|
||||
if !ok {
|
||||
return command
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
// SupportsSystemd checks whether command systemd-run exists
|
||||
func (ne *Nsenter) SupportsSystemd() (string, bool) {
|
||||
systemdRunPath, ok := ne.paths["systemd-run"]
|
||||
return systemdRunPath, ok && systemdRunPath != ""
|
||||
}
|
||||
|
||||
// EvalSymlinks returns the path name on the host after evaluating symlinks on the
|
||||
// host.
|
||||
// mustExist makes EvalSymlinks to return error when the path does not
|
||||
// exist. When it's false, it evaluates symlinks of the existing part and
|
||||
// blindly adds the non-existing part:
|
||||
// pathname: /mnt/volume/non/existing/directory
|
||||
// /mnt/volume exists
|
||||
// non/existing/directory does not exist
|
||||
// -> It resolves symlinks in /mnt/volume to say /mnt/foo and returns
|
||||
// /mnt/foo/non/existing/directory.
|
||||
//
|
||||
// BEWARE! EvalSymlinks is not able to detect symlink looks with mustExist=false!
|
||||
// If /tmp/link is symlink to /tmp/link, EvalSymlinks(/tmp/link/foo) returns /tmp/link/foo.
|
||||
func (ne *Nsenter) EvalSymlinks(pathname string, mustExist bool) (string, error) {
|
||||
var args []string
|
||||
if mustExist {
|
||||
// "realpath -e: all components of the path must exist"
|
||||
args = []string{"-e", pathname}
|
||||
} else {
|
||||
// "realpath -m: no path components need exist or be a directory"
|
||||
args = []string{"-m", pathname}
|
||||
}
|
||||
outBytes, err := ne.Exec("realpath", args).CombinedOutput()
|
||||
if err != nil {
|
||||
glog.Infof("failed to resolve symbolic links on %s: %v", pathname, err)
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(outBytes)), nil
|
||||
}
|
||||
|
||||
// KubeletPath returns the path name that can be accessed by containerized
|
||||
// kubelet. It is recommended to resolve symlinks on the host by EvalSymlinks
|
||||
// before calling this function
|
||||
func (ne *Nsenter) KubeletPath(pathname string) string {
|
||||
return filepath.Join(ne.hostRootFsPath, pathname)
|
||||
}
|
||||
|
||||
// NewFakeNsenter returns a Nsenter that does not run "nsenter --mount=... --",
|
||||
// but runs everything in the same mount namespace as the unit test binary.
|
||||
// rootfsPath is supposed to be a symlink, e.g. /tmp/xyz/rootfs -> /.
|
||||
// This fake Nsenter is enough for most operations, e.g. to resolve symlinks,
|
||||
// but it's not enough to call /bin/mount - unit tests don't run as root.
|
||||
func NewFakeNsenter(rootfsPath string) (*Nsenter, error) {
|
||||
executor := &fakeExec{
|
||||
rootfsPath: rootfsPath,
|
||||
}
|
||||
// prepare /rootfs/bin, usr/bin and usr/sbin
|
||||
bin := filepath.Join(rootfsPath, "bin")
|
||||
if err := os.Symlink("/bin", bin); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
usr := filepath.Join(rootfsPath, "usr")
|
||||
if err := os.Mkdir(usr, 0755); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
usrbin := filepath.Join(usr, "bin")
|
||||
if err := os.Symlink("/usr/bin", usrbin); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
usrsbin := filepath.Join(usr, "sbin")
|
||||
if err := os.Symlink("/usr/sbin", usrsbin); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewNsenter(rootfsPath, executor)
|
||||
}
|
||||
|
||||
type fakeExec struct {
|
||||
rootfsPath string
|
||||
}
|
||||
|
||||
func (f fakeExec) Command(cmd string, args ...string) exec.Cmd {
|
||||
// This will intentionaly panic if Nsenter does not provide enough arguments.
|
||||
realCmd := args[2]
|
||||
realArgs := args[3:]
|
||||
return exec.New().Command(realCmd, realArgs...)
|
||||
}
|
||||
|
||||
func (fakeExec) LookPath(file string) (string, error) {
|
||||
return "", errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (fakeExec) CommandContext(ctx context.Context, cmd string, args ...string) exec.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
var _ exec.Interface = fakeExec{}
|
311
vendor/k8s.io/utils/nsenter/nsenter_test.go
generated
vendored
311
vendor/k8s.io/utils/nsenter/nsenter_test.go
generated
vendored
@ -1,311 +0,0 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright 2018 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 nsenter
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"k8s.io/utils/exec"
|
||||
)
|
||||
|
||||
func TestExec(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
command string
|
||||
args []string
|
||||
expectedOutput string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "simple command",
|
||||
command: "echo",
|
||||
args: []string{"hello", "world"},
|
||||
expectedOutput: "hello world\n",
|
||||
},
|
||||
{
|
||||
name: "nozero exit code",
|
||||
command: "false",
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
executor := fakeExec{
|
||||
rootfsPath: "/rootfs",
|
||||
}
|
||||
for _, test := range tests {
|
||||
ns := Nsenter{
|
||||
hostRootFsPath: "/rootfs",
|
||||
executor: executor,
|
||||
}
|
||||
cmd := ns.Exec(test.command, test.args)
|
||||
outBytes, err := cmd.CombinedOutput()
|
||||
out := string(outBytes)
|
||||
if err != nil && !test.expectError {
|
||||
t.Errorf("Test %q: unexpected error: %s", test.name, err)
|
||||
}
|
||||
if err == nil && test.expectError {
|
||||
t.Errorf("Test %q: expected error, got none", test.name)
|
||||
}
|
||||
if test.expectedOutput != out {
|
||||
t.Errorf("test %q: expected output %q, got %q", test.name, test.expectedOutput, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKubeletPath(t *testing.T) {
|
||||
tests := []struct {
|
||||
rootfs string
|
||||
hostpath string
|
||||
expectedKubeletPath string
|
||||
}{
|
||||
{
|
||||
// simple join
|
||||
"/rootfs",
|
||||
"/some/path",
|
||||
"/rootfs/some/path",
|
||||
},
|
||||
{
|
||||
// squash slashes
|
||||
"/rootfs/",
|
||||
"//some/path",
|
||||
"/rootfs/some/path",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
ns := Nsenter{
|
||||
hostRootFsPath: test.rootfs,
|
||||
}
|
||||
out := ns.KubeletPath(test.hostpath)
|
||||
if out != test.expectedKubeletPath {
|
||||
t.Errorf("Expected path %q, got %q", test.expectedKubeletPath, out)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvalSymlinks(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
mustExist bool
|
||||
prepare func(tmpdir string) (src string, expectedDst string, err error)
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "simple file /src",
|
||||
mustExist: true,
|
||||
prepare: func(tmpdir string) (src string, expectedDst string, err error) {
|
||||
src = filepath.Join(tmpdir, "src")
|
||||
err = ioutil.WriteFile(src, []byte{}, 0644)
|
||||
return src, src, err
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-existing file /src",
|
||||
mustExist: true,
|
||||
prepare: func(tmpdir string) (src string, expectedDst string, err error) {
|
||||
src = filepath.Join(tmpdir, "src")
|
||||
return src, "", nil
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "non-existing file /src/ with mustExist=false",
|
||||
mustExist: false,
|
||||
prepare: func(tmpdir string) (src string, expectedDst string, err error) {
|
||||
src = filepath.Join(tmpdir, "src")
|
||||
return src, src, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "non-existing file /existing/path/src with mustExist=false with existing directories",
|
||||
mustExist: false,
|
||||
prepare: func(tmpdir string) (src string, expectedDst string, err error) {
|
||||
src = filepath.Join(tmpdir, "existing/path")
|
||||
if err := os.MkdirAll(src, 0755); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
src = filepath.Join(src, "src")
|
||||
return src, src, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "simple symlink /src -> /dst",
|
||||
mustExist: false,
|
||||
prepare: func(tmpdir string) (src string, expectedDst string, err error) {
|
||||
dst := filepath.Join(tmpdir, "dst")
|
||||
if err = ioutil.WriteFile(dst, []byte{}, 0644); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
src = filepath.Join(tmpdir, "src")
|
||||
err = os.Symlink(dst, src)
|
||||
return src, dst, err
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "dangling symlink /src -> /non-existing-path",
|
||||
mustExist: true,
|
||||
prepare: func(tmpdir string) (src string, expectedDst string, err error) {
|
||||
dst := filepath.Join(tmpdir, "non-existing-path")
|
||||
src = filepath.Join(tmpdir, "src")
|
||||
err = os.Symlink(dst, src)
|
||||
return src, "", err
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "dangling symlink /src -> /non-existing-path with mustExist=false",
|
||||
mustExist: false,
|
||||
prepare: func(tmpdir string) (src string, expectedDst string, err error) {
|
||||
dst := filepath.Join(tmpdir, "non-existing-path")
|
||||
src = filepath.Join(tmpdir, "src")
|
||||
err = os.Symlink(dst, src)
|
||||
return src, dst, err
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "symlink to directory /src/file, where /src is link to /dst",
|
||||
mustExist: true,
|
||||
prepare: func(tmpdir string) (src string, expectedDst string, err error) {
|
||||
dst := filepath.Join(tmpdir, "dst")
|
||||
if err = os.Mkdir(dst, 0755); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
dstFile := filepath.Join(dst, "file")
|
||||
if err = ioutil.WriteFile(dstFile, []byte{}, 0644); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
src = filepath.Join(tmpdir, "src")
|
||||
if err = os.Symlink(dst, src); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
srcFile := filepath.Join(src, "file")
|
||||
return srcFile, dstFile, nil
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "symlink to non-existing directory: /src/file, where /src is link to /dst and dst does not exist",
|
||||
mustExist: true,
|
||||
prepare: func(tmpdir string) (src string, expectedDst string, err error) {
|
||||
dst := filepath.Join(tmpdir, "dst")
|
||||
|
||||
src = filepath.Join(tmpdir, "src")
|
||||
if err = os.Symlink(dst, src); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
srcFile := filepath.Join(src, "file")
|
||||
return srcFile, "", nil
|
||||
},
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "symlink to non-existing directory: /src/file, where /src is link to /dst and dst does not exist with mustExist=false",
|
||||
mustExist: false,
|
||||
prepare: func(tmpdir string) (src string, expectedDst string, err error) {
|
||||
dst := filepath.Join(tmpdir, "dst")
|
||||
dstFile := filepath.Join(dst, "file")
|
||||
|
||||
src = filepath.Join(tmpdir, "src")
|
||||
if err = os.Symlink(dst, src); err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
srcFile := filepath.Join(src, "file")
|
||||
return srcFile, dstFile, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
ns := Nsenter{
|
||||
hostRootFsPath: "/rootfs",
|
||||
executor: fakeExec{
|
||||
rootfsPath: "/rootfs",
|
||||
},
|
||||
}
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "nsenter-hostpath-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
src, expectedDst, err := test.prepare(tmpdir)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
dst, err := ns.EvalSymlinks(src, test.mustExist)
|
||||
if err != nil && !test.expectError {
|
||||
t.Errorf("Test %q: unexpected error: %s", test.name, err)
|
||||
}
|
||||
if err == nil && test.expectError {
|
||||
t.Errorf("Test %q: expected error, got none", test.name)
|
||||
}
|
||||
if dst != expectedDst {
|
||||
t.Errorf("Test %q: expected destination %q, got %q", test.name, expectedDst, dst)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewNsenter(t *testing.T) {
|
||||
// Create a symlink /tmp/xyz/rootfs -> / and use it as rootfs path
|
||||
// It should resolve all binaries correctly, the test runs on Linux
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "nsenter-hostpath-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
rootfs := filepath.Join(tmpdir, "rootfs")
|
||||
if err = os.Symlink("/", rootfs); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = NewNsenter(rootfs, exec.New())
|
||||
if err != nil {
|
||||
t.Errorf("Error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewNsenterError(t *testing.T) {
|
||||
// Create empty dir /tmp/xyz/rootfs and use it as rootfs path
|
||||
// It should resolve all binaries correctly, the test runs on Linux
|
||||
|
||||
tmpdir, err := ioutil.TempDir("", "nsenter-hostpath-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
rootfs := filepath.Join(tmpdir, "rootfs")
|
||||
if err = os.MkdirAll(rootfs, 0755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = NewNsenter(rootfs, exec.New())
|
||||
if err == nil {
|
||||
t.Errorf("Expected error, got none")
|
||||
}
|
||||
}
|
56
vendor/k8s.io/utils/nsenter/nsenter_unsupported.go
generated
vendored
56
vendor/k8s.io/utils/nsenter/nsenter_unsupported.go
generated
vendored
@ -1,56 +0,0 @@
|
||||
// +build !linux
|
||||
|
||||
/*
|
||||
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 nsenter
|
||||
|
||||
import (
|
||||
"k8s.io/utils/exec"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultHostRootFsPath is path to host's filesystem mounted into container
|
||||
// with kubelet.
|
||||
DefaultHostRootFsPath = "/rootfs"
|
||||
)
|
||||
|
||||
// Nsenter is part of experimental support for running the kubelet
|
||||
// in a container.
|
||||
type Nsenter struct {
|
||||
// a map of commands to their paths on the host filesystem
|
||||
Paths map[string]string
|
||||
}
|
||||
|
||||
// NewNsenter constructs a new instance of Nsenter
|
||||
func NewNsenter(hostRootFsPath string, executor exec.Interface) (*Nsenter, error) {
|
||||
return &Nsenter{}, nil
|
||||
}
|
||||
|
||||
// Exec executes nsenter commands in hostProcMountNsPath mount namespace
|
||||
func (ne *Nsenter) Exec(cmd string, args []string) exec.Cmd {
|
||||
return nil
|
||||
}
|
||||
|
||||
// AbsHostPath returns the absolute runnable path for a specified command
|
||||
func (ne *Nsenter) AbsHostPath(command string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// SupportsSystemd checks whether command systemd-run exists
|
||||
func (ne *Nsenter) SupportsSystemd() (string, bool) {
|
||||
return "", false
|
||||
}
|
9
vendor/k8s.io/utils/pointer/OWNERS
generated
vendored
9
vendor/k8s.io/utils/pointer/OWNERS
generated
vendored
@ -1,9 +0,0 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
approvers:
|
||||
- apelisse
|
||||
- stewart-yu
|
||||
- thockin
|
||||
reviewers:
|
||||
- apelisse
|
||||
- stewart-yu
|
||||
- thockin
|
86
vendor/k8s.io/utils/pointer/pointer.go
generated
vendored
86
vendor/k8s.io/utils/pointer/pointer.go
generated
vendored
@ -1,86 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 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 pointer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// AllPtrFieldsNil tests whether all pointer fields in a struct are nil. This is useful when,
|
||||
// for example, an API struct is handled by plugins which need to distinguish
|
||||
// "no plugin accepted this spec" from "this spec is empty".
|
||||
//
|
||||
// This function is only valid for structs and pointers to structs. Any other
|
||||
// type will cause a panic. Passing a typed nil pointer will return true.
|
||||
func AllPtrFieldsNil(obj interface{}) bool {
|
||||
v := reflect.ValueOf(obj)
|
||||
if !v.IsValid() {
|
||||
panic(fmt.Sprintf("reflect.ValueOf() produced a non-valid Value for %#v", obj))
|
||||
}
|
||||
if v.Kind() == reflect.Ptr {
|
||||
if v.IsNil() {
|
||||
return true
|
||||
}
|
||||
v = v.Elem()
|
||||
}
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
if v.Field(i).Kind() == reflect.Ptr && !v.Field(i).IsNil() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Int32Ptr returns a pointer to an int32
|
||||
func Int32Ptr(i int32) *int32 {
|
||||
return &i
|
||||
}
|
||||
|
||||
// Int64Ptr returns a pointer to an int64
|
||||
func Int64Ptr(i int64) *int64 {
|
||||
return &i
|
||||
}
|
||||
|
||||
// Int32PtrDerefOr dereference the int32 ptr and returns it i not nil,
|
||||
// else returns def.
|
||||
func Int32PtrDerefOr(ptr *int32, def int32) int32 {
|
||||
if ptr != nil {
|
||||
return *ptr
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// BoolPtr returns a pointer to a bool
|
||||
func BoolPtr(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
// StringPtr returns a pointer to the passed string.
|
||||
func StringPtr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
// Float32Ptr returns a pointer to the passed float32.
|
||||
func Float32Ptr(i float32) *float32 {
|
||||
return &i
|
||||
}
|
||||
|
||||
// Float64Ptr returns a pointer to the passed float64.
|
||||
func Float64Ptr(i float64) *float64 {
|
||||
return &i
|
||||
}
|
72
vendor/k8s.io/utils/pointer/pointer_test.go
generated
vendored
72
vendor/k8s.io/utils/pointer/pointer_test.go
generated
vendored
@ -1,72 +0,0 @@
|
||||
/*
|
||||
Copyright 2018 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 pointer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAllPtrFieldsNil(t *testing.T) {
|
||||
testCases := []struct {
|
||||
obj interface{}
|
||||
expected bool
|
||||
}{
|
||||
{struct{}{}, true},
|
||||
{struct{ Foo int }{12345}, true},
|
||||
{&struct{ Foo int }{12345}, true},
|
||||
{struct{ Foo *int }{nil}, true},
|
||||
{&struct{ Foo *int }{nil}, true},
|
||||
{struct {
|
||||
Foo int
|
||||
Bar *int
|
||||
}{12345, nil}, true},
|
||||
{&struct {
|
||||
Foo int
|
||||
Bar *int
|
||||
}{12345, nil}, true},
|
||||
{struct {
|
||||
Foo *int
|
||||
Bar *int
|
||||
}{nil, nil}, true},
|
||||
{&struct {
|
||||
Foo *int
|
||||
Bar *int
|
||||
}{nil, nil}, true},
|
||||
{struct{ Foo *int }{new(int)}, false},
|
||||
{&struct{ Foo *int }{new(int)}, false},
|
||||
{struct {
|
||||
Foo *int
|
||||
Bar *int
|
||||
}{nil, new(int)}, false},
|
||||
{&struct {
|
||||
Foo *int
|
||||
Bar *int
|
||||
}{nil, new(int)}, false},
|
||||
{(*struct{})(nil), true},
|
||||
}
|
||||
for i, tc := range testCases {
|
||||
name := fmt.Sprintf("case[%d]", i)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
actualErr := AllPtrFieldsNil(tc.obj)
|
||||
if !reflect.DeepEqual(tc.expected, actualErr) {
|
||||
t.Errorf("%s: expected %t, got %t", name, tc.expected, !tc.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
70
vendor/k8s.io/utils/temp/dir.go
generated
vendored
70
vendor/k8s.io/utils/temp/dir.go
generated
vendored
@ -1,70 +0,0 @@
|
||||
/*
|
||||
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 temp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// Directory is an interface to a temporary directory, in which you can
|
||||
// create new files.
|
||||
type Directory interface {
|
||||
// NewFile creates a new file in that directory. Calling NewFile
|
||||
// with the same filename twice will result in an error.
|
||||
NewFile(name string) (io.WriteCloser, error)
|
||||
// Delete removes the directory and its content.
|
||||
Delete() error
|
||||
}
|
||||
|
||||
// Dir is wrapping an temporary directory on disk.
|
||||
type Dir struct {
|
||||
// Name is the name (full path) of the created directory.
|
||||
Name string
|
||||
}
|
||||
|
||||
var _ Directory = &Dir{}
|
||||
|
||||
// CreateTempDir returns a new Directory wrapping a temporary directory
|
||||
// on disk.
|
||||
func CreateTempDir(prefix string) (*Dir, error) {
|
||||
name, err := ioutil.TempDir("", fmt.Sprintf("%s-", prefix))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Dir{
|
||||
Name: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// NewFile creates a new file in the specified directory.
|
||||
func (d *Dir) NewFile(name string) (io.WriteCloser, error) {
|
||||
return os.OpenFile(
|
||||
filepath.Join(d.Name, name),
|
||||
os.O_WRONLY|os.O_CREATE|os.O_TRUNC|os.O_EXCL,
|
||||
0700,
|
||||
)
|
||||
}
|
||||
|
||||
// Delete the underlying directory, and all of its content.
|
||||
func (d *Dir) Delete() error {
|
||||
return os.RemoveAll(d.Name)
|
||||
}
|
90
vendor/k8s.io/utils/temp/dir_test.go
generated
vendored
90
vendor/k8s.io/utils/temp/dir_test.go
generated
vendored
@ -1,90 +0,0 @@
|
||||
/*
|
||||
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 temp
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTempDir(t *testing.T) {
|
||||
dir, err := CreateTempDir("prefix")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Delete the directory no matter what.
|
||||
defer dir.Delete()
|
||||
|
||||
// Make sure we have created the dir, with the proper name
|
||||
_, err = os.Stat(dir.Name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.HasPrefix(filepath.Base(dir.Name), "prefix") {
|
||||
t.Fatalf(`Directory doesn't start with "prefix": %q`,
|
||||
dir.Name)
|
||||
}
|
||||
|
||||
// Verify that the directory is empty
|
||||
entries, err := ioutil.ReadDir(dir.Name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(entries) != 0 {
|
||||
t.Fatalf("Directory should be empty, has %d elements",
|
||||
len(entries))
|
||||
}
|
||||
|
||||
// Create a couple of files
|
||||
_, err = dir.NewFile("ONE")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = dir.NewFile("TWO")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// We can't create the same file twice
|
||||
_, err = dir.NewFile("TWO")
|
||||
if err == nil {
|
||||
t.Fatal("NewFile should fail to create the same file twice")
|
||||
}
|
||||
|
||||
// We have created only two files
|
||||
entries, err = ioutil.ReadDir(dir.Name)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(entries) != 2 {
|
||||
t.Fatalf("ReadDir should have two elements, has %d elements",
|
||||
len(entries))
|
||||
}
|
||||
|
||||
// Verify that deletion works
|
||||
err = dir.Delete()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = os.Stat(dir.Name)
|
||||
if err == nil {
|
||||
t.Fatal("Directory should be gone, still present.")
|
||||
}
|
||||
}
|
19
vendor/k8s.io/utils/temp/doc.go
generated
vendored
19
vendor/k8s.io/utils/temp/doc.go
generated
vendored
@ -1,19 +0,0 @@
|
||||
/*
|
||||
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 temp provides an interface to handle temporary files and
|
||||
// directories.
|
||||
package temp // import "k8s.io/utils/temp"
|
66
vendor/k8s.io/utils/temp/temptest/dir.go
generated
vendored
66
vendor/k8s.io/utils/temp/temptest/dir.go
generated
vendored
@ -1,66 +0,0 @@
|
||||
/*
|
||||
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 temptest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"k8s.io/utils/temp"
|
||||
)
|
||||
|
||||
// FakeDir implements a Directory that is not backed on the
|
||||
// filesystem. This is useful for testing since the created "files" are
|
||||
// simple bytes.Buffer that can be inspected.
|
||||
type FakeDir struct {
|
||||
Files map[string]*FakeFile
|
||||
Deleted bool
|
||||
}
|
||||
|
||||
var _ temp.Directory = &FakeDir{}
|
||||
|
||||
// NewFile returns a new FakeFile if the filename doesn't exist already.
|
||||
// This function will fail if the directory has already been deleted.
|
||||
func (d *FakeDir) NewFile(name string) (io.WriteCloser, error) {
|
||||
if d.Deleted {
|
||||
return nil, errors.New("can't create file in deleted FakeDir")
|
||||
}
|
||||
if d.Files == nil {
|
||||
d.Files = map[string]*FakeFile{}
|
||||
}
|
||||
f := d.Files[name]
|
||||
if f != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"FakeDir already has file named %q",
|
||||
name,
|
||||
)
|
||||
}
|
||||
f = &FakeFile{}
|
||||
d.Files[name] = f
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// Delete doesn't remove anything, but records that the directory has
|
||||
// been deleted.
|
||||
func (d *FakeDir) Delete() error {
|
||||
if d.Deleted {
|
||||
return errors.New("failed to re-delete FakeDir")
|
||||
}
|
||||
d.Deleted = true
|
||||
return nil
|
||||
}
|
67
vendor/k8s.io/utils/temp/temptest/dir_test.go
generated
vendored
67
vendor/k8s.io/utils/temp/temptest/dir_test.go
generated
vendored
@ -1,67 +0,0 @@
|
||||
/*
|
||||
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 temptest
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFakeDir(t *testing.T) {
|
||||
d := &FakeDir{}
|
||||
|
||||
f, err := d.NewFile("ONE")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
n, err := io.WriteString(f, "Bonjour!")
|
||||
if n != 8 || err != nil {
|
||||
t.Fatalf(
|
||||
`WriteString(f, "Bonjour!") = (%v, %v), expected (%v, %v)`,
|
||||
n, err,
|
||||
0, nil,
|
||||
)
|
||||
}
|
||||
if got := d.Files["ONE"].Buffer.String(); got != "Bonjour!" {
|
||||
t.Fatalf(`file content is %q, expected "Bonjour!"`, got)
|
||||
}
|
||||
|
||||
f, err = d.NewFile("ONE")
|
||||
if err == nil {
|
||||
t.Fatal("Same file could be created twice.")
|
||||
}
|
||||
|
||||
err = d.Delete()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = d.Delete()
|
||||
if err == nil {
|
||||
t.Fatal("FakeDir could be deleted twice.")
|
||||
}
|
||||
|
||||
f, err = d.NewFile("TWO")
|
||||
if err == nil {
|
||||
t.Fatal("NewFile could be created in deleted dir")
|
||||
}
|
||||
|
||||
if !d.Deleted {
|
||||
t.Fatal("FakeDir should be deleted.")
|
||||
}
|
||||
}
|
19
vendor/k8s.io/utils/temp/temptest/doc.go
generated
vendored
19
vendor/k8s.io/utils/temp/temptest/doc.go
generated
vendored
@ -1,19 +0,0 @@
|
||||
/*
|
||||
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 temptest provides utilities for testing temp
|
||||
// files/directories testing.
|
||||
package temptest
|
57
vendor/k8s.io/utils/temp/temptest/example_test.go
generated
vendored
57
vendor/k8s.io/utils/temp/temptest/example_test.go
generated
vendored
@ -1,57 +0,0 @@
|
||||
/*
|
||||
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 temptest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"k8s.io/utils/temp"
|
||||
)
|
||||
|
||||
func TestedCode(dir temp.Directory) error {
|
||||
f, err := dir.NewFile("filename")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = io.WriteString(f, "Bonjour!")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return dir.Delete()
|
||||
}
|
||||
|
||||
func Example() {
|
||||
dir := FakeDir{}
|
||||
|
||||
err := TestedCode(&dir)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if dir.Deleted == false {
|
||||
panic(errors.New("Directory should have been deleted"))
|
||||
}
|
||||
|
||||
if dir.Files["filename"] == nil {
|
||||
panic(errors.New(`"filename" should have been created`))
|
||||
}
|
||||
|
||||
fmt.Println(dir.Files["filename"].Buffer.String())
|
||||
// Output: Bonjour!
|
||||
}
|
52
vendor/k8s.io/utils/temp/temptest/file.go
generated
vendored
52
vendor/k8s.io/utils/temp/temptest/file.go
generated
vendored
@ -1,52 +0,0 @@
|
||||
/*
|
||||
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 temptest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// FakeFile is an implementation of a WriteCloser, that records what has
|
||||
// been written in the file (in a bytes.Buffer) and if the file has been
|
||||
// closed.
|
||||
type FakeFile struct {
|
||||
Buffer bytes.Buffer
|
||||
Closed bool
|
||||
}
|
||||
|
||||
var _ io.WriteCloser = &FakeFile{}
|
||||
|
||||
// Write appends the contents of p to the Buffer. If the file has
|
||||
// already been closed, an error is returned.
|
||||
func (f *FakeFile) Write(p []byte) (n int, err error) {
|
||||
if f.Closed {
|
||||
return 0, errors.New("can't write to closed FakeFile")
|
||||
}
|
||||
return f.Buffer.Write(p)
|
||||
}
|
||||
|
||||
// Close records that the file has been closed. If the file has already
|
||||
// been closed, an error is returned.
|
||||
func (f *FakeFile) Close() error {
|
||||
if f.Closed {
|
||||
return errors.New("FakeFile was closed multiple times")
|
||||
}
|
||||
f.Closed = true
|
||||
return nil
|
||||
}
|
56
vendor/k8s.io/utils/temp/temptest/file_test.go
generated
vendored
56
vendor/k8s.io/utils/temp/temptest/file_test.go
generated
vendored
@ -1,56 +0,0 @@
|
||||
/*
|
||||
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 temptest
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFakeFile(t *testing.T) {
|
||||
f := &FakeFile{}
|
||||
|
||||
n, err := io.WriteString(f, "Bonjour!")
|
||||
if n != 8 || err != nil {
|
||||
t.Fatalf(
|
||||
`WriteString(f, "Bonjour!") = (%v, %v), expected (%v, %v)`,
|
||||
n, err,
|
||||
8, nil,
|
||||
)
|
||||
}
|
||||
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// File can't be closed twice.
|
||||
err = f.Close()
|
||||
if err == nil {
|
||||
t.Fatal("FakeFile could be closed twice")
|
||||
}
|
||||
|
||||
// File is not writable after close.
|
||||
n, err = io.WriteString(f, "Bonjour!")
|
||||
if n != 0 || err == nil {
|
||||
t.Fatalf(
|
||||
`WriteString(f, "Bonjour!") = (%v, %v), expected (%v, %v)`,
|
||||
n, err,
|
||||
0, "non-nil",
|
||||
)
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user