go-cli-config-flag
Bug (USABILITY-001): main() hardcoded the config path, so committed alternate configs (e.g. config.e2e.json) were unusable, and the load path was untestable.
Fix (stdlib only):
1. flag.String("config", defaultConfigPath, ...) — --config/-config with the original path as default, so no-flag behavior is byte-for-byte unchanged.
2. Extracted run(configPath string) error out of main for direct unit testing.
3. All config-load failures are wrapped with a stable CONFIG_LOAD_ERROR marker; main prints to stderr and exits 1.
main.go
package main
import (
"flag"
"fmt"
"os"
)
// defaultConfigPath is the original hardcoded config path. Keeping it as the
// flag default means deployments that never passed --config behave identically,
// while committed alternate configs become usable.
const defaultConfigPath = "config.json"
func main() {
configPath := flag.String("config", defaultConfigPath,
"path to the JSON config file (default: "+defaultConfigPath+")")
flag.Parse()
if err := run(*configPath); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
config.go (marker + validation)
const ErrConfigLoad = "CONFIG_LOAD_ERROR"
func loadConfig(path string) (Config, error) {
b, err := os.ReadFile(path)
if err != nil {
return Config{}, fmt.Errorf("%s: %w", ErrConfigLoad, err)
}
cfg := defaultConfig() // {Port:8080, Host:"<ip-address>", HealthPath:"/health"}
if err := json.Unmarshal(b, &cfg); err != nil {
return Config{}, fmt.Errorf("%s: %w", ErrConfigLoad, err)
}
if cfg.Port < 1 || cfg.Port > 65535 {
return Config{}, fmt.Errorf("%s: invalid port %d in %s", ErrConfigLoad, cfg.Port, path)
}
// ...empty Host/HealthPath fall back to defaults...
return cfg, nil
}
server.go (extracted run)
// run loads config at configPath and serves HTTP until SIGINT/SIGTERM.
// Extracted from main so tests can drive it directly (e.g. error path).
func run(configPath string) error {
cfg, err := loadConfig(configPath)
if err != nil {
return err
}
srv := &http.Server{Addr: fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), Handler: newHandler(cfg)}
errCh := make(chan error, 1)
go func() { errCh <- srv.ListenAndServe() }()
log.Printf("listening on %s (config: %s)", srv.Addr, configPath)
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
select {
case err := <-errCh:
if err == http.ErrServerClosed {
return nil
}
return fmt.Errorf("server error: %w", err)
case <-stop:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
return srv.Shutdown(ctx)
}
}
Committed configs — config.json (default, port 8080) and config.e2e.json (port 9080), both with health_path: "/health".
Project at `/tmp/gocliflag`, built with `go build -o app .`, verified live: | # | Invocation | Result | |---|---|---| | 1 | `./app -config config.e2e.json` → `curl http://<ip-address>:9080/health` | **HTTP 200, body `ok`** (`listening on <ip-address>:9080 (config: config.e2e.json)`) | | 2 | `./app -config missing.json` | **exit=1**, stderr: `CONFIG_LOAD_ERROR: open missing.json: no such file or directory` | | 3 | `./app` (no flag) → `curl .../health` | **HTTP 200 `ok`** — loads default `config.json`; behavior identical to pre-fix default | Unit tests: `go vet ./...` clean; `go test ./...` → **ok, 8/8 PASS** covering: default-field fallback, e2e config parse, missing file → marker, malformed JSON → marker, invalid port → marker, `run(missing)` returns marker error, `/health` returns 200 via httptest, custom `health_path` honored. Edge cases tested live/unit: - Empty config file (`-config /dev/null`) → exit 1, `CONFIG_LOAD_ERROR: unexpected end of JSON input` - Both `-config` and `--config` accepted (stdlib flag handles both) - Invalid port (70000) and malformed JSON → `CONFIG_LOAD_ERROR`, exit 1 - Graceful shutdown on SIGTERM (no dangling listeners; confirmed port released) - Note: in this sandbox, ports 8080/18080 are occupied by unrelated harness services, so invocation 3 was live-verified on a free port via a temporary default config; the committed default `config.json` remains port 8080, canonical and unchanged.
{"model": "deepseek-v4-flash", "problem_class": "go-cli-config-flag", "result": "passed", "tests": 8}