WIP: rebase fork onto upstream/main (v1.103.0) #15

Closed
codinget wants to merge 670 commits from webnet into save/webnet-2026-07-29
2 changed files with 191 additions and 16 deletions
Showing only changes of commit 6ff761c5f8 - Show all commits
+27 -16
View File
@@ -23,8 +23,9 @@ package jsonoutput
import (
"errors"
"flag"
"fmt"
"io"
"strconv"
"strings"
)
var _ flag.Value = &SchemaVersion{}
@@ -46,32 +47,42 @@ type SchemaVersion struct {
func (v *SchemaVersion) String() string {
if v.IsSet {
return strconv.Itoa(v.Version)
} else {
return "(not set)"
}
return strconv.FormatBool(false)
}
// Set is called when the user passes the flag as a command-line argument.
func (v *SchemaVersion) Set(s string) error {
if v.IsSet {
return errors.New("received multiple instances of --json; only pass it once")
// Delegate to a FlagSet to parse this as both a BoolVar and an IntVar.
// This is less efficient than copying the implementation from the standard library
// but this design makes it likelier that Set will inherit any upstream fixes.
fs := flag.NewFlagSet("", flag.ContinueOnError)
fs.BoolVar(&v.IsSet, "bool", false, "")
fs.IntVar(&v.Version, "int", 0, "")
fs.SetOutput(io.Discard) // silence
// First, try to parse as an IntVar to handle -flag=INT.
// This order is important because -bool=0 will parse as false.
if err := fs.Parse([]string{"-int=" + s}); err == nil {
v.IsSet = true
return nil
}
// If that fails, parse as a BoolVar to handle -flag and -flag=false.
// This is checked last for compatibility with the boolean -json flag.
if err := fs.Parse([]string{"-bool=" + s}); err != nil {
// Unwrap the header added by FlagSet.failf:
// `invalid boolean value "invalid" for -bool: `
bits := strings.SplitN(err.Error(), ": ", 2)
return errors.New(bits[len(bits)-1])
}
v.IsSet = true
// If the user doesn't supply a schema version, default to 1.
// This ensures that any existing scripts will continue to get their
// current output.
if s == "true" {
if v.IsSet {
v.Version = 1
return nil
} else {
v.Version = 0 // if unset, zero out the Version
}
version, err := strconv.Atoi(s)
if err != nil {
return fmt.Errorf("invalid integer value passed to --json: %q", s)
}
v.Version = version
return nil
}
@@ -0,0 +1,164 @@
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package jsonoutput_test
import (
"flag"
"math"
"testing"
gcmp "github.com/google/go-cmp/cmp"
"github.com/kballard/go-shellquote"
"tailscale.com/cmd/tailscale/cli/jsonoutput"
)
func TestSchemaVersion(t *testing.T) {
for _, tc := range []struct {
name string
args string
want jsonoutput.SchemaVersion
wantErr string
wantStr string
}{
{
name: "none",
want: jsonoutput.SchemaVersion{IsSet: false, Version: 0},
wantStr: "false",
},
{
name: "default",
args: "-got",
want: jsonoutput.SchemaVersion{IsSet: true, Version: 1},
wantStr: "1",
},
{
name: "true",
args: "-got=true",
want: jsonoutput.SchemaVersion{IsSet: true, Version: 1},
wantStr: "1",
},
{
name: "false",
args: "-got=false",
want: jsonoutput.SchemaVersion{IsSet: false, Version: 0},
wantStr: "false",
},
{
// Test that -got=0 isnt interpreted as -bool=0, i.e. false.
name: "zero_not_false",
args: "-got=0",
want: jsonoutput.SchemaVersion{IsSet: true, Version: 0},
wantStr: "0",
},
{
name: "one",
args: "-got=1",
want: jsonoutput.SchemaVersion{IsSet: true, Version: 1},
wantStr: "1",
},
{
name: "two",
args: "-got=2",
want: jsonoutput.SchemaVersion{IsSet: true, Version: 2},
wantStr: "2",
},
{
name: "max",
args: "-got=2147483647",
want: jsonoutput.SchemaVersion{IsSet: true, Version: math.MaxInt32},
wantStr: "2147483647",
},
{
name: "min",
args: "-got=-2147483648",
want: jsonoutput.SchemaVersion{IsSet: true, Version: math.MinInt32},
wantStr: "-2147483648",
},
{
name: "invalid",
args: "-got=invalid",
wantErr: `invalid boolean value "invalid" for -got: parse error`,
},
{
name: "float",
args: "-got=1.3",
wantErr: `invalid boolean value "1.3" for -got: parse error`,
},
{
name: "space",
args: "-got=' '",
wantErr: `invalid boolean value " " for -got: parse error`,
},
{
name: "trailing_space",
args: "-got='1 '",
wantErr: `invalid boolean value "1 " for -got: parse error`,
},
} {
args, err := shellquote.Split(tc.args)
if err != nil {
t.Fatalf("broken args %q: %v", tc.args, err)
}
// Test both Set and String methods.
t.Run(tc.name, func(t *testing.T) {
var got jsonoutput.SchemaVersion
fs := flag.NewFlagSet("name", flag.ContinueOnError)
fs.Var(&got, "got", "usage")
err = fs.Parse(args)
if err != nil && tc.wantErr == "" {
t.Errorf("parse error: %v", err)
} else if err != nil && err.Error() != tc.wantErr {
t.Errorf("parse error mismatch: %q, want %q", err, tc.wantErr)
} else if err == nil && tc.wantErr != "" {
t.Errorf("parse error: %v, want %q", err, tc.wantErr)
}
if len(fs.Args()) != 0 {
t.Errorf("unexpected positional arguments: %q", fs.Args())
}
if diff := gcmp.Diff(tc.want, got); diff != "" {
t.Errorf("parse mismatch: -want +got\n%s", diff)
}
if s := got.String(); s != tc.wantStr && tc.wantStr != "" {
t.Errorf("string %q, want %q", s, tc.wantStr)
}
})
if tc.args == "" {
continue // nothing to clobber
}
if tc.wantErr != "" {
continue // clobbering will just trigger another error
}
// The last -got flag will clobber all previous -got flags.
t.Run(tc.name+"/clobber", func(t *testing.T) {
var got jsonoutput.SchemaVersion
fs := flag.NewFlagSet("name", flag.ContinueOnError)
fs.Var(&got, "got", "usage")
sentinel := []string{"-got=-1"}
if err := fs.Parse(append(sentinel, args...)); err != nil {
t.Errorf("parse error: %v", err)
}
if got.Version == -1 {
t.Errorf("sentinel detected: flag didnt clobber")
}
if len(fs.Args()) != 0 {
t.Errorf("unexpected positional arguments: %q", fs.Args())
}
if diff := gcmp.Diff(tc.want, got); diff != "" {
t.Errorf("parse mismatch: -want +got\n%s", diff)
}
})
}
}