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

refactoring pipelinerun unit test #2876

Merged
merged 1 commit into from
Jul 29, 2020

Conversation

pritidesai
Copy link
Member

@pritidesai pritidesai commented Jun 29, 2020

Changes

Creating a common function reconcileRun to:

  • Create an instance of the PipelineRun controller - getPipelineRunController
  • Reconcile specified Data - PipelineRun, Pipeline, Tasks, etc.
  • Verify PipelineRun was reconciled
  • Check events introduced in PR Finish setting up events for pipeline runs #2874

All of the above steps were repeating in all the test functions 😢

Updated all unit tests in pipelinerun_test.go to utilize this common function.

Further possible refactoring:

  • Great suggestion from @afrittoli on using Matches instead of retrieving resources directly, here, which can go in a separate PR.

  • One more PR would be great to consolidate namespace instead of hardcoding foo everywhere.

  • One test failure results in metrics recorder failure for all unit tests and very hard to spot what was the actual cause of failure. Possibly fix the way we register metrics view.

Submitter Checklist

These are the criteria that every PR should meet, please check them off as you
review them:

  • Includes tests (if functionality changed/added)
  • Includes docs (if user facing)
  • Commit messages follow commit message best practices
  • Release notes block has been filled in or deleted (only if no user facing changes)

See the contribution guide for more details.

Double check this list of stuff that's easy to miss:

Reviewer Notes

If API changes are included, additive changes must be approved by at least two OWNERS and backwards incompatible changes must be approved by more than 50% of the OWNERS, and they must first be added in a backwards compatible way.

Release Notes

NONE

@tekton-robot tekton-robot added the size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. label Jun 29, 2020
@tekton-robot
Copy link
Collaborator

This PR cannot be merged: expecting exactly one kind/ label

Available kind/ labels are:

kind/question: Issues or PRs that are questions around the project or a particular feature
kind/bug: Categorizes issue or PR as related to a bug.
kind/flake: Categorizes issue or PR as related to a flakey test
kind/cleanup: Categorizes issue or PR as related to cleaning up code, process, or technical debt.
kind/design: Categorizes issue or PR as related to design.
kind/documentation: Categorizes issue or PR as related to documentation.
kind/feature: Categorizes issue or PR as related to a new feature.
kind/misc: Categorizes issue or PR as a miscellaneuous one.

@pritidesai
Copy link
Member Author

/kind cleanup

@tekton-robot tekton-robot added the kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt. label Jun 29, 2020
@tekton-robot
Copy link
Collaborator

This PR cannot be merged: expecting exactly one kind/ label

Available kind/ labels are:

kind/question: Issues or PRs that are questions around the project or a particular feature
kind/bug: Categorizes issue or PR as related to a bug.
kind/flake: Categorizes issue or PR as related to a flakey test
kind/cleanup: Categorizes issue or PR as related to cleaning up code, process, or technical debt.
kind/design: Categorizes issue or PR as related to design.
kind/documentation: Categorizes issue or PR as related to documentation.
kind/feature: Categorizes issue or PR as related to a new feature.
kind/misc: Categorizes issue or PR as a miscellaneuous one.

@@ -45,7 +45,6 @@ import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
k8stesting "k8s.io/client-go/testing"
ktesting "k8s.io/client-go/testing"
Copy link
Member Author

@pritidesai pritidesai Jun 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the same module was imported twice with different labels 😕

if err != nil {
t.Fatalf("Somehow had error getting reconciled run out of fake client: %s", err)
if len(actions) < 2 {
t.Fatalf("Expected client to have at least two action implementation but it has %d", len(actions))
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have added check on length before accessing that index in the list

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for explaining! really helps when reviewing :D

and good call!!

@tekton-robot tekton-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jul 3, 2020
@pritidesai
Copy link
Member Author

@vdemeester I rebased this PR but the label (needs-rebase) wasn't removed, weird 🤔

@tekton-robot tekton-robot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jul 7, 2020
@pritidesai
Copy link
Member Author

/test pull-tekton-pipeline-integration-tests

@tekton-robot tekton-robot added do-not-merge/release-note-label-needed Indicates that a PR should not merge because it's missing one of the release note labels. release-note-none Denotes a PR that doesnt merit a release note. and removed do-not-merge/release-note-label-needed Indicates that a PR should not merge because it's missing one of the release note labels. labels Jul 20, 2020
@pritidesai
Copy link
Member Author

Diff in this PR looks huge 🤦‍♀️. Its all about introducing a common function to build PipelineRun which is used by all unit tests in pipelinerun_test.go instead of repeating them in all the tests, would appreciate your review 🙏

Copy link
Member

@vdemeester vdemeester left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link

@ghost ghost left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome, thank you!

@tekton-robot
Copy link
Collaborator

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: sbwsg

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@tekton-robot tekton-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Jul 21, 2020
@bobcatfish
Copy link
Collaborator

Diff in this PR looks huge 🤦‍♀️.

it's pretty amazing that it's size/XL and it's only one file! I'd love to find ways to have less tests in there one day 🙏 or maybe targeting the repetition is the way to go!! thanks for taking this on @pritidesai !

Copy link
Collaborator

@bobcatfish bobcatfish left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great! I have some feedback on how the code is organized over all (i.e.: you've made the code a lot better, here are some ways we can make it even better).

The only thing I'd want to see changed before merging this PR is the names of the new functions, to better reflect that they do more than just "get", other than that I'm happy to merge :D

Thanks again @pritidesai !!

if err != nil {
t.Fatalf("Somehow had error getting reconciled run out of fake client: %s", err)
if len(actions) < 2 {
t.Fatalf("Expected client to have at least two action implementation but it has %d", len(actions))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for explaining! really helps when reviewing :D

and good call!!

rs []*v1alpha1.PipelineResource,
conditions []*v1alpha1.Condition,
namespace, prName string,
wantEvents []string,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: using newlines like this is not very standard in our Go codebase, we're much more likely to put these together on one line. ways to improve this:

}
}

func getReconciledRun(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's important to try to indicate with naming whether mutations / assertions are happening in functions - reading the name of getReconciledRun I would expect it to be only retrieving a run, but it seems to be doing several things:

  • instantiating a controller
  • reconciling
  • asserting against events

Some alternative naming ideas: reconcileRun (dropping "get" makes it seem less like it's just retrieving) assertRunSucceeds

Another idea, mentioned below as well: create a struct that holds all the args below and expose several functions instead of just one - this will probably still be significantly easier to read and work with than the amount of repetition we have today

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renamed getReconciledRun to reconcileRun

Creating struct is a great suggestion, I created a struct PipelineRunTest and made reconcileRun as an attribute to PipelineRunTest.

recorder := testAssets.Recorder

if reconcileError := c.Reconciler.Reconcile(context.Background(), namespace+"/"+prName); reconcileError != nil {
t.Fatalf("Error reconciling: %s", reconcileError)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

have you considered using t.Helper? (https://blog.golang.org/go1.9)

It might be worth comparing what it looks like when one of the tests using this helper fails using it vs not using it (it might make more sense if you were to break this function up into a few)

Copy link
Member Author

@pritidesai pritidesai Jul 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, tried it, very useful, instead of pointing back at the common utility (reconcileRun), it points back where the failure is originated, in actual test function 👍

@pritidesai
Copy link
Member Author

Based on @bobcatfish's comments, I have added these changes:

  • Renamed getReconciledRun to reconcileRun
  • added t.Helper to reconcileRun
  • Created a new struct with all the arguments which were passed to getReconciledRun and define this common function as an attribute to PipelineRunTest
func (prt PipelineRunTest) reconcileRun() (*v1beta1.PipelineRun, test.Clients) {

@pritidesai
Copy link
Member Author

/test pull-tekton-pipeline-integration-tests

@pritidesai
Copy link
Member Author

@bobcatfish its ready for review again, PTAL 😸

There could be more refactoring done, mainly the way namespace and pipelineRun name are hard coded in the functions.

@tekton-robot tekton-robot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jul 27, 2020
@tekton-robot
Copy link
Collaborator

@pritidesai: PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository.

Copy link
Collaborator

@bobcatfish bobcatfish left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @pritidesai !! I described one step further that I think we can go that would make the PipelineRunTest interface just a little bit better, but im also happy to merge as is if you'd like (esp if you want to just get this improvement in so you can stop dealing with rebasing all the time!!)

}

// Check generated events match what's expected
if len(prt.WantEvents) > 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is fairly minor but something feels weird to me about having WantEvents and PRName be attributes of PipelineRunTest - i think it's that this makes it so that if you want to call reconcileRun more than once in the same test, e.g. for two different pipelineruns, one reconciled after the other, you'd need to modify PipelineRunTest

I could see making 2 changes:

  • Make WantEvents and PRName and Namespace arguments to this function
  • Have a NewPipelineRunTest function that returns an instance of PipelineRunTest and takes Test and Data as arguments

this way you can call NewPipelineRunTest to create your controller with the state it needs (store the testAssets as an attribute of PipelineRunTest) and you can call reconcileRun with as many runs as you want

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @bobcatfish, I have added NewPipelineRunTest and updated reconcileRun by adding function parameters (Namespace, PipelineRunName, wantEvents, and permanentError).

Now, the PipelineRunTest has:

        test.Data  `json:"inline"`
	Test       *testing.T
	TestAssets test.Assets
	Cancel     func()

Also, have added defer on Cancel as part of each unit test instead of making it part of common function.

Copy link
Member Author

@pritidesai pritidesai Jul 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Data can be dropped from the PipelineRunTest struct since NewPipelineRunTest is creating the controller and instantiating PipelineRunTest with assets, data is not used/needed anywhere beyond NewPipelineRunTest - But I think it makes PipelineRunTest complete (with inputs and assets), can create separate PR to drop it if needed 😉

creating a common function to build reconciledRun and updating all
tests to use that function wherever possible
@vdemeester vdemeester removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jul 29, 2020
Copy link
Collaborator

@bobcatfish bobcatfish left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for cleaning this up and tolerating all the back and forth @pritidesai !

/lgtm


// NewPipelineRunTest returns PipelineRunTest with a new PipelineRun controller created with specified state through data
// This PipelineRunTest can be reused for multiple PipelineRuns by calling reconcileRun for each pipelineRun
func NewPipelineRunTest(data test.Data, t *testing.T) *PipelineRunTest {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm now just nitpicking, but i'd put the functions that are related to PipelineRunTest near the declaration of the struct. dont wanna block the PR tho!

Copy link
Member Author

@pritidesai pritidesai Jul 29, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks @bobcatfish I was planning on moving all helper functions including:

  • PipelineRunTest struct declaration and related functions
  • getPipelineRunController
  • conditionCheckFromTaskRun
  • checkEvents
  • eventFromChannel

etc, to a separate file /pkg/reconciler/pipelinerun/pipelinerun_helpers_test.go as a follow up PR. pkg/reconciler/pipelinerun/pipelinerun_test.go is a combination of helper utilities and actual unit tests and helper functions are spread across this file 😞 Will be easier to introduce more tests and/or reviewing them with such separation.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay great!

A couple of thoughts:

  • Somewhere in the top level test directory might be a good place to go, since that is where we are already putting our test libs there (might even make sense to work it into https://github.com/tektoncd/pipeline/blob/master/test/controller.go)
  • I want to caution against naming and calling code "helpers" - this is totally a common practice but I think it's useful to get out of the habit, for a couple of reasons:
    • People tend to use this terminology about libs used for test code - sometimes they use it for the code under tests as well but i think that's less common - and it seems almost like it's putting the code down, i.e., "i need this code to help me" whereas I think that well factored test code is just as important as any other well factored code, so libraries for test code are just as important as other code < rant over >
    • The other (probably bigger and less my personal opinion) reason is that "helper" is not a very specific name - good naming and code organization requires thinking about what the code's responsibility is and naming and organizing it based on that; using names like "helper" "manager" "library" "util" is a short cut that prevents us from doing that. More on this at https://dave.cheney.net/2019/01/08/avoid-package-names-like-base-util-or-common and https://blog.golang.org/package-names in the section "bad package names":

Avoid meaningless package names. Packages named util, common, or misc provide clients with no sense of what the package contains. This makes it harder for clients to use the package and makes it harder for maintainers to keep the package focused. Over time, they accumulate dependencies that can make compilation significantly and unnecessarily slower, especially in large programs. And since such package names are generic, they are more likely to collide with other packages imported by client code, forcing clients to invent names to distinguish them.

@tekton-robot tekton-robot added the lgtm Indicates that a PR is ready to be merged. label Jul 29, 2020
@tekton-robot tekton-robot merged commit d1c770a into tektoncd:master Jul 29, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt. lgtm Indicates that a PR is ready to be merged. release-note-none Denotes a PR that doesnt merit a release note. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants