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

How do we manually verify that the expected value in the unittests is correct? #306

Closed
jlewi opened this issue Aug 23, 2019 · 20 comments
Closed

Comments

@jlewi
Copy link
Contributor

jlewi commented Aug 23, 2019

How are we supposed to verify that the expected values used by unittests is correct?

As an example: kubeflow/kubeflow#3986 appears to be due to a bug in the kustomize manifest resulting in environment variables not being set correctly to the variable value; instead they remained set to the variable name e.g. `$(userid-prefix)

I would expect this is something we test in the unittest.

So my expectation would be that I can look at the unittest and verify by inspection that the expected value is what it should be.

But when I look at the unittest

value: $(userid-header)

It looks like we are specifying the kustomize files (i.e. the input) and not the expected output.
For example here:

imagePullPolicy: $(policy)

We are setting

        image: gcr.io/kubeflow-images-public/jupyter-web-app:v0.5.0
        imagePullPolicy: $(policy)

The expected value for imagePullPolicy should not be $(policy). So either that is the input value and not the expected value or else our tests are wrong.

Can someone familiar with the unittest infrastructure help educate me?

My expectation is that unittests would work as follows

  • A golden set of manifests for different scenarios would be checked in somewhere (either as go code or as separate files in a "golden") directory
    • Thus I can look at those golden files to see if the expected value is what it should be
  • The tests would be comparing the generated files to the golden files.
  • There might be some scripts or utilities to regenerate the golden files (e.g. by running kustomize)
    • During code review it should be easy to inspect the diff to the golden files to allow manual verification of the new golden data

/cc @johnugeorge @yanniszark @kkasravi

@jlewi
Copy link
Contributor Author

jlewi commented Aug 23, 2019

What is this code doing?

th.assertActualEqualsExpected(m, string(expected))

Looks like we are running kustomize to get the expected value.

@kkasravi
Copy link
Contributor

kkasravi commented Aug 23, 2019

if values aren't replaced and one or more warnings are emitted

2019/08/23 12:29:59 well-defined vars that were never replaced: clusterDomain

we should FAIL on that unit test.

This is slightly complicated by the fact that we produce and run golang tests under overlays.
I think the best approach would be to

  • call kfctl's generate so that the top level kustomization.yamls are produced
  • the go unit test should be based on this kustomization.yaml
  • generate should be called for all supported config files
  • any non-resolved vars should result in a FAIL with failure indicating the config file and var
  • the resulting yaml stream should be piped to kubectl apply --dry-run --validate -f -

The unit tests are based on kustomize's unit test framework kusttestharness

@kkasravi
Copy link
Contributor

@jlewi I've opened #308 PTAL

@jlewi
Copy link
Contributor Author

jlewi commented Aug 24, 2019

thanks @kkasravi

generate should be called for all supported config files

What is a support config file in this case? Do you mean all support KfDef YAML files?

All of your comments sound like sound improvements. I'm still not clear how we would ensure that the expected values are correct.

How do we fix the unittests so that its easy to verify by manual inspection that the expected values are correct?

@kkasravi
Copy link
Contributor

As far as vars go - it would be easy to grep for '$(' values

@jlewi
Copy link
Contributor Author

jlewi commented Aug 25, 2019

How does grepping for "$(" allow me to verify that the expected output is correct?

Shouldn't the unittests be doing something like taking a kustomization.yaml, params.env as input running kustomize build and then ensuring the generated output matches some expected golden output?

Here's the deployment for the profiles controller
https://github.com/kubeflow/manifests/blob/master/profiles/base/deployment.yaml

It has several variables e.g. $(userid-header) in the command line argument.

So my expectation is that using the unittest I can verify that kustomize is correctly substituting in the correct values for those variables and generating the appropriate command line argument.

Looking at the unittest
https://github.com/kubeflow/manifests/blob/master/tests/profiles-base_test.go

How can I verify that the expected value that the unittest is testing against is what I expect? I'd like to be able to inspect the unittest and verify that the unittest is using an expected value that ensures the parameters are correctly substituted. I'd expect that by looking at the unittest I could see that the input was params.env with $(userid-header) set to some value e.g. "fake-header" and that the expected value for the deployment had a command line like

 args:
       ....
        - "-userid-header"
        - fake-header       

@kkasravi
Copy link
Contributor

so some kind of output like

admin=xxxx
userid-header=yyyy
userid-prefix=zzzz

and

apiVersion: apps/v1
kind: Deployment
metadata:
  name: deployment
spec:
  template:
    spec:
      containers:
      - command:
        - /manager
        args:
        - "-userid-header"
        - yyyy
        - "-userid-prefix"
        - zzzz
        image: gcr.io/kubeflow-images-public/profile-controller:v20190619-v0-219-gbd3daa8c-dirty-1ced0e
        imagePullPolicy: Always
        name: manager
      - command:
        - /opt/kubeflow/access-management
        args:
        - "-cluster-admin"
        - xxxx
        - "-userid-header"
        - yyyy
        - "-userid-prefix"
        - zzzz
        image: gcr.io/kubeflow-images-public/kfam:v20190612-v0-170-ga06cdb79-dirty-a33ee4
        imagePullPolicy: Always
        name: kfam
      serviceAccountName: controller-service-account

with the substitutions in red or something?

@jlewi
Copy link
Contributor Author

jlewi commented Aug 25, 2019

What does in read mean? Can we follow the normal structure of go uniitests?

The normal structure of a go unittest would be to define a testCase struct that captures input and expected output. So in our case that might be something like the following

type testCase struct {
  kustomization ...
  params map[string]string
  kustomizeAppDir string
  expectedYamlDir 
}

So kustomization would be the input to a kustomization file and params would be a list of the parameters, and kustomizeAppDir would be the directory of the application manifest. (We could add other inputs as necessary).

expectedYamlDir would be a directory (or file) where I could find the fully evaluated YAML representing the expected output of kustomize build.

So now to verify that the expected output is correct I can just look at expectedYamlDir. So that's where I'd expect to see the Deployment spec you listed above.

So now if the test compares the output of kustomize build to that expectedYamlDir and the test passes then I know my kustomize files will produce the expected output (i.e. that the expected substitutions take place).

Right now its not clear to me what the unittests are testing. I'm guessing they are just verifying that if you run kustomize you generate valid K8s specs.

@kkasravi
Copy link
Contributor

ok, I'll implement in #308

@jlewi
Copy link
Contributor Author

jlewi commented Aug 27, 2019

Thanks @kkasravi

Repasting your [comment] (#296 (comment))

Each unit test embeds the content of current files in the unit test - for example
profiles-base_test.go contains the following files

$ ☞  grep write[FK] profiles-base_test.go
	th.writeF("/manifests/profiles/base/crd.yaml", `
	th.writeF("/manifests/profiles/base/service-account.yaml", `
	th.writeF("/manifests/profiles/base/cluster-role-binding.yaml", `
	th.writeF("/manifests/profiles/base/role.yaml", `
	th.writeF("/manifests/profiles/base/role-binding.yaml", `
	th.writeF("/manifests/profiles/base/service.yaml", `
	th.writeF("/manifests/profiles/base/deployment.yaml", `
	th.writeF("/manifests/profiles/base/params.yaml", `
	th.writeF("/manifests/profiles/base/params.env", `
	th.writeK("/manifests/profiles/base", `

this is run through kustomize build and the current files are run through kustomize build. Then both are EncodeAsYaml and compared.

If the PR author makes changes and doesn't run generate, then failure is guaranteed. In order to update the unit tests with their changes, they should run generate. Now the unit test has embedded their changes and expected should equal actual.

So IIUC; the way the unittests are currently designed is we take the kustomize packages at commit N and commit N-1; run kustomize build for both and then compare the output?

@kkasravi
Copy link
Contributor

correct. However the actual vs expected were reversed - see PR #325

@jlewi
Copy link
Contributor Author

jlewi commented Dec 13, 2019

I'm still not sure how I'm supposed to verify that the expected output is correct. It looks like make generate is creating a kustomize package

value: $(project)

And not generating the expected output of kustomize.

See also @krishnadurai 's comment

#665 (comment)

I think my expectation is that

  • make generate recurses through the manifests directory and runs kustomize build to generate golden set of files
  • Tests run kustomize build and compare against the golden files

So a reviewer is checking the following

  • Looks at the diff of golden files to see if expected output of kustomize build is correct
  • Ensures tests pass

It looks like gen-test-target.sh might be generating parts of the kustomize package. Is there any reason we can't create suitable kustomization.yaml files such that kustomize build generates suitable output?

@krishnadurai
Copy link
Contributor

One of the ways of generating kustomize output is by using '-o' flag as suggested by the this PR: kubernetes-sigs/kustomize#960. This generates individual output files and can dump them into a folder.

I suggest we could use this kustomize feature in gen-test-target.sh instead of trying to interpret kustomization.yaml.

This will allow us to maintain a set of golden output files and compare against them.

I'll take a stab at implementing this.

@jtfogarty
Copy link

/kind feature

@jlewi
Copy link
Contributor Author

jlewi commented Mar 15, 2020

@krishnadurai any update on this? I think I'm going to need these changes to the test infra before I can start refactoring the manifests (e.g. #962) to start removing some of the kfctl magic (#774)

@jlewi
Copy link
Contributor Author

jlewi commented Mar 16, 2020

I created a one off example of what this might look like here:
https://github.com/jlewi/manifests/blob/stacks/tests/jupyter/jupyter-web-app/base_v3/jupyter-jupyter-web-app-base_v3_test.go

Next steps would be

  1. Update generate_tests.py to generate YAML files in appropriate directory by running kustomize build -o
  2. Generate ${PACKAGE}_test.go files for all manifests

@krishnadurai
Copy link
Contributor

/assign

@jlewi
Copy link
Contributor Author

jlewi commented Mar 16, 2020

@krishnadurai I think I almost have something working in https://github.com/jlewi/manifests/tree/stacks
which is ready for a PR.

How does this compare to what you have or were planning on working on? What's the best way to combine efforts? Want me to split out these changes into their own branch/PR?

@krishnadurai
Copy link
Contributor

krishnadurai commented Mar 16, 2020

@jlewi I was planning to pick this up and change https://github.com/kubeflow/manifests/blob/master/hack/gen-test-target.sh to a python script.
Though it seems that you have already made the changes splitting resources in an output directory with kustomize build -o, which was what I was looking to contribute.

I'll be looking forward to reviewing your work when it is ready.

Want me to split out these changes into their own branch/PR?

I'm fine with a single PR.

/unassign

jlewi pushed a commit to jlewi/manifests that referenced this issue Mar 16, 2020
…AML resources.

* Per kubeflow#306 to allow reviewers to verify that the expected
  output is correct we should check in the result of "kustomize build -o"
  so that reviewers can review the diff and verify that it is correct.

* This also simplifies the test generation code; the python script
  generate_tests.py just recourses over the directory tree and runs "kustomize build -o" and checks in the output into the test_data directory.

* This is different from what the tests are currently doing.

  * Currently what the generation scripts do is generate "kustomization.yaml" files and then generate the expected output from that when the test is run.

  * This makes it very difficult to validate the expected output and to
    debug whether the expected output is correct.

  * Going forward, per kubeflow#1014, I think what we want to do is check in test cases
    corresponding to kustomization.yaml files corresponding to various
    kustomizations that we want to validate are working correctly

  * Our generate scripts would then run "kustomize build" to generate expected
    output and check that in so that we can validate that the expected output
    is correct.

* Also change the tests data structure so that it mirrors the kustomize directory tree rather than flattening the tests into the "tests" directory.

  * Fix kubeflow#683

* Right now running the unittests takes a long time

  * The problem is that we generate unittests for every "kustomization.yaml"
    file
  * Per kubeflow#1014 this is kind of pointless/redundant because most of these
    tests aren't actually testing kustomizations.
  * We will address this in follow on PRs which will add more appropriate
    tests and remove some of these unnecessary/redundant tests.
jlewi pushed a commit to jlewi/manifests that referenced this issue Mar 21, 2020
…AML resources.

* Per kubeflow#306 to allow reviewers to verify that the expected
  output is correct we should check in the result of "kustomize build -o"
  so that reviewers can review the diff and verify that it is correct.

* This also simplifies the test generation code; the python script
  generate_tests.py just recourses over the directory tree and runs "kustomize build -o" and checks in the output into the test_data directory.

* This is different from what the tests are currently doing.

  * Currently what the generation scripts do is generate "kustomization.yaml" files and then generate the expected output from that when the test is run.

  * This makes it very difficult to validate the expected output and to
    debug whether the expected output is correct.

  * Going forward, per kubeflow#1014, I think what we want to do is check in test cases
    corresponding to kustomization.yaml files corresponding to various
    kustomizations that we want to validate are working correctly

  * Our generate scripts would then run "kustomize build" to generate expected
    output and check that in so that we can validate that the expected output
    is correct.

* Also change the tests data structure so that it mirrors the kustomize directory tree rather than flattening the tests into the "tests" directory.

  * Fix kubeflow#683

* Right now running the unittests takes a long time

  * The problem is that we generate unittests for every "kustomization.yaml"
    file
  * Per kubeflow#1014 this is kind of pointless/redundant because most of these
    tests aren't actually testing kustomizations.
  * We will address this in follow on PRs which will add more appropriate
    tests and remove some of these unnecessary/redundant tests.
k8s-ci-robot pushed a commit that referenced this issue Mar 23, 2020
…AML resources. (#1016)

* unittests should compare result of kustomize build to golden set of YAML resources.

* Per #306 to allow reviewers to verify that the expected
  output is correct we should check in the result of "kustomize build -o"
  so that reviewers can review the diff and verify that it is correct.

* This also simplifies the test generation code; the python script
  generate_tests.py just recourses over the directory tree and runs "kustomize build -o" and checks in the output into the test_data directory.

* This is different from what the tests are currently doing.

  * Currently what the generation scripts do is generate "kustomization.yaml" files and then generate the expected output from that when the test is run.

  * This makes it very difficult to validate the expected output and to
    debug whether the expected output is correct.

  * Going forward, per #1014, I think what we want to do is check in test cases
    corresponding to kustomization.yaml files corresponding to various
    kustomizations that we want to validate are working correctly

  * Our generate scripts would then run "kustomize build" to generate expected
    output and check that in so that we can validate that the expected output
    is correct.

* Also change the tests data structure so that it mirrors the kustomize directory tree rather than flattening the tests into the "tests" directory.

  * Fix #683

* Right now running the unittests takes a long time

  * The problem is that we generate unittests for every "kustomization.yaml"
    file
  * Per #1014 this is kind of pointless/redundant because most of these
    tests aren't actually testing kustomizations.
  * We will address this in follow on PRs which will add more appropriate
    tests and remove some of these unnecessary/redundant tests.

* Cherry pick AWS fixes.

* Regenerate the tests.

* Fix the unittests; need to update the generate logic to remove unused tests
 to remove tests that aren't part of this PR.

* Address comments.

* Rebase on master and regenerate the tests.
@jlewi
Copy link
Contributor Author

jlewi commented Mar 25, 2020

#1016 has been merged so closing this.

@jlewi jlewi closed this as completed Mar 25, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants