From 22eec24a2276753f684b91f25de0df643bebc07a Mon Sep 17 00:00:00 2001 From: agaro Date: Wed, 3 Apr 2024 18:46:23 -0400 Subject: [PATCH 1/9] feat: adding step 'pods converge to selector' --- kubedog.go | 1 + pkg/kube/kube.go | 4 ++++ pkg/kube/pod/pod.go | 41 ++++++++++++++++++++++++++++++++++++++ pkg/kube/pod/pod_helper.go | 8 ++++++-- 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/kubedog.go b/kubedog.go index 43c5e08..8096eea 100644 --- a/kubedog.go +++ b/kubedog.go @@ -65,6 +65,7 @@ func (kdt *Test) SetScenario(scenario *godog.ScenarioContext) { kdt.scenario.Step(`^some pods in namespace (\S+) with selector (\S+) don't have "([^"]*)" in logs since ([^"]*) time$`, kdt.KubeClientSet.SomePodsInNamespaceWithSelectorDontHaveStringInLogsSinceTime) kdt.scenario.Step(`^(?:the )?pods in namespace (\S+) with selector (\S+) have no errors in logs since ([^"]*) time$`, kdt.KubeClientSet.PodsInNamespaceWithSelectorHaveNoErrorsInLogsSinceTime) kdt.scenario.Step(`^(?:the )?pods in namespace (\S+) with selector (\S+) have some errors in logs since ([^"]*) time$`, kdt.KubeClientSet.PodsInNamespaceWithSelectorHaveSomeErrorsInLogsSinceTime) + kdt.scenario.Step(`^(?:all )?(?:the )?(?:pod|pods) in (?:the )?namespace (\S+) with (?:the )?label selector (\S+) (?:should )?converge to (?:the )?field selector (\S+)$`, kdt.KubeClientSet.PodsInNamespaceWithLabelSelectorConvergeToFieldSelector) kdt.scenario.Step(`^(?:the )?pods in namespace (\S+) with selector (\S+) should have labels (\S+)$`, kdt.KubeClientSet.PodsInNamespaceWithSelectorShouldHaveLabels) kdt.scenario.Step(`^(?:the )?pod (\S+) in namespace (\S+) should have labels (\S+)$`, kdt.KubeClientSet.PodInNamespaceShouldHaveLabels) //syntax-generation:title-2:Others diff --git a/pkg/kube/kube.go b/pkg/kube/kube.go index 474f8cb..335cf30 100644 --- a/pkg/kube/kube.go +++ b/pkg/kube/kube.go @@ -256,6 +256,10 @@ func (kc *ClientSet) PodsInNamespaceWithSelectorHaveSomeErrorsInLogsSinceTime(na return pod.PodsInNamespaceWithSelectorHaveSomeErrorsInLogsSinceTime(kc.KubeInterface, namespace, selector, timestamp) } +func (kc *ClientSet) PodsInNamespaceWithLabelSelectorConvergeToFieldSelector(namespace, labelSelector, fieldSelector string) error { + return pod.PodsInNamespaceWithLabelSelectorConvergeToFieldSelector(kc.KubeInterface, kc.getExpBackoff(), namespace, labelSelector, fieldSelector) +} + func (kc *ClientSet) PodsInNamespaceWithSelectorShouldHaveLabels(namespace, selector, labels string) error { return pod.PodsInNamespaceWithSelectorShouldHaveLabels(kc.KubeInterface, namespace, selector, labels) } diff --git a/pkg/kube/pod/pod.go b/pkg/kube/pod/pod.go index faaa342..a120acc 100644 --- a/pkg/kube/pod/pod.go +++ b/pkg/kube/pod/pod.go @@ -17,6 +17,7 @@ package pod import ( "context" "fmt" + "reflect" "strings" "time" @@ -85,6 +86,46 @@ func PodsWithSelectorHaveRestartCountLessThan(kubeClientset kubernetes.Interface return nil } +func PodsInNamespaceWithLabelSelectorConvergeToFieldSelector(kubeClientset kubernetes.Interface, expBackoff wait.Backoff, namespace, labelSelector, fieldSelector string) error { + return util.RetryOnAnyError(&expBackoff, func() error { + podList, err := GetPodListWithLabelSelector(kubeClientset, namespace, labelSelector) + if err != nil { + return err + } + n := len(podList.Items) + if n == 0 { + return fmt.Errorf("no pods matched label selector '%s'", labelSelector) + } + log.Infof("found '%d' pods with label selector '%s'", n, labelSelector) + + podListWithSelector, err := GetPodListWithLabelSelectorAndFieldSelector(kubeClientset, namespace, labelSelector, fieldSelector) + if err != nil { + return err + } + m := len(podListWithSelector.Items) + if m == 0 { + return fmt.Errorf("no pods matched label selector '%s' and field selector '%s'", labelSelector, fieldSelector) + } + log.Infof("found '%d' pods with label selector '%s' and field selector '%s'", m, labelSelector, fieldSelector) + + message := fmt.Sprintf("'%d/%d' pod(s) with label selector '%s' converged to field selector '%s'", m, n, labelSelector, fieldSelector) + if n != m { + return errors.New(message) + } + podsUIDs := []string{} + podsWithSelectorUIDs := []string{} + for i, _ := range podList.Items { + podsUIDs = append(podsUIDs, string(podList.Items[i].UID)) + podsWithSelectorUIDs = append(podsWithSelectorUIDs, string(podListWithSelector.Items[i].UID)) + } + if !reflect.DeepEqual(podsUIDs, podsWithSelectorUIDs) { + return fmt.Errorf("pods UIDs with label selector '%s' do not match pods UIDs with said label selector and field selector '%s': '%v' and '%v', respectively", labelSelector, fieldSelector, podsUIDs, podsWithSelectorUIDs) + } + log.Info(message) + return nil + }) +} + func SomeOrAllPodsInNamespaceWithSelectorHaveStringInLogsSinceTime(kubeClientset kubernetes.Interface, expBackoff wait.Backoff, SomeOrAll, namespace, selector, searchKeyword string, since time.Time) error { return util.RetryOnAnyError(&expBackoff, func() error { pods, err := GetPodListWithLabelSelector(kubeClientset, namespace, selector) diff --git a/pkg/kube/pod/pod_helper.go b/pkg/kube/pod/pod_helper.go index 6a0d1df..a4130af 100644 --- a/pkg/kube/pod/pod_helper.go +++ b/pkg/kube/pod/pod_helper.go @@ -29,13 +29,17 @@ import ( "k8s.io/client-go/kubernetes" ) -func GetPodListWithLabelSelector(kubeClientset kubernetes.Interface, namespace, selector string) (*corev1.PodList, error) { +func GetPodListWithLabelSelector(kubeClientset kubernetes.Interface, namespace, labelSelector string) (*corev1.PodList, error) { + return GetPodListWithLabelSelectorAndFieldSelector(kubeClientset, namespace, labelSelector, "") +} + +func GetPodListWithLabelSelectorAndFieldSelector(kubeClientset kubernetes.Interface, namespace, labelSelector, fieldSelector string) (*corev1.PodList, error) { if err := common.ValidateClientset(kubeClientset); err != nil { return nil, err } pods, err := util.RetryOnError(&util.DefaultRetry, util.IsRetriable, func() (interface{}, error) { - return kubeClientset.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{LabelSelector: selector}) + return kubeClientset.CoreV1().Pods(namespace).List(context.Background(), metav1.ListOptions{LabelSelector: labelSelector, FieldSelector: fieldSelector}) }) if err != nil { return nil, errors.Wrap(err, "failed to list pods") From f440d480ab4f8b44734fd53ad263f690ea30fc1d Mon Sep 17 00:00:00 2001 From: agaro Date: Wed, 3 Apr 2024 18:48:31 -0400 Subject: [PATCH 2/9] docs(syntax-generation): better handling of bracket replacements --- generate/syntax/replace/replace.go | 75 +++++++ generate/syntax/replace/replace_test.go | 282 ++++++++++++++++++++++++ 2 files changed, 357 insertions(+) create mode 100644 generate/syntax/replace/replace.go create mode 100644 generate/syntax/replace/replace_test.go diff --git a/generate/syntax/replace/replace.go b/generate/syntax/replace/replace.go new file mode 100644 index 0000000..0aa2464 --- /dev/null +++ b/generate/syntax/replace/replace.go @@ -0,0 +1,75 @@ +package replace + +import ( + "bytes" + "regexp" + "strings" +) + +const regExp_CharsWithinBrackets = "([^(]*)" + +type Replacement struct { + Replacee string + Replacer string +} + +func (r Replacement) Replace(src string) string { + return strings.ReplaceAll(src, r.Replacee, r.Replacer) +} + +type Replacements []Replacement + +func (rs Replacements) Replace(src string) string { + new := src + for _, r := range rs { + new = r.Replace(new) + } + return new +} + +type BracketsReplacement struct { + Opening Replacement + Closing Replacement +} + +func (br BracketsReplacement) Replace(src string) string { + re, _ := regexp.Compile(br.getRegExp()) + // TODO: check error + new := re.ReplaceAllFunc([]byte(src), br.replaceSingle) + return string(new) +} + +func (br BracketsReplacement) replaceSingle(src []byte) []byte { + s := string(src) + s = br.Opening.Replace(s) + s = br.Closing.Replace(s) + return []byte(s) +} + +func (br BracketsReplacement) getRegExp() string { + return escapeEveryCharacter(br.Opening.Replacee) + + regExp_CharsWithinBrackets + + escapeEveryCharacter(br.Closing.Replacee) +} + +func escapeEveryCharacter(s string) string { + var buffer bytes.Buffer + for _, c := range s { + if c == ' ' { + continue + } + buffer.WriteString(`\`) + buffer.WriteRune(c) + } + return buffer.String() +} + +type BracketsReplacements []BracketsReplacement + +func (brs BracketsReplacements) Replace(src string) string { + new := src + for _, br := range brs { + new = br.Replace(new) + } + return new +} diff --git a/generate/syntax/replace/replace_test.go b/generate/syntax/replace/replace_test.go new file mode 100644 index 0000000..b63b36c --- /dev/null +++ b/generate/syntax/replace/replace_test.go @@ -0,0 +1,282 @@ +package replace + +import ( + "reflect" + "testing" +) + +// func TestReplacement_Replace(t *testing.T) { +// type fields struct { +// Replacee string +// Replacer string +// } +// type args struct { +// src string +// } +// tests := []struct { +// name string +// fields fields +// args args +// want string +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// r := Replacement{ +// Replacee: tt.fields.Replacee, +// Replacer: tt.fields.Replacer, +// } +// if got := r.Replace(tt.args.src); got != tt.want { +// t.Errorf("Replacement.Replace() = %v, want %v", got, tt.want) +// } +// }) +// } +// } + +// func TestReplacements_Replace(t *testing.T) { +// type args struct { +// src string +// } +// tests := []struct { +// name string +// rs Replacements +// args args +// want string +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// if got := tt.rs.Replace(tt.args.src); got != tt.want { +// t.Errorf("Replacements.Replace() = %v, want %v", got, tt.want) +// } +// }) +// } +// } + +func TestBracketsReplacement_Replace(t *testing.T) { + type fields struct { + Opening Replacement + Closing Replacement + } + type args struct { + src string + } + tests := []struct { + name string + fields fields + args args + want string + }{ + { + name: "Positive Test: '(?:' & ' )?' Case 1", + fields: fields{ + Opening: Replacement{ + Replacee: "(?:", + Replacer: "[", + }, + Closing: Replacement{ + Replacee: " )?", + Replacer: "] ", + }, + }, + args: args{ + src: `(?:I )?wait (?:for )?(\d+) (minutes|seconds)`, + }, + want: `[I] wait [for] (\d+) (minutes|seconds)`, + }, + { + name: "Positive Test: '(?:' & ' )?' Case 2", + fields: fields{ + Opening: Replacement{ + Replacee: "(?:", + Replacer: "[", + }, + Closing: Replacement{ + Replacee: " )?", + Replacer: "] ", + }, + }, + args: args{ + src: `(?:all )?(?:the )?(?:pod|pods) in (?:the )?namespace (\S+) with (?:the )?label selector (\S+) (?:should )?converge to (?:the )?field selector (\S+)`, + }, + want: `[all] [the] (?:pod|pods) in [the] namespace (\S+) with [the] label selector (\S+) [should] converge to [the] field selector (\S+)`, + }, + { + name: "Positive Test: '(?:' & ')' Case 1", + fields: fields{ + Opening: Replacement{ + Replacee: "(?:", + Replacer: "(", + }, + Closing: Replacement{ + Replacee: ")", + Replacer: ")", + }, + }, + args: args{ + src: `[all] [the] (?:pod|pods) in [the] namespace (\S+) with [the] label selector (\S+) [should] converge to [the] field selector (\S+)`, + }, + want: `[all] [the] (pod|pods) in [the] namespace (\S+) with [the] label selector (\S+) [should] converge to [the] field selector (\S+)`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + br := BracketsReplacement{ + Opening: tt.fields.Opening, + Closing: tt.fields.Closing, + } + if got := br.Replace(tt.args.src); got != tt.want { + t.Errorf("BracketsReplacement.Replace() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestBracketsReplacement_replaceSingle(t *testing.T) { + type fields struct { + Opening Replacement + Closing Replacement + } + type args struct { + src []byte + } + tests := []struct { + name string + fields fields + args args + want []byte + }{ + { + name: "Positive Test", + fields: fields{ + Opening: Replacement{ + Replacee: "(?:", + Replacer: "(", + }, + Closing: Replacement{ + Replacee: " )", + Replacer: ")", + }, + }, + args: args{ + src: []byte("(?:I )"), + }, + want: []byte("(I)"), + }, + { + name: "Positive Test", + fields: fields{ + Opening: Replacement{ + Replacee: "(?:", + Replacer: "(", + }, + Closing: Replacement{ + Replacee: ")", + Replacer: ")", + }, + }, + args: args{ + src: []byte("(?:pod|pods)"), + }, + want: []byte("(pod|pods)"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + br := BracketsReplacement{ + Opening: tt.fields.Opening, + Closing: tt.fields.Closing, + } + if got := br.replaceSingle(tt.args.src); !reflect.DeepEqual(got, tt.want) { + t.Errorf("BracketsReplacement.replaceSingle() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestBracketsReplacement_getRegExp(t *testing.T) { + type fields struct { + Opening Replacement + Closing Replacement + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "Positive Test", + fields: fields{ + Opening: Replacement{ + Replacee: "(?:", + Replacer: "(", + }, + Closing: Replacement{ + Replacee: " )", + Replacer: ")", + }, + }, + want: `\(\?\:` + regExp_CharsWithinBrackets + `\)`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + br := BracketsReplacement{ + Opening: tt.fields.Opening, + Closing: tt.fields.Closing, + } + if got := br.getRegExp(); got != tt.want { + t.Errorf("BracketsReplacement.getRegExp() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_escapeEveryCharacter(t *testing.T) { + type args struct { + s string + } + tests := []struct { + name string + args args + want string + }{ + { + name: "Positive Test", + args: args{ + s: "(?:", + }, + want: `\(\?\:`, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := escapeEveryCharacter(tt.args.s); got != tt.want { + t.Errorf("escapeEveryCharacter() = %v, want %v", got, tt.want) + } + }) + } +} + +// func TestBracketsReplacements_Replace(t *testing.T) { +// type args struct { +// src string +// } +// tests := []struct { +// name string +// brs BracketsReplacements +// args args +// want string +// }{ +// // TODO: Add test cases. +// } +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// if got := tt.brs.Replace(tt.args.src); got != tt.want { +// t.Errorf("BracketsReplacements.Replace() = %v, want %v", got, tt.want) +// } +// }) +// } +// } From 87d4eff0b99682fe3ff2027f023ae2bf299f7fde Mon Sep 17 00:00:00 2001 From: agaro Date: Wed, 3 Apr 2024 19:35:32 -0400 Subject: [PATCH 3/9] docs(syntax-generation): using bracket replacements --- docs/syntax.md | 1 + generate/syntax/main.go | 49 +++++++--- generate/syntax/replace/replace.go | 17 +++- generate/syntax/replace/replace_test.go | 121 ++++++++++-------------- 4 files changed, 98 insertions(+), 90 deletions(-) diff --git a/docs/syntax.md b/docs/syntax.md index 40457d6..5b02a48 100644 --- a/docs/syntax.md +++ b/docs/syntax.md @@ -34,6 +34,7 @@ Below you will find the step syntax next to the name of the method it utilizes. - ` some pods in namespace with selector don't have "" in logs since time` kdt.KubeClientSet.SomePodsInNamespaceWithSelectorDontHaveStringInLogsSinceTime - ` [the] pods in namespace with selector have no errors in logs since time` kdt.KubeClientSet.PodsInNamespaceWithSelectorHaveNoErrorsInLogsSinceTime - ` [the] pods in namespace with selector have some errors in logs since time` kdt.KubeClientSet.PodsInNamespaceWithSelectorHaveSomeErrorsInLogsSinceTime +- ` [all] [the] (pod|pods) in [the] namespace with [the] label selector [should] converge to [the] field selector ` kdt.KubeClientSet.PodsInNamespaceWithLabelSelectorConvergeToFieldSelector - ` [the] pods in namespace with selector should have labels ` kdt.KubeClientSet.PodsInNamespaceWithSelectorShouldHaveLabels - ` [the] pod in namespace should have labels ` kdt.KubeClientSet.PodInNamespaceShouldHaveLabels diff --git a/generate/syntax/main.go b/generate/syntax/main.go index 70e7b9a..5472db8 100644 --- a/generate/syntax/main.go +++ b/generate/syntax/main.go @@ -21,6 +21,7 @@ import ( "strconv" "strings" + "github.com/keikoproj/kubedog/generate/syntax/replace" log "github.com/sirupsen/logrus" ) @@ -48,18 +49,37 @@ const ( destinationFileBeginning = "# Syntax" + newLine + "Below you will find the step syntax next to the name of the method it utilizes. Here GK stands for [Gherkin](https://cucumber.io/docs/gherkin/reference/#keywords) Keyword and words in brackets ([]) are optional:" + newLine ) -var replacers = []struct { - replacee string - replacer string -}{ - {`(?:`, `[`}, - {` )?`, `] `}, - {`)?`, `]`}, - {`(\d+)`, ``}, - {`(\S+)`, ``}, - {`([^"]*)`, ``}, - {`\(`, `(`}, - {`\)`, `)`}, +var replacements = replace.Replacements{ + {Replacee: `(\d+)`, Replacer: ``}, + {Replacee: `(\S+)`, Replacer: ``}, + {Replacee: `([^"]*)`, Replacer: ``}, +} + +var bracketsReplacements = replace.BracketsReplacements{ + { + Opening: replace.Replacement{ + Replacee: `(?:`, Replacer: `[`}, + Closing: replace.Replacement{ + Replacee: ` )?`, Replacer: `] `}, + }, + { + Opening: replace.Replacement{ + Replacee: `(?:`, Replacer: `[`}, + Closing: replace.Replacement{ + Replacee: `)?`, Replacer: `]`}, + }, + { + Opening: replace.Replacement{ + Replacee: `(?:`, Replacer: `(`}, + Closing: replace.Replacement{ + Replacee: `)`, Replacer: `)`}, + }, + { + Opening: replace.Replacement{ + Replacee: `\(`, Replacer: `(`}, + Closing: replace.Replacement{ + Replacee: `\)`, Replacer: `)`}, + }, } func main() { @@ -143,9 +163,8 @@ func processStep(rawStep string) string { processedStep := rawStepSplit[1] processedStep = strings.TrimPrefix(processedStep, stepPrefix) processedStep = strings.TrimSuffix(processedStep, stepSuffix) - for _, r := range replacers { - processedStep = strings.ReplaceAll(processedStep, r.replacee, r.replacer) - } + processedStep = replacements.Replace(processedStep) + processedStep = bracketsReplacements.Replace(processedStep) method := rawStepSplit[2] method = strings.TrimPrefix(method, methodPrefix) method = strings.TrimSuffix(method, methodSuffix) diff --git a/generate/syntax/replace/replace.go b/generate/syntax/replace/replace.go index 0aa2464..ac7f82f 100644 --- a/generate/syntax/replace/replace.go +++ b/generate/syntax/replace/replace.go @@ -1,3 +1,17 @@ +/* +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 replace import ( @@ -55,9 +69,6 @@ func (br BracketsReplacement) getRegExp() string { func escapeEveryCharacter(s string) string { var buffer bytes.Buffer for _, c := range s { - if c == ' ' { - continue - } buffer.WriteString(`\`) buffer.WriteRune(c) } diff --git a/generate/syntax/replace/replace_test.go b/generate/syntax/replace/replace_test.go index b63b36c..5a0049d 100644 --- a/generate/syntax/replace/replace_test.go +++ b/generate/syntax/replace/replace_test.go @@ -1,3 +1,17 @@ +/* +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 replace import ( @@ -5,56 +19,6 @@ import ( "testing" ) -// func TestReplacement_Replace(t *testing.T) { -// type fields struct { -// Replacee string -// Replacer string -// } -// type args struct { -// src string -// } -// tests := []struct { -// name string -// fields fields -// args args -// want string -// }{ -// // TODO: Add test cases. -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// r := Replacement{ -// Replacee: tt.fields.Replacee, -// Replacer: tt.fields.Replacer, -// } -// if got := r.Replace(tt.args.src); got != tt.want { -// t.Errorf("Replacement.Replace() = %v, want %v", got, tt.want) -// } -// }) -// } -// } - -// func TestReplacements_Replace(t *testing.T) { -// type args struct { -// src string -// } -// tests := []struct { -// name string -// rs Replacements -// args args -// want string -// }{ -// // TODO: Add test cases. -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// if got := tt.rs.Replace(tt.args.src); got != tt.want { -// t.Errorf("Replacements.Replace() = %v, want %v", got, tt.want) -// } -// }) -// } -// } - func TestBracketsReplacement_Replace(t *testing.T) { type fields struct { Opening Replacement @@ -120,6 +84,40 @@ func TestBracketsReplacement_Replace(t *testing.T) { }, want: `[all] [the] (pod|pods) in [the] namespace (\S+) with [the] label selector (\S+) [should] converge to [the] field selector (\S+)`, }, + { + name: "Positive Test: '(?:' & ' )?' Case 3", + fields: fields{ + Opening: Replacement{ + Replacee: "(?:", + Replacer: "[", + }, + Closing: Replacement{ + Replacee: " )?", + Replacer: "] ", + }, + }, + args: args{ + src: `(?:I )?send (\d+) tps to ingress (\S+) in (?:the )?namespace (\S+) (?:available )?on port (\d+) and path ([^"]*) for (\d+) (minutes|seconds) expecting up to (\d+) error(?:s)?`, + }, + want: `[I] send (\d+) tps to ingress (\S+) in [the] namespace (\S+) [available] on port (\d+) and path ([^"]*) for (\d+) (minutes|seconds) expecting up to (\d+) error(?:s)?`, + }, + { + name: "Positive Test: '(?:' & ')?' Case 1", + fields: fields{ + Opening: Replacement{ + Replacee: "(?:", + Replacer: "[", + }, + Closing: Replacement{ + Replacee: ")?", + Replacer: "]", + }, + }, + args: args{ + src: `[I] send (\d+) tps to ingress (\S+) in [the] namespace (\S+) [available] on port (\d+) and path ([^"]*) for (\d+) (minutes|seconds) expecting up to (\d+) error(?:s)?`, + }, + want: `[I] send (\d+) tps to ingress (\S+) in [the] namespace (\S+) [available] on port (\d+) and path ([^"]*) for (\d+) (minutes|seconds) expecting up to (\d+) error[s]`, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -218,7 +216,7 @@ func TestBracketsReplacement_getRegExp(t *testing.T) { Replacer: ")", }, }, - want: `\(\?\:` + regExp_CharsWithinBrackets + `\)`, + want: `\(\?\:` + regExp_CharsWithinBrackets + `\ \)`, }, } for _, tt := range tests { @@ -259,24 +257,3 @@ func Test_escapeEveryCharacter(t *testing.T) { }) } } - -// func TestBracketsReplacements_Replace(t *testing.T) { -// type args struct { -// src string -// } -// tests := []struct { -// name string -// brs BracketsReplacements -// args args -// want string -// }{ -// // TODO: Add test cases. -// } -// for _, tt := range tests { -// t.Run(tt.name, func(t *testing.T) { -// if got := tt.brs.Replace(tt.args.src); got != tt.want { -// t.Errorf("BracketsReplacements.Replace() = %v, want %v", got, tt.want) -// } -// }) -// } -// } From a708abaac5d28401b7962c7884fe02474585ed01 Mon Sep 17 00:00:00 2001 From: agaro Date: Thu, 4 Apr 2024 14:15:40 -0400 Subject: [PATCH 4/9] chore: finish ToDo --- generate/syntax/replace/replace.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/generate/syntax/replace/replace.go b/generate/syntax/replace/replace.go index ac7f82f..ba13d83 100644 --- a/generate/syntax/replace/replace.go +++ b/generate/syntax/replace/replace.go @@ -16,6 +16,7 @@ package replace import ( "bytes" + "log" "regexp" "strings" ) @@ -47,8 +48,10 @@ type BracketsReplacement struct { } func (br BracketsReplacement) Replace(src string) string { - re, _ := regexp.Compile(br.getRegExp()) - // TODO: check error + re, err := regexp.Compile(br.getRegExp()) + if err != nil { + log.Fatal(err) + } new := re.ReplaceAllFunc([]byte(src), br.replaceSingle) return string(new) } From 9a2a04711d070006b9a0f8788b493acae667b58a Mon Sep 17 00:00:00 2001 From: agaro Date: Thu, 4 Apr 2024 14:20:30 -0400 Subject: [PATCH 5/9] chore: linter suggested changes --- pkg/kube/pod/pod.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/kube/pod/pod.go b/pkg/kube/pod/pod.go index a120acc..719de94 100644 --- a/pkg/kube/pod/pod.go +++ b/pkg/kube/pod/pod.go @@ -114,7 +114,7 @@ func PodsInNamespaceWithLabelSelectorConvergeToFieldSelector(kubeClientset kuber } podsUIDs := []string{} podsWithSelectorUIDs := []string{} - for i, _ := range podList.Items { + for i := range podList.Items { podsUIDs = append(podsUIDs, string(podList.Items[i].UID)) podsWithSelectorUIDs = append(podsWithSelectorUIDs, string(podListWithSelector.Items[i].UID)) } From 7f7649ea32093498dcc3fadab63f79de0bc14003 Mon Sep 17 00:00:00 2001 From: agaro Date: Mon, 8 Apr 2024 19:50:12 -0400 Subject: [PATCH 6/9] test: adding TestPodsInNamespaceWithLabelSelectorConvergeToFieldSelector --- pkg/kube/pod/pod_test.go | 60 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/pkg/kube/pod/pod_test.go b/pkg/kube/pod/pod_test.go index 3dd0856..ba27815 100644 --- a/pkg/kube/pod/pod_test.go +++ b/pkg/kube/pod/pod_test.go @@ -17,9 +17,11 @@ package pod import ( "testing" + "github.com/keikoproj/kubedog/internal/util" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/wait" fakeDiscovery "k8s.io/client-go/discovery/fake" "k8s.io/client-go/dynamic" fakeDynamic "k8s.io/client-go/dynamic/fake" @@ -131,3 +133,61 @@ func Test_PodsInNamespaceWithSelectorShouldHaveLabels(t *testing.T) { }) } } + +func TestPodsInNamespaceWithLabelSelectorConvergeToFieldSelector(t *testing.T) { + type args struct { + kubeClientset kubernetes.Interface + expBackoff wait.Backoff + namespace string + labelSelector string + fieldSelector string + } + namespaceName := "test-ns" + ns := v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: namespaceName}} + podSucceeded := v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-xhhxj", + Namespace: "test-ns", + Labels: map[string]string{ + "app": "test-service", + }, + }, + Status: v1.PodStatus{ + Phase: v1.PodSucceeded, + }, + } + tests := []struct { + name string + args args + wantErr bool + }{ + { + name: "Positive Test", + args: args{ + kubeClientset: fake.NewSimpleClientset(&ns, &podSucceeded), + expBackoff: util.DefaultRetry, + namespace: namespaceName, + labelSelector: "app=test-service", + fieldSelector: "status.phase=Succeeded", + }, + }, + { + name: "Negative Test: no pods with label selector", + args: args{ + kubeClientset: fake.NewSimpleClientset(&ns), + expBackoff: util.DefaultRetry, + namespace: namespaceName, + labelSelector: "app=test-service", + fieldSelector: "status.phase=Succeeded", + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := PodsInNamespaceWithLabelSelectorConvergeToFieldSelector(tt.args.kubeClientset, tt.args.expBackoff, tt.args.namespace, tt.args.labelSelector, tt.args.fieldSelector); (err != nil) != tt.wantErr { + t.Errorf("PodsInNamespaceWithLabelSelectorConvergeToFieldSelector() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} From bd91bb53d8f02e19f160d8f5c053e23f8bd9bbce Mon Sep 17 00:00:00 2001 From: agaro Date: Mon, 8 Apr 2024 21:19:22 -0400 Subject: [PATCH 7/9] ci: adding codecov.yml --- codecov.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 codecov.yml diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..dd89762 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,8 @@ +coverage: + range: "70...90" + round: down + precision: 2 + +ignore: + - "examples" + - "generate" \ No newline at end of file From 105210f66326f20a1d0c7555a747c305a743c21f Mon Sep 17 00:00:00 2001 From: agaro Date: Tue, 9 Apr 2024 12:09:00 -0400 Subject: [PATCH 8/9] ci: codecov/codecov-action@v4 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46c856e..cc2313c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: - name: Run 'all' make target run: make all - name: Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 with: file: ./coverage.txt # optional flags: unittests # optional From e61f7c3815a68d457f311b72f1fe70dec671861c Mon Sep 17 00:00:00 2001 From: agaro Date: Tue, 9 Apr 2024 12:29:50 -0400 Subject: [PATCH 9/9] ci: adding CODECOV_TOKEN --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cc2313c..df76078 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,8 @@ jobs: run: make all - name: Codecov uses: codecov/codecov-action@v4 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: file: ./coverage.txt # optional flags: unittests # optional