go-hep.org/x/hep@v0.38.1/cmd/root2fits/main_test.go (about) 1 // Copyright ©2020 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 main 6 7 import ( 8 "fmt" 9 "io" 10 "os" 11 "path/filepath" 12 "reflect" 13 "strings" 14 "testing" 15 16 "codeberg.org/astrogo/fitsio" 17 "go-hep.org/x/hep/groot" 18 "go-hep.org/x/hep/groot/rtree" 19 ) 20 21 func TestConvert(t *testing.T) { 22 tmp, err := os.MkdirTemp("", "root2fits-") 23 if err != nil { 24 t.Fatalf("%+v", err) 25 } 26 defer os.RemoveAll(tmp) 27 28 for _, tc := range []struct { 29 name string 30 nevts int 31 data func(i int) any 32 want string 33 }{ 34 { 35 name: "builtins", 36 nevts: 5, 37 data: func(i int) any { 38 type D struct { 39 B bool 40 I8 int8 41 I16 int16 42 I32 int32 43 I64 int64 44 U8 uint8 45 U16 uint16 46 U32 uint32 47 U64 uint64 48 F32 float32 49 F64 float64 50 Str string 51 } 52 return &D{ 53 B: i%2 == 0, 54 I8: int8(-i), 55 I16: int16(-i), 56 I32: int32(-i), 57 I64: int64(-i), 58 U8: uint8(i), 59 U16: uint16(i), 60 U32: uint32(i), 61 U64: uint64(i), 62 F32: float32(i), 63 F64: float64(i), 64 Str: fmt.Sprintf("%05d", i), 65 } 66 }, 67 want: `== 00001/00005 ================================================================= 68 B | true 69 I8 | 0 70 I16 | 0 71 I32 | 0 72 I64 | 0 73 U8 | 0 74 U16 | 0 75 U32 | 0 76 U64 | 0 77 F32 | 0 78 F64 | 0 79 Str | 00000 80 == 00002/00005 ================================================================= 81 B | false 82 I8 | 255 83 I16 | -1 84 I32 | -1 85 I64 | -1 86 U8 | 1 87 U16 | 1 88 U32 | 1 89 U64 | 1 90 F32 | 1 91 F64 | 1 92 Str | 00001 93 == 00003/00005 ================================================================= 94 B | true 95 I8 | 254 96 I16 | -2 97 I32 | -2 98 I64 | -2 99 U8 | 2 100 U16 | 2 101 U32 | 2 102 U64 | 2 103 F32 | 2 104 F64 | 2 105 Str | 00002 106 == 00004/00005 ================================================================= 107 B | false 108 I8 | 253 109 I16 | -3 110 I32 | -3 111 I64 | -3 112 U8 | 3 113 U16 | 3 114 U32 | 3 115 U64 | 3 116 F32 | 3 117 F64 | 3 118 Str | 00003 119 == 00005/00005 ================================================================= 120 B | true 121 I8 | 252 122 I16 | -4 123 I32 | -4 124 I64 | -4 125 U8 | 4 126 U16 | 4 127 U32 | 4 128 U64 | 4 129 F32 | 4 130 F64 | 4 131 Str | 00004 132 `, 133 }, 134 { 135 name: "arrays", 136 nevts: 5, 137 data: func(i int) any { 138 type D struct { 139 B [3]bool 140 I8 [3]int8 141 I16 [3]int16 142 I32 [3]int32 143 I64 [3]int64 144 U8 [3]uint8 145 U16 [3]uint16 146 U32 [3]uint32 147 U64 [3]uint64 148 F32 [3]float32 149 F64 [3]float64 150 } 151 return &D{ 152 B: [3]bool{i%2 == 0, (i+1)%2 == 0, (i+2)%2 == 0}, 153 I8: [3]int8{int8(-i), int8(-i - 1), int8(-i - 2)}, 154 I16: [3]int16{int16(-i), int16(-i - 1), int16(-i - 2)}, 155 I32: [3]int32{int32(-i), int32(-i - 1), int32(-i - 2)}, 156 I64: [3]int64{int64(-i), int64(-i - 1), int64(-i - 2)}, 157 U8: [3]uint8{uint8(i), uint8(i + 1), uint8(i + 2)}, 158 U16: [3]uint16{uint16(i), uint16(i + 1), uint16(i + 2)}, 159 U32: [3]uint32{uint32(i), uint32(i + 1), uint32(i + 2)}, 160 U64: [3]uint64{uint64(i), uint64(i + 1), uint64(i + 2)}, 161 F32: [3]float32{float32(10 + i), float32(20 + i), float32(30 + i)}, 162 F64: [3]float64{float64(10 + i), float64(20 + i), float64(30 + i)}, 163 } 164 }, 165 want: `== 00001/00005 ================================================================= 166 B | [true false true] 167 I8 | [0 255 254] 168 I16 | [0 -1 -2] 169 I32 | [0 -1 -2] 170 I64 | [0 -1 -2] 171 U8 | [0 1 2] 172 U16 | [0 1 2] 173 U32 | [0 1 2] 174 U64 | [0 1 2] 175 F32 | [10 20 30] 176 F64 | [10 20 30] 177 == 00002/00005 ================================================================= 178 B | [false true false] 179 I8 | [255 254 253] 180 I16 | [-1 -2 -3] 181 I32 | [-1 -2 -3] 182 I64 | [-1 -2 -3] 183 U8 | [1 2 3] 184 U16 | [1 2 3] 185 U32 | [1 2 3] 186 U64 | [1 2 3] 187 F32 | [11 21 31] 188 F64 | [11 21 31] 189 == 00003/00005 ================================================================= 190 B | [true false true] 191 I8 | [254 253 252] 192 I16 | [-2 -3 -4] 193 I32 | [-2 -3 -4] 194 I64 | [-2 -3 -4] 195 U8 | [2 3 4] 196 U16 | [2 3 4] 197 U32 | [2 3 4] 198 U64 | [2 3 4] 199 F32 | [12 22 32] 200 F64 | [12 22 32] 201 == 00004/00005 ================================================================= 202 B | [false true false] 203 I8 | [253 252 251] 204 I16 | [-3 -4 -5] 205 I32 | [-3 -4 -5] 206 I64 | [-3 -4 -5] 207 U8 | [3 4 5] 208 U16 | [3 4 5] 209 U32 | [3 4 5] 210 U64 | [3 4 5] 211 F32 | [13 23 33] 212 F64 | [13 23 33] 213 == 00005/00005 ================================================================= 214 B | [true false true] 215 I8 | [252 251 250] 216 I16 | [-4 -5 -6] 217 I32 | [-4 -5 -6] 218 I64 | [-4 -5 -6] 219 U8 | [4 5 6] 220 U16 | [4 5 6] 221 U32 | [4 5 6] 222 U64 | [4 5 6] 223 F32 | [14 24 34] 224 F64 | [14 24 34] 225 `, 226 }, 227 } { 228 t.Run(tc.name, func(t *testing.T) { 229 var ( 230 fname = filepath.Join(tmp, tc.name+".root") 231 tname = "tree" 232 oname = filepath.Join(tmp, tc.name+".fits") 233 ) 234 // create 235 func() { 236 f, err := groot.Create(fname) 237 if err != nil { 238 t.Fatalf("could not create write ROOT file %q: %v", fname, err) 239 } 240 defer f.Close() 241 242 ptr := tc.data(0) 243 wvars := rtree.WriteVarsFromStruct(ptr) 244 tw, err := rtree.NewWriter(f, tname, wvars) 245 if err != nil { 246 t.Fatalf("could not create tree writer: %v", err) 247 } 248 249 for i := range int(tc.nevts) { 250 want := reflect.ValueOf(tc.data(i)).Elem().Interface() 251 for j, wvar := range wvars { 252 v := reflect.ValueOf(wvar.Value).Elem() 253 want := reflect.ValueOf(want).Field(j) 254 v.Set(want) 255 } 256 _, err = tw.Write() 257 if err != nil { 258 t.Fatalf("could not write event %d: %v", i, err) 259 } 260 } 261 262 err = tw.Close() 263 if err != nil { 264 t.Fatalf("could not close tree writer: %v", err) 265 } 266 267 err = f.Close() 268 if err != nil { 269 t.Fatalf("could not close write ROOT file %q: %v", fname, err) 270 } 271 }() 272 273 err := process(oname, tname, fname) 274 if err != nil { 275 t.Fatalf("could not convert ROOT tree to FITS table: %+v", err) 276 } 277 278 got := new(strings.Builder) 279 err = display(got, tname, oname) 280 if err != nil { 281 t.Fatalf("could not display FITS table content: %+v", err) 282 } 283 284 if got, want := got.String(), tc.want; got != want { 285 t.Fatalf("invalid FITS table content.\ngot:\n%s\nwant:\n%s\n", got, want) 286 } 287 }) 288 } 289 } 290 291 func display(o io.Writer, hname, fname string) error { 292 r, err := os.Open(fname) 293 if err != nil { 294 return fmt.Errorf("could not open file %q: %w", fname, err) 295 } 296 defer r.Close() 297 298 f, err := fitsio.Open(r) 299 if err != nil { 300 return fmt.Errorf("could not open FITS file %q: %w", fname, err) 301 } 302 defer f.Close() 303 304 hdu := f.Get(hname) 305 if hdu.Type() == fitsio.IMAGE_HDU { 306 return fmt.Errorf("HDU %q not a table", hname) 307 } 308 309 table := hdu.(*fitsio.Table) 310 ncols := len(table.Cols()) 311 nrows := table.NumRows() 312 rows, err := table.Read(0, nrows) 313 if err != nil { 314 return fmt.Errorf("could not read FITS table range: %w", err) 315 } 316 hdrline := strings.Repeat("=", 80-15) 317 maxname := 10 318 for _, col := range table.Cols() { 319 if len(col.Name) > maxname { 320 maxname = len(col.Name) 321 } 322 } 323 324 data := make([]any, ncols) 325 names := make([]string, ncols) 326 for i, col := range table.Cols() { 327 names[i] = col.Name 328 data[i] = reflect.New(col.Type()).Interface() 329 } 330 331 rowfmt := fmt.Sprintf("%%-%ds | %%v\n", maxname) 332 for irow := 0; rows.Next(); irow++ { 333 err = rows.Scan(data...) 334 if err != nil { 335 return fmt.Errorf("could not read row %d: %w", irow, err) 336 } 337 fmt.Fprintf(o, "== %05d/%05d %s\n", irow+1, nrows, hdrline) 338 for i := range ncols { 339 rv := reflect.Indirect(reflect.ValueOf(data[i])) 340 fmt.Fprintf(o, rowfmt, names[i], rv.Interface()) 341 } 342 } 343 344 err = rows.Err() 345 if err != nil { 346 return fmt.Errorf("could not scan table: %w", err) 347 } 348 349 return nil 350 }