-
Notifications
You must be signed in to change notification settings - Fork 292
ci-operator multi-stage: MultiStageTestStep #100
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
Conversation
petr-muller
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pretty well readable ever for me not really knowing what to expect here, nice job!
Some comments and questions inline.
pkg/api/types_test.go
Outdated
| RpmBuildCommands: "make rpms", | ||
| Images: []ProjectDirectoryImageBuildStepConfiguration{{To: "img"}}, | ||
| } | ||
| for _, i := range []string{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally I would prefer this to be t.Run() based so it's executed in parallel and does not stop on first failure.
pkg/defaults/defaults.go
Outdated
| if testStep.MultiStageTestConfiguration != nil { | ||
| var err error | ||
| if step, err = steps.MultiStageTestStep(*testStep, config, podClient, jobSpec); err != nil { | ||
| return nil, nil, fmt.Errorf("unable to create end to end test step: %v", err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we actually calling this end to end step in the user-facing documentation? The design doc calls it multi-stage tests, I think we should call it that way even here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer relevant because of #100 (comment).
pkg/steps/multi_stage.go
Outdated
| func (s *multiStageTestStep) Done() (bool, error) { return false, nil } | ||
| func (s *multiStageTestStep) Name() string { return s.name } | ||
| func (s *multiStageTestStep) Description() string { | ||
| return fmt.Sprintf("Run test %s", s.name) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Run multi-stage test..."?
pkg/steps/multi_stage.go
Outdated
| } | ||
| func (s *multiStageTestStep) SubTests() []*junit.TestCase { return s.subTests } | ||
|
|
||
| func (s multiStageTestStep) runSteps(ctx context.Context, steps []api.TestStep, shortCircuit bool) error { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm... is the non-pointer receiver here intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. Is there a problem?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, that's what I asked.
Using a value receiver was based on the (reasonable but not optimal) assumption that it is what methods that don't mutate the object should use. After reading some golang nonsense, I suppose the size of the object is the contention here? Changed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we don't mutate the object, we don't need to pass the pointer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right but we also don't need to copy the values around every time either
/shrug
pkg/steps/multi_stage.go
Outdated
| if errs != nil { | ||
| return nil, utilerrors.NewAggregate(errs) | ||
| } | ||
| return ret, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be simplified to just return ret, utilerrors.NewAggregate(errs) as NewAggregate is documented to return nil for an empty slice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't know that.
pkg/steps/multi_stage.go
Outdated
| for _, step := range steps { | ||
| image := step.Image | ||
| if s.config.IsPipelineImage(step.Image) { | ||
| image = api.PipelineImageStream + ":" + step.Image |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
would prefer Sprintf over concatenation
pkg/steps/multi_stage.go
Outdated
| errs = append(errs, err) | ||
| continue | ||
| } | ||
| name := s.name + "-" + step.Name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here too
pkg/steps/multi_stage.go
Outdated
| return s.runPods(ctx, pods, shortCircuit) | ||
| } | ||
|
|
||
| func (s multiStageTestStep) genPods(steps []api.TestStep) ([]coreapi.Pod, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need to cut the name here. let it be generatePods
| break | ||
| } | ||
| } | ||
| if err := waitForPodCompletion(s.podClient.Pods(s.jobSpec.Namespace), pod.Name, nil, false); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need to pass a notifier here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this outstanding?
pkg/steps/multi_stage.go
Outdated
| } | ||
| } | ||
| if errs == nil { | ||
| return nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need for that
pkg/steps/multi_stage.go
Outdated
| podClient PodClient, | ||
| jobSpec *api.JobSpec, | ||
| ) (api.Step, error) { | ||
| return newMultiStageTestStep(testConfig, config, podClient, jobSpec), nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why we need an extra unexported function and pass again the same args there, to return a new struct?
Also, this doesn't return any error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error removed, the extra function is used by the tests.
pkg/api/types_test.go
Outdated
| want: true, | ||
| }} { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| if ret := tc.conf.BuildsImage("image"); ret != tc.want { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"image" here is a critical input to understanding a test case -- let's add it as a field to the test struct
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what you mean, do you want image: "image" repeated in all cases?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I read
{
name: "not in `images`",
conf: ReleaseBuildConfiguration{
Images: []ProjectDirectoryImageBuildStepConfiguration{
{To: "egami"},
},
},
}As a test case and it's not obvious to me what is or is not in the list, and even what we expect the output to be
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's easier to read the test code here (a one-line conditional), but… changed.
stevekuznetsov
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking great. Two things I'd like to see:
- a test that starts from CI Operator configuration YAML and tests the output YAML for the Pods end-to-end, with all of the bells and whistles -- artifacts, resources, etc
- where are we calling
resolver.Resolve? before we dostep = steps.MultiStageTestStep(*testStep, config, podClient, jobSpec)right?
pkg/api/types_test.go
Outdated
| want: true, | ||
| }} { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| if ret := tc.conf.BuildsImage("image"); ret != tc.want { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes
pkg/api/types_test.go
Outdated
| want: true, | ||
| }} { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| if ret := tc.conf.BuildsImage("image"); ret != tc.want { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I read
{
name: "not in `images`",
conf: ReleaseBuildConfiguration{
Images: []ProjectDirectoryImageBuildStepConfiguration{
{To: "egami"},
},
},
}As a test case and it's not obvious to me what is or is not in the list, and even what we expect the output to be
pkg/steps/multi_stage.go
Outdated
| podClient PodClient | ||
| jobSpec *api.JobSpec | ||
| pre, test, post []api.TestStep | ||
| subTests []*junit.TestCase |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should elide this until we do the follow-up to add it
pkg/steps/multi_stage_test.go
Outdated
| } | ||
| } | ||
|
|
||
| func TestGenPods(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: TestGeneratePods
pkg/steps/multi_stage_test.go
Outdated
| expected := []coreapi.Pod{{ | ||
| ObjectMeta: meta.ObjectMeta{ | ||
| Name: "test-step0", | ||
| Labels: map[string]string{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we be expecting org/repo/branch here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was based on master before #92 merged.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So shouldn't this test be failing right now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should have been more explicit in my comment, I corrected that when I rebased.
pkg/steps/multi_stage_test.go
Outdated
| var pods []*coreapi.Pod | ||
| fakecs.PrependReactor("create", "pods", func(action clientgo_testing.Action) (bool, runtime.Object, error) { | ||
| pod := action.(clientgo_testing.CreateAction).GetObject().(*coreapi.Pod) | ||
| for _, f := range tc.failures { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: we're not running out of bytes in GitHub's git servers so let's use full variable names
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll hear from my orthopedist.
pkg/steps/multi_stage_test.go
Outdated
| names = append(names, p.ObjectMeta.Name) | ||
| } | ||
| if !reflect.DeepEqual(names, tc.expected) { | ||
| t.Error(diff.ObjectReflectDiff(names, tc.expected)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general for people who have never touched the tests in this file, it would be nice to be much more verbose:
t.Errorf("%s: did not execute correct pods: %v", testCase.name, diff.ObjectReflectDiff(names, tc.expected))In addition, I find it nice to explain what and why in the test name, like "test step fails so no other test steps run" or "pre step fails so no test steps run"
stevekuznetsov
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple things seem outstanding otherwise this LGTM
| break | ||
| } | ||
| } | ||
| if err := waitForPodCompletion(s.podClient.Pods(s.jobSpec.Namespace), pod.Name, nil, false); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this outstanding?
pkg/steps/multi_stage.go
Outdated
| }.AsSelector().String(), | ||
| }, | ||
| ) | ||
| if errors.IsNotFound(err) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems outstanding?
|
I think all reviews have been addressed. Rebased, squashed. |
|
Follow-up work identified in the review (other than those listed in the original description):
|
stevekuznetsov
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's either resolve the comment about the notifier if it's not needed or add it as a sub-task to the JIRA card so we don't lose it.
/lgtm
/approve
/hold
Whenever you do whatever is appropriate there, @bbguimaraes feel free to cancel the hold
|
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: bbguimaraes, stevekuznetsov The full list of commands accepted by this bot can be found here. The pull request process is described here DetailsNeeds approval from an approver in each of these files:
Approvers can indicate their approval by writing |
|
Ah, it took me a while to understand what the notifier question was about. The final version of the code, with artifact handling, does pass an artifact worker as a notifier — that's what I was thinking about when I read and responded to the question. It is not necessary here because the lifetime of the pod is exactly how much we want to wait. There is even a higher-level function we could use that does exactly that, but it would add noise to the subsequent PRs. |
|
ACK /hold cancel |
…artPod This seems generically useful, and not something that needs to be restricted to multi-step, where it has been since 8cf5f87 (ci-operator multi-stage: MultiStageTestStep, 2019-07-30, openshift#100).
Basic implementation of the multi-stage test type. Subset of
#85, based on
#98. Design document:
https://docs.google.com/document/d/1md-1BMf4_7mtKgGVoeZ3jOh4zSIBSjwl6vTTAYESwIM.
Fully-functional but lacking management of artifacts, the test secret, cluster
profile, and release image(s).