clone

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

feat/atlas-next-horizon
atlas / format / vnext / block_test.go
block_test.go 5.0K
1package vnext23import (4 "bytes"5 "encoding/binary"6 "math"7 "testing"8)910func TestPackedBlockRefusesAttackerCountsBeforeAllocation(t *testing.T) {11 field := Field{ID: CoreID("feature.title"), Name: "title", Kind: KindString}12 table := Table{TypeID: CoreID("type.feature"), Rows: 1, Columns: []Column{{Field: field, Values: []Value{StringValue("Sample Place")}}}}13 data, err := EncodeBlock([32]byte{}, table)14 if err != nil {15 t.Fatal(err)16 }17 binary.LittleEndian.PutUint32(data[12:16], math.MaxUint32)18 if _, err := DecodeBlock(data); err == nil {19 t.Fatal("attacker-sized row count was accepted")20 }21 geometry := []byte{byte(GeometryPoint), 0xff, 0xff, 0xff, 0xff}22 if _, err := decodeGeometry(geometry); err == nil {23 t.Fatal("attacker-sized geometry part count was accepted")24 }25}2627func TestPackedBlockRoundTripsEveryWireKind(t *testing.T) {28 t.Parallel()2930 typeID := IDFromName("com.example", "type.observation")31 fields := []Field{32 {ID: IDFromName("com.example", "bool"), Name: "bool", Kind: KindBool, Optional: true},33 {ID: IDFromName("com.example", "int"), Name: "int", Kind: KindInt64, Optional: true},34 {ID: IDFromName("com.example", "float"), Name: "float", Kind: KindFloat64, Optional: true},35 {ID: IDFromName("com.example", "string"), Name: "string", Kind: KindString, Optional: true},36 {ID: IDFromName("com.example", "bytes"), Name: "bytes", Kind: KindBytes, Optional: true},37 {ID: IDFromName("com.example", "ref"), Name: "ref", Kind: KindID, Optional: true},38 }39 schema := Schema{Types: []Type{{ID: typeID, Name: "Observation", Fields: fields}}}40 hash, err := schema.Hash()41 if err != nil {42 t.Fatalf("hash schema: %v", err)43 }44 want := Table{45 TypeID: typeID,46 Rows: 2,47 Columns: []Column{48 {Field: fields[0], Values: []Value{BoolValue(true), NullValue()}},49 {Field: fields[1], Values: []Value{Int64Value(-42), Int64Value(9001)}},50 {Field: fields[2], Values: []Value{Float64Value(3.5), Float64Value(-0.25)}},51 {Field: fields[3], Values: []Value{StringValue("north"), StringValue("雪")}},52 {Field: fields[4], Values: []Value{BytesValue([]byte{0, 1, 2}), BytesValue(nil)}},53 {Field: fields[5], Values: []Value{IDValue(IDFromName("com.example", "one")), IDValue(IDFromName("com.example", "two"))}},54 },55 }5657 data, err := EncodeBlock(hash, want)58 if err != nil {59 t.Fatalf("encode block: %v", err)60 }61 got, err := DecodeBlock(data)62 if err != nil {63 t.Fatalf("decode block: %v", err)64 }65 if diff := diffTable(want, got.Table); diff != "" {66 t.Fatal(diff)67 }68 reencoded, err := EncodeBlock(got.SchemaHash, got.Table)69 if err != nil {70 t.Fatalf("re-encode block: %v", err)71 }72 if !bytes.Equal(data, reencoded) {73 t.Fatal("canonical block changed after decode and re-encode")74 }75}7677func TestOlderRuntimePreservesUnknownColumns(t *testing.T) {78 t.Parallel()7980 typeID := CoreID("type.feature")81 known := Field{ID: CoreID("feature.title"), Name: "title", Kind: KindString}82 foreign := Field{ID: IDFromName("com.example.rescue", "feature.triage"), Name: "triage", Kind: KindInt64}83 readerSchema := Schema{Types: []Type{{ID: typeID, Name: "Feature", Fields: []Field{known}}}}84 fileSchema, err := UnionSchemas(readerSchema, Schema{Types: []Type{{ID: typeID, Name: "Feature", Fields: []Field{foreign}}}})85 if err != nil {86 t.Fatalf("make file schema: %v", err)87 }88 table := Table{TypeID: typeID, Rows: 1, Columns: []Column{89 {Field: known, Values: []Value{StringValue("Clinic")}},90 {Field: foreign, Values: []Value{Int64Value(4)}},91 }}92 hash, err := fileSchema.Hash()93 if err != nil {94 t.Fatalf("hash file schema: %v", err)95 }96 data, err := EncodeBlock(hash, table)97 if err != nil {98 t.Fatalf("encode file block: %v", err)99 }100 block, err := DecodeBlock(data)101 if err != nil {102 t.Fatalf("decode file block: %v", err)103 }104 view, err := ProjectTable(readerSchema, fileSchema, block.Table)105 if err != nil {106 t.Fatalf("project through old reader: %v", err)107 }108 if len(view.Known.Columns) != 1 || view.Known.Columns[0].Field.ID != known.ID {109 t.Fatalf("known projection = %#v", view.Known.Columns)110 }111 if len(view.Foreign.Columns) != 1 || view.Foreign.Columns[0].Field.ID != foreign.ID {112 t.Fatalf("foreign projection = %#v", view.Foreign.Columns)113 }114115 reencoded, err := EncodeBlock(block.SchemaHash, view.Complete())116 if err != nil {117 t.Fatalf("re-encode projected block: %v", err)118 }119 if !bytes.Equal(data, reencoded) {120 t.Fatal("old reader did not preserve the foreign column byte-for-byte")121 }122}123124func diffTable(want, got Table) string {125 if want.TypeID != got.TypeID || want.Rows != got.Rows || len(want.Columns) != len(got.Columns) {126 return "decoded table shape differs"127 }128 byID := make(map[ID]Column, len(got.Columns))129 for _, column := range got.Columns {130 byID[column.Field.ID] = column131 }132 for _, left := range want.Columns {133 right, held := byID[left.Field.ID]134 if !held || left.Field.Kind != right.Field.Kind || left.Field.Optional != right.Field.Optional || len(left.Values) != len(right.Values) {135 return "decoded column shape differs"136 }137 for row := range left.Values {138 if !left.Values[row].Equal(right.Values[row]) {139 return "decoded value differs"140 }141 }142 }143 return ""144}