Comments (and one test comment) now describe the rules on their own terms instead of by comparison to another downloader. No behavior change.
223 lines
7.3 KiB
Go
223 lines
7.3 KiB
Go
package cli
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestParseArgs(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
args []string
|
|
action Action
|
|
wantVal map[string]string
|
|
uris []string
|
|
}{
|
|
{"short attached", []string{"-x16", "https://e.com"}, Run, map[string]string{"max-connection-per-server": "16"}, []string{"https://e.com"}},
|
|
{"long equals + bool", []string{"--split=8", "-c", "u"}, Run, map[string]string{"split": "8", "continue": "true"}, []string{"u"}},
|
|
{"long separate value", []string{"--out", "f.bin", "u"}, Run, map[string]string{"out": "f.bin"}, []string{"u"}},
|
|
{"bool bundle", []string{"-cq", "u"}, Run, map[string]string{"continue": "true", "quiet": "true"}, []string{"u"}},
|
|
{"repeatable header", []string{"--header", "A: 1", "--header", "B: 2", "u"}, Run, map[string]string{"header": "A: 1\nB: 2"}, []string{"u"}},
|
|
{"two uris", []string{"a", "b"}, Run, map[string]string{}, []string{"a", "b"}},
|
|
{"permute flag after uri", []string{"u", "--split=8"}, Run, map[string]string{"split": "8"}, []string{"u"}},
|
|
{"permute interleaved", []string{"a", "-c", "b", "--quiet"}, Run, map[string]string{"continue": "true", "quiet": "true"}, []string{"a", "b"}},
|
|
{"dash dash terminator", []string{"--split=8", "--", "--not-a-flag", "u"}, Run, map[string]string{"split": "8"}, []string{"--not-a-flag", "u"}},
|
|
{"long int + magnet uri", []string{"--bt-stop-timeout=120", "magnet:x"}, Run, map[string]string{"bt-stop-timeout": "120"}, []string{"magnet:x"}},
|
|
{"help", []string{"--help"}, ShowHelp, nil, nil},
|
|
{"help short", []string{"-h"}, ShowHelp, nil, nil},
|
|
{"version", []string{"-v"}, ShowVersion, nil, nil},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
vals, uris, action, _, err := parseArgs(tc.args)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if action != tc.action {
|
|
t.Fatalf("action = %v, want %v", action, tc.action)
|
|
}
|
|
if tc.action != Run {
|
|
return
|
|
}
|
|
for k, want := range tc.wantVal {
|
|
if vals[k] != want {
|
|
t.Errorf("vals[%q] = %q, want %q", k, vals[k], want)
|
|
}
|
|
}
|
|
if len(uris) != len(tc.uris) {
|
|
t.Fatalf("uris = %v, want %v", uris, tc.uris)
|
|
}
|
|
for i := range uris {
|
|
if uris[i] != tc.uris[i] {
|
|
t.Errorf("uris[%d] = %q, want %q", i, uris[i], tc.uris[i])
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseArgsErrors(t *testing.T) {
|
|
for _, args := range [][]string{
|
|
{"--unknown-flag"},
|
|
{"-Q"}, // -Q is not a defined short flag
|
|
{"--out"}, // missing value
|
|
} {
|
|
if _, _, _, _, err := parseArgs(args); err == nil {
|
|
t.Errorf("parseArgs(%v) = nil error, want error", args)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseSize(t *testing.T) {
|
|
tests := []struct {
|
|
in string
|
|
want int64
|
|
}{
|
|
{"", 0},
|
|
{"100", 100},
|
|
{"512K", 512 << 10},
|
|
{"20M", 20 << 20},
|
|
{"1024M", 1 << 30}, // there is no G unit; the 1G bound is typed as 1024M
|
|
{"1Mi", 1 << 20}, // the first K/M is taken and the rest discarded
|
|
{"10MB", 10 << 20},
|
|
{"2k", 2 << 10},
|
|
}
|
|
for _, tc := range tests {
|
|
got, err := parseSize(tc.in)
|
|
if err != nil {
|
|
t.Errorf("parseSize(%q) error: %v", tc.in, err)
|
|
continue
|
|
}
|
|
if got != tc.want {
|
|
t.Errorf("parseSize(%q) = %d, want %d", tc.in, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateRange(t *testing.T) {
|
|
x := byLong["max-connection-per-server"]
|
|
if err := validate(x, "16"); err != nil {
|
|
t.Errorf("validate 16: %v", err)
|
|
}
|
|
if err := validate(x, "99"); err == nil {
|
|
t.Errorf("validate 99: want out-of-range error")
|
|
}
|
|
a := byLong["file-allocation"]
|
|
if err := validate(a, "bogus"); err == nil {
|
|
t.Errorf("validate enum bogus: want error")
|
|
}
|
|
}
|
|
|
|
func TestValidateIntFloor(t *testing.T) {
|
|
// Min:0 now enforces a floor of 0: negatives are rejected.
|
|
m := byLong["max-tries"] // Min 0, "0 = unlimited" runtime meaning preserved
|
|
if err := validate(m, "0"); err != nil {
|
|
t.Errorf("validate max-tries 0: %v, want nil (0 stays a valid input)", err)
|
|
}
|
|
if err := validate(m, "-1"); err == nil {
|
|
t.Errorf("validate max-tries -1: want below-minimum error")
|
|
}
|
|
}
|
|
|
|
func TestValidateFloatNonNegative(t *testing.T) {
|
|
// Float options (seed-time, seed-ratio) reject negatives: a negative
|
|
// SeedTime would fire the seed timer immediately and a negative SeedRatio
|
|
// is silently ignored, so neither is a meaningful input.
|
|
for _, name := range []string{"seed-ratio", "seed-time"} {
|
|
o := byLong[name]
|
|
if err := validate(o, "-1"); err == nil {
|
|
t.Errorf("validate %s -1: want below-minimum error", name)
|
|
}
|
|
if err := validate(o, "0"); err != nil {
|
|
t.Errorf("validate %s 0: %v, want nil", name, err)
|
|
}
|
|
if err := validate(o, "1.5"); err != nil {
|
|
t.Errorf("validate %s 1.5: %v, want nil", name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateBool(t *testing.T) {
|
|
b := byLong["enable-dht"]
|
|
// Only literal true/false are accepted (case-sensitive), with surrounding
|
|
// whitespace trimmed.
|
|
for _, ok := range []string{"true", "false", " true ", "false\t"} {
|
|
if err := validate(b, ok); err != nil {
|
|
t.Errorf("validate bool %q: %v", ok, err)
|
|
}
|
|
}
|
|
// The invented spellings and any case variant must now fail.
|
|
for _, bad := range []string{"yes", "no", "1", "0", "on", "off", "flase",
|
|
"True", "TRUE", "tRuE", "False", "FALSE"} {
|
|
if err := validate(b, bad); err == nil {
|
|
t.Errorf("validate bool %q: want error (only true/false accepted)", bad)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateSizeBounds(t *testing.T) {
|
|
k := byLong["min-split-size"] // 1M..1024M (no G unit)
|
|
if err := validate(k, "20M"); err != nil {
|
|
t.Errorf("validate min-split-size 20M: %v", err)
|
|
}
|
|
if err := validate(k, "512K"); err == nil {
|
|
t.Errorf("validate min-split-size 512K: want below-minimum error")
|
|
}
|
|
// 1025M exceeds the 1<<30 cap and still parses, so it tests the bounds path
|
|
// (where "2G" would have failed earlier as an unknown unit).
|
|
if err := validate(k, "1025M"); err == nil {
|
|
t.Errorf("validate min-split-size 1025M: want above-maximum error")
|
|
}
|
|
}
|
|
|
|
func TestParseSizeNegative(t *testing.T) {
|
|
if _, err := parseSize("-1"); err == nil {
|
|
t.Errorf("parseSize(-1): want error (size parsing rejects negative values)")
|
|
}
|
|
if _, err := parseSize("-5M"); err == nil {
|
|
t.Errorf("parseSize(-5M): want error")
|
|
}
|
|
}
|
|
|
|
// size parsing knows only K and M; a "G" suffix is not a unit and must be
|
|
// rejected rather than silently treated as 2^30.
|
|
func TestParseSizeNoGigaUnit(t *testing.T) {
|
|
for _, in := range []string{"1G", "1g", "1Gi", "5GB"} {
|
|
if _, err := parseSize(in); err == nil {
|
|
t.Errorf("parseSize(%q): want error (no G unit)", in)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExplicitConfMissingFatal(t *testing.T) {
|
|
missing := filepath.Join(t.TempDir(), "no-such-file.conf")
|
|
// Default (non-explicit) missing path is silently ignored.
|
|
if err := applyConfig(newOptions(), missing, false); err != nil {
|
|
t.Errorf("applyConfig default missing: %v, want nil", err)
|
|
}
|
|
// Explicit missing path is fatal.
|
|
if err := applyConfig(newOptions(), missing, true); err == nil {
|
|
t.Errorf("applyConfig explicit missing: want error")
|
|
}
|
|
}
|
|
|
|
func TestOptionsGetters(t *testing.T) {
|
|
o := newOptions()
|
|
o.vals["split"] = "8"
|
|
o.vals["min-split-size"] = "20M"
|
|
o.vals["continue"] = "true"
|
|
o.vals["seed-ratio"] = "1.5"
|
|
if o.Int("split") != 8 {
|
|
t.Errorf("Int split = %d", o.Int("split"))
|
|
}
|
|
if o.Size("min-split-size") != 20<<20 {
|
|
t.Errorf("Size min-split-size = %d", o.Size("min-split-size"))
|
|
}
|
|
if !o.Bool("continue") {
|
|
t.Errorf("Bool continue = false")
|
|
}
|
|
if o.Float("seed-ratio") != 1.5 {
|
|
t.Errorf("Float seed-ratio = %v", o.Float("seed-ratio"))
|
|
}
|
|
}
|