go-hep.org/x/hep@v0.38.1/groot/rhist/rootio_test.go (about) 1 // Copyright ©2018 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 rhist_test 6 7 import ( 8 "bufio" 9 "bytes" 10 "fmt" 11 "os" 12 "path/filepath" 13 "reflect" 14 "strings" 15 "testing" 16 17 "go-hep.org/x/hep/groot/internal/rtests" 18 "go-hep.org/x/hep/groot/rhist" 19 "go-hep.org/x/hep/groot/riofs" 20 "go-hep.org/x/hep/hbook" 21 "go-hep.org/x/hep/hbook/yodacnv" 22 ) 23 24 func TestCreate(t *testing.T) { 25 26 dir, err := os.MkdirTemp("", "groot-") 27 if err != nil { 28 t.Fatal(err) 29 } 30 defer os.RemoveAll(dir) 31 32 for i, tc := range []struct { 33 Name string 34 Skip bool 35 Want []rtests.ROOTer 36 ROOT string 37 }{ 38 { 39 Name: "TAxis", 40 Want: []rtests.ROOTer{rhist.NewAxis("xaxis")}, 41 ROOT: "retrieved: [xaxis]\n", 42 }, 43 { 44 Name: "TH1I", 45 ROOT: "retrieved: [h1i]\n", 46 Want: []rtests.ROOTer{ 47 func() *rhist.H1I { 48 h := hbook.NewH1D(100, 0, 100) 49 h.Annotation()["name"] = "h1i" 50 h.Annotation()["title"] = "my title" 51 h.Fill(-1, 1) 52 h.Fill(+200, 1) 53 h.Fill(1, 1) 54 h.Fill(2, 1) 55 h.Fill(3, 10) 56 return rhist.NewH1IFrom(h) 57 }(), 58 }, 59 }, 60 { 61 Name: "TH1F", 62 ROOT: "retrieved: [h1f]\n", 63 Want: []rtests.ROOTer{ 64 func() *rhist.H1F { 65 h := hbook.NewH1D(100, 0, 100) 66 h.Annotation()["name"] = "h1f" 67 h.Annotation()["title"] = "my title" 68 h.Fill(-1, 1) 69 h.Fill(+200, 1) 70 h.Fill(1, 1) 71 h.Fill(2, 1) 72 h.Fill(3, 10) 73 return rhist.NewH1FFrom(h) 74 }(), 75 }, 76 }, 77 { 78 Name: "TH1D", 79 ROOT: "retrieved: [h1d]\n", 80 Want: []rtests.ROOTer{ 81 func() *rhist.H1D { 82 h := hbook.NewH1D(100, 0, 100) 83 h.Annotation()["name"] = "h1d" 84 h.Annotation()["title"] = "my title" 85 h.Fill(-1, 1) 86 h.Fill(+200, 1) 87 h.Fill(1, 1) 88 h.Fill(2, 1) 89 h.Fill(3, 10) 90 return rhist.NewH1DFrom(h) 91 }(), 92 }, 93 }, 94 { 95 Name: "TH2I", 96 ROOT: "retrieved: [h2i]\n", 97 Want: []rtests.ROOTer{ 98 func() *rhist.H2I { 99 h := hbook.NewH2D(100, 0, 100, 50, 0, 50) 100 h.Annotation()["name"] = "h2i" 101 h.Annotation()["title"] = "my title" 102 h.Fill(-1, -1, 1) 103 h.Fill(+200, 200, 1) 104 h.Fill(1, 1, 1) 105 h.Fill(2, 2, 1) 106 h.Fill(3, 3, 10) 107 return rhist.NewH2IFrom(h) 108 }(), 109 }, 110 }, 111 { 112 Name: "TH2F", 113 ROOT: "retrieved: [h2f]\n", 114 Want: []rtests.ROOTer{ 115 func() *rhist.H2F { 116 h := hbook.NewH2D(100, 0, 100, 50, 0, 50) 117 h.Annotation()["name"] = "h2f" 118 h.Annotation()["title"] = "my title" 119 h.Fill(-1, -1, 1) 120 h.Fill(+200, 200, 1) 121 h.Fill(1, 1, 1) 122 h.Fill(2, 2, 1) 123 h.Fill(3, 3, 10) 124 return rhist.NewH2FFrom(h) 125 }(), 126 }, 127 }, 128 { 129 Name: "TH2D", 130 ROOT: "retrieved: [h2d]\n", 131 Want: []rtests.ROOTer{ 132 func() *rhist.H2D { 133 h := hbook.NewH2D(100, 0, 100, 50, 0, 50) 134 h.Annotation()["name"] = "h2d" 135 h.Annotation()["title"] = "my title" 136 h.Fill(-1, -1, 1) 137 h.Fill(+200, 200, 1) 138 h.Fill(1, 1, 1) 139 h.Fill(2, 2, 1) 140 h.Fill(3, 3, 10) 141 return rhist.NewH2DFrom(h) 142 }(), 143 }, 144 }, 145 { 146 Name: "TGraph", 147 ROOT: "retrieved: [tg]\n", 148 Want: []rtests.ROOTer{ 149 func() rtests.ROOTer { 150 hg := hbook.NewS2D( 151 hbook.Point2D{X: 1, Y: 1}, 152 hbook.Point2D{X: 2, Y: 1.5}, 153 hbook.Point2D{X: -1, Y: +2}, 154 ) 155 hg.Annotation()["name"] = "tg" 156 hg.Annotation()["title"] = "my title" 157 return rhist.NewGraphFrom(hg).(rtests.ROOTer) 158 }(), 159 }, 160 }, 161 { 162 Name: "TGraphErrors", 163 ROOT: "retrieved: [tge]\n", 164 Want: []rtests.ROOTer{ 165 func() rtests.ROOTer { 166 hg := hbook.NewS2D( 167 hbook.Point2D{X: 1, Y: 1, ErrX: hbook.Range{Min: 2, Max: 2}, ErrY: hbook.Range{Min: 3, Max: 3}}, 168 hbook.Point2D{X: 2, Y: 1.5, ErrX: hbook.Range{Min: 2, Max: 2}, ErrY: hbook.Range{Min: 3, Max: 3}}, 169 hbook.Point2D{X: -1, Y: +2, ErrX: hbook.Range{Min: 2, Max: 2}, ErrY: hbook.Range{Min: 3, Max: 3}}, 170 ) 171 hg.Annotation()["name"] = "tge" 172 hg.Annotation()["title"] = "my title" 173 return rhist.NewGraphErrorsFrom(hg).(rtests.ROOTer) 174 }(), 175 }, 176 }, 177 { 178 Name: "TGraphAsymmErrors", 179 ROOT: "retrieved: [tgae]\n", 180 Want: []rtests.ROOTer{ 181 func() rtests.ROOTer { 182 hg := hbook.NewS2D( 183 hbook.Point2D{X: 1, Y: 1, ErrX: hbook.Range{Min: 1, Max: 2}, ErrY: hbook.Range{Min: 3, Max: 4}}, 184 hbook.Point2D{X: 2, Y: 1.5, ErrX: hbook.Range{Min: 1, Max: 2}, ErrY: hbook.Range{Min: 3, Max: 4}}, 185 hbook.Point2D{X: -1, Y: +2, ErrX: hbook.Range{Min: 1, Max: 2}, ErrY: hbook.Range{Min: 3, Max: 4}}, 186 ) 187 hg.Annotation()["name"] = "tgae" 188 hg.Annotation()["title"] = "my title" 189 return rhist.NewGraphAsymmErrorsFrom(hg).(rtests.ROOTer) 190 }(), 191 }, 192 }, 193 } { 194 fname := filepath.Join(dir, fmt.Sprintf("out-%d.root", i)) 195 t.Run(tc.Name, func(t *testing.T) { 196 if tc.Skip { 197 t.Skip() 198 } 199 200 w, err := riofs.Create(fname) 201 if err != nil { 202 t.Fatal(err) 203 } 204 205 for i := range tc.Want { 206 var ( 207 kname = fmt.Sprintf("key-%s-%02d", tc.Name, i) 208 want = tc.Want[i] 209 ) 210 211 err = w.Put(kname, want) 212 if err != nil { 213 t.Fatal(err) 214 } 215 } 216 217 if got, want := len(w.Keys()), len(tc.Want); got != want { 218 t.Fatalf("invalid number of keys. got=%d, want=%d", got, want) 219 } 220 221 err = w.Close() 222 if err != nil { 223 t.Fatalf("error closing file: %v", err) 224 } 225 226 r, err := riofs.Open(fname) 227 if err != nil { 228 t.Fatal(err) 229 } 230 defer r.Close() 231 232 if got, want := len(r.Keys()), len(tc.Want); got != want { 233 t.Fatalf("invalid number of keys. got=%d, want=%d", got, want) 234 } 235 236 for i := range tc.Want { 237 var ( 238 kname = fmt.Sprintf("key-%s-%02d", tc.Name, i) 239 want = tc.Want[i] 240 ) 241 242 rgot, err := r.Get(kname) 243 if err != nil { 244 t.Fatal(err) 245 } 246 247 switch rgot := rgot.(type) { 248 case yodacnv.Marshaler: 249 got, err := rgot.MarshalYODA() 250 if err != nil { 251 t.Fatalf("could not marshal 'rgot' to YODA: %+v", err) 252 } 253 want, err := want.(yodacnv.Marshaler).MarshalYODA() 254 if err != nil { 255 t.Fatalf("could not marshal 'want' to YODA: %+v", err) 256 } 257 if !bytes.Equal(got, want) { 258 t.Fatalf("error reading back value[%d].\ngot:\n%s\nwant:\n%s", i, got, want) 259 } 260 261 default: 262 if got := rgot.(rtests.ROOTer); !reflect.DeepEqual(got, want) { 263 t.Fatalf("error reading back value[%d].\ngot = %#v\nwant= %#v", i, got, want) 264 } 265 } 266 } 267 268 err = r.Close() 269 if err != nil { 270 t.Fatalf("error closing file: %v", err) 271 } 272 273 if !rtests.HasROOT { 274 t.Logf("skip test with ROOT/C++") 275 return 276 } 277 278 const rootls = `#include <iostream> 279 #include "TFile.h" 280 #include "TNamed.h" 281 282 void rootls(const char *fname, const char *kname) { 283 auto f = TFile::Open(fname); 284 auto o = f->Get<TNamed>(kname); 285 if (o == NULL) { 286 std:cerr << "could not retrieve [" << kname << "]" << std::endl; 287 o->ClassName(); 288 } 289 std::cout << "retrieved: [" << o->GetName() << "]" << std::endl; 290 } 291 ` 292 for i := range tc.Want { 293 kname := fmt.Sprintf("key-%s-%02d", tc.Name, i) 294 295 out, err := rtests.RunCxxROOT("rootls", []byte(rootls), fname, kname) 296 if err != nil { 297 t.Fatalf("ROOT/C++ could not open file %q:\n%s", fname, string(out)) 298 } 299 if got := stripLine(t, out); got != tc.ROOT { 300 t.Fatalf("invalid ROOT/C++ output:\ngot:\n%s\nwant:\n%s", got, tc.ROOT) 301 } 302 } 303 }) 304 } 305 } 306 307 func stripLine(t *testing.T, raw []byte) string { 308 r := bytes.NewReader(bytes.TrimSpace(raw)) 309 scan := bufio.NewScanner(r) 310 scan.Scan() 311 312 var o strings.Builder 313 for scan.Scan() { 314 o.WriteString(scan.Text() + "\n") 315 } 316 if err := scan.Err(); err != nil { 317 t.Fatalf("could not scan text:\n%q\nerr: %+v", raw, err) 318 } 319 320 return o.String() 321 }