Skip to content

Conversation

@mwbrooks
Copy link
Member

@mwbrooks mwbrooks commented Apr 11, 2025

Summary

🎶 We all live in a yellow context, a yellow context, a yellow context 🛥️

This pull request combs through our code to update references to .Context() for more consistency. A few common changes are:

  • Pulling out context from someFunc(cmd.Context(), ...) to someFunc(ctx, ...)
    • Verifying that cmd.Context() is our mocked context, which can happen from:
      • cmd.SetContext(ctxMock) before Cobra execution
      • cmd.ExecuteContext(ctxMock) after Cobra execution
  • Catching a few missed places where .Setup provides the mocked context

Reviewers

Production code is modified in this PR, but I don't think we'll see any regressions or issues.

Requirements

@mwbrooks mwbrooks added code health M-T: Test improvements and anything that improves code health semver:patch Use on pull requests to describe the release version increment labels Apr 11, 2025
@mwbrooks mwbrooks added this to the Next Release milestone Apr 11, 2025
@mwbrooks mwbrooks self-assigned this Apr 11, 2025
@codecov
Copy link

codecov bot commented Apr 11, 2025

Codecov Report

Attention: Patch coverage is 59.72222% with 29 lines in your changes missing coverage. Please review.

Project coverage is 62.92%. Comparing base (c084fde) to head (94a3fdf).
Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
cmd/triggers/access.go 16.66% 5 Missing ⚠️
cmd/auth/list.go 50.00% 4 Missing ⚠️
cmd/triggers/create.go 0.00% 4 Missing ⚠️
cmd/root.go 57.14% 3 Missing ⚠️
internal/update/update.go 0.00% 3 Missing ⚠️
cmd/project/create.go 33.33% 2 Missing ⚠️
cmd/upgrade/upgrade.go 0.00% 2 Missing ⚠️
internal/update/cli.go 0.00% 2 Missing ⚠️
cmd/app/app.go 50.00% 1 Missing ⚠️
cmd/platform/run.go 0.00% 1 Missing ⚠️
... and 2 more
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mwbrooks mwbrooks marked this pull request as ready for review April 11, 2025 19:28
@mwbrooks mwbrooks requested a review from a team as a code owner April 11, 2025 19:28
Copy link
Member Author

@mwbrooks mwbrooks left a 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!

Comment on lines +49 to +51
ctx := cmd.Context()
if cmd.CalledAs() == "workspace" {
clients.IO.PrintInfo(cmd.Context(), false, fmt.Sprintf(
clients.IO.PrintInfo(ctx, false, fmt.Sprintf(
Copy link
Member Author

@mwbrooks mwbrooks Apr 11, 2025

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.

Copy link
Member

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)
Copy link
Member Author

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")
Copy link
Member Author

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 ctx AFAIK
    • Instead, we often call span.Context()... to get it and modify it for API calls
  • We are moving toward a rule of thumb where the context should not be modified after a Cobra Execution

Copy link
Member

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 🤔

Copy link
Member

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!

Copy link
Member Author

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.

Comment on lines 208 to 220
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)
})
Copy link
Member Author

@mwbrooks mwbrooks Apr 11, 2025

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.

Copy link
Member

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...

context package 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:

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 👾

Copy link
Member Author

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!

Copy link
Member Author

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! 🪄 🙅🏻

Copy link
Member

@zimeg zimeg left a 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")
Copy link
Member

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")
Copy link
Member

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!

Comment on lines +49 to +51
ctx := cmd.Context()
if cmd.CalledAs() == "workspace" {
clients.IO.PrintInfo(cmd.Context(), false, fmt.Sprintf(
clients.IO.PrintInfo(ctx, false, fmt.Sprintf(
Copy link
Member

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 😉

Comment on lines 208 to 220
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)
})
Copy link
Member

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...

context package 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:

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())
Copy link
Member

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>)
Copy link
Member

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

@mwbrooks
Copy link
Member Author

Thanks for the thorough review @zimeg and suggesting the change to Init(ctx) 🙇🏻 Merging! :shipit:

@mwbrooks mwbrooks merged commit 85749e2 into main Apr 14, 2025
6 checks passed
@mwbrooks mwbrooks deleted the mbrooks-slackcontext-tabletestcommand-expectedasserts-background-context branch April 14, 2025 20:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

code health M-T: Test improvements and anything that improves code health semver:patch Use on pull requests to describe the release version increment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants