New Upstream Release - golang-k8s-utils
Ready changes
Summary
Merged new upstream version: 0.0~git20230505.9f67429 (was: 0.0~git20221128.99ec85e).
Diff
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 3e3a48f..e2042f4 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -4,7 +4,7 @@ jobs:
build:
strategy:
matrix:
- go-version: [1.18.x, 1.19.x]
+ go-version: [1.19.x, 1.20.x]
platform: [windows-latest]
runs-on: ${{ matrix.platform }}
steps:
@@ -24,7 +24,7 @@ jobs:
test:
strategy:
matrix:
- go-version: [1.18.x, 1.19.x]
+ go-version: [1.19.x, 1.20.x]
platform: [ubuntu-latest, macos-latest]
runs-on: ${{ matrix.platform }}
steps:
@@ -47,7 +47,7 @@ jobs:
- name: Lint
run: |
docker run --rm -v `pwd`:/go/src/k8s.io/klog -w /go/src/k8s.io/klog \
- golangci/golangci-lint:v1.46.2 golangci-lint run --disable-all -v \
+ golangci/golangci-lint:v1.51.2 golangci-lint run --disable-all -v \
-E govet -E misspell -E gofmt -E ineffassign -E golint
apidiff:
runs-on: ubuntu-latest
@@ -56,7 +56,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v3
with:
- go-version: 1.19.x
+ go-version: 1.20.x
- name: Add GOBIN to PATH
run: echo "$(go env GOPATH)/bin" >> $GITHUB_PATH
- name: Install dependencies
diff --git a/cpuset/OWNERS b/cpuset/OWNERS
new file mode 100644
index 0000000..0ec2b08
--- /dev/null
+++ b/cpuset/OWNERS
@@ -0,0 +1,8 @@
+# See the OWNERS docs at https://go.k8s.io/owners
+
+approvers:
+ - dchen1107
+ - derekwaynecarr
+ - ffromani
+ - klueska
+ - SergeyKanzhelev
diff --git a/cpuset/cpuset.go b/cpuset/cpuset.go
new file mode 100644
index 0000000..52912d9
--- /dev/null
+++ b/cpuset/cpuset.go
@@ -0,0 +1,256 @@
+/*
+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 cpuset represents a collection of CPUs in a 'set' data structure.
+//
+// It can be used to represent core IDs, hyper thread siblings, CPU nodes, or processor IDs.
+//
+// The only special thing about this package is that
+// methods are provided to convert back and forth from Linux 'list' syntax.
+// See http://man7.org/linux/man-pages/man7/cpuset.7.html#FORMATS for details.
+//
+// Future work can migrate this to use a 'set' library, and relax the dubious 'immutable' property.
+//
+// This package was originally developed in the 'kubernetes' repository.
+package cpuset
+
+import (
+ "bytes"
+ "fmt"
+ "reflect"
+ "sort"
+ "strconv"
+ "strings"
+)
+
+// CPUSet is a thread-safe, immutable set-like data structure for CPU IDs.
+type CPUSet struct {
+ elems map[int]struct{}
+}
+
+// New returns a new CPUSet containing the supplied elements.
+func New(cpus ...int) CPUSet {
+ s := CPUSet{
+ elems: map[int]struct{}{},
+ }
+ for _, c := range cpus {
+ s.add(c)
+ }
+ return s
+}
+
+// add adds the supplied elements to the CPUSet.
+// It is intended for internal use only, since it mutates the CPUSet.
+func (s CPUSet) add(elems ...int) {
+ for _, elem := range elems {
+ s.elems[elem] = struct{}{}
+ }
+}
+
+// Size returns the number of elements in this set.
+func (s CPUSet) Size() int {
+ return len(s.elems)
+}
+
+// IsEmpty returns true if there are zero elements in this set.
+func (s CPUSet) IsEmpty() bool {
+ return s.Size() == 0
+}
+
+// Contains returns true if the supplied element is present in this set.
+func (s CPUSet) Contains(cpu int) bool {
+ _, found := s.elems[cpu]
+ return found
+}
+
+// Equals returns true if the supplied set contains exactly the same elements
+// as this set (s IsSubsetOf s2 and s2 IsSubsetOf s).
+func (s CPUSet) Equals(s2 CPUSet) bool {
+ return reflect.DeepEqual(s.elems, s2.elems)
+}
+
+// filter returns a new CPU set that contains all of the elements from this
+// set that match the supplied predicate, without mutating the source set.
+func (s CPUSet) filter(predicate func(int) bool) CPUSet {
+ r := New()
+ for cpu := range s.elems {
+ if predicate(cpu) {
+ r.add(cpu)
+ }
+ }
+ return r
+}
+
+// IsSubsetOf returns true if the supplied set contains all the elements
+func (s CPUSet) IsSubsetOf(s2 CPUSet) bool {
+ result := true
+ for cpu := range s.elems {
+ if !s2.Contains(cpu) {
+ result = false
+ break
+ }
+ }
+ return result
+}
+
+// Union returns a new CPU set that contains all of the elements from this
+// set and all of the elements from the supplied sets, without mutating
+// either source set.
+func (s CPUSet) Union(s2 ...CPUSet) CPUSet {
+ r := New()
+ for cpu := range s.elems {
+ r.add(cpu)
+ }
+ for _, cs := range s2 {
+ for cpu := range cs.elems {
+ r.add(cpu)
+ }
+ }
+ return r
+}
+
+// Intersection returns a new CPU set that contains all of the elements
+// that are present in both this set and the supplied set, without mutating
+// either source set.
+func (s CPUSet) Intersection(s2 CPUSet) CPUSet {
+ return s.filter(func(cpu int) bool { return s2.Contains(cpu) })
+}
+
+// Difference returns a new CPU set that contains all of the elements that
+// are present in this set and not the supplied set, without mutating either
+// source set.
+func (s CPUSet) Difference(s2 CPUSet) CPUSet {
+ return s.filter(func(cpu int) bool { return !s2.Contains(cpu) })
+}
+
+// List returns a slice of integers that contains all elements from
+// this set. The list is sorted.
+func (s CPUSet) List() []int {
+ result := s.UnsortedList()
+ sort.Ints(result)
+ return result
+}
+
+// UnsortedList returns a slice of integers that contains all elements from
+// this set.
+func (s CPUSet) UnsortedList() []int {
+ result := make([]int, 0, len(s.elems))
+ for cpu := range s.elems {
+ result = append(result, cpu)
+ }
+ return result
+}
+
+// String returns a new string representation of the elements in this CPU set
+// in canonical linux CPU list format.
+//
+// See: http://man7.org/linux/man-pages/man7/cpuset.7.html#FORMATS
+func (s CPUSet) String() string {
+ if s.IsEmpty() {
+ return ""
+ }
+
+ elems := s.List()
+
+ type rng struct {
+ start int
+ end int
+ }
+
+ ranges := []rng{{elems[0], elems[0]}}
+
+ for i := 1; i < len(elems); i++ {
+ lastRange := &ranges[len(ranges)-1]
+ // if this element is adjacent to the high end of the last range
+ if elems[i] == lastRange.end+1 {
+ // then extend the last range to include this element
+ lastRange.end = elems[i]
+ continue
+ }
+ // otherwise, start a new range beginning with this element
+ ranges = append(ranges, rng{elems[i], elems[i]})
+ }
+
+ // construct string from ranges
+ var result bytes.Buffer
+ for _, r := range ranges {
+ if r.start == r.end {
+ result.WriteString(strconv.Itoa(r.start))
+ } else {
+ result.WriteString(fmt.Sprintf("%d-%d", r.start, r.end))
+ }
+ result.WriteString(",")
+ }
+ return strings.TrimRight(result.String(), ",")
+}
+
+// Parse CPUSet constructs a new CPU set from a Linux CPU list formatted string.
+//
+// See: http://man7.org/linux/man-pages/man7/cpuset.7.html#FORMATS
+func Parse(s string) (CPUSet, error) {
+ // Handle empty string.
+ if s == "" {
+ return New(), nil
+ }
+
+ result := New()
+
+ // Split CPU list string:
+ // "0-5,34,46-48" => ["0-5", "34", "46-48"]
+ ranges := strings.Split(s, ",")
+
+ for _, r := range ranges {
+ boundaries := strings.SplitN(r, "-", 2)
+ if len(boundaries) == 1 {
+ // Handle ranges that consist of only one element like "34".
+ elem, err := strconv.Atoi(boundaries[0])
+ if err != nil {
+ return New(), err
+ }
+ result.add(elem)
+ } else if len(boundaries) == 2 {
+ // Handle multi-element ranges like "0-5".
+ start, err := strconv.Atoi(boundaries[0])
+ if err != nil {
+ return New(), err
+ }
+ end, err := strconv.Atoi(boundaries[1])
+ if err != nil {
+ return New(), err
+ }
+ if start > end {
+ return New(), fmt.Errorf("invalid range %q (%d > %d)", r, start, end)
+ }
+ // start == end is acceptable (1-1 -> 1)
+
+ // Add all elements to the result.
+ // e.g. "0-5", "46-48" => [0, 1, 2, 3, 4, 5, 46, 47, 48].
+ for e := start; e <= end; e++ {
+ result.add(e)
+ }
+ }
+ }
+ return result, nil
+}
+
+// Clone returns a copy of this CPU set.
+func (s CPUSet) Clone() CPUSet {
+ r := New()
+ for elem := range s.elems {
+ r.add(elem)
+ }
+ return r
+}
diff --git a/cpuset/cpuset_test.go b/cpuset/cpuset_test.go
new file mode 100644
index 0000000..275cc9e
--- /dev/null
+++ b/cpuset/cpuset_test.go
@@ -0,0 +1,358 @@
+/*
+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 cpuset
+
+import (
+ "reflect"
+ "sort"
+ "testing"
+)
+
+func TestCPUSetSize(t *testing.T) {
+ testCases := []struct {
+ cpuset CPUSet
+ expected int
+ }{
+ {New(), 0},
+ {New(5), 1},
+ {New(1, 2, 3, 4, 5), 5},
+ }
+
+ for _, c := range testCases {
+ actual := c.cpuset.Size()
+ if actual != c.expected {
+ t.Errorf("expected: %d, actual: %d, cpuset: [%v]", c.expected, actual, c.cpuset)
+ }
+ }
+}
+
+func TestCPUSetIsEmpty(t *testing.T) {
+ testCases := []struct {
+ cpuset CPUSet
+ expected bool
+ }{
+ {New(), true},
+ {New(5), false},
+ {New(1, 2, 3, 4, 5), false},
+ }
+
+ for _, c := range testCases {
+ actual := c.cpuset.IsEmpty()
+ if actual != c.expected {
+ t.Errorf("expected: %t, IsEmpty() returned: %t, cpuset: [%v]", c.expected, actual, c.cpuset)
+ }
+ }
+}
+
+func TestCPUSetContains(t *testing.T) {
+ testCases := []struct {
+ cpuset CPUSet
+ mustContain []int
+ mustNotContain []int
+ }{
+ {New(), []int{}, []int{1, 2, 3, 4, 5}},
+ {New(5), []int{5}, []int{1, 2, 3, 4}},
+ {New(1, 2, 4, 5), []int{1, 2, 4, 5}, []int{0, 3, 6}},
+ }
+
+ for _, c := range testCases {
+ for _, elem := range c.mustContain {
+ if !c.cpuset.Contains(elem) {
+ t.Errorf("expected cpuset to contain element %d: [%v]", elem, c.cpuset)
+ }
+ }
+ for _, elem := range c.mustNotContain {
+ if c.cpuset.Contains(elem) {
+ t.Errorf("expected cpuset not to contain element %d: [%v]", elem, c.cpuset)
+ }
+ }
+ }
+}
+
+func TestCPUSetEqual(t *testing.T) {
+ shouldEqual := []struct {
+ s1 CPUSet
+ s2 CPUSet
+ }{
+ {New(), New()},
+ {New(5), New(5)},
+ {New(1, 2, 3, 4, 5), New(1, 2, 3, 4, 5)},
+ {New(5, 4, 3, 2, 1), New(1, 2, 3, 4, 5)},
+ }
+
+ shouldNotEqual := []struct {
+ s1 CPUSet
+ s2 CPUSet
+ }{
+ {New(), New(5)},
+ {New(5), New()},
+ {New(), New(1, 2, 3, 4, 5)},
+ {New(1, 2, 3, 4, 5), New()},
+ {New(5), New(1, 2, 3, 4, 5)},
+ {New(1, 2, 3, 4, 5), New(5)},
+ }
+
+ for _, c := range shouldEqual {
+ if !c.s1.Equals(c.s2) {
+ t.Errorf("expected cpusets to be equal: s1: [%v], s2: [%v]", c.s1, c.s2)
+ }
+ }
+ for _, c := range shouldNotEqual {
+ if c.s1.Equals(c.s2) {
+ t.Errorf("expected cpusets to not be equal: s1: [%v], s2: [%v]", c.s1, c.s2)
+ }
+ }
+}
+
+func TestCPUSetIsSubsetOf(t *testing.T) {
+ shouldBeSubset := []struct {
+ s1 CPUSet
+ s2 CPUSet
+ }{
+ // A set is a subset of itself
+ {New(), New()},
+ {New(5), New(5)},
+ {New(1, 2, 3, 4, 5), New(1, 2, 3, 4, 5)},
+
+ // Empty set is a subset of every set
+ {New(), New(5)},
+ {New(), New(1, 2, 3, 4, 5)},
+
+ {New(5), New(1, 2, 3, 4, 5)},
+ {New(1, 2, 3), New(1, 2, 3, 4, 5)},
+ {New(4, 5), New(1, 2, 3, 4, 5)},
+ {New(2, 3), New(1, 2, 3, 4, 5)},
+ }
+
+ shouldNotBeSubset := []struct {
+ s1 CPUSet
+ s2 CPUSet
+ }{
+ // A set with more elements is not a subset.
+ {New(5), New()},
+
+ // Disjoint set is not a subset.
+ {New(6), New(5)},
+ }
+
+ for _, c := range shouldBeSubset {
+ if !c.s1.IsSubsetOf(c.s2) {
+ t.Errorf("expected s1 to be a subset of s2: s1: [%v], s2: [%v]", c.s1, c.s2)
+ }
+ }
+ for _, c := range shouldNotBeSubset {
+ if c.s1.IsSubsetOf(c.s2) {
+ t.Errorf("expected s1 to not be a subset of s2: s1: [%v], s2: [%v]", c.s1, c.s2)
+ }
+ }
+}
+
+func TestCPUSetUnion(t *testing.T) {
+ testCases := []struct {
+ s1 CPUSet
+ others []CPUSet
+ expected CPUSet
+ }{
+ {New(5), []CPUSet{}, New(5)},
+
+ {New(), []CPUSet{New()}, New()},
+
+ {New(), []CPUSet{New(5)}, New(5)},
+ {New(5), []CPUSet{New()}, New(5)},
+ {New(5), []CPUSet{New(5)}, New(5)},
+
+ {New(), []CPUSet{New(1, 2, 3, 4, 5)}, New(1, 2, 3, 4, 5)},
+ {New(1, 2, 3, 4, 5), []CPUSet{New()}, New(1, 2, 3, 4, 5)},
+ {New(1, 2, 3, 4, 5), []CPUSet{New(1, 2, 3, 4, 5)}, New(1, 2, 3, 4, 5)},
+
+ {New(5), []CPUSet{New(1, 2, 3, 4, 5)}, New(1, 2, 3, 4, 5)},
+ {New(1, 2, 3, 4, 5), []CPUSet{New(5)}, New(1, 2, 3, 4, 5)},
+
+ {New(1, 2), []CPUSet{New(3, 4, 5)}, New(1, 2, 3, 4, 5)},
+ {New(1, 2, 3), []CPUSet{New(3, 4, 5)}, New(1, 2, 3, 4, 5)},
+
+ {New(), []CPUSet{New(1, 2, 3, 4, 5), New(4, 5)}, New(1, 2, 3, 4, 5)},
+ {New(1, 2, 3, 4, 5), []CPUSet{New(), New(4)}, New(1, 2, 3, 4, 5)},
+ {New(1, 2, 3, 4, 5), []CPUSet{New(1, 2, 3, 4, 5), New(1, 5)}, New(1, 2, 3, 4, 5)},
+ }
+
+ for _, c := range testCases {
+ result := c.s1.Union(c.others...)
+ if !result.Equals(c.expected) {
+ t.Errorf("expected the union of s1 and s2 to be [%v] (got [%v]), others: [%v]", c.expected, result, c.others)
+ }
+ }
+}
+
+func TestCPUSetIntersection(t *testing.T) {
+ testCases := []struct {
+ s1 CPUSet
+ s2 CPUSet
+ expected CPUSet
+ }{
+ {New(), New(), New()},
+
+ {New(), New(5), New()},
+ {New(5), New(), New()},
+ {New(5), New(5), New(5)},
+
+ {New(), New(1, 2, 3, 4, 5), New()},
+ {New(1, 2, 3, 4, 5), New(), New()},
+ {New(1, 2, 3, 4, 5), New(1, 2, 3, 4, 5), New(1, 2, 3, 4, 5)},
+
+ {New(5), New(1, 2, 3, 4, 5), New(5)},
+ {New(1, 2, 3, 4, 5), New(5), New(5)},
+
+ {New(1, 2), New(3, 4, 5), New()},
+ {New(1, 2, 3), New(3, 4, 5), New(3)},
+ }
+
+ for _, c := range testCases {
+ result := c.s1.Intersection(c.s2)
+ if !result.Equals(c.expected) {
+ t.Errorf("expected the intersection of s1 and s2 to be [%v] (got [%v]), s1: [%v], s2: [%v]", c.expected, result, c.s1, c.s2)
+ }
+ }
+}
+
+func TestCPUSetDifference(t *testing.T) {
+ testCases := []struct {
+ s1 CPUSet
+ s2 CPUSet
+ expected CPUSet
+ }{
+ {New(), New(), New()},
+
+ {New(), New(5), New()},
+ {New(5), New(), New(5)},
+ {New(5), New(5), New()},
+
+ {New(), New(1, 2, 3, 4, 5), New()},
+ {New(1, 2, 3, 4, 5), New(), New(1, 2, 3, 4, 5)},
+ {New(1, 2, 3, 4, 5), New(1, 2, 3, 4, 5), New()},
+
+ {New(5), New(1, 2, 3, 4, 5), New()},
+ {New(1, 2, 3, 4, 5), New(5), New(1, 2, 3, 4)},
+
+ {New(1, 2), New(3, 4, 5), New(1, 2)},
+ {New(1, 2, 3), New(3, 4, 5), New(1, 2)},
+ }
+
+ for _, c := range testCases {
+ result := c.s1.Difference(c.s2)
+ if !result.Equals(c.expected) {
+ t.Errorf("expected the difference of s1 and s2 to be [%v] (got [%v]), s1: [%v], s2: [%v]", c.expected, result, c.s1, c.s2)
+ }
+ }
+}
+
+func TestCPUSetList(t *testing.T) {
+ testCases := []struct {
+ set CPUSet
+ expected []int // must be sorted
+ }{
+ {New(), []int{}},
+ {New(5), []int{5}},
+ {New(1, 2, 3, 4, 5), []int{1, 2, 3, 4, 5}},
+ {New(5, 4, 3, 2, 1), []int{1, 2, 3, 4, 5}},
+ }
+
+ for _, c := range testCases {
+ result := c.set.List()
+ if !reflect.DeepEqual(result, c.expected) {
+ t.Errorf("unexpected List() contents. got [%v] want [%v] (set: [%v])", result, c.expected, c.set)
+ }
+
+ // We cannot rely on internal storage order details for a unit test.
+ // The best we can do is to sort the output of 'UnsortedList'.
+ result = c.set.UnsortedList()
+ sort.Ints(result)
+ if !reflect.DeepEqual(result, c.expected) {
+ t.Errorf("unexpected UnsortedList() contents. got [%v] want [%v] (set: [%v])", result, c.expected, c.set)
+ }
+ }
+}
+
+func TestCPUSetString(t *testing.T) {
+ testCases := []struct {
+ set CPUSet
+ expected string
+ }{
+ {New(), ""},
+ {New(5), "5"},
+ {New(1, 2, 3, 4, 5), "1-5"},
+ {New(1, 2, 3, 5, 6, 8), "1-3,5-6,8"},
+ }
+
+ for _, c := range testCases {
+ result := c.set.String()
+ if result != c.expected {
+ t.Errorf("expected set as string to be %s (got \"%s\"), s: [%v]", c.expected, result, c.set)
+ }
+ }
+}
+
+func TestParse(t *testing.T) {
+ positiveTestCases := []struct {
+ cpusetString string
+ expected CPUSet
+ }{
+ {"", New()},
+ {"5", New(5)},
+ {"1,2,3,4,5", New(1, 2, 3, 4, 5)},
+ {"1-5", New(1, 2, 3, 4, 5)},
+ {"1-2,3-5", New(1, 2, 3, 4, 5)},
+ {"5,4,3,2,1", New(1, 2, 3, 4, 5)}, // Range ordering
+ {"3-6,1-5", New(1, 2, 3, 4, 5, 6)}, // Overlapping ranges
+ {"3-3,5-5", New(3, 5)}, // Very short ranges
+ }
+
+ for _, c := range positiveTestCases {
+ result, err := Parse(c.cpusetString)
+ if err != nil {
+ t.Errorf("expected error not to have occurred: %v", err)
+ }
+ if !result.Equals(c.expected) {
+ t.Errorf("expected string \"%s\" to parse as [%v] (got [%v])", c.cpusetString, c.expected, result)
+ }
+ }
+
+ negativeTestCases := []string{
+ // Non-numeric entries
+ "nonnumeric", "non-numeric", "no,numbers", "0-a", "a-0", "0,a", "a,0", "1-2,a,3-5",
+ // Incomplete sequences
+ "0,", "0,,", ",3", ",,3", "0,,3",
+ // Incomplete ranges and/or negative numbers
+ "-1", "1-", "1,2-,3", "1,-2,3", "-1--2", "--1", "1--",
+ // Reversed ranges
+ "3-0", "0--3"}
+ for _, c := range negativeTestCases {
+ result, err := Parse(c)
+ if err == nil {
+ t.Errorf("expected parse failure of \"%s\", but it succeeded as \"%s\"", c, result.String())
+ }
+ }
+}
+
+func TestClone(t *testing.T) {
+ original := New(1, 2, 3, 4, 5)
+ clone := original.Clone()
+
+ if !original.Equals(clone) {
+ t.Errorf("expected clone [%v] to equal original [%v]", clone, original)
+ }
+}
diff --git a/debian/changelog b/debian/changelog
index f6d784b..859b1d7 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+golang-k8s-utils (0.0~git20230505.9f67429-1) UNRELEASED; urgency=low
+
+ * New upstream snapshot.
+
+ -- Debian Janitor <janitor@jelmer.uk> Mon, 22 May 2023 22:35:43 -0000
+
golang-k8s-utils (0.0~git20221128.99ec85e-1) unstable; urgency=medium
* New upstream snapshot
diff --git a/exec/testing/fake_exec.go b/exec/testing/fake_exec.go
index 7380689..7c125a6 100644
--- a/exec/testing/fake_exec.go
+++ b/exec/testing/fake_exec.go
@@ -20,6 +20,7 @@ import (
"context"
"fmt"
"io"
+ "sync"
"k8s.io/utils/exec"
)
@@ -32,11 +33,12 @@ type FakeExec struct {
// ExactOrder enforces that commands are called in the order they are scripted,
// and with the exact same arguments
ExactOrder bool
- // DisableScripts removes the requirement that a slice of FakeCommandAction be
- // populated before calling Command(). This makes the fakeexec (and subsequent
- // calls to Run() or CombinedOutput() always return success and there is no
- // ability to set their output.
+ // DisableScripts removes the requirement that CommandScripts be populated
+ // before calling Command(). This makes Command() and subsequent calls to
+ // Run() or CombinedOutput() always return success and empty output.
DisableScripts bool
+
+ mu sync.Mutex
}
var _ exec.Interface = &FakeExec{}
@@ -44,18 +46,15 @@ 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
+// Command returns the next unexecuted command in CommandScripts.
+// This function is safe for concurrent access as long as the underlying
+// FakeExec struct is not modified during execution.
func (fake *FakeExec) Command(cmd string, args ...string) exec.Cmd {
if fake.DisableScripts {
fakeCmd := &FakeCmd{DisableScripts: true}
return InitFakeCmd(fakeCmd, cmd, args...)
}
- 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++
- fakeCmd := fake.CommandScript[i](cmd, args...)
+ fakeCmd := fake.nextCommand(cmd, args)
if fake.ExactOrder {
argv := append([]string{cmd}, args...)
fc := fakeCmd.(*FakeCmd)
@@ -74,6 +73,18 @@ func (fake *FakeExec) Command(cmd string, args ...string) exec.Cmd {
return fakeCmd
}
+func (fake *FakeExec) nextCommand(cmd string, args []string) exec.Cmd {
+ fake.mu.Lock()
+ defer fake.mu.Unlock()
+
+ 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...)
diff --git a/inotify/README.md b/inotify/README.md
index 0c723a8..fbaa166 100644
--- a/inotify/README.md
+++ b/inotify/README.md
@@ -1,5 +1,5 @@
This is a fork of golang.org/x/exp/inotify before it was deleted.
-Please use gopkg.in/fsnotify.v0 instead.
+Please use gopkg.in/fsnotify.v1 instead.
-For updates, see: https://fsnotify.org/
+For updates, see: https://github.com/fsnotify/fsnotify
diff --git a/inotify/inotify_linux.go b/inotify/inotify_linux.go
index fbb50c2..2b9eabb 100644
--- a/inotify/inotify_linux.go
+++ b/inotify/inotify_linux.go
@@ -9,23 +9,23 @@
Package inotify implements a wrapper for the Linux inotify system.
Example:
- watcher, err := inotify.NewWatcher()
- if err != nil {
- log.Fatal(err)
- }
- err = watcher.Watch("/tmp")
- if err != nil {
- log.Fatal(err)
- }
- for {
- select {
- case ev := <-watcher.Event:
- log.Println("event:", ev)
- case err := <-watcher.Error:
- log.Println("error:", err)
- }
- }
+ watcher, err := inotify.NewWatcher()
+ if err != nil {
+ log.Fatal(err)
+ }
+ err = watcher.Watch("/tmp")
+ if err != nil {
+ log.Fatal(err)
+ }
+ for {
+ select {
+ case ev := <-watcher.Event:
+ log.Println("event:", ev)
+ case err := <-watcher.Error:
+ log.Println("error:", err)
+ }
+ }
*/
package inotify // import "k8s.io/utils/inotify"
diff --git a/lru/lru.go b/lru/lru.go
index 5d0077a..47f1352 100644
--- a/lru/lru.go
+++ b/lru/lru.go
@@ -5,7 +5,7 @@ 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
+ 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,
@@ -22,6 +22,7 @@ import (
)
type Key = groupcache.Key
+type EvictionFunc = func(key Key, value interface{})
// Cache is a thread-safe fixed size LRU cache.
type Cache struct {
@@ -36,6 +37,13 @@ func New(size int) *Cache {
}
}
+// NewWithEvictionFunc creates an LRU of the given size with the given eviction func.
+func NewWithEvictionFunc(size int, f EvictionFunc) *Cache {
+ c := New(size)
+ c.cache.OnEvicted = f
+ return c
+}
+
// Add adds a value to the cache.
func (c *Cache) Add(key Key, value interface{}) {
c.lock.Lock()
diff --git a/lru/lru_test.go b/lru/lru_test.go
index 25b824b..2fcaf79 100644
--- a/lru/lru_test.go
+++ b/lru/lru_test.go
@@ -113,3 +113,20 @@ func TestGetRace(t *testing.T) {
// let them run
time.Sleep(5 * time.Second)
}
+
+func TestEviction(t *testing.T) {
+ var seenKey Key
+ var seenVal interface{}
+
+ lru := NewWithEvictionFunc(1, func(key Key, value interface{}) {
+ seenKey = key
+ seenVal = value
+ })
+
+ lru.Add(1, 2)
+ lru.Add(3, 4)
+
+ if seenKey != 1 || seenVal != 2 {
+ t.Errorf("unexpected eviction data: key=%v val=%v", seenKey, seenVal)
+ }
+}
diff --git a/net/ebtables/ebtables.go b/net/ebtables/ebtables.go
index 3e984a2..88b6e3b 100644
--- a/net/ebtables/ebtables.go
+++ b/net/ebtables/ebtables.go
@@ -121,10 +121,17 @@ func getEbtablesVersionString(exec utilexec.Interface) (string, error) {
if err != nil {
return "", err
}
- versionMatcher := regexp.MustCompile(`v([0-9]+\.[0-9]+\.[0-9]+)`)
- match := versionMatcher.FindStringSubmatch(string(bytes))
+ return parseVersion(string(bytes))
+}
+
+func parseVersion(version string) (string, error) {
+ // the regular expression contains `v?` at the beginning because
+ // different OS distros have different version format output i.e
+ // either starts with `v` or it doesn't
+ versionMatcher := regexp.MustCompile(`v?([0-9]+\.[0-9]+\.[0-9]+)`)
+ match := versionMatcher.FindStringSubmatch(version)
if match == nil {
- return "", fmt.Errorf("no ebtables version found in string: %s", bytes)
+ return "", fmt.Errorf("no ebtables version found in string: %s", version)
}
return match[1], nil
}
diff --git a/net/ebtables/ebtables_test.go b/net/ebtables/ebtables_test.go
index 9933493..1920ac9 100644
--- a/net/ebtables/ebtables_test.go
+++ b/net/ebtables/ebtables_test.go
@@ -167,3 +167,61 @@ Bridge chain: TEST, entries: 0, policy: ACCEPT`), nil, nil
t.Errorf("expected err = nil")
}
}
+
+func Test_parseVersion(t *testing.T) {
+ tests := []struct {
+ name string
+ version string
+ want string
+ wantErr bool
+ }{
+ {
+ name: "version starting with `v`",
+ version: "v2.0.10",
+ want: "2.0.10",
+ wantErr: false,
+ },
+ {
+ name: "version without containing `v`",
+ version: "2.0.10",
+ want: "2.0.10",
+ wantErr: false,
+ },
+ {
+ name: "version containing `v` in between the regex expression match",
+ version: "2.0v.10",
+ want: "",
+ wantErr: true,
+ },
+ {
+ name: "version containing `v` after the regex expression match",
+ version: "2.0.10v",
+ want: "2.0.10",
+ wantErr: false,
+ },
+ {
+ name: "version starting with `v` and containing a symbol in between",
+ version: "v2.0.10-4",
+ want: "2.0.10",
+ wantErr: false,
+ },
+ {
+ name: "version starting with `v` and containing a symbol/alphabets in between",
+ version: "v2.0a.10-4",
+ want: "",
+ wantErr: true,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := parseVersion(tt.version)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("parseVersion() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if got != tt.want {
+ t.Errorf("parseVersion() got = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
diff --git a/nsenter/nsenter.go b/nsenter/nsenter.go
index 237b636..6f847db 100644
--- a/nsenter/nsenter.go
+++ b/nsenter/nsenter.go
@@ -49,26 +49,28 @@ type Nsenter = NSEnter
//
// NSEnter requires:
//
-// 1. Docker >= 1.6 due to the dependency on the slave propagation mode
+// 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
+// 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
+// 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
+// 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",
+// 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
+// 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
+//
+// 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
@@ -174,10 +176,13 @@ func (ne *NSEnter) SupportsSystemd() (string, bool) {
// 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
+//
+// /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.
+//
+// /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.
diff --git a/set/OWNERS b/set/OWNERS
new file mode 100644
index 0000000..9d2d33e
--- /dev/null
+++ b/set/OWNERS
@@ -0,0 +1,8 @@
+# See the OWNERS docs at https://go.k8s.io/owners
+
+reviewers:
+ - logicalhan
+ - thockin
+approvers:
+ - logicalhan
+ - thockin
diff --git a/set/ordered.go b/set/ordered.go
new file mode 100644
index 0000000..2b2c11f
--- /dev/null
+++ b/set/ordered.go
@@ -0,0 +1,53 @@
+/*
+Copyright 2023 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 set
+
+// ordered is a constraint that permits any ordered type: any type
+// that supports the operators < <= >= >.
+// If future releases of Go add new ordered types,
+// this constraint will be modified to include them.
+type ordered interface {
+ integer | float | ~string
+}
+
+// integer is a constraint that permits any integer type.
+// If future releases of Go add new predeclared integer types,
+// this constraint will be modified to include them.
+type integer interface {
+ signed | unsigned
+}
+
+// float is a constraint that permits any floating-point type.
+// If future releases of Go add new predeclared floating-point types,
+// this constraint will be modified to include them.
+type float interface {
+ ~float32 | ~float64
+}
+
+// signed is a constraint that permits any signed integer type.
+// If future releases of Go add new predeclared signed integer types,
+// this constraint will be modified to include them.
+type signed interface {
+ ~int | ~int8 | ~int16 | ~int32 | ~int64
+}
+
+// unsigned is a constraint that permits any unsigned integer type.
+// If future releases of Go add new predeclared unsigned integer types,
+// this constraint will be modified to include them.
+type unsigned interface {
+ ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
+}
diff --git a/set/set.go b/set/set.go
new file mode 100644
index 0000000..172482c
--- /dev/null
+++ b/set/set.go
@@ -0,0 +1,229 @@
+/*
+Copyright 2023 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 set
+
+import (
+ "sort"
+)
+
+// Empty is public since it is used by some internal API objects for conversions between external
+// string arrays and internal sets, and conversion logic requires public types today.
+type Empty struct{}
+
+// Set is a set of the same type elements, implemented via map[ordered]struct{} for minimal memory consumption.
+type Set[E ordered] map[E]Empty
+
+// New creates a new set.
+func New[E ordered](items ...E) Set[E] {
+ ss := Set[E]{}
+ ss.Insert(items...)
+ return ss
+}
+
+// KeySet creates a Set[E] from a keys of a map[E](? extends interface{}).
+func KeySet[E ordered, A any](theMap map[E]A) Set[E] {
+ ret := Set[E]{}
+ for key := range theMap {
+ ret.Insert(key)
+ }
+ return ret
+}
+
+// Insert adds items to the set.
+func (s Set[E]) Insert(items ...E) Set[E] {
+ for _, item := range items {
+ s[item] = Empty{}
+ }
+ return s
+}
+
+// Delete removes all items from the set.
+func (s Set[E]) Delete(items ...E) Set[E] {
+ for _, item := range items {
+ delete(s, item)
+ }
+ return s
+}
+
+// Has returns true if and only if item is contained in the set.
+func (s Set[E]) Has(item E) bool {
+ _, contained := s[item]
+ return contained
+}
+
+// HasAll returns true if and only if all items are contained in the set.
+func (s Set[E]) HasAll(items ...E) bool {
+ for _, item := range items {
+ if !s.Has(item) {
+ return false
+ }
+ }
+ return true
+}
+
+// HasAny returns true if any items are contained in the set.
+func (s Set[E]) HasAny(items ...E) bool {
+ for _, item := range items {
+ if s.Has(item) {
+ return true
+ }
+ }
+ return false
+}
+
+// Union returns a new set which includes items in either s1 or s2.
+// For example:
+// s1 = {a1, a2}
+// s2 = {a3, a4}
+// s1.Union(s2) = {a1, a2, a3, a4}
+// s2.Union(s1) = {a1, a2, a3, a4}
+func (s Set[E]) Union(s2 Set[E]) Set[E] {
+ result := Set[E]{}
+ result.Insert(s.UnsortedList()...)
+ result.Insert(s2.UnsortedList()...)
+ return result
+}
+
+// Len returns the number of elements in the set.
+func (s Set[E]) Len() int {
+ return len(s)
+}
+
+// Intersection returns a new set which includes the item in BOTH s1 and s2
+// For example:
+// s1 = {a1, a2}
+// s2 = {a2, a3}
+// s1.Intersection(s2) = {a2}
+func (s Set[E]) Intersection(s2 Set[E]) Set[E] {
+ var walk, other Set[E]
+ result := Set[E]{}
+ if s.Len() < s2.Len() {
+ walk = s
+ other = s2
+ } else {
+ walk = s2
+ other = s
+ }
+ for key := range walk {
+ if other.Has(key) {
+ result.Insert(key)
+ }
+ }
+ return result
+}
+
+// IsSuperset returns true if and only if s1 is a superset of s2.
+func (s Set[E]) IsSuperset(s2 Set[E]) bool {
+ for item := range s2 {
+ if !s.Has(item) {
+ return false
+ }
+ }
+ return true
+}
+
+// Difference returns a set of objects that are not in s2
+// For example:
+// s1 = {a1, a2, a3}
+// s2 = {a1, a2, a4, a5}
+// s1.Difference(s2) = {a3}
+// s2.Difference(s1) = {a4, a5}
+func (s Set[E]) Difference(s2 Set[E]) Set[E] {
+ result := Set[E]{}
+ for key := range s {
+ if !s2.Has(key) {
+ result.Insert(key)
+ }
+ }
+ return result
+}
+
+// Equal returns true if and only if s1 is equal (as a set) to s2.
+// Two sets are equal if their membership is identical.
+func (s Set[E]) Equal(s2 Set[E]) bool {
+ return s.Len() == s.Len() && s.IsSuperset(s2)
+}
+
+type sortableSlice[E ordered] []E
+
+func (s sortableSlice[E]) Len() int {
+ return len(s)
+}
+func (s sortableSlice[E]) Less(i, j int) bool { return s[i] < s[j] }
+func (s sortableSlice[E]) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+
+// SortedList returns the contents as a sorted slice.
+func (s Set[E]) SortedList() []E {
+ res := make(sortableSlice[E], 0, s.Len())
+ for key := range s {
+ res = append(res, key)
+ }
+ sort.Sort(res)
+ return res
+}
+
+// UnsortedList returns the slice with contents in random order.
+func (s Set[E]) UnsortedList() []E {
+ res := make([]E, 0, len(s))
+ for key := range s {
+ res = append(res, key)
+ }
+ return res
+}
+
+// PopAny returns a single element from the set.
+func (s Set[E]) PopAny() (E, bool) {
+ for key := range s {
+ s.Delete(key)
+ return key, true
+ }
+ var zeroValue E
+ return zeroValue, false
+}
+
+// Clone returns a new set which is a copy of the current set.
+func (s Set[T]) Clone() Set[T] {
+ result := make(Set[T], len(s))
+ for key := range s {
+ result.Insert(key)
+ }
+ return result
+}
+
+// SymmetricDifference returns a set of elements which are in either of the sets, but not in their intersection.
+// For example:
+// s1 = {a1, a2, a3}
+// s2 = {a1, a2, a4, a5}
+// s1.SymmetricDifference(s2) = {a3, a4, a5}
+// s2.SymmetricDifference(s1) = {a3, a4, a5}
+func (s Set[T]) SymmetricDifference(s2 Set[T]) Set[T] {
+ return s.Difference(s2).Union(s2.Difference(s))
+}
+
+// Clear empties the set.
+// It is preferable to replace the set with a newly constructed set,
+// but not all callers can do that (when there are other references to the map).
+// In some cases the set *won't* be fully cleared, e.g. a Set[float32] containing NaN
+// can't be cleared because NaN can't be removed.
+// For sets containing items of a type that is reflexive for ==,
+// this is optimized to a single call to runtime.mapclear().
+func (s Set[T]) Clear() Set[T] {
+ for key := range s {
+ delete(s, key)
+ }
+ return s
+}
diff --git a/set/set_test.go b/set/set_test.go
new file mode 100644
index 0000000..ea2d9d5
--- /dev/null
+++ b/set/set_test.go
@@ -0,0 +1,365 @@
+/*
+Copyright 2023 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 set
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestStringSetHasAll(t *testing.T) {
+ s := New[string]()
+ s2 := New[string]()
+ if len(s) != 0 {
+ t.Errorf("Expected len=0: %d", len(s))
+ }
+ s.Insert("a", "b")
+ if len(s) != 2 {
+ t.Errorf("Expected len=2: %d", len(s))
+ }
+ s.Insert("c")
+ if s.Has("d") {
+ t.Errorf("Unexpected contents: %#v", s)
+ }
+ if !s.Has("a") {
+ t.Errorf("Missing contents: %#v", s)
+ }
+ s.Delete("a")
+ if s.Has("a") {
+ t.Errorf("Unexpected contents: %#v", s)
+ }
+ s.Insert("a")
+ if s.HasAll("a", "b", "d") {
+ t.Errorf("Unexpected contents: %#v", s)
+ }
+ if !s.HasAll("a", "b") {
+ t.Errorf("Missing contents: %#v", s)
+ }
+ s2.Insert("a", "b", "d")
+ if s.IsSuperset(s2) {
+ t.Errorf("Unexpected contents: %#v", s)
+ }
+ s2.Delete("d")
+ if !s.IsSuperset(s2) {
+ t.Errorf("Missing contents: %#v", s)
+ }
+}
+
+func TestTypeInference(t *testing.T) {
+ s := New("a", "b", "c")
+ if len(s) != 3 {
+ t.Errorf("Expected len=3: %d", len(s))
+ }
+}
+
+func TestStringSetDeleteMultiples(t *testing.T) {
+ s := New[string]()
+ s.Insert("a", "b", "c")
+ if len(s) != 3 {
+ t.Errorf("Expected len=3: %d", len(s))
+ }
+
+ s.Delete("a", "c")
+ if len(s) != 1 {
+ t.Errorf("Expected len=1: %d", len(s))
+ }
+ if s.Has("a") {
+ t.Errorf("Unexpected contents: %#v", s)
+ }
+ if s.Has("c") {
+ t.Errorf("Unexpected contents: %#v", s)
+ }
+ if !s.Has("b") {
+ t.Errorf("Missing contents: %#v", s)
+ }
+}
+
+func TestNewStringSetWithMultipleStrings(t *testing.T) {
+ s := New[string]("a", "b", "c")
+ if len(s) != 3 {
+ t.Errorf("Expected len=3: %d", len(s))
+ }
+ if !s.Has("a") || !s.Has("b") || !s.Has("c") {
+ t.Errorf("Unexpected contents: %#v", s)
+ }
+}
+
+func TestStringSetSortedList(t *testing.T) {
+ s := New[string]("z", "y", "x", "a")
+ if !reflect.DeepEqual(s.SortedList(), []string{"a", "x", "y", "z"}) {
+ t.Errorf("SortedList gave unexpected result: %#v", s.SortedList())
+ }
+}
+
+func TestStringSetUnsortedList(t *testing.T) {
+ s := New[string]("z", "y", "x", "a")
+ ul := s.UnsortedList()
+ if len(ul) != 4 || !s.Has("z") || !s.Has("y") || !s.Has("x") || !s.Has("a") {
+ t.Errorf("UnsortedList gave unexpected result: %#v", s.UnsortedList())
+ }
+}
+
+func TestStringSetDifference(t *testing.T) {
+ a := New[string]("1", "2", "3")
+ b := New[string]("1", "2", "4", "5")
+ c := a.Difference(b)
+ d := b.Difference(a)
+ if len(c) != 1 {
+ t.Errorf("Expected len=1: %d", len(c))
+ }
+ if !c.Has("3") {
+ t.Errorf("Unexpected contents: %#v", c.SortedList())
+ }
+ if len(d) != 2 {
+ t.Errorf("Expected len=2: %d", len(d))
+ }
+ if !d.Has("4") || !d.Has("5") {
+ t.Errorf("Unexpected contents: %#v", d.SortedList())
+ }
+}
+
+func TestStringSetHasAny(t *testing.T) {
+ a := New[string]("1", "2", "3")
+
+ if !a.HasAny("1", "4") {
+ t.Errorf("expected true, got false")
+ }
+
+ if a.HasAny("0", "4") {
+ t.Errorf("expected false, got true")
+ }
+}
+
+func TestStringSetEquals(t *testing.T) {
+ // Simple case (order doesn't matter)
+ a := New[string]("1", "2")
+ b := New[string]("2", "1")
+ if !a.Equal(b) {
+ t.Errorf("Expected to be equal: %v vs %v", a, b)
+ }
+
+ // It is a set; duplicates are ignored
+ b = New[string]("2", "2", "1")
+ if !a.Equal(b) {
+ t.Errorf("Expected to be equal: %v vs %v", a, b)
+ }
+
+ // Edge cases around empty sets / empty strings
+ a = New[string]()
+ b = New[string]()
+ if !a.Equal(b) {
+ t.Errorf("Expected to be equal: %v vs %v", a, b)
+ }
+
+ b = New[string]("1", "2", "3")
+ if a.Equal(b) {
+ t.Errorf("Expected to be not-equal: %v vs %v", a, b)
+ }
+
+ b = New[string]("1", "2", "")
+ if a.Equal(b) {
+ t.Errorf("Expected to be not-equal: %v vs %v", a, b)
+ }
+
+ // Check for equality after mutation
+ a = New[string]()
+ a.Insert("1")
+ if a.Equal(b) {
+ t.Errorf("Expected to be not-equal: %v vs %v", a, b)
+ }
+
+ a.Insert("2")
+ if a.Equal(b) {
+ t.Errorf("Expected to be not-equal: %v vs %v", a, b)
+ }
+
+ a.Insert("")
+ if !a.Equal(b) {
+ t.Errorf("Expected to be equal: %v vs %v", a, b)
+ }
+
+ a.Delete("")
+ if a.Equal(b) {
+ t.Errorf("Expected to be not-equal: %v vs %v", a, b)
+ }
+}
+
+func TestStringUnion(t *testing.T) {
+ tests := []struct {
+ s1 Set[string]
+ s2 Set[string]
+ expected Set[string]
+ }{
+ {
+ New[string]("1", "2", "3", "4"),
+ New[string]("3", "4", "5", "6"),
+ New[string]("1", "2", "3", "4", "5", "6"),
+ },
+ {
+ New[string]("1", "2", "3", "4"),
+ New[string](),
+ New[string]("1", "2", "3", "4"),
+ },
+ {
+ New[string](),
+ New[string]("1", "2", "3", "4"),
+ New[string]("1", "2", "3", "4"),
+ },
+ {
+ New[string](),
+ New[string](),
+ New[string](),
+ },
+ }
+
+ for _, test := range tests {
+ union := test.s1.Union(test.s2)
+ if union.Len() != test.expected.Len() {
+ t.Errorf("Expected union.Len()=%d but got %d", test.expected.Len(), union.Len())
+ }
+
+ if !union.Equal(test.expected) {
+ t.Errorf("Expected union.Equal(expected) but not true. union:%v expected:%v", union.SortedList(), test.expected.SortedList())
+ }
+ }
+}
+
+func TestStringIntersection(t *testing.T) {
+ tests := []struct {
+ s1 Set[string]
+ s2 Set[string]
+ expected Set[string]
+ }{
+ {
+ New[string]("1", "2", "3", "4"),
+ New[string]("3", "4", "5", "6"),
+ New[string]("3", "4"),
+ },
+ {
+ New[string]("1", "2", "3", "4"),
+ New[string]("1", "2", "3", "4"),
+ New[string]("1", "2", "3", "4"),
+ },
+ {
+ New[string]("1", "2", "3", "4"),
+ New[string](),
+ New[string](),
+ },
+ {
+ New[string](),
+ New[string]("1", "2", "3", "4"),
+ New[string](),
+ },
+ {
+ New[string](),
+ New[string](),
+ New[string](),
+ },
+ }
+
+ for _, test := range tests {
+ intersection := test.s1.Intersection(test.s2)
+ if intersection.Len() != test.expected.Len() {
+ t.Errorf("Expected intersection.Len()=%d but got %d", test.expected.Len(), intersection.Len())
+ }
+
+ if !intersection.Equal(test.expected) {
+ t.Errorf("Expected intersection.Equal(expected) but not true. intersection:%v expected:%v", intersection.SortedList(), test.expected.SortedList())
+ }
+ }
+}
+
+func TestKeySet(t *testing.T) {
+ m := map[string]string{
+ "hallo": "world",
+ "goodbye": "and goodnight",
+ }
+ expected := []string{"goodbye", "hallo"}
+ gotList := KeySet(m).SortedList() // List() returns a sorted list
+ if len(gotList) != len(m) {
+ t.Fatalf("got %v elements, wanted %v", len(gotList), len(m))
+ }
+ for i, entry := range KeySet(m).SortedList() {
+ if entry != expected[i] {
+ t.Errorf("got %v, expected %v", entry, expected[i])
+ }
+ }
+}
+
+func TestSetSymmetricDifference(t *testing.T) {
+ a := New("1", "2", "3")
+ b := New("1", "2", "4", "5")
+ c := a.SymmetricDifference(b)
+ d := b.SymmetricDifference(a)
+ if !c.Equal(New("3", "4", "5")) {
+ t.Errorf("Unexpected contents: %#v", c.SortedList())
+ }
+ if !d.Equal(New("3", "4", "5")) {
+ t.Errorf("Unexpected contents: %#v", d.SortedList())
+ }
+}
+
+func TestSetClear(t *testing.T) {
+ s := New[string]()
+ s.Insert("a", "b", "c")
+ if s.Len() != 3 {
+ t.Errorf("Expected len=3: %d", s.Len())
+ }
+
+ s.Clear()
+ if s.Len() != 0 {
+ t.Errorf("Expected len=0: %d", s.Len())
+ }
+}
+
+func TestSetClearWithSharedReference(t *testing.T) {
+ s := New[string]()
+ s.Insert("a", "b", "c")
+ if s.Len() != 3 {
+ t.Errorf("Expected len=3: %d", s.Len())
+ }
+
+ m := s
+ s.Clear()
+ if s.Len() != 0 {
+ t.Errorf("Expected len=0 on the cleared set: %d", s.Len())
+ }
+ if m.Len() != 0 {
+ t.Errorf("Expected len=0 on the shared reference: %d", m.Len())
+ }
+}
+
+func TestSetClearInSeparateFunction(t *testing.T) {
+ s := New[string]()
+ s.Insert("a", "b", "c")
+ if s.Len() != 3 {
+ t.Errorf("Expected len=3: %d", s.Len())
+ }
+
+ clearSetAndAdd(s, "d")
+ if s.Len() != 1 {
+ t.Errorf("Expected len=1: %d", s.Len())
+ }
+ if !s.Has("d") {
+ t.Errorf("Unexpected contents: %#v", s)
+ }
+}
+
+func clearSetAndAdd[T ordered](s Set[T], a T) {
+ s.Clear()
+ s.Insert(a)
+}
diff --git a/trace/trace.go b/trace/trace.go
index a0b07a6..187eb5d 100644
--- a/trace/trace.go
+++ b/trace/trace.go
@@ -65,6 +65,11 @@ func durationToMilliseconds(timeDuration time.Duration) int64 {
}
type traceItem interface {
+ // rLock must be called before invoking time or writeItem.
+ rLock()
+ // rUnlock must be called after processing the item is complete.
+ rUnlock()
+
// time returns when the trace was recorded as completed.
time() time.Time
// writeItem outputs the traceItem to the buffer. If stepThreshold is non-nil, only output the
@@ -79,6 +84,10 @@ type traceStep struct {
fields []Field
}
+// rLock doesn't need to do anything because traceStep instances are immutable.
+func (s traceStep) rLock() {}
+func (s traceStep) rUnlock() {}
+
func (s traceStep) time() time.Time {
return s.stepTime
}
@@ -106,6 +115,14 @@ type Trace struct {
traceItems []traceItem
}
+func (t *Trace) rLock() {
+ t.lock.RLock()
+}
+
+func (t *Trace) rUnlock() {
+ t.lock.RUnlock()
+}
+
func (t *Trace) time() time.Time {
if t.endTime != nil {
return *t.endTime
@@ -231,8 +248,10 @@ func (t *Trace) logTrace() {
func (t *Trace) writeTraceSteps(b *bytes.Buffer, formatter string, stepThreshold *time.Duration) {
lastStepTime := t.startTime
for _, stepOrTrace := range t.traceItems {
+ stepOrTrace.rLock()
stepOrTrace.writeItem(b, formatter, lastStepTime, stepThreshold)
lastStepTime = stepOrTrace.time()
+ stepOrTrace.rUnlock()
}
}
More details
Historical runs
- failed: src/k8s.io/utils/set/set.go:84:9: error: expected declaration
- nothing-to-do: Last upstream version 0.0~git20221128.99ec85e already imported.
- worker-timeout: No keepalives received in 1:00:14.156043.
- nothing-new-to-do: Last upstream version 0.0~git20221128.99ec85e already imported.
- success: Merged new upstream version 0.0~git20221128.99ec85e
- push-failed: Failed to push result branch: Connection closed: Connection closed early The remote server unexpectedly closed the connection.
- success: Merged new upstream version 0.0~git20220210.3a6ce19