mirror of
https://github.com/jpillora/chisel.git
synced 2026-10-12 02:16:57 +00:00
232 lines
6.7 KiB
Go
232 lines
6.7 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/jpillora/opts"
|
|
)
|
|
|
|
// helpText renders a command's help at a fixed total line width, bypassing
|
|
// terminal detection so the wrapping is deterministic under `go test`.
|
|
func helpText(t *testing.T, cmd opts.Opts, width int) string {
|
|
t.Helper()
|
|
_, err := cmd.SetLineWidth(width).ParseArgsError([]string{"chisel", "--help"})
|
|
if err == nil {
|
|
t.Fatal("help did not stop parsing")
|
|
}
|
|
return err.Error()
|
|
}
|
|
|
|
// optionLines returns the flag list, which is the section opts wraps. The
|
|
// surrounding blocks (remotes, signals) are rendered verbatim by design.
|
|
func optionLines(help string) []string {
|
|
lines := strings.Split(help, "\n")
|
|
for i, line := range lines {
|
|
if strings.TrimSpace(line) != "Options:" {
|
|
continue
|
|
}
|
|
for j, line := range lines[i+1:] {
|
|
if strings.TrimSpace(line) == "" {
|
|
return lines[i+1 : i+1+j]
|
|
}
|
|
}
|
|
return lines[i+1:]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func helpCommands(t *testing.T) map[string]func() opts.Opts {
|
|
t.Helper()
|
|
return map[string]func() opts.Opts{
|
|
"server": func() opts.Opts { return newServerCLI(newServerOptions()) },
|
|
"client": func() opts.Opts { return newClientCLI(newClientOptions()) },
|
|
}
|
|
}
|
|
|
|
// The help tags hold unwrapped prose, so opts is free to wrap them to whatever
|
|
// width it detects. Words longer than the width are left to overflow.
|
|
func TestFlagHelpWrapsToLineWidth(t *testing.T) {
|
|
t.Setenv("NO_COLOR", "1")
|
|
const padWidth = 2
|
|
for name, newCmd := range helpCommands(t) {
|
|
for _, width := range []int{60, 80, 96} {
|
|
t.Run(fmt.Sprintf("%s/%d", name, width), func(t *testing.T) {
|
|
lines := optionLines(helpText(t, newCmd(), width))
|
|
if len(lines) == 0 {
|
|
t.Fatal("no option lines rendered")
|
|
}
|
|
for _, line := range lines {
|
|
if len(line) <= width-padWidth {
|
|
continue
|
|
}
|
|
if fields := strings.Fields(line); len(fields) == 1 {
|
|
continue //a single unbreakable word
|
|
}
|
|
t.Errorf("line exceeds width %d (%d chars): %q", width, len(line), line)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFlagHelpReflowsWithWidth(t *testing.T) {
|
|
t.Setenv("NO_COLOR", "1")
|
|
for name, newCmd := range helpCommands(t) {
|
|
t.Run(name, func(t *testing.T) {
|
|
narrow := optionLines(helpText(t, newCmd(), 60))
|
|
wide := optionLines(helpText(t, newCmd(), 96))
|
|
if len(narrow) <= len(wide) {
|
|
t.Fatalf("help did not reflow: %d lines at width 60, %d at width 96", len(narrow), len(wide))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Every flag carrying a help tag must survive into the rendered help.
|
|
func TestHelpListsEveryFlag(t *testing.T) {
|
|
t.Setenv("NO_COLOR", "1")
|
|
want := map[string][]string{
|
|
"server": {
|
|
"--host", "--port", "--keygen", "--key", "--keyfile", "--authfile",
|
|
"--auth", "--backend", "--socks5", "--reverse", "--keepalive",
|
|
"--tls-key", "--tls-cert", "--tls-domain", "--tls-ca", "--proxy",
|
|
"--pid", "--verbose",
|
|
},
|
|
"client": {
|
|
"--fingerprint", "--auth", "--keepalive", "--max-retry-count",
|
|
"--min-retry-interval", "--max-retry-interval", "--proxy",
|
|
"--tls-skip-verify", "--tls-ca", "--tls-cert", "--tls-key",
|
|
"--verbose", "--header", "--hostname", "--sni", "--pid",
|
|
},
|
|
}
|
|
for name, newCmd := range helpCommands(t) {
|
|
t.Run(name, func(t *testing.T) {
|
|
help := strings.Join(optionLines(helpText(t, newCmd(), 96)), "\n")
|
|
for _, flag := range want[name] {
|
|
//a flag with a short name renders as "--verbose, -v"
|
|
if !strings.Contains(help, flag+" ") && !strings.Contains(help, flag+",") {
|
|
t.Errorf("%s missing from help", flag)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAllFlagsHaveHelpTags(t *testing.T) {
|
|
for _, config := range []any{serverOptions{}, clientOptions{}} {
|
|
checkFlagHelpTags(t, reflect.TypeOf(config))
|
|
}
|
|
}
|
|
|
|
func checkFlagHelpTags(t *testing.T, typeOf reflect.Type) {
|
|
t.Helper()
|
|
for i := 0; i < typeOf.NumField(); i++ {
|
|
field := typeOf.Field(i)
|
|
if field.PkgPath != "" {
|
|
continue
|
|
}
|
|
optsTag := field.Tag.Get("opts")
|
|
if optsTagValue(optsTag, "mode") == "arg" {
|
|
continue
|
|
}
|
|
if field.Tag.Get("help") != "" {
|
|
continue
|
|
}
|
|
if field.Type.Kind() == reflect.Struct && optsTag != "-" {
|
|
checkFlagHelpTags(t, field.Type)
|
|
continue
|
|
}
|
|
if optsTag != "-" {
|
|
t.Errorf("%s.%s has no help tag", typeOf.Name(), field.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestServerOptions(t *testing.T) {
|
|
server := newServerOptions()
|
|
cli := newCLI(server, newClientOptions())
|
|
_, err := cli.ParseArgsError([]string{
|
|
"chisel", "server",
|
|
"--host", "127.0.0.1",
|
|
"-p", "9090",
|
|
"--key", "seed",
|
|
"--keyfile", "key.pem",
|
|
"--authfile", "users.json",
|
|
"--auth", "user:pass",
|
|
"--keepalive", "30s",
|
|
"--backend", "http://first",
|
|
"--proxy", "http://last",
|
|
"--socks5",
|
|
"--reverse",
|
|
"--tls-key", "tls-key.pem",
|
|
"--tls-cert", "tls-cert.pem",
|
|
"--tls-domain", "one.example",
|
|
"--tls-domain", "two.example",
|
|
"--tls-ca", "ca.pem",
|
|
"--pid",
|
|
"-v",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if server.Host != "127.0.0.1" || server.Port != "9090" {
|
|
t.Fatalf("listen options not parsed: host=%q port=%q", server.Host, server.Port)
|
|
}
|
|
if server.Config.Proxy != "http://last" {
|
|
t.Fatalf("proxy alias order not preserved: %q", server.Config.Proxy)
|
|
}
|
|
if server.KeepAlive != 30*time.Second || !server.Socks5 || !server.Reverse || !server.PID || !server.Verbose {
|
|
t.Fatalf("server options not parsed: %+v", server)
|
|
}
|
|
if want := []string{"one.example", "two.example"}; !reflect.DeepEqual(server.Config.TLS.Domains, want) {
|
|
t.Fatalf("TLS domains: got %q, want %q", server.Config.TLS.Domains, want)
|
|
}
|
|
}
|
|
|
|
func TestClientOptions(t *testing.T) {
|
|
client := newClientOptions()
|
|
cli := newCLI(newServerOptions(), client)
|
|
_, err := cli.ParseArgsError([]string{
|
|
"chisel", "client",
|
|
"--fingerprint", "fingerprint",
|
|
"--auth", "user:pass",
|
|
"--keepalive", "15s",
|
|
"--max-retry-count", "7",
|
|
"--min-retry-interval", "2s",
|
|
"--max-retry-interval", "1m",
|
|
"--proxy", "socks5://localhost:1080",
|
|
"--header", "Foo: one",
|
|
"--header", "Bar: two",
|
|
"--hostname", "host.example",
|
|
"--sni", "sni.example",
|
|
"--tls-ca", "ca.pem",
|
|
"--tls-skip-verify",
|
|
"--tls-key", "key.pem",
|
|
"--tls-cert", "cert.pem",
|
|
"--pid",
|
|
"-v",
|
|
"wss://server.example",
|
|
"3000",
|
|
"R:2222:localhost:22",
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if client.Server != "wss://server.example" {
|
|
t.Fatalf("server: %q", client.Server)
|
|
}
|
|
if want := []string{"3000", "R:2222:localhost:22"}; !reflect.DeepEqual(client.Remotes, want) {
|
|
t.Fatalf("remotes: got %q, want %q", client.Remotes, want)
|
|
}
|
|
if want := []string{"Foo: one", "Bar: two"}; !reflect.DeepEqual(client.Header, want) {
|
|
t.Fatalf("headers: got %q, want %q", client.Header, want)
|
|
}
|
|
if client.KeepAlive != 15*time.Second || client.MaxRetryCount != 7 || !client.Config.TLS.SkipVerify || !client.PID || !client.Verbose {
|
|
t.Fatalf("client options not parsed: %+v", client)
|
|
}
|
|
}
|