{"action":"created","issue":{"url":"https://api.github.com/repos/kubernetes/client-go/issues/500","repository_url":"https://api.github.com/repos/kubernetes/client-go","labels_url":"https://api.github.com/repos/kubernetes/client-go/issues/500/labels{/name}","comments_url":"https://api.github.com/repos/kubernetes/client-go/issues/500/comments","events_url":"https://api.github.com/repos/kubernetes/client-go/issues/500/events","html_url":"https://github.com/kubernetes/client-go/issues/500","id":375725282,"node_id":"MDU6SXNzdWUzNzU3MjUyODI=","number":500,"title":"Reactor Usage","user":{"login":"pcj","id":50580,"node_id":"MDQ6VXNlcjUwNTgw","avatar_url":"https://avatars3.githubusercontent.com/u/50580?v=4","gravatar_id":"","url":"https://api.github.com/users/pcj","html_url":"https://github.com/pcj","followers_url":"https://api.github.com/users/pcj/followers","following_url":"https://api.github.com/users/pcj/following{/other_user}","gists_url":"https://api.github.com/users/pcj/gists{/gist_id}","starred_url":"https://api.github.com/users/pcj/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/pcj/subscriptions","organizations_url":"https://api.github.com/users/pcj/orgs","repos_url":"https://api.github.com/users/pcj/repos","events_url":"https://api.github.com/users/pcj/events{/privacy}","received_events_url":"https://api.github.com/users/pcj/received_events","type":"User","site_admin":false},"labels":[{"id":778140025,"node_id":"MDU6TGFiZWw3NzgxNDAwMjU=","url":"https://api.github.com/repos/kubernetes/client-go/labels/lifecycle/rotten","name":"lifecycle/rotten","color":"604460","default":false}],"state":"closed","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":11,"created_at":"2018-10-30T22:46:35Z","updated_at":"2019-04-11T15:20:54Z","closed_at":"2019-04-11T15:20:53Z","author_association":"NONE","body":"Asking a question and adding reactor example for some future poor schmuck like myself that spent the afternoon trying to figure out why my reactor was not called.  \r\n\r\nThe key is understanding that that most generated / fake clientsets add in a \"catch-all\" reactor for the `ObjectTracker` that will always terminate the ReactorChain before it gets to yours.  So, although you'd think `.AddReactor` would work, in practice it's never called.  \r\n\r\nConsider this snippet in which the `ReactorFunc` will never be called:\r\n\r\n```go\r\nimport (\r\n\t\"k8s.io/client-go/kubernetes/fake\"\r\n\tfakebatchv1 \"k8s.io/client-go/kubernetes/typed/batch/v1/fake\"\r\n\tk8stesting \"k8s.io/client-go/testing\"\r\n)\r\nfakeClient := fake.NewSimpleClientset()\r\nfakeClient.BatchV1().(*fakebatchv1.FakeBatchV1).Fake.AddReactor(\"*\", \"*\", func(action k8stesting.Action) (bool, runtime.Object, error) {\r\n    t.Logf(\"I'm never triggered :(\")\r\n    return true, nil, nil\r\n})\r\n```\r\n\r\nLooking at https://github.com/kubernetes/client-go/blob/master/kubernetes/fake/clientset_generated.go, we see this code:\r\n\r\n```go\r\n\r\n// NewSimpleClientset returns a clientset that will respond with the provided objects.\r\n// It's backed by a very simple object tracker that processes creates, updates and deletions as-is,\r\n// without applying any validations and/or defaults. It shouldn't be considered a replacement\r\n// for a real clientset and is mostly useful in simple unit tests.\r\nfunc NewSimpleClientset(objects ...runtime.Object) *Clientset {\r\n\to := testing.NewObjectTracker(scheme, codecs.UniversalDecoder())\r\n\r\n        ...\r\n\r\n\tfakePtr := testing.Fake{}\r\n\tfakePtr.AddReactor(\"*\", \"*\", testing.ObjectReaction(o))\r\n\r\n        ...\r\n\r\n\treturn &Clientset{fakePtr, &fakediscovery.FakeDiscovery{Fake: &fakePtr}}\r\n}\r\n```\r\n\r\nIn the `testing/fake.go` base clientset, one finds the `Invokes` function that is called by all the derived fake client implementations:\r\n\r\n```go\r\n\r\n// Invokes records the provided Action and then invokes the ReactionFunc that\r\n// handles the action if one exists. defaultReturnObj is expected to be of the\r\n// same type a normal call would return.\r\nfunc (c *Fake) Invokes(action Action, defaultReturnObj runtime.Object) (runtime.Object, error) {\r\n\tc.Lock()\r\n\tdefer c.Unlock()\r\n\r\n\tc.actions = append(c.actions, action)\r\n\tfor _, reactor := range c.ReactionChain {\r\n\r\n\t\tif !reactor.Handles(action) {\r\n\t\t\tcontinue\r\n\t\t}\r\n\r\n\t\thandled, ret, err := reactor.React(action)\r\n\t\tif !handled {\r\n\t\t\tcontinue\r\n\t\t}\r\n\r\n\t\treturn ret, err\r\n\t}\r\n\r\n\treturn defaultReturnObj, nil\r\n}\r\n```\r\n\r\nSo the first ReactorFunc that handles the action will be called.  Since there is always a `*, *` in the front if the reactor chain, any other one added via the `AddReactor` function will never be called.\r\n\r\nThe workaround is to use `PrependReactor` instead:\r\n\r\n```go\r\nfakeClient := fake.NewSimpleClientset()\r\nfakeClient.BatchV1().(*fakebatchv1.FakeBatchV1).Fake.PrependReactor(\"*\", \"*\", func(action k8stesting.Action) (bool, runtime.Object, error) {\r\n    t.Logf(\"This works :)\")\r\n    return true, nil, nil\r\n})\r\n```\r\n\r\nMy question is, am I doing this wrong?  How is the `AddReactor` function ever useful?  I would like to write idiomatic tests but unclear on the reactor usage.\r\n"},"comment":{"url":"https://api.github.com/repos/kubernetes/client-go/issues/comments/482159402","html_url":"https://github.com/kubernetes/client-go/issues/500#issuecomment-482159402","issue_url":"https://api.github.com/repos/kubernetes/client-go/issues/500","id":482159402,"node_id":"MDEyOklzc3VlQ29tbWVudDQ4MjE1OTQwMg==","user":{"login":"k8s-ci-robot","id":20407524,"node_id":"MDQ6VXNlcjIwNDA3NTI0","avatar_url":"https://avatars0.githubusercontent.com/u/20407524?v=4","gravatar_id":"","url":"https://api.github.com/users/k8s-ci-robot","html_url":"https://github.com/k8s-ci-robot","followers_url":"https://api.github.com/users/k8s-ci-robot/followers","following_url":"https://api.github.com/users/k8s-ci-robot/following{/other_user}","gists_url":"https://api.github.com/users/k8s-ci-robot/gists{/gist_id}","starred_url":"https://api.github.com/users/k8s-ci-robot/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/k8s-ci-robot/subscriptions","organizations_url":"https://api.github.com/users/k8s-ci-robot/orgs","repos_url":"https://api.github.com/users/k8s-ci-robot/repos","events_url":"https://api.github.com/users/k8s-ci-robot/events{/privacy}","received_events_url":"https://api.github.com/users/k8s-ci-robot/received_events","type":"User","site_admin":false},"created_at":"2019-04-11T15:20:54Z","updated_at":"2019-04-11T15:20:54Z","author_association":"CONTRIBUTOR","body":"@antoineco: Closing this issue.\n\n<details>\n\nIn response to [this](https://github.com/kubernetes/client-go/issues/500#issuecomment-482159393):\n\n>Aha! One can now mutate `actionCopy` directly and the state is shared. That's a great improvement, thanks for the pointer @liggitt and sorry for missing the update in v11.\r\n>\r\n>/close\n\n\nInstructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository.\n</details>"}}
{"actor":{"display_login":"k8s-ci-robot"}}