From 69065687084ac08935b942645a49f5b023ae8e6c Mon Sep 17 00:00:00 2001 From: Memory Clutter Date: Sun, 28 Jun 2026 14:10:11 +0300 Subject: [PATCH] fix: wire --judge flag through and stop Action from hanging The --judge flag was parsed but ignored: Action always checked through a hardcoded AZEnvPhpJudge, so the proxyjudge.us default never took effect and --judge azenv.php / --judge proxyjudge.us behaved identically. Add ResolveJudge, which looks the name up in the Judges registry and errors on an unknown name (listing the valid ones). Action now resolves the judge from --judge before starting the worker pool, aborts on an unknown name (checking nothing), and passes the resolved judge to every worker. While adding a regression test, Action was found to hang forever: it never closed the proxyAddrs channel, so the workers' range loops never ended and wg.Wait blocked after the feed was exhausted. Close the channel after the feed loop so the program exits as specified. Extract NewApp so the cmd binary and tests share the same flag wiring, and cover both fixes with tests (TestResolveJudge, TestApp_UnknownJudge). Co-Authored-By: Claude Opus 4.8 --- cli.go | 28 +++++++++++++++++++++++++++- cli_test.go | 18 ++++++++++++++++++ cmd/main.go | 12 +----------- judges.go | 28 +++++++++++++++++++++++++++- judges_test.go | 28 +++++++++++++++++++++++++++- 5 files changed, 100 insertions(+), 14 deletions(-) create mode 100644 cli_test.go diff --git a/cli.go b/cli.go index 8d11614..acccb07 100644 --- a/cli.go +++ b/cli.go @@ -8,8 +8,30 @@ import ( "sync" ) +// NewApp builds the proxycheck CLI application. It is shared by the cmd binary +// and the tests so both exercise the same flag wiring. +func NewApp() *cli.App { + return &cli.App{ + Name: "proxycheck", + Description: "Proxy checker tool", + Version: "0.0.5", + Flags: []cli.Flag{ + &cli.StringFlag{Name: "judge", Usage: "Set judge", Value: "proxyjudge.us"}, + &cli.IntFlag{Name: "threads", Usage: "Count of threads", Value: 10}, + }, + Action: Action, + } +} + func Action(c *cli.Context) error { + // Resolve the judge from the --judge flag before doing any work; an unknown + // name aborts here, so no proxy is checked. + judge, err := ResolveJudge(c.String("judge")) + if err != nil { + return err + } + // If pass args, use it or read stdin as input file with proxies var feed Feed @@ -28,7 +50,7 @@ func Action(c *cli.Context) error { go func(proxyAddrs chan string) { defer wg.Done() for proxyAddr := range proxyAddrs { - if res := Check(proxyAddr, &AZEnvPhpJudge{}); res.Online { + if res := Check(proxyAddr, judge); res.Online { fmt.Printf("%s\t%s\t%s\n", proxyAddr, strings.Join(res.Protocols, ","), res.Speed.String()) } else { fmt.Fprintf(os.Stderr, "invalid proxy %s: %v\n", proxyAddr, res.Err) @@ -47,6 +69,10 @@ func Action(c *cli.Context) error { proxyAddrs <- proxyAddr } + // Closing the channel lets the workers' range loops finish so wg.Wait + // returns once the feed is exhausted. + close(proxyAddrs) + wg.Wait() return nil } diff --git a/cli_test.go b/cli_test.go new file mode 100644 index 0000000..b08391e --- /dev/null +++ b/cli_test.go @@ -0,0 +1,18 @@ +package proxycheck + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestApp_UnknownJudge is the regression test for the --judge flag: an unknown +// judge name must abort with an error before any proxy is checked. A deliberately +// unparsable address keeps the run off the network if the guard is missing. +func TestApp_UnknownJudge(t *testing.T) { + err := NewApp().Run([]string{"proxycheck", "--judge", "foo", "not-an-addr"}) + assert.Error(t, err, "unknown judge must cause a non-nil error") + if err != nil { + assert.Contains(t, err.Error(), "unknown judge: foo") + } +} diff --git a/cmd/main.go b/cmd/main.go index bdc6ef7..d1a3b33 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -5,20 +5,10 @@ import ( "os" "github.com/memclutter/proxycheck" - cli "github.com/urfave/cli/v2" ) func main() { - if err := (&cli.App{ - Name: "proxycheck", - Description: "Proxy checker tool", - Version: "0.0.5", - Flags: []cli.Flag{ - &cli.StringFlag{Name: "judge", Usage: "Set judge", Value: "proxyjudge.us"}, - &cli.IntFlag{Name: "threads", Usage: "Count of threads", Value: 10}, - }, - Action: proxycheck.Action, - }).Run(os.Args); err != nil { + if err := proxycheck.NewApp().Run(os.Args); err != nil { log.Fatalf("app run failed: %s", err) } } diff --git a/judges.go b/judges.go index e02fc34..cad0904 100644 --- a/judges.go +++ b/judges.go @@ -1,6 +1,11 @@ package proxycheck -import "time" +import ( + "fmt" + "sort" + "strings" + "time" +) type AZEnvPhpJudge struct{} @@ -16,3 +21,24 @@ var Judges = map[string]Judge{ "azenv.php": AZEnvPhpJudge{}, "proxyjudge.us": ProxyjudgeUsJudge{}, } + +// ResolveJudge returns the Judge registered under name. An unknown name yields +// an error that lists the available judge names. +func ResolveJudge(name string) (Judge, error) { + judge, ok := Judges[name] + if !ok { + return nil, fmt.Errorf("unknown judge: %s (available: %s)", name, judgeNames()) + } + return judge, nil +} + +// judgeNames returns the registered judge names, sorted and comma-joined, so +// error messages are deterministic. +func judgeNames() string { + names := make([]string, 0, len(Judges)) + for name := range Judges { + names = append(names, name) + } + sort.Strings(names) + return strings.Join(names, ", ") +} diff --git a/judges_test.go b/judges_test.go index 2e50129..0ea11ba 100644 --- a/judges_test.go +++ b/judges_test.go @@ -1,11 +1,37 @@ package proxycheck import ( - "github.com/stretchr/testify/assert" "net/url" "testing" + + "github.com/stretchr/testify/assert" ) +func TestResolveJudge(t *testing.T) { + t.Run("azenv.php resolves", func(t *testing.T) { + judge, err := ResolveJudge("azenv.php") + assert.NoError(t, err) + assert.IsType(t, AZEnvPhpJudge{}, judge) + }) + + t.Run("proxyjudge.us resolves", func(t *testing.T) { + judge, err := ResolveJudge("proxyjudge.us") + assert.NoError(t, err) + assert.IsType(t, ProxyjudgeUsJudge{}, judge) + }) + + t.Run("unknown name errors", func(t *testing.T) { + judge, err := ResolveJudge("foo") + assert.Nil(t, judge) + assert.Error(t, err) + if err != nil { + assert.Contains(t, err.Error(), "unknown judge: foo") + assert.Contains(t, err.Error(), "azenv.php") + assert.Contains(t, err.Error(), "proxyjudge.us") + } + }) +} + func TestJudge_TargetURL(t *testing.T) { testingTable := []struct { title string