diff --git a/main.go b/main.go index 3eaf722..ce1a4cd 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( _ "embed" "errors" "fmt" + "io" "log" "os" "os/signal" @@ -52,11 +53,38 @@ func main() { log.Fatal("this program must not run as root") } + err := buildCommand(os.Stderr).Parse(os.Args[1:]) + if errors.Is(err, errSuccess) || errors.Is(err, command.ErrHelp) { + internal.Exit(0) + panic("unreachable") + } + if errors.Is(err, command.ErrNoMatch) || errors.Is(err, command.ErrEmptyTree) { + internal.Exit(1) + panic("unreachable") + } + if err == nil { + log.Fatal("unreachable") + } + + var flagError command.FlagError + if !errors.As(err, &flagError) { + log.Printf("command: %v", err) + internal.Exit(1) + panic("unreachable") + } + fmsg.Verbose(flagError.Error()) + if flagError.Success() { + internal.Exit(0) + } + internal.Exit(1) +} + +func buildCommand(out io.Writer) command.Command { var ( flagVerbose bool flagJSON bool ) - c := command.New(os.Stderr, log.Printf, "fortify", func([]string) error { fmsg.Store(flagVerbose); return nil }). + c := command.New(out, log.Printf, "fortify", func([]string) error { fmsg.Store(flagVerbose); return nil }). Flag(&flagVerbose, "v", command.BoolFlag(false), "Print debug messages to the console"). Flag(&flagJSON, "json", command.BoolFlag(false), "Serialise output as JSON when applicable") @@ -264,30 +292,7 @@ func main() { c.Command("shim", command.UsageInternal, func([]string) error { shim.Main(); return errSuccess }) c.Command("init", command.UsageInternal, func([]string) error { init0.Main(); return errSuccess }) - err := c.Parse(os.Args[1:]) - if errors.Is(err, errSuccess) || errors.Is(err, command.ErrHelp) { - internal.Exit(0) - panic("unreachable") - } - if errors.Is(err, command.ErrNoMatch) || errors.Is(err, command.ErrEmptyTree) { - internal.Exit(1) - panic("unreachable") - } - if err == nil { - log.Fatal("unreachable") - } - - var flagError command.FlagError - if !errors.As(err, &flagError) { - log.Printf("command: %v", err) - internal.Exit(1) - panic("unreachable") - } - fmsg.Verbose(flagError.Error()) - if flagError.Success() { - internal.Exit(0) - } - internal.Exit(1) + return c } func runApp(a fst.App, config *fst.Config) { diff --git a/main_test.go b/main_test.go new file mode 100644 index 0000000..d965f3e --- /dev/null +++ b/main_test.go @@ -0,0 +1,81 @@ +package main + +import ( + "bytes" + "errors" + "flag" + "testing" + + "git.gensokyo.uk/security/fortify/command" +) + +func TestHelp(t *testing.T) { + testCases := []struct { + name string + args []string + want string + }{ + { + "main", []string{}, ` +Usage: fortify [-h | --help] [-v] [--json] COMMAND [OPTIONS] + +Commands: + app Launch app defined by the specified config file + run Configure and start a permissive default sandbox + show Show the contents of an app configuration + ps List active apps and their state + version Show fortify version + license Show full license text + template Produce a config template + help Show this help message + +`, + }, + { + "run", []string{"run", "-h"}, ` +Usage: fortify run [-h | --help] [--dbus-config ] [--dbus-system ] [--mpris] [--dbus-log] [--id ] [-a ] [-g ] [-d ] [-u ] [--wayland] [-X] [--dbus] [--pulse] COMMAND [OPTIONS] + +Flags: + -X Share X11 socket and allow connection + -a int + Fortify application ID + -d string + Application home directory (default "os") + -dbus + Proxy D-Bus connection + -dbus-config string + Path to D-Bus proxy config file, or "builtin" for defaults (default "builtin") + -dbus-log + Force logging in the D-Bus proxy + -dbus-system string + Path to system D-Bus proxy config file, or "nil" to disable (default "nil") + -g value + Groups inherited by the app process + -id string + App ID, leave empty to disable security context app_id + -mpris + Allow owning MPRIS D-Bus path, has no effect if custom config is available + -pulse + Share PulseAudio socket and cookie + -u string + Passwd name within sandbox (default "chronos") + -wayland + Allow Wayland connections + +`, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + out := new(bytes.Buffer) + c := buildCommand(out) + if err := c.Parse(tc.args); !errors.Is(err, command.ErrHelp) && !errors.Is(err, flag.ErrHelp) { + t.Errorf("Parse: error = %v; want %v", + err, command.ErrHelp) + } + if got := out.String(); got != tc.want { + t.Errorf("Parse: %s want %s", got, tc.want) + } + }) + } +}