go-hep.org/x/hep@v0.38.1/groot/rtree/writer_test.go (about) 1 // Copyright ©2019 The go-hep Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package rtree 6 7 import ( 8 "compress/flate" 9 "fmt" 10 "math/rand/v2" 11 "os" 12 "path/filepath" 13 "reflect" 14 "sync" 15 "testing" 16 17 "go-hep.org/x/hep/groot/internal/rcompress" 18 "go-hep.org/x/hep/groot/rbase" 19 "go-hep.org/x/hep/groot/riofs" 20 ) 21 22 func TestFlattenArrayType(t *testing.T) { 23 for _, tc := range []struct { 24 typ any 25 want any 26 shape []int 27 }{ 28 { 29 typ: int32(0), 30 want: int32(0), 31 shape: nil, 32 }, 33 { 34 typ: [2]int32{}, 35 want: int32(0), 36 shape: []int{2}, 37 }, 38 { 39 typ: [2][3]int32{}, 40 want: int32(0), 41 shape: []int{2, 3}, 42 }, 43 { 44 typ: [2][3][4]int32{}, 45 want: int32(0), 46 shape: []int{2, 3, 4}, 47 }, 48 { 49 typ: [2][3][4][0]int32{}, 50 want: int32(0), 51 shape: []int{2, 3, 4, 0}, 52 }, 53 { 54 typ: [2][3][4][5]int32{}, 55 want: int32(0), 56 shape: []int{2, 3, 4, 5}, 57 }, 58 { 59 typ: [2][3][4][5][6]int32{}, 60 want: int32(0), 61 shape: []int{2, 3, 4, 5, 6}, 62 }, 63 { 64 typ: [2][3][4][0]struct{}{}, 65 want: struct{}{}, 66 shape: []int{2, 3, 4, 0}, 67 }, 68 { 69 typ: [2][3][4][0][]string{}, 70 want: []string{}, 71 shape: []int{2, 3, 4, 0}, 72 }, 73 { 74 typ: []string{}, 75 want: []string{}, 76 shape: nil, 77 }, 78 } { 79 t.Run(fmt.Sprintf("%T", tc.typ), func(t *testing.T) { 80 rt, shape := flattenArrayType(reflect.TypeOf(tc.typ)) 81 if got, want := rt, reflect.TypeOf(tc.want); got != want { 82 t.Fatalf("invalid array element type: got=%v, want=%v", got, want) 83 } 84 85 if got, want := shape, tc.shape; !reflect.DeepEqual(got, want) { 86 t.Fatalf("invalid shape: got=%v, want=%v", got, want) 87 } 88 }) 89 } 90 } 91 92 func TestInvalidTreeMerger(t *testing.T) { 93 var ( 94 w wtree 95 src = rbase.NewObjString("foo") 96 ) 97 98 err := w.ROOTMerge(src) 99 if err == nil { 100 t.Fatalf("expected an error") 101 } 102 103 const want = "rtree: can not merge src=*rbase.ObjString into dst=*rtree.wtree" 104 if got, want := err.Error(), want; got != want { 105 t.Fatalf("invalid ROOTMerge error. got=%q, want=%q", got, want) 106 } 107 } 108 109 func TestConcurrentWrite(t *testing.T) { 110 tmp, err := os.MkdirTemp("", "groot-rtree-") 111 if err != nil { 112 t.Fatal(err) 113 } 114 defer func() { 115 _ = os.RemoveAll(tmp) 116 }() 117 118 const N = 10 119 var wg sync.WaitGroup 120 wg.Add(N) 121 122 for i := range N { 123 go func(i int) { 124 defer wg.Done() 125 f, err := riofs.Create(filepath.Join(tmp, fmt.Sprintf("file-%03d.root", i))) 126 if err != nil { 127 t.Errorf("could not create root file: %+v", err) 128 return 129 } 130 defer f.Close() 131 132 var ( 133 evt struct { 134 N int32 135 Sli []float64 `groot:"Sli[N]"` 136 } 137 wvars = WriteVarsFromStruct(&evt) 138 ) 139 w, err := NewWriter(f, "tree", wvars) 140 if err != nil { 141 t.Errorf("could not create tree writer: %+v", err) 142 return 143 } 144 defer w.Close() 145 146 rng := rand.New(rand.NewPCG(1234, 1234)) 147 for range 100 { 148 evt.N = rng.Int32N(10) + 1 149 evt.Sli = evt.Sli[:0] 150 for range int(evt.N) { 151 evt.Sli = append(evt.Sli, rng.Float64()) 152 } 153 _, err = w.Write() 154 if err != nil { 155 t.Errorf("could not write event %d: %+v", i, err) 156 return 157 } 158 } 159 160 err = w.Close() 161 if err != nil { 162 t.Errorf("could not close tree writer: %+v", err) 163 return 164 } 165 166 err = f.Close() 167 if err != nil { 168 t.Errorf("could not close root file: %+v", err) 169 return 170 } 171 }(i) 172 } 173 wg.Wait() 174 } 175 176 func TestWriteThisStreamers(t *testing.T) { 177 tmp, err := os.MkdirTemp("", "groot-rtree-") 178 if err != nil { 179 t.Fatalf("could not create dir: %v", err) 180 } 181 defer os.RemoveAll(tmp) 182 183 fname := filepath.Join(tmp, "streamers.root") 184 o, err := riofs.Create(fname) 185 if err != nil { 186 t.Fatalf("could not create output ROOT file: %+v", err) 187 } 188 defer o.Close() 189 190 var evt struct { 191 F1 []float64 `groot:"F1"` 192 F2 []float64 `groot:"F2"` 193 F3 []int64 `groot:"F3"` 194 F4 []int64 `groot:"F4"` 195 } 196 197 wvars := WriteVarsFromStruct(&evt) 198 tree, err := NewWriter(o, "tree", wvars) 199 if err != nil { 200 t.Fatalf("could not create output ROOT tree %q: %+v", "tree", err) 201 } 202 203 for i := range 10 { 204 evt.F1 = []float64{float64(i)} 205 evt.F2 = []float64{float64(i), float64(i)} 206 evt.F3 = []int64{int64(i)} 207 evt.F4 = []int64{int64(i), int64(i)} 208 209 _, err = tree.Write() 210 if err != nil { 211 t.Fatalf("could not write event %d: %+v", i, err) 212 } 213 } 214 215 err = tree.Close() 216 if err != nil { 217 t.Fatalf("could not close tree: %+v", err) 218 } 219 220 err = o.Close() 221 if err != nil { 222 t.Fatalf("could not close file: %+v", err) 223 } 224 225 f, err := riofs.Open(fname) 226 if err != nil { 227 t.Fatalf("could not re-open ROOT file: %+v", err) 228 } 229 defer f.Close() 230 231 sinfos := make(map[string]int) 232 for _, si := range f.StreamerInfos() { 233 sinfos[si.Name()]++ 234 } 235 236 for _, tc := range []struct { 237 name string 238 want int 239 }{ 240 {"vector<double>", 1}, 241 {"vector<int64_t>", 1}, 242 } { 243 got := sinfos[tc.name] 244 if got != tc.want { 245 t.Errorf("invalid count for %q: got=%d, want=%d", tc.name, got, tc.want) 246 } 247 } 248 } 249 250 func TestWriterWithCompression(t *testing.T) { 251 tmp, err := os.MkdirTemp("", "groot-rtree-") 252 if err != nil { 253 t.Fatal(err) 254 } 255 defer func() { 256 _ = os.RemoveAll(tmp) 257 }() 258 259 for _, tc := range []struct { 260 wopt WriteOption 261 want rcompress.Kind 262 }{ 263 // {WithoutCompression(flate.BestCompression), rcompress.}, 264 {WithLZ4(flate.BestCompression), rcompress.LZ4}, 265 {WithLZMA(flate.BestCompression), rcompress.LZMA}, 266 {WithZlib(flate.BestCompression), rcompress.ZLIB}, 267 {WithZstd(flate.BestCompression), rcompress.ZSTD}, 268 } { 269 t.Run("alg-"+tc.want.String(), func(t *testing.T) { 270 fname := filepath.Join(tmp, "groot-alg-"+tc.want.String()) 271 f, err := riofs.Create(fname) 272 if err != nil { 273 t.Fatalf("could not create file %q: %v", fname, err) 274 } 275 defer f.Close() 276 277 var ( 278 evt struct { 279 N int32 280 Sli []float64 `groot:"Sli[N]"` 281 } 282 wvars = WriteVarsFromStruct(&evt) 283 ) 284 w, err := NewWriter(f, "tree", wvars, tc.wopt) 285 if err != nil { 286 t.Errorf("could not create tree writer: %+v", err) 287 return 288 } 289 defer w.Close() 290 291 rng := rand.New(rand.NewPCG(1234, 1234)) 292 for i := range 100 { 293 evt.N = rng.Int32N(10) + 1 294 evt.Sli = evt.Sli[:0] 295 for range int(evt.N) { 296 evt.Sli = append(evt.Sli, rng.Float64()) 297 } 298 _, err = w.Write() 299 if err != nil { 300 t.Errorf("could not write event %d: %+v", i, err) 301 return 302 } 303 } 304 305 err = w.Close() 306 if err != nil { 307 t.Errorf("could not close tree writer: %+v", err) 308 return 309 } 310 311 err = f.Close() 312 if err != nil { 313 t.Errorf("could not close root file: %+v", err) 314 return 315 } 316 317 { 318 f, err := riofs.Open(fname) 319 if err != nil { 320 t.Fatalf("could not open ROOT file %q: %v", fname, err) 321 } 322 defer f.Close() 323 324 tree, err := riofs.Get[Tree](f, "tree") 325 if err != nil { 326 t.Fatalf("could not open tree: %v", err) 327 } 328 bname := "Sli" 329 b := tree.Branch(bname) 330 if b == nil { 331 t.Fatalf("could not retrieve branch %q", bname) 332 } 333 bb, ok := b.(*tbranch) 334 if !ok { 335 t.Fatalf("unexpected type for branch %q: %T", bname, b) 336 } 337 xset := rcompress.SettingsFrom(int32(bb.compress)) 338 if got, want := xset.Alg, tc.want; got != want { 339 t.Fatalf("invalid compression algorithm for branch %q: got=%v, want=%v", b.Name(), got, want) 340 } 341 } 342 }) 343 } 344 }