-
Notifications
You must be signed in to change notification settings - Fork 24
refactor: update .Context() references for consistency #33
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
refactor: update .Context() references for consistency #33
Conversation
…into mbrooks-slackcontext-tabletestcommand-expectedasserts-background
…edasserts-background-context
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #33 +/- ##
==========================================
- Coverage 62.95% 62.92% -0.04%
==========================================
Files 210 210
Lines 22127 22149 +22
==========================================
+ Hits 13930 13937 +7
- Misses 7113 7127 +14
- Partials 1084 1085 +1 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
mwbrooks
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.
🦮 Comments to guide the reviewer safely toward the end!
| ctx := cmd.Context() | ||
| if cmd.CalledAs() == "workspace" { | ||
| clients.IO.PrintInfo(cmd.Context(), false, fmt.Sprintf( | ||
| clients.IO.PrintInfo(ctx, false, fmt.Sprintf( |
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.
note: I think this is a better pattern and I remember @zimeg has also asked that we reduce the amount of arguments that are functions calls. The main benefit is that other functions in the code block can use the same context ctx ensuring more consistency through the code.
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 noticed too you're updating it to be a first declaration in a function, which I'm so much a fan of too 🙏 ✨
In later comments you note that we often avoid modifying ctx as well and I think this makes it more clear that it's meant to be more of a function argument and not a variable to change 😉
| }, nil) | ||
| cm.AddDefaultMocks() | ||
| setupAppLinkCommandMocks(t, cm, cf) | ||
| setupAppLinkCommandMocks(t, ctx, cm, cf) |
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.
note: This catches a .Setup(t *testing.T, ctx context.Context, ...) that provides the ctx. We can now use it instead of setupAppLinkCommandMocks(...) creating a non-mocked context.
| var span opentracing.Span | ||
| ctx := cmd.Context() | ||
| span, ctx = opentracing.StartSpanFromContext(ctx, "cmd.Collaborators.Update") | ||
| span, _ := opentracing.StartSpanFromContext(ctx, "cmd.Collaborators.Update") |
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.
thought: This is up for debate. 🧠 OpenTracing span's return a new context that contains the span (and all of our values). Right now, we inconsistently replace the current ctx with the span's ctx. I've leaned toward not replacing our ctx because:
- We never access the span through the
ctxAFAIK- Instead, we often call
span.Context()...to get it and modify it for API calls
- Instead, we often call
- We are moving toward a rule of thumb where the context should not be modified after a Cobra Execution
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.
@mwbrooks Thanks for calling this out. How we use span in context is interesting to me.
We might consider follow ups for this, but IIRC spans are most useful for collecting traces that measure the beginning and end of function calls? I'm not sure if these can be gathered now, but we might find tools to visualize these useful in debugging slow processes 🔍
Not updating this ctx might cause strangeness in the trace callstack and make for confusing debugs if we explore this, but I have not tried it at all!
No blocker for this PR since we might consider this a standalone task altogether, but I'm also curious if this isn't seeming like the right use of context to you 🤔
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.
Making this more consistent overall is a nice change in this PR too!
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.
Yea, you bring up a good point about the span's forming a track callback. I'd expect that we'd want to update the context in that case.
Since we don't lean too heavily on the traces, I'll keep this PR as-is. The intention is to make everything consistent (not updating the context). Then, like you said, we can have a follow-up PR that updates everything together.
| cobra.OnInitialize(func() { | ||
| err := InitConfig(clients, rootCmd) | ||
| ctx := rootCmd.Context() | ||
| err := InitConfig(ctx, clients, rootCmd) | ||
| if err != nil { | ||
| clients.IO.PrintError(rootCmd.Context(), err.Error()) | ||
| clients.IO.PrintError(ctx, err.Error()) | ||
| clients.Os.Exit(int(iostreams.ExitError)) | ||
| } | ||
| }) | ||
| // Since we use the *E cobra lifecycle methods, OnFinalize is one of the few ways we can ensure something _always_ runs at the end of any command invocation, regardless if an error is raised or not during execution. | ||
| cobra.OnFinalize(func() { | ||
| cleanup(rootCmd.Context(), clients) | ||
| ctx := rootCmd.Context() | ||
| cleanup(ctx, clients) | ||
| }) |
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.
note: This is a unique area of our code w.r.t context.
The cobra.OnInitialize(...) and cobra.OnFinalize(...) are lifecycle functions called after rootCmd.ExecuteContext(...). I think it would be a nice addition for Cobra to pass the cmd cobra.Command into these functions, but it doesn't.
Since the context may change dramatically before-execution and after-execution, we can get the most up-to-date context with rootCmd.Context(). I think...I haven't tested what happens when you set sub-command sets the context, whether it rolls up to the root command.
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 is so interesting too!
I'm still thinking about the span example and updating context throughout the function callstack and it might make sense that values set in "lower" levels of context aren't shared "above"? At least if the ctx is not returned...
contextpackage that makes it easy to pass request-scoped values
Function calls might be similar to "requests" in this context 🔗 https://go.dev/blog/context
I agree that this is a confusing area overall though.
It's not clear to me how immutable values and pointers pair sometimes... But I am finding that we're setting the context as expected and I'm not finding that cobra modifies this:
Line 371 in b76e933
| if err := rootCmd.ExecuteContext(ctx); err != nil { |
To me this seems like a fine change, but I'm also curious if we can pass ctx to Init instead?
FWIW the current implementation seems to work when gathering context values from within a command RunE handler 👾
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.
Good points! Given how we use ctx in the OnInitialize and OnFinalize, I think we could safely use the ctx passed to Init. The upside is that the code is cleaner and easier to read.
Happy to make that change now before merging this PR!
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.
Commit 9f25031 passes ctx into Init(ctx) and uses it for OnInitialize and OnFinalize. All the magic is removed! 🪄 🙅🏻
zimeg
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.
@mwbrooks LGTM and swimmingly so 🫧 🐟
This PR has super good improvements to code health and asks a few great questions. Thanks a ton for making comments about these.
I responded with a few ideas about how we might want to use ctx throughout command lifecycles and with spans but these might both be explorations for follow up PRs. But no blocking notes within 🎶 🚢 💨
| var span opentracing.Span | ||
| ctx := cmd.Context() | ||
| span, ctx = opentracing.StartSpanFromContext(ctx, "cmd.Collaborators.Update") | ||
| span, _ := opentracing.StartSpanFromContext(ctx, "cmd.Collaborators.Update") |
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.
@mwbrooks Thanks for calling this out. How we use span in context is interesting to me.
We might consider follow ups for this, but IIRC spans are most useful for collecting traces that measure the beginning and end of function calls? I'm not sure if these can be gathered now, but we might find tools to visualize these useful in debugging slow processes 🔍
Not updating this ctx might cause strangeness in the trace callstack and make for confusing debugs if we explore this, but I have not tried it at all!
No blocker for this PR since we might consider this a standalone task altogether, but I'm also curious if this isn't seeming like the right use of context to you 🤔
| var span opentracing.Span | ||
| ctx := cmd.Context() | ||
| span, ctx = opentracing.StartSpanFromContext(ctx, "cmd.Collaborators.Update") | ||
| span, _ := opentracing.StartSpanFromContext(ctx, "cmd.Collaborators.Update") |
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.
Making this more consistent overall is a nice change in this PR too!
| ctx := cmd.Context() | ||
| if cmd.CalledAs() == "workspace" { | ||
| clients.IO.PrintInfo(cmd.Context(), false, fmt.Sprintf( | ||
| clients.IO.PrintInfo(ctx, false, fmt.Sprintf( |
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 noticed too you're updating it to be a first declaration in a function, which I'm so much a fan of too 🙏 ✨
In later comments you note that we often avoid modifying ctx as well and I think this makes it more clear that it's meant to be more of a function argument and not a variable to change 😉
| cobra.OnInitialize(func() { | ||
| err := InitConfig(clients, rootCmd) | ||
| ctx := rootCmd.Context() | ||
| err := InitConfig(ctx, clients, rootCmd) | ||
| if err != nil { | ||
| clients.IO.PrintError(rootCmd.Context(), err.Error()) | ||
| clients.IO.PrintError(ctx, err.Error()) | ||
| clients.Os.Exit(int(iostreams.ExitError)) | ||
| } | ||
| }) | ||
| // Since we use the *E cobra lifecycle methods, OnFinalize is one of the few ways we can ensure something _always_ runs at the end of any command invocation, regardless if an error is raised or not during execution. | ||
| cobra.OnFinalize(func() { | ||
| cleanup(rootCmd.Context(), clients) | ||
| ctx := rootCmd.Context() | ||
| cleanup(ctx, clients) | ||
| }) |
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 is so interesting too!
I'm still thinking about the span example and updating context throughout the function callstack and it might make sense that values set in "lower" levels of context aren't shared "above"? At least if the ctx is not returned...
contextpackage that makes it easy to pass request-scoped values
Function calls might be similar to "requests" in this context 🔗 https://go.dev/blog/context
I agree that this is a confusing area overall though.
It's not clear to me how immutable values and pointers pair sometimes... But I am finding that we're setting the context as expected and I'm not finding that cobra modifies this:
Line 371 in b76e933
| if err := rootCmd.ExecuteContext(ctx); err != nil { |
To me this seems like a fine change, but I'm also curious if we can pass ctx to Init instead?
FWIW the current implementation seems to work when gathering context values from within a command RunE handler 👾
| for name, tt := range tests { | ||
| t.Run(name, func(t *testing.T) { | ||
| ctx := t.Context() | ||
| ctx := slackcontext.MockContext(t.Context()) |
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.
👁️🗨️
| // | ||
| // use, | ||
| // clients.IO.PrintTrace(cmd.Context(), slacktrace.<NAMEDCONST>) | ||
| // clients.IO.PrintTrace(ctx, slacktrace.<NAMEDCONST>) |
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 is so useful for encouraging shared styles
…edasserts-background-context
|
Thanks for the thorough review @zimeg and suggesting the change to |
Summary
This pull request combs through our code to update references to
.Context()for more consistency. A few common changes are:someFunc(cmd.Context(), ...)tosomeFunc(ctx, ...)cmd.Context()is our mocked context, which can happen from:cmd.SetContext(ctxMock)before Cobra executioncmd.ExecuteContext(ctxMock)after Cobra execution.Setupprovides the mocked contextReviewers
Production code is modified in this PR, but I don't think we'll see any regressions or issues.
Requirements