clone

an offline map explorer where each world, from a game map to a city's open data, ships as one .atlas file

main
atlas / tools / corpussmoke / main.go
main.go 4.3K
1// Command corpussmoke walks a real library of .atlas files and reports which2// of them parse and validate.3//4// It is deliberately NOT a CI gate and never will be: it reads an installed5// library — the maintainer's, typically tens of gigabytes — that no fresh6// checkout has. CI's rule is that every required test's inputs are committed7// or built by the run; this command is the other half of the bargain, the8// deep check a maintainer runs by hand over data that cannot be committed.9//10// It asserts invariants only: the container opens, the manifest re-reads,11// validation passes, every world's packed locations unpack. It compares no12// stamps, no hashes and no content — the days of holding the tree to captured13// bytes from a private library are over.14//15// Usage:16//17// atlas-corpussmoke [-bundles dir]18// make corpus-smoke19//20// The library is the -bundles flag, else $ATLAS_BUNDLES_DIR, else the21// application's own data directory — the same resolution `atlas serve` uses.22package main2324import (25 "archive/zip"26 "encoding/json"27 "flag"28 "fmt"29 "os"30 "path/filepath"3132 "github.com/FelineStateMachine/atlas/format/bundle"33)3435const (36 bundlesDirEnv = "ATLAS_BUNDLES_DIR"37 appIdentifier = "dev.felinestatemachine.atlas"38)3940func main() {41 bundles := flag.String("bundles", "", "the library of .atlas files (default $"+bundlesDirEnv+", else the application's own data directory)")42 flag.Parse()4344 dir, err := libraryDir(*bundles)45 if err != nil {46 fmt.Fprintln(os.Stderr, "corpussmoke:", err)47 os.Exit(1)48 }49 failed, err := smoke(dir)50 if err != nil {51 fmt.Fprintln(os.Stderr, "corpussmoke:", err)52 os.Exit(1)53 }54 if failed > 0 {55 os.Exit(1)56 }57}5859func smoke(dir string) (failed int, err error) {60 paths, err := filepath.Glob(filepath.Join(dir, "*.atlas"))61 if err != nil {62 return 0, err63 }64 if len(paths) == 0 {65 return 0, fmt.Errorf("%s holds no .atlas files", dir)66 }67 older := 068 for _, path := range paths {69 problem := check(path)70 switch {71 case problem == nil:72 fmt.Printf("ok %s\n", filepath.Base(path))73 case isOlderFormat(path):74 // A library accretes: builds from before this format version sit75 // beside current ones, and a v%d reader refusing them is the76 // reader doing its job, not a finding about this tree.77 older++78 default:79 failed++80 fmt.Printf("FAIL %s\n %v\n", filepath.Base(path), problem)81 }82 }83 fmt.Printf("%d bundles: %d ok, %d failed, %d older than format v%d\n",84 len(paths), len(paths)-failed-older, failed, older, bundle.FormatVersion)85 return failed, nil86}8788func check(path string) error {89 reader, err := bundle.Open(path)90 if err != nil {91 return err92 }93 defer reader.Close()94 if err := reader.Validate(); err != nil {95 return err96 }97 for _, world := range reader.Manifest.Worlds {98 if _, err := reader.Locations(world.Slug); err != nil {99 return fmt.Errorf("world %s: %w", world.Slug, err)100 }101 }102 return nil103}104105// isOlderFormat peeks at the raw container alone — straight through106// archive/zip, past the reader's version refusal — because a bundle that107// says it is an earlier format is old, not broken. Format 1 announces itself108// by layout: its manifest is atlas.json, a name this format retired. A109// manifest.json that carries an earlier formatVersion is the same answer110// said in the current layout.111func isOlderFormat(path string) bool {112 archive, err := zip.OpenReader(path)113 if err != nil {114 return false115 }116 defer archive.Close()117 names := make(map[string]*zip.File, len(archive.File))118 for _, file := range archive.File {119 names[file.Name] = file120 }121 manifest, current := names["manifest.json"]122 if !current {123 _, v1 := names["atlas.json"]124 return v1125 }126 entry, err := manifest.Open()127 if err != nil {128 return false129 }130 defer entry.Close()131 var peek struct {132 FormatVersion int `json:"formatVersion"`133 }134 if err := json.NewDecoder(entry).Decode(&peek); err != nil {135 return false136 }137 return peek.FormatVersion > 0 && peek.FormatVersion < bundle.FormatVersion138}139140func libraryDir(flagged string) (string, error) {141 if flagged != "" {142 return flagged, nil143 }144 if fromEnv := os.Getenv(bundlesDirEnv); fromEnv != "" {145 return fromEnv, nil146 }147 base, err := os.UserConfigDir()148 if err != nil {149 return "", fmt.Errorf("no -bundles given and no config directory to fall back on: %w", err)150 }151 return filepath.Join(base, appIdentifier, "bundles"), nil152}