Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ title: Changelog
* `[Added]` Add `checksum.files`, `checksum.sh`, and `checksum.persist` command checksum syntax while keeping the old checksum format compatible.
* `[Added]` Add `lets self upgrade --pre` to opt into upgrading to the latest prerelease.
* `[Added]` Add `lets self fix` config migration command with `--dry-run` preview output for deprecated checksum syntax.
* `[Fixed]` Allow `lets self upgrade` downloads to respond to Ctrl-C.
* `[Fixed]` Make checksum calculation respect command-level `work_dir` overrides.
* `[Fixed]` Restore the release checkout after the GoReleaser dry run so prerelease publishing does not fail on a dirty `go.mod`.

Expand Down
20 changes: 16 additions & 4 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,24 @@ func getContext() context.Context {
ch := make(chan os.Signal, 1)
signal.Notify(ch, os.Interrupt, syscall.SIGTERM)

ctx, cancel := context.WithCancel(context.Background())
return signalContext(context.Background(), ch, func() {
signal.Stop(ch)
})
}

func signalContext(parent context.Context, ch <-chan os.Signal, stop func()) context.Context {
ctx, cancel := context.WithCancel(parent)

go func() {
sig := <-ch
log.Printf("signal received: %s", sig)
cancel()
defer stop()

select {
case sig := <-ch:
log.Printf("signal received: %s", sig)
cancel()
case <-parent.Done():
cancel()
}
}()

return ctx
Expand Down
54 changes: 54 additions & 0 deletions internal/cli/cli_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package cli

import (
"context"
"os"
"testing"
"time"

cmdpkg "github.com/lets-cli/lets/internal/cmd"
"github.com/lets-cli/lets/internal/settings"
Expand Down Expand Up @@ -68,6 +71,57 @@ func TestFailOnConfigError(t *testing.T) {
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

suggestion (testing): Consider a test case where the parent context is already canceled before creating the signal context

The existing subtests cover (1) cancellation on first signal and (2) cancellation when a live parent is later canceled. Please add a subtest for a parent context that is already canceled before signalContext is called, to verify the returned context is immediately done and the stop callback is invoked in that scenario.


func TestSignalContext(t *testing.T) {
assertClosed := func(t *testing.T, ch <-chan struct{}, message string) {
t.Helper()

select {
case <-ch:
case <-time.After(time.Second):
t.Fatal(message)
}
}

t.Run("cancels and stops signal notification after first signal", func(t *testing.T) {
signals := make(chan os.Signal, 1)
stopped := make(chan struct{})
ctx := signalContext(context.Background(), signals, func() {
close(stopped)
})

signals <- os.Interrupt

assertClosed(t, ctx.Done(), "expected signal context to be canceled")
assertClosed(t, stopped, "expected signal notification to stop")
})

t.Run("cancels when parent context is canceled", func(t *testing.T) {
parent, cancel := context.WithCancel(context.Background())
stopped := make(chan struct{})
ctx := signalContext(parent, make(chan os.Signal), func() {
close(stopped)
})

cancel()

assertClosed(t, ctx.Done(), "expected signal context to be canceled by parent")
assertClosed(t, stopped, "expected signal notification to stop")
})

t.Run("cancels when parent context is already canceled", func(t *testing.T) {
parent, cancel := context.WithCancel(context.Background())
cancel()

stopped := make(chan struct{})
ctx := signalContext(parent, make(chan os.Signal), func() {
close(stopped)
})

assertClosed(t, ctx.Done(), "expected signal context to be canceled by parent")
assertClosed(t, stopped, "expected signal notification to stop")
})
}

func TestShouldCheckForUpdate(t *testing.T) {
defaultSettings := settings.Default()

Expand Down
Loading