Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Some Gomega assertions are long-winded #4135

Open
mogsie opened this issue Sep 7, 2024 · 8 comments
Open

Some Gomega assertions are long-winded #4135

mogsie opened this issue Sep 7, 2024 · 8 comments
Assignees
Labels
kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt. triage/accepted Indicates an issue or PR is ready to be actively worked on.

Comments

@mogsie
Copy link
Contributor

mogsie commented Sep 7, 2024

What broke? What's expected?

Kubebuilder creates/scaffolds a lot of test code. This test code uses Ginkgo and Gomega. Some of the tests as written don't take advantage of Gomega's testing framework. Since kubebuilder is likely to be one of those projects that introduce developers to Gomega, I think it is useful if the scaffolded code provides the best possible code samples.

Avoiding Expect(err)

For example, from test/e2e/v4/plugin_cluster_test.go

		outputGet, err := kbc.Kubectl.Get(
			false,
			"pods",
			"-n", "kube-system",
			"-l", "k8s-app=calico-node",
			"-o", "jsonpath={.items[*].status.phase}",
		)
		Expect(err).NotTo(HaveOccurred(), "Failed to get Calico pods")
		Expect(outputGet).To(ContainSubstring("Running"), "All Calico pods should be in Running state")

adds two variables to the scope, and then checks both responses. the two assertions could be replaced with an inline call:

		Expect(kbc.Kubectl.Get(
			false,
			"pods",
			"-n", "kube-system",
			"-l", "k8s-app=calico-node",
			"-o", "jsonpath={.items[*].status.phase}",
		).To(ContainSubstring("Running"), "All Calico pods should be in Running state")

This ensures that the error is nil, in addition to checking the output.

Edit this has been decided against, as it hides the number of return values, which makes it harder to learn the API being tested. The desire is therefore to keep the three-line version, instead of in-lining it.

Errors

Similarly, when the output is not checked, the code generally has Expect(err). This can also be in-lined:

			_, err = kbc.Kubectl.Command("label", "namespaces", kbc.Kubectl.Namespace,
				"metrics=enabled")
			ExpectWithOffset(2, err).NotTo(HaveOccurred())

can be shortened to

			Expect(kbc.Kubectl.Command("label", "namespaces", kbc.Kubectl.Namespace,
				"metrics=enabled").Error().NotTo(HaveOccurred())

The Error() call ensures that the last return value (the error) is nil, and ignores the output.

Eventually

The use of Eventually() often uses a mix of assertion styles because the function passed to Eventually is not passed a Gomega instance:

		EventuallyWithOffset(1, func() error {
			_, err := kbc.Kubectl.Get(
				true,
				"secrets", "webhook-server-cert")
			return err
		}, time.Minute, time.Second).Should(Succeed())

could be simplified to

		EventuallyWithOffset(1, func(g Gomega) {
			g.Expect(kbc.Kubectl.Get(
				true,
				"secrets", "webhook-server-cert")).Error().ToNot(HaveOccurred())
		}, time.Minute, time.Second).Should(Succeed())

Reproducing this issue

No response

KubeBuilder (CLI) Version

master

PROJECT version

No response

Plugin versions

No response

Other versions

No response

Extra Labels

/kind cleanup

@mogsie mogsie added the kind/bug Categorizes issue or PR as related to a bug. label Sep 7, 2024
@k8s-ci-robot k8s-ci-robot added the kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt. label Sep 7, 2024
@camilamacedo86 camilamacedo86 removed the kind/bug Categorizes issue or PR as related to a bug. label Sep 8, 2024
@camilamacedo86 camilamacedo86 added the triage/accepted Indicates an issue or PR is ready to be actively worked on. label Sep 8, 2024
@mogsie
Copy link
Contributor Author

mogsie commented Sep 8, 2024

Some other weird stuff I found while fixing things:

This code never worked (as intended), but was hiding a configuration error:

By("validating that pod(s) status.phase=Running")
getMemcachedPodStatus := func() error {
	status, err := kbc.Kubectl.Get(true, "pods", "-l",
		fmt.Sprintf("app.kubernetes.io/name=%s", kbc.Kind),
		"-o", "jsonpath={.items[*].status}",
	)
	ExpectWithOffset(2, err).NotTo(HaveOccurred())
	if !strings.Contains(status, "\"phase\":\"Running\"") {
		return err
	}
	return nil
}
EventuallyWithOffset(1, getMemcachedPodStatus, time.Minute, time.Second).Should(Succeed())

The "if not strings contains" returns err which is nil. It exposes that it's checking the wrong pod: by sprintf'ing app.kubernetes.io/name=%s", kbc.Kind — resulting in -l app.kubernetes.io/name=Foorelf

When I fixed the code to be "correct" the test failed because it never could find this pod 😄

edit: The fix is just wrapping kbc.Kind with strings.ToLower.
edit: The fix is replacing kbc.Kind the "e2e" prefix.

mogsie added a commit to mogsie/kubebuilder that referenced this issue Sep 8, 2024
Part of kubernetes-sigs#4135

This commits cleans up "plugin_cluster_test.go" to be less verbose and
use better use of Gomega's capabilities.

Notably, variables are used only when the output of some command is used
later on in the test.  If we only assert things about the command
output, it is generally in-lined.

Eventually functions are converted from `() error` to `(g Gomega)`, and
Expects are converted to g.Expect inside the functions.  All offsets
have been removed, since this isn't "helper" code, we want to know which
line the test failed on.

Finally, the test for verifying available status has been fixed so that
it checks the correct pod.
@mogsie
Copy link
Contributor Author

mogsie commented Sep 8, 2024

The following test fails for me because the resulting pod tries to run as root and for some reason my kind cluster disallows that.

func creatingAPI(kbc *utils.TestContext) {
	By("creating API definition with deploy-image/v1-alpha plugin")
	err := kbc.CreateAPI(
		"--group", kbc.Group,
		"--version", kbc.Version,
		"--kind", kbc.Kind,
		"--plugins", "deploy-image/v1-alpha",
		"--image", "busybox:1.36.1",
		"--make=false",
		"--manifests=false",
	)
	ExpectWithOffset(1, err).NotTo(HaveOccurred())
}

Is it OK for me to add a --run-as-user 1001 option here? Either that, or I could change the verification to verify that the podspec doesn't have runAsUser? But I'm not 100% sure of the main reason behind this test.

mogsie added a commit to mogsie/kubebuilder that referenced this issue Sep 8, 2024
Part of kubernetes-sigs#4135

This commits cleans up "plugin_cluster_test.go" to be less verbose and
use better use of Gomega's capabilities.

Notably, variables are used only when the output of some command is used
later on in the test.  If we only assert things about the command
output, it is generally in-lined.

Eventually functions are converted from `() error` to `(g Gomega)`, and
Expects are converted to g.Expect inside the functions.  All offsets
have been removed, since this isn't "helper" code, we want to know which
line the test failed on.

Finally, the test for verifying available status has been fixed so that
it checks the correct pod. Also, the "run-as-user" flag is passed in the
"no options" case, since kubernetes doesn't allow the target pod to run
as root.
@mogsie
Copy link
Contributor Author

mogsie commented Sep 8, 2024

@camilamacedo86 I hope that it's OK for me to make small, focused PRs as I make may way around the codebase. I prefer many smaller, short-lived PRs like this, to avoid merge conflicts that changes like this could cause.

mogsie added a commit to mogsie/kubebuilder that referenced this issue Sep 8, 2024
Replaced some bespoke Eventually() signatures (e.g. func() bool and
func() (int, error)) with the standard func(g Gomega), allowing gomega
assertions to happen inside the Eventually().

Generally you _expect_ something _to_ happen
and _eventually_, something _should_ happen.

This is a part of kubernetes-sigs#4135
camilamacedo86 pushed a commit that referenced this issue Sep 8, 2024
Replaced some bespoke Eventually() signatures (e.g. func() bool and
func() (int, error)) with the standard func(g Gomega), allowing gomega
assertions to happen inside the Eventually().

Generally you _expect_ something _to_ happen
and _eventually_, something _should_ happen.

This is a part of #4135
sarthaksarthak9 pushed a commit to sarthaksarthak9/kubebuilder that referenced this issue Sep 9, 2024
Part of kubernetes-sigs#4135

This commits cleans up "plugin_cluster_test.go" to be less verbose and
use better use of Gomega's capabilities.

Notably, variables are used only when the output of some command is used
later on in the test.  If we only assert things about the command
output, it is generally in-lined.

Eventually functions are converted from `() error` to `(g Gomega)`, and
Expects are converted to g.Expect inside the functions.  All offsets
have been removed, since this isn't "helper" code, we want to know which
line the test failed on.

Finally, the test for verifying available status has been fixed so that
it checks the correct pod. Also, the "run-as-user" flag is passed in the
"no options" case, since kubernetes doesn't allow the target pod to run
as root.
sarthaksarthak9 pushed a commit to sarthaksarthak9/kubebuilder that referenced this issue Sep 9, 2024
…ernetes-sigs#4142)

Replaced some bespoke Eventually() signatures (e.g. func() bool and
func() (int, error)) with the standard func(g Gomega), allowing gomega
assertions to happen inside the Eventually().

Generally you _expect_ something _to_ happen
and _eventually_, something _should_ happen.

This is a part of kubernetes-sigs#4135
@camilamacedo86
Copy link
Member

camilamacedo86 commented Sep 11, 2024

Hi @mogsie

One problem that I notice with the changes is that we are no longer able to check the results when them fails.
See: from https://github.com/kubernetes-sigs/kubebuilder/actions/runs/10807585375/job/29978696165?pr=4154

  Expected
      <[]uint8 | len:151, cap:1024>: [78, 65, 77, 69, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 69, 78, 68, 80, 79, 73, 78, 84, 83, 32, 32, 32, 65, 71, 69, 10, 112, 114, 111, 106, 101, 99, 116, 45, 118, 52, 45, 109, 117, 108, 116, 105, 103, 114, 111, 117, 112, 45, 99, 111, 110, 116, 114, 111, 108, 108, 101, 114, 45, 109, 97, 110, 97, 103, 101, 114, 45, 109, 101, 116, 114, 105, 99, 115, 45, 115, 101, 114, 118, 105, 99, 101, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 50, 109, 49, 115, 10]
  to contain substring
      <string>: 8443

So, we need to change all in its regards. I mean, we need to ensure that we are able to check the output message as before in the logs.
Because as a developer I need to know why my tests is not passing and what is the output.
That is much more important than make the code less verbose.

Do you think that you could solve this one?
Maybe it is only missing covert the bytes to string

You can use the PR: #4154 to validate this one.

@mogsie
Copy link
Contributor Author

mogsie commented Sep 11, 2024

Sure, I can look at this.

@camilamacedo86
Copy link
Member

Hi @mogsie

I found the fix for it and keep all amazing changes that you have been doing see:

#4158

@mogsie
Copy link
Contributor Author

mogsie commented Sep 11, 2024

Hi @camilamacedo86

I hope you found the change from

func Run(cmd *exec.Cmd) ([]byte, error) {

to

func Run(cmd *exec.Cmd) (string, error) {

a satisfactory fix to the problem?

@camilamacedo86
Copy link
Member

HI @mogsie

Please feel free to continue improve tests as you see fit.
Also, to close this issue when you see that all that you want to do regards this specific topic is done.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt. triage/accepted Indicates an issue or PR is ready to be actively worked on.
Projects
None yet
Development

No branches or pull requests

3 participants